Skip to content

Commit

Permalink
Improve getting started experience
Browse files Browse the repository at this point in the history
  • Loading branch information
inlined committed May 3, 2024
1 parent 13514d9 commit 4546024
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 4 deletions.
45 changes: 41 additions & 4 deletions src/apphosting/index.ts
Expand Up @@ -2,7 +2,7 @@ import * as repo from "./repo";
import * as poller from "../operation-poller";
import * as apphosting from "../gcp/apphosting";
import * as githubConnections from "./githubConnections";
import { logBullet, logSuccess, logWarning } from "../utils";
import { logBullet, logSuccess, logWarning, sleep } from "../utils";
import {
apphostingOrigin,
artifactRegistryDomain,
Expand Down Expand Up @@ -36,6 +36,33 @@ const apphostingPollerOptions: Omit<poller.OperationPollerOptions, "operationRes
maxBackoff: 10_000,
};

async function tlsReady(url: string): Promise<boolean> {
// Note, we do not use the helper libraries because they impose additional logic on content type and parsing.
try {
await fetch(url);
return true;
} catch (err) {
// At the time of this writing, the error code is ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE.
// I've chosen to use a regexp in an attempt to be forwards compatible with new versions of
// SSL.
const maybeNodeError = err as { cause: { code: string } };
if (/HANDSHAKE_FAILURE/.test(maybeNodeError?.cause?.code)) {

Check warning on line 49 in src/apphosting/index.ts

View workflow job for this annotation

GitHub Actions / lint (20)

Use `String#includes()` method with a string instead
return false;
}
return true;
}
}

async function awaitTlsReady(url: string): Promise<void> {
let ready;
do {
ready = await tlsReady(url);
if (!ready) {
await sleep(1000 /* ms */);
}
} while (!ready);
}

/**
* Set up a new App Hosting backend.
*/
Expand Down Expand Up @@ -121,6 +148,7 @@ export async function doSetup(
});

await setDefaultTrafficPolicy(projectId, location, backendId, branch);
logSuccess(`Successfully created backend:\n\t${backend.name}`);

const confirmRollout = await promptOnce({
type: "confirm",
Expand All @@ -134,11 +162,14 @@ export async function doSetup(
return;
}

const url = `https://${backend.uri}`;
logBullet(
`You may also track this rollout at:\n\t${consoleOrigin()}/project/${projectId}/apphosting`,
);
// TODO: Previous versions of this command printed the URL before the rollout started so that
// if a user does exit they will know where to go later. Should this be re-added?
const createRolloutSpinner = ora(
"Starting a new rollout... This make take a few minutes. It's safe to exit now.",
"Starting a new rollout; this make take a few minutes. It's safe to exit now.",
).start();
await orchestrateRollout(projectId, location, backendId, {
source: {
Expand All @@ -147,7 +178,13 @@ export async function doSetup(
},
},
});
createRolloutSpinner.succeed(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
createRolloutSpinner.succeed("Rollout complete");
if (!(await tlsReady(url))) {
const tlsSpinner = ora("Waiting for TLS certificate");
await awaitTlsReady(url);
tlsSpinner.succeed("TLS certificate ready");
}
logSuccess(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
}

/**
Expand Down Expand Up @@ -341,7 +378,7 @@ export async function orchestrateRollout(
if (tries >= 5) {
throw err;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
await sleep(1000);
} else {
throw err;
}
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Expand Up @@ -542,6 +542,11 @@ export async function promiseWithSpinner<T>(action: () => Promise<T>, message: s
return data;
}

/** Creates a promise that resolves after a given timeout. await to "sleep". */
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
* Return a "destroy" function for a Node.js HTTP server. MUST be called on
* server creation (e.g. right after `.listen`), BEFORE any connections.
Expand Down

0 comments on commit 4546024

Please sign in to comment.