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 Apr 16, 2024
1 parent de3f129 commit 28bcf75
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 8 deletions.
51 changes: 43 additions & 8 deletions src/apphosting/index.ts
Expand Up @@ -4,7 +4,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, promiseWithSpinner, sleep } from "../utils";
import {
apphostingOrigin,
artifactRegistryDomain,
Expand Down Expand Up @@ -34,6 +34,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 }};

Check failure on line 46 in src/apphosting/index.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Insert `·`
if (/HANDSHAKE_FAILURE/.test(maybeNodeError?.cause?.code)) {
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,16 +148,20 @@ export async function doSetup(
return;
}

await orchestrateRollout(projectId, location, backendId, {
const url = `https://${backend.uri}`;
await orchestrateRollout(projectId, location, backendId, url, {
source: {
codebase: {
branch,
},
},
});

logSuccess(`Successfully created backend:\n\t${backend.name}`);
logSuccess(`Your site is now deployed at:\n\thttps://${backend.uri}`);
if (!await tlsReady(url)) {

Check failure on line 160 in src/apphosting/index.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Replace `await·tlsReady(url` with `(await·tlsReady(url)`
await promiseWithSpinner(() => awaitTlsReady(url), "Waiting for TLS certificate");
}

logSuccess(`Your site is now ready at:\n\t${url}`);
}

/**
Expand Down Expand Up @@ -279,9 +310,10 @@ export async function orchestrateRollout(
projectId: string,
location: string,
backendId: string,
url: string,
buildInput: DeepOmit<Build, apphosting.BuildOutputOnlyFields | "name">,
): Promise<{ rollout: Rollout; build: Build }> {
logBullet("Starting a new rollout... this may take a few minutes.");
logBullet("Starting a new rollout.");
const buildId = await apphosting.getNextRolloutId(projectId, location, backendId, 1);
const buildOp = await apphosting.createBuild(projectId, location, backendId, buildId, buildInput);

Expand Down Expand Up @@ -309,7 +341,7 @@ export async function orchestrateRollout(
if (tries >= 5) {
throw err;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
await sleep(1000);
} else {
throw err;
}
Expand All @@ -324,6 +356,10 @@ export async function orchestrateRollout(
rolloutBody,
);

logSuccess(
`Your rollout has been initiated; when it completes your backend will be available at ${url}. ` +
"You may now cancel this command or let it continue running to monitor progress");

Check failure on line 361 in src/apphosting/index.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Replace `"You·may·now·cancel·this·command·or·let·it·continue·running·to·monitor·progress"` with `··"You·may·now·cancel·this·command·or·let·it·continue·running·to·monitor·progress",⏎··`

const rolloutPoll = poller.pollOperation<Rollout>({
...apphostingPollerOptions,
pollerName: `create-${projectId}-${location}-backend-${backendId}-rollout-${buildId}`,
Expand All @@ -335,8 +371,7 @@ export async function orchestrateRollout(
operationResourceName: buildOp.name,
});

const [rollout, build] = await Promise.all([rolloutPoll, buildPoll]);
logSuccess("Rollout completed.");
const [rollout, build] = await promiseWithSpinner(() => Promise.all([rolloutPoll, buildPoll]), "Waiting for rollout to complete");

Check failure on line 374 in src/apphosting/index.ts

View workflow job for this annotation

GitHub Actions / unit (18)

Replace `()·=>·Promise.all([rolloutPoll,·buildPoll]),·"Waiting·for·rollout·to·complete"` with `⏎····()·=>·Promise.all([rolloutPoll,·buildPoll]),⏎····"Waiting·for·rollout·to·complete",⏎··`

if (build.state !== "READY") {
if (!build.buildLogsUri) {
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Expand Up @@ -529,6 +529,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 28bcf75

Please sign in to comment.