@caelo-cms/provisioning 0.10.61 → 0.10.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/caddy/Caddyfile.production +7 -0
- package/caddy/Caddyfile.staging +7 -0
- package/dist/admin-internal-answer.d.ts +12 -0
- package/dist/admin-internal-answer.d.ts.map +1 -0
- package/dist/admin-internal-answer.js +32 -0
- package/dist/admin-internal-answer.js.map +1 -0
- package/dist/admin-internal-google.d.ts +4 -0
- package/dist/admin-internal-google.d.ts.map +1 -0
- package/dist/admin-internal-google.js +161 -0
- package/dist/admin-internal-google.js.map +1 -0
- package/dist/admin-internal-self-hosted.d.ts +30 -0
- package/dist/admin-internal-self-hosted.d.ts.map +1 -0
- package/dist/admin-internal-self-hosted.js +92 -0
- package/dist/admin-internal-self-hosted.js.map +1 -0
- package/dist/admin-internal.d.ts +33 -12
- package/dist/admin-internal.d.ts.map +1 -1
- package/dist/admin-internal.js +27 -159
- package/dist/admin-internal.js.map +1 -1
- package/dist/agent/command.d.ts +7 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +1 -0
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/lock.d.ts +13 -1
- package/dist/agent/lock.d.ts.map +1 -1
- package/dist/agent/lock.js +62 -5
- package/dist/agent/lock.js.map +1 -1
- package/dist/agent/run.d.ts +5 -1
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +8 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +192 -90
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +11 -1
- package/dist/caddy.js.map +1 -1
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +95 -38
- package/dist/cli.js.map +1 -1
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +5 -0
- package/dist/compose.js.map +1 -1
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +10 -7
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts +21 -0
- package/dist/converge-job-preflight.d.ts.map +1 -0
- package/dist/converge-job-preflight.js +111 -0
- package/dist/converge-job-preflight.js.map +1 -0
- package/dist/converge-job.d.ts +8 -2
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +14 -0
- package/dist/converge-job.js.map +1 -1
- package/dist/gateway-credentials.d.ts +5 -3
- package/dist/gateway-credentials.d.ts.map +1 -1
- package/dist/gateway-credentials.js +5 -8
- package/dist/gateway-credentials.js.map +1 -1
- package/dist/gcloud.d.ts +29 -0
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +66 -0
- package/dist/gcloud.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +10 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +3 -1
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +10 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +12 -0
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +113 -28
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-iap.d.ts +2 -0
- package/dist/mcp-iap.d.ts.map +1 -1
- package/dist/mcp-iap.js +50 -17
- package/dist/mcp-iap.js.map +1 -1
- package/dist/owner-reset.d.ts +16 -19
- package/dist/owner-reset.d.ts.map +1 -1
- package/dist/owner-reset.js +92 -19
- package/dist/owner-reset.js.map +1 -1
- package/dist/preview-plan.d.ts +71 -0
- package/dist/preview-plan.d.ts.map +1 -0
- package/dist/preview-plan.js +168 -0
- package/dist/preview-plan.js.map +1 -0
- package/dist/public-role-isolation.d.ts +2 -0
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +9 -3
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +4 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/self-hosted-internal-secret.d.ts +27 -0
- package/dist/self-hosted-internal-secret.d.ts.map +1 -0
- package/dist/self-hosted-internal-secret.js +69 -0
- package/dist/self-hosted-internal-secret.js.map +1 -0
- package/dist/self-hosted-release.d.ts +3 -1
- package/dist/self-hosted-release.d.ts.map +1 -1
- package/dist/self-hosted-release.js +6 -2
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +13 -1
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +26 -2
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-converge.d.ts +5 -2
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +73 -8
- package/dist/stack-converge.js.map +1 -1
- package/dist/staging-admin.d.ts +4 -1
- package/dist/staging-admin.d.ts.map +1 -1
- package/dist/staging-admin.js.map +1 -1
- package/dist/state-backend.d.ts +3 -2
- package/dist/state-backend.d.ts.map +1 -1
- package/dist/state-backend.js +3 -2
- package/dist/state-backend.js.map +1 -1
- package/dist/state-command.d.ts +17 -0
- package/dist/state-command.d.ts.map +1 -1
- package/dist/state-command.js +34 -5
- package/dist/state-command.js.map +1 -1
- package/dist/state-migrate.d.ts +14 -3
- package/dist/state-migrate.d.ts.map +1 -1
- package/dist/state-migrate.js +31 -7
- package/dist/state-migrate.js.map +1 -1
- package/dist/state-pulumi.d.ts +0 -2
- package/dist/state-pulumi.d.ts.map +1 -1
- package/dist/state-pulumi.js +19 -20
- package/dist/state-pulumi.js.map +1 -1
- package/dist/static-security-headers.d.ts +86 -0
- package/dist/static-security-headers.d.ts.map +1 -0
- package/dist/static-security-headers.js +126 -0
- package/dist/static-security-headers.js.map +1 -0
- package/dist/static-svg-gcp.d.ts +28 -0
- package/dist/static-svg-gcp.d.ts.map +1 -0
- package/dist/static-svg-gcp.js +106 -0
- package/dist/static-svg-gcp.js.map +1 -0
- package/dist/upgrade/app-roll.d.ts +3 -0
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +12 -5
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/backup.d.ts +2 -0
- package/dist/upgrade/backup.d.ts.map +1 -1
- package/dist/upgrade/backup.js +5 -4
- package/dist/upgrade/backup.js.map +1 -1
- package/dist/upgrade/converge.d.ts +7 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +19 -7
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +38 -3
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +31 -9
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -5
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +114 -25
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +17 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +23 -12
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/reporter.d.ts +2 -0
- package/dist/upgrade/reporter.d.ts.map +1 -1
- package/dist/upgrade/reporter.js +1 -0
- package/dist/upgrade/reporter.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +11 -1
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +5 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +12 -2
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +22 -13
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +16 -3
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +9 -4
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +10 -8
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-reporter.d.ts.map +1 -1
- package/dist/upgrader-reporter.js +1 -0
- package/dist/upgrader-reporter.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +70 -23
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +118 -39
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +67 -11
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-internal-answer.ts +43 -0
- package/src/admin-internal-google.ts +187 -0
- package/src/admin-internal-self-hosted.test.ts +70 -0
- package/src/admin-internal-self-hosted.ts +128 -0
- package/src/admin-internal.ts +59 -174
- package/src/admin-memory.test.ts +1 -0
- package/src/agent/command.ts +3 -0
- package/src/agent/lock-config.test.ts +57 -2
- package/src/agent/lock.ts +68 -5
- package/src/agent/run.test.ts +18 -1
- package/src/agent/run.ts +17 -6
- package/src/caddy-redirects.test.ts +1 -0
- package/src/caddy.ts +12 -0
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +102 -44
- package/src/compose.ts +12 -0
- package/src/converge-job-gcp.ts +16 -11
- package/src/converge-job-preflight.test.ts +108 -0
- package/src/converge-job-preflight.ts +124 -0
- package/src/converge-job.test.ts +50 -0
- package/src/converge-job.ts +38 -2
- package/src/gateway-credentials.test.ts +4 -3
- package/src/gateway-credentials.ts +6 -10
- package/src/gateway-env-isolation.test.ts +1 -0
- package/src/gcloud.test.ts +93 -1
- package/src/gcloud.ts +82 -0
- package/src/index.test.ts +38 -1
- package/src/infra-contract.test.ts +4 -3
- package/src/infra-contract.ts +10 -1
- package/src/install-job.ts +5 -2
- package/src/install-state.ts +13 -0
- package/src/lifecycle.ts +134 -28
- package/src/mcp-iap.test.ts +66 -5
- package/src/mcp-iap.ts +55 -17
- package/src/owner-reset-providers.test.ts +370 -0
- package/src/owner-reset.test.ts +2 -2
- package/src/owner-reset.ts +108 -19
- package/src/preview-plan.test.ts +233 -0
- package/src/preview-plan.ts +226 -0
- package/src/public-role-isolation.test.ts +13 -2
- package/src/public-role-isolation.ts +14 -4
- package/src/release-contract.ts +4 -0
- package/src/self-hosted-compose.test.ts +25 -0
- package/src/self-hosted-internal-secret.test.ts +61 -0
- package/src/self-hosted-internal-secret.ts +80 -0
- package/src/self-hosted-release.ts +10 -3
- package/src/self-hosted-upgrade.test.ts +35 -1
- package/src/self-hosted-upgrade.ts +40 -3
- package/src/stack-contract.test.ts +1 -0
- package/src/stack-converge.test.ts +89 -5
- package/src/stack-converge.ts +81 -8
- package/src/staging-admin.test.ts +1 -1
- package/src/staging-admin.ts +2 -1
- package/src/state-backend.ts +3 -2
- package/src/state-command.test.ts +31 -0
- package/src/state-command.ts +35 -8
- package/src/state-migrate.test.ts +58 -7
- package/src/state-migrate.ts +48 -10
- package/src/state-pulumi.ts +20 -21
- package/src/static-security-headers.test.ts +124 -0
- package/src/static-security-headers.ts +154 -0
- package/src/static-svg-gcp.test.ts +133 -0
- package/src/static-svg-gcp.ts +140 -0
- package/src/upgrade/app-roll.ts +18 -7
- package/src/upgrade/backup.ts +14 -5
- package/src/upgrade/converge.ts +25 -7
- package/src/upgrade/gcp-lookup.test.ts +80 -1
- package/src/upgrade/gcp-lookup.ts +61 -11
- package/src/upgrade/health.test.ts +132 -8
- package/src/upgrade/health.ts +169 -31
- package/src/upgrade/release-images.test.ts +17 -0
- package/src/upgrade/release-images.ts +26 -14
- package/src/upgrade/reporter.ts +3 -0
- package/src/upgrade/upgrade.test.ts +137 -8
- package/src/upgrade/upgrade.ts +14 -2
- package/src/upgrader-gcp.ts +28 -13
- package/src/upgrader-iam-preflight.test.ts +6 -3
- package/src/upgrader-iam-preflight.ts +26 -5
- package/src/upgrader-job.ts +14 -6
- package/src/upgrader-reporter.ts +1 -0
- package/src/wizards/gcp-pulumi.test.ts +1 -1
- package/src/wizards/gcp-pulumi.ts +147 -38
- package/src/wizards/gcp.ts +89 -9
- package/src/wizards/stack-work-dir.test.ts +175 -0
- package/stacks/aws/index.ts +40 -0
- package/stacks/azure/index.ts +22 -1
- package/stacks/gcp/README.md +4 -0
- package/stacks/gcp/index.ts +6 -2
- package/stacks/gcp/Pulumi.production.yaml +0 -16
package/src/upgrade/app-roll.ts
CHANGED
|
@@ -57,6 +57,8 @@ import {
|
|
|
57
57
|
describeServiceJson,
|
|
58
58
|
findCurrentRevision,
|
|
59
59
|
findServiceUrl,
|
|
60
|
+
type GcloudCredentials,
|
|
61
|
+
lookupFailure,
|
|
60
62
|
pinTraffic,
|
|
61
63
|
resolveGcpResourceName,
|
|
62
64
|
} from "./gcp-lookup.js";
|
|
@@ -78,6 +80,8 @@ export interface AppRollInstall {
|
|
|
78
80
|
readonly domain: string;
|
|
79
81
|
/** The protected staging host the admin is told about (#650). */
|
|
80
82
|
readonly stagingHost: string;
|
|
83
|
+
/** Whose gcloud credentials the lookups run with; names the fix for an auth failure. */
|
|
84
|
+
readonly credentials: GcloudCredentials;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/** The release image one service rolls to. */
|
|
@@ -147,15 +151,20 @@ export async function planAppRoll(
|
|
|
147
151
|
const services: ServicePlan[] = [];
|
|
148
152
|
for (const slug of ROLL_ORDER) {
|
|
149
153
|
const prefix = `caelo-${install.env}-${slug}`;
|
|
150
|
-
const
|
|
154
|
+
const lookup = await resolveGcpResourceName(
|
|
151
155
|
run,
|
|
152
156
|
"run-service",
|
|
153
157
|
prefix,
|
|
154
158
|
install.projectId,
|
|
155
159
|
install.region,
|
|
156
160
|
);
|
|
157
|
-
if (
|
|
158
|
-
return {
|
|
161
|
+
if (lookup.status !== "found") {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: lookupFailure(lookup, `${prefix}* Cloud Run service`, install.credentials),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const serviceName = lookup.name;
|
|
159
168
|
const priorRevision = await findCurrentRevision(
|
|
160
169
|
run,
|
|
161
170
|
install.projectId,
|
|
@@ -404,10 +413,12 @@ export async function executeAppRoll(
|
|
|
404
413
|
// migrations, and roll a healthy release back.
|
|
405
414
|
if (deps.health) {
|
|
406
415
|
const s = r.step("Checking the health check can reach admin + gateway...");
|
|
407
|
-
const base = await deps.health
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
const base = await deps.health
|
|
417
|
+
.baseline((reason) => s.progress(reason))
|
|
418
|
+
.catch((e: unknown) => ({
|
|
419
|
+
ok: false as const,
|
|
420
|
+
error: e instanceof Error ? e.message : String(e),
|
|
421
|
+
}));
|
|
411
422
|
if (!base.ok) {
|
|
412
423
|
s.failed(`Health check can't reach the install: ${base.error}`);
|
|
413
424
|
r.warn(
|
package/src/upgrade/backup.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { GcloudRunner } from "../gcloud-retry.js";
|
|
15
15
|
import { gcpNamePrefix } from "../gcp-names.js";
|
|
16
|
-
import { resolveGcpResourceName } from "./gcp-lookup.js";
|
|
16
|
+
import { type GcloudCredentials, lookupFailure, resolveGcpResourceName } from "./gcp-lookup.js";
|
|
17
17
|
|
|
18
18
|
/** A finished on-demand backup. */
|
|
19
19
|
export interface SqlBackup {
|
|
@@ -38,22 +38,31 @@ export type SqlBackupResult =
|
|
|
38
38
|
*/
|
|
39
39
|
export async function takeSqlBackup(
|
|
40
40
|
run: GcloudRunner,
|
|
41
|
-
install: {
|
|
41
|
+
install: {
|
|
42
|
+
readonly projectId: string;
|
|
43
|
+
readonly env: string;
|
|
44
|
+
readonly credentials: GcloudCredentials;
|
|
45
|
+
},
|
|
42
46
|
purpose: string,
|
|
43
47
|
now: () => Date = () => new Date(),
|
|
44
48
|
): Promise<SqlBackupResult> {
|
|
45
|
-
const
|
|
49
|
+
const lookup = await resolveGcpResourceName(
|
|
46
50
|
run,
|
|
47
51
|
"sql-instance",
|
|
48
52
|
`${gcpNamePrefix(install.env)}-pg`,
|
|
49
53
|
install.projectId,
|
|
50
54
|
);
|
|
51
|
-
if (
|
|
55
|
+
if (lookup.status !== "found") {
|
|
52
56
|
return {
|
|
53
57
|
ok: false,
|
|
54
|
-
error:
|
|
58
|
+
error: lookupFailure(
|
|
59
|
+
lookup,
|
|
60
|
+
`${gcpNamePrefix(install.env)}-pg* Cloud SQL instance in project ${install.projectId}`,
|
|
61
|
+
install.credentials,
|
|
62
|
+
),
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
const instance = lookup.name;
|
|
57
66
|
const description = `caelo-cms ${purpose} ${now().toISOString()}`;
|
|
58
67
|
const project = ["--project", install.projectId];
|
|
59
68
|
const create = await run([
|
package/src/upgrade/converge.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
import { ensureUpgraderJob, resolveUpgraderTarget } from "../upgrader-setup.js";
|
|
66
66
|
import { ensureUrlMapRedirectsAccess } from "../url-map-redirects-access.js";
|
|
67
67
|
import type { AppRollHooks, AppRollPlan, RollPlan, RollSlug } from "./app-roll.js";
|
|
68
|
+
import { lookupFailure } from "./gcp-lookup.js";
|
|
68
69
|
import type { UpgradeReporter } from "./reporter.js";
|
|
69
70
|
|
|
70
71
|
/**
|
|
@@ -116,6 +117,13 @@ export interface ConvergeSession {
|
|
|
116
117
|
* infrastructure contract version be recorded.
|
|
117
118
|
*/
|
|
118
119
|
complete(): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Whether the part before the app-roll changed access the health check
|
|
122
|
+
* relies on (an IAM binding, MCP access, the gateway's ingress). Google
|
|
123
|
+
* applies such changes only after a while, so the health baseline then
|
|
124
|
+
* waits for a refusal to clear (health.ts, #734).
|
|
125
|
+
*/
|
|
126
|
+
accessJustChanged(): boolean;
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
/** Start a converge for the install `plan` rolls. */
|
|
@@ -137,6 +145,7 @@ export function startConverge(
|
|
|
137
145
|
services,
|
|
138
146
|
};
|
|
139
147
|
let incomplete = false;
|
|
148
|
+
let accessChanged = false;
|
|
140
149
|
// Env vars the rolls must leave as the services have them, because the
|
|
141
150
|
// thing they point at could not be set up.
|
|
142
151
|
const leaveUntouched: string[] = [];
|
|
@@ -179,10 +188,12 @@ export function startConverge(
|
|
|
179
188
|
|
|
180
189
|
async function stackInvariants(): Promise<boolean> {
|
|
181
190
|
// IAM bindings + CDN policy added to the stacks after this install was
|
|
182
|
-
// provisioned (stack-contract.ts),
|
|
183
|
-
//
|
|
191
|
+
// provisioned (stack-contract.ts), a gcp-firebase gateway's ingress
|
|
192
|
+
// (#732), and GCP's SSH/RDP-from-anywhere firewall rules removed
|
|
193
|
+
// (default-firewall.ts). Before the app-roll: its health baseline
|
|
194
|
+
// reaches the gateway through that ingress. Idempotent.
|
|
184
195
|
const s = r.step(
|
|
185
|
-
"Ensuring the IAM bindings
|
|
196
|
+
"Ensuring the IAM bindings, CDN settings and gateway ingress the stack declares, removing the default network's pre-populated firewall rules...",
|
|
186
197
|
);
|
|
187
198
|
const report = await ensureStackInvariants(target, runDeps);
|
|
188
199
|
const applied = report.outcomes.filter((o) => o.status === "applied");
|
|
@@ -193,6 +204,7 @@ export function startConverge(
|
|
|
193
204
|
`Stack invariants ok (${report.outcomes.length - failed.length}/${report.outcomes.length}, ${applied.length} applied)`,
|
|
194
205
|
);
|
|
195
206
|
}
|
|
207
|
+
if (applied.length > 0) accessChanged = true;
|
|
196
208
|
for (const o of applied) r.info(` applied: ${o.id} (${o.why})`);
|
|
197
209
|
for (const o of failed) {
|
|
198
210
|
const line = ` ${o.onFailure === "abort" ? "FAILED" : "not applied"}: ${o.id} — ${o.why}\n ${o.error ?? ""}`;
|
|
@@ -213,7 +225,7 @@ export function startConverge(
|
|
|
213
225
|
// audit is what verifies it and fails the upgrade while it remains.
|
|
214
226
|
const s = r.step("Removing public_role from the Cloud SQL superuser group...");
|
|
215
227
|
const outcome = await revokePublicRoleMemberships(
|
|
216
|
-
{ projectId: install.projectId, env: install.env },
|
|
228
|
+
{ projectId: install.projectId, env: install.env, credentials: install.credentials },
|
|
217
229
|
runDeps,
|
|
218
230
|
);
|
|
219
231
|
if (outcome.status === "failed") {
|
|
@@ -250,6 +262,8 @@ export function startConverge(
|
|
|
250
262
|
: { ok: false as const, error: "admin IAP backend service not found" };
|
|
251
263
|
if (mcp.ok) {
|
|
252
264
|
s.done(`MCP access ready (${mcp.serviceAccount}; ${mcp.operators.length} operator(s))`);
|
|
265
|
+
if (mcp.granted.length > 0) accessChanged = true;
|
|
266
|
+
for (const g of mcp.granted) r.info(` granted: ${g}`);
|
|
253
267
|
return;
|
|
254
268
|
}
|
|
255
269
|
s.warned(`MCP access not configured: ${mcp.error}`);
|
|
@@ -408,12 +422,15 @@ export function startConverge(
|
|
|
408
422
|
// #613 — public_role moves onto its own password right before the
|
|
409
423
|
// gateway's roll. The gateway has no other database login (#635).
|
|
410
424
|
const s = r.step("Preparing the gateway's database login (public_role)...");
|
|
411
|
-
const
|
|
412
|
-
if (
|
|
413
|
-
s.failed(
|
|
425
|
+
const lookup = await findSqlInstance(install, { run });
|
|
426
|
+
if (lookup.status !== "found") {
|
|
427
|
+
s.failed(
|
|
428
|
+
`${lookupFailure(lookup, "Cloud SQL instance", install.credentials)}. Aborting upgrade.`,
|
|
429
|
+
);
|
|
414
430
|
r.warn("Migrations ran; no traffic was shifted. Re-run upgrade once the instance is found.");
|
|
415
431
|
return false;
|
|
416
432
|
}
|
|
433
|
+
const sqlInstance = lookup.name;
|
|
417
434
|
const roleTarget = { projectId: install.projectId, env: install.env, sqlInstance };
|
|
418
435
|
const roleDeps = { ...runDeps, ...(deps.http ? { http: deps.http } : {}) };
|
|
419
436
|
s.done("public_role ready to switch");
|
|
@@ -645,5 +662,6 @@ export function startConverge(
|
|
|
645
662
|
},
|
|
646
663
|
|
|
647
664
|
complete: () => !incomplete,
|
|
665
|
+
accessJustChanged: () => accessChanged,
|
|
648
666
|
};
|
|
649
667
|
}
|
|
@@ -10,13 +10,92 @@
|
|
|
10
10
|
import { describe, expect, it } from "bun:test";
|
|
11
11
|
import type { GcloudResult } from "../gcloud.js";
|
|
12
12
|
import type { GcloudRunner } from "../gcloud-retry.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
findServingImage,
|
|
15
|
+
lookupFailure,
|
|
16
|
+
resolveGcpResourceName,
|
|
17
|
+
servingImageDigests,
|
|
18
|
+
} from "./gcp-lookup.js";
|
|
14
19
|
|
|
15
20
|
const INDEX = `sha256:${"a".repeat(64)}`;
|
|
16
21
|
const PLATFORM = `sha256:${"b".repeat(64)}`;
|
|
17
22
|
const REPO = "europe-west1-docker.pkg.dev/caelo-website/caelo-cms-images/admin";
|
|
18
23
|
|
|
19
24
|
const ok = (stdout: string): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
25
|
+
const fail = (stderr: string): GcloudResult => ({ ok: false, stdout: "", stderr, exitCode: 1 });
|
|
26
|
+
const REAUTH =
|
|
27
|
+
"ERROR: (gcloud.run.services.list) There was a problem refreshing your current auth tokens: Reauthentication failed. cannot prompt during non-interactive execution.";
|
|
28
|
+
|
|
29
|
+
describe("resolveGcpResourceName (#630)", () => {
|
|
30
|
+
const lookup = (r: GcloudResult) =>
|
|
31
|
+
resolveGcpResourceName(async () => r, "run-service", "caelo-production-admin", "acme", "eu");
|
|
32
|
+
|
|
33
|
+
it("finds the first listed name", async () => {
|
|
34
|
+
expect(await lookup(ok("\ncaelo-production-admin-3efcfea\nother\n"))).toEqual({
|
|
35
|
+
status: "found",
|
|
36
|
+
name: "caelo-production-admin-3efcfea",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("an empty listing is not-found", async () => {
|
|
41
|
+
expect(await lookup(ok("\n"))).toEqual({ status: "not-found" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("a failed listing is an error with gcloud's stderr, never not-found", async () => {
|
|
45
|
+
expect(await lookup(fail(REAUTH))).toEqual({
|
|
46
|
+
status: "error",
|
|
47
|
+
stderr: REAUTH,
|
|
48
|
+
authExpired: true,
|
|
49
|
+
});
|
|
50
|
+
expect(await lookup(fail("ERROR: PERMISSION_DENIED: run.services.list"))).toEqual({
|
|
51
|
+
status: "error",
|
|
52
|
+
stderr: "ERROR: PERMISSION_DENIED: run.services.list",
|
|
53
|
+
authExpired: false,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("lookupFailure", () => {
|
|
59
|
+
const what = "caelo-production-admin* Cloud Run service";
|
|
60
|
+
|
|
61
|
+
it("not-found reads as before", () => {
|
|
62
|
+
expect(lookupFailure({ status: "not-found" }, what, "operator")).toBe(
|
|
63
|
+
"Could not find the caelo-production-admin* Cloud Run service",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("expired operator credentials name gcloud auth login and what gcloud said", () => {
|
|
68
|
+
const m = lookupFailure(
|
|
69
|
+
{ status: "error", stderr: REAUTH, authExpired: true },
|
|
70
|
+
what,
|
|
71
|
+
"operator",
|
|
72
|
+
);
|
|
73
|
+
expect(m).toContain("gcloud credentials expired");
|
|
74
|
+
expect(m).toContain("gcloud auth login");
|
|
75
|
+
expect(m).toContain("gcloud auth application-default login");
|
|
76
|
+
expect(m).toContain("Reauthentication failed");
|
|
77
|
+
expect(m).not.toContain("Could not find");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("a job never tells anyone to run gcloud auth login", () => {
|
|
81
|
+
const m = lookupFailure({ status: "error", stderr: REAUTH, authExpired: true }, what, "job");
|
|
82
|
+
expect(m).not.toContain("gcloud auth login");
|
|
83
|
+
expect(m).toContain("Nothing was changed");
|
|
84
|
+
expect(m).toContain("Cloud Shell");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("any other failure surfaces gcloud's first error line", () => {
|
|
88
|
+
expect(
|
|
89
|
+
lookupFailure(
|
|
90
|
+
{ status: "error", stderr: "ERROR: PERMISSION_DENIED: nope\nmore", authExpired: false },
|
|
91
|
+
what,
|
|
92
|
+
"operator",
|
|
93
|
+
),
|
|
94
|
+
).toBe(
|
|
95
|
+
"Could not list the caelo-production-admin* Cloud Run service: gcloud failed (PERMISSION_DENIED: nope)",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
20
99
|
|
|
21
100
|
function gcloud(revision: GcloudResult): GcloudRunner {
|
|
22
101
|
return async (args) => {
|
|
@@ -8,13 +8,70 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
|
+
import {
|
|
12
|
+
GCLOUD_REAUTH_HINT,
|
|
13
|
+
type GcloudResult,
|
|
14
|
+
gcloudErrorLine,
|
|
15
|
+
isGcloudAuthError,
|
|
16
|
+
} from "../gcloud.js";
|
|
11
17
|
import type { GcloudRunner } from "../gcloud-retry.js";
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Whose gcloud credentials a lookup ran with. It decides the fix an auth
|
|
21
|
+
* failure names: the Owner logs in again; a job in the install's project
|
|
22
|
+
* cannot, so it points at the break-glass run instead (#630).
|
|
23
|
+
*/
|
|
24
|
+
export type GcloudCredentials = "operator" | "job";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The outcome of finding a deployed resource by name prefix. A failed
|
|
28
|
+
* listing is NOT "not found": expired credentials used to read as
|
|
29
|
+
* "Could not find caelo-production-admin*" while the service was there (#630).
|
|
30
|
+
*/
|
|
31
|
+
export type GcpLookup =
|
|
32
|
+
| { readonly status: "found"; readonly name: string }
|
|
33
|
+
| { readonly status: "not-found" }
|
|
34
|
+
| { readonly status: "error"; readonly stderr: string; readonly authExpired: boolean };
|
|
35
|
+
|
|
36
|
+
/** A lookup that did not find its resource. */
|
|
37
|
+
export type GcpLookupMiss = Exclude<GcpLookup, { readonly status: "found" }>;
|
|
38
|
+
|
|
39
|
+
/** The first name a `--format=value(name)` listing printed, as a {@link GcpLookup}. */
|
|
40
|
+
export function lookupFromListing(r: GcloudResult): GcpLookup {
|
|
41
|
+
if (!r.ok) {
|
|
42
|
+
return { status: "error", stderr: r.stderr, authExpired: isGcloudAuthError(r.stderr) };
|
|
43
|
+
}
|
|
44
|
+
const name = r.stdout
|
|
45
|
+
.split("\n")
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.find((s) => s.length > 0);
|
|
48
|
+
return name ? { status: "found", name } : { status: "not-found" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The error for a lookup that found nothing, for the operator or the job
|
|
53
|
+
* that ran it.
|
|
54
|
+
*
|
|
55
|
+
* @param what the resource, as the operator knows it, e.g. "caelo-production-admin* Cloud Run service"
|
|
56
|
+
*/
|
|
57
|
+
export function lookupFailure(
|
|
58
|
+
lookup: GcpLookupMiss,
|
|
59
|
+
what: string,
|
|
60
|
+
credentials: GcloudCredentials,
|
|
61
|
+
): string {
|
|
62
|
+
if (lookup.status === "not-found") return `Could not find the ${what}`;
|
|
63
|
+
const said = gcloudErrorLine(lookup.stderr);
|
|
64
|
+
if (!lookup.authExpired) return `Could not list the ${what}: gcloud failed (${said})`;
|
|
65
|
+
return credentials === "operator"
|
|
66
|
+
? `Could not look up the ${what}: ${GCLOUD_REAUTH_HINT} (gcloud: ${said})`
|
|
67
|
+
: `Could not look up the ${what}: Google rejected this job's credentials (${said}). Nothing was changed. Start the update again; if it fails the same way, run the break-glass upgrade from Cloud Shell, which repairs the job's access.`;
|
|
68
|
+
}
|
|
69
|
+
|
|
13
70
|
/**
|
|
14
71
|
* Pulumi auto-naming appends a random 7-char suffix to every resource
|
|
15
72
|
* (e.g. `caelo-production-admin-3efcfea`), so lifecycle commands find the
|
|
16
|
-
* deployed name by prefix
|
|
17
|
-
*
|
|
73
|
+
* deployed name by prefix: the first match, "not-found" when the listing is
|
|
74
|
+
* empty, "error" with gcloud's stderr when the listing itself failed.
|
|
18
75
|
*/
|
|
19
76
|
export async function resolveGcpResourceName(
|
|
20
77
|
run: GcloudRunner,
|
|
@@ -22,7 +79,7 @@ export async function resolveGcpResourceName(
|
|
|
22
79
|
prefix: string,
|
|
23
80
|
projectId: string,
|
|
24
81
|
region?: string,
|
|
25
|
-
): Promise<
|
|
82
|
+
): Promise<GcpLookup> {
|
|
26
83
|
const args =
|
|
27
84
|
kind === "run-service"
|
|
28
85
|
? [
|
|
@@ -47,14 +104,7 @@ export async function resolveGcpResourceName(
|
|
|
47
104
|
`name~^${prefix}`,
|
|
48
105
|
"--format=value(name)",
|
|
49
106
|
];
|
|
50
|
-
|
|
51
|
-
if (!r.ok) return null;
|
|
52
|
-
return (
|
|
53
|
-
r.stdout
|
|
54
|
-
.split("\n")
|
|
55
|
-
.map((s) => s.trim())
|
|
56
|
-
.find((s) => s.length > 0) ?? null
|
|
57
|
-
);
|
|
107
|
+
return lookupFromListing(await run(args));
|
|
58
108
|
}
|
|
59
109
|
|
|
60
110
|
/** A Cloud Run lookup is regional; there is no default region to guess. */
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
evaluateGatewayDeepHealth,
|
|
10
10
|
type HealthTarget,
|
|
11
11
|
healthOrigins,
|
|
12
|
+
IAM_PROPAGATION_WINDOW_MS,
|
|
12
13
|
} from "./health.js";
|
|
13
14
|
|
|
14
15
|
const migrations = (adminPending = 0, publicPending = 0) => ({
|
|
@@ -153,6 +154,7 @@ describe("createIapHealthProbe", () => {
|
|
|
153
154
|
run: gcloudOk,
|
|
154
155
|
http: net.http,
|
|
155
156
|
sleep: async () => {},
|
|
157
|
+
accessJustChanged: false,
|
|
156
158
|
now: () => 1000,
|
|
157
159
|
});
|
|
158
160
|
expect(await probe.deep({ version: "0.11.0" })).toEqual({
|
|
@@ -198,6 +200,7 @@ describe("createIapHealthProbe", () => {
|
|
|
198
200
|
run: gcloudOk,
|
|
199
201
|
http: net.http,
|
|
200
202
|
sleep: async () => {},
|
|
203
|
+
accessJustChanged: false,
|
|
201
204
|
attempts: 5,
|
|
202
205
|
});
|
|
203
206
|
expect((await probe.deep({ version: null })).ok).toBe(true);
|
|
@@ -211,6 +214,7 @@ describe("createIapHealthProbe", () => {
|
|
|
211
214
|
run: gcloudOk,
|
|
212
215
|
http: stuck.http,
|
|
213
216
|
sleep: async () => {},
|
|
217
|
+
accessJustChanged: false,
|
|
214
218
|
attempts: 2,
|
|
215
219
|
});
|
|
216
220
|
const r = await failing.deep({ version: null });
|
|
@@ -227,6 +231,7 @@ describe("createIapHealthProbe", () => {
|
|
|
227
231
|
run: gcloudOk,
|
|
228
232
|
http: net.http,
|
|
229
233
|
sleep: async () => {},
|
|
234
|
+
accessJustChanged: false,
|
|
230
235
|
});
|
|
231
236
|
const r = await probe.baseline();
|
|
232
237
|
expect(r.ok).toBe(false);
|
|
@@ -234,7 +239,7 @@ describe("createIapHealthProbe", () => {
|
|
|
234
239
|
expect(r.error).toContain("IAP may not admit caelo-mcp@acme.iam.gserviceaccount.com");
|
|
235
240
|
});
|
|
236
241
|
|
|
237
|
-
it("baseline succeeds when the old admin answers its shallow check and
|
|
242
|
+
it("#733 — baseline succeeds when the old admin answers its shallow check and an old gateway its own JSON 404", async () => {
|
|
238
243
|
const net = network({
|
|
239
244
|
admin: () => [200, JSON.stringify({ ok: true, version: "0.10.43" })],
|
|
240
245
|
gateway: () => [404, '{"ok":false}'],
|
|
@@ -243,6 +248,7 @@ describe("createIapHealthProbe", () => {
|
|
|
243
248
|
run: gcloudOk,
|
|
244
249
|
http: net.http,
|
|
245
250
|
sleep: async () => {},
|
|
251
|
+
accessJustChanged: false,
|
|
246
252
|
});
|
|
247
253
|
expect(await probe.baseline()).toEqual({
|
|
248
254
|
ok: true,
|
|
@@ -250,16 +256,133 @@ describe("createIapHealthProbe", () => {
|
|
|
250
256
|
});
|
|
251
257
|
});
|
|
252
258
|
|
|
253
|
-
it("baseline fails
|
|
254
|
-
const
|
|
255
|
-
ok:
|
|
256
|
-
|
|
257
|
-
|
|
259
|
+
it("#733 — baseline fails when the gateway answers Google's HTML 404, naming the ingress", async () => {
|
|
260
|
+
const net = network({
|
|
261
|
+
admin: () => [200, JSON.stringify({ ok: true, version: "0.10.20" })],
|
|
262
|
+
gateway: () => [404, "<!DOCTYPE html><html><title>Error 404 (Not Found)!!1</title>"],
|
|
263
|
+
});
|
|
264
|
+
const probe = createIapHealthProbe(FIREBASE, {
|
|
265
|
+
run: gcloudOk,
|
|
266
|
+
http: net.http,
|
|
267
|
+
sleep: async () => {},
|
|
268
|
+
accessJustChanged: false,
|
|
258
269
|
});
|
|
259
|
-
const probe = createIapHealthProbe(FIREBASE, { run: gcloudOk, http, sleep: async () => {} });
|
|
260
270
|
const r = await probe.baseline();
|
|
261
271
|
expect(r.ok).toBe(false);
|
|
262
|
-
if (!r.ok)
|
|
272
|
+
if (!r.ok) {
|
|
273
|
+
expect(r.error).toContain("answered HTTP 404 without JSON");
|
|
274
|
+
expect(r.error).toContain('ingress "all"');
|
|
275
|
+
}
|
|
276
|
+
const lb = createIapHealthProbe(
|
|
277
|
+
{ ...FIREBASE, provider: "gcp" },
|
|
278
|
+
{ run: gcloudOk, http: net.http, sleep: async () => {}, accessJustChanged: false },
|
|
279
|
+
);
|
|
280
|
+
const viaLb = await lb.baseline();
|
|
281
|
+
if (!viaLb.ok) expect(viaLb.error).toContain("route /api/* to the gateway backend");
|
|
282
|
+
expect(viaLb.ok).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("#734 — a refused signJwt right after converge granted it", () => {
|
|
286
|
+
/** signJwt answers 403 `denials` times, then signs. */
|
|
287
|
+
function propagating(denials: number) {
|
|
288
|
+
let signs = 0;
|
|
289
|
+
const http: HttpFetch = async (url) => {
|
|
290
|
+
if (url.includes(":signJwt")) {
|
|
291
|
+
signs++;
|
|
292
|
+
return signs <= denials
|
|
293
|
+
? {
|
|
294
|
+
ok: false,
|
|
295
|
+
status: 403,
|
|
296
|
+
text: async () => "Permission 'iam.serviceAccounts.signJwt' denied",
|
|
297
|
+
}
|
|
298
|
+
: { ok: true, status: 200, text: async () => JSON.stringify({ signedJwt: "s.j" }) };
|
|
299
|
+
}
|
|
300
|
+
return url.includes("admin")
|
|
301
|
+
? { ok: true, status: 200, text: async () => JSON.stringify({ version: "0.10.20" }) }
|
|
302
|
+
: { ok: false, status: 404, text: async () => '{"ok":false}' };
|
|
303
|
+
};
|
|
304
|
+
return { http, signs: () => signs };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
it("waits for the grant to apply, saying why, then passes", async () => {
|
|
308
|
+
const net = propagating(4);
|
|
309
|
+
const slept: number[] = [];
|
|
310
|
+
const waits: string[] = [];
|
|
311
|
+
const probe = createIapHealthProbe(FIREBASE, {
|
|
312
|
+
run: gcloudOk,
|
|
313
|
+
http: net.http,
|
|
314
|
+
sleep: async (ms) => {
|
|
315
|
+
slept.push(ms);
|
|
316
|
+
},
|
|
317
|
+
accessJustChanged: true,
|
|
318
|
+
});
|
|
319
|
+
expect(await probe.baseline((reason) => waits.push(reason))).toEqual({
|
|
320
|
+
ok: true,
|
|
321
|
+
detail: "admin 0.10.20 and gateway reachable",
|
|
322
|
+
});
|
|
323
|
+
expect(net.signs()).toBe(5);
|
|
324
|
+
expect(slept).toEqual([5_000, 10_000, 20_000, 30_000]);
|
|
325
|
+
expect(waits).toHaveLength(4);
|
|
326
|
+
expect(waits[0]).toContain("Waiting for the access the converge step just granted");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("gives up after the propagation window: the grant exists, re-run in a few minutes", async () => {
|
|
330
|
+
const net = propagating(Number.POSITIVE_INFINITY);
|
|
331
|
+
let slept = 0;
|
|
332
|
+
const probe = createIapHealthProbe(FIREBASE, {
|
|
333
|
+
run: gcloudOk,
|
|
334
|
+
http: net.http,
|
|
335
|
+
sleep: async (ms) => {
|
|
336
|
+
slept += ms;
|
|
337
|
+
},
|
|
338
|
+
accessJustChanged: true,
|
|
339
|
+
});
|
|
340
|
+
const r = await probe.baseline();
|
|
341
|
+
expect(slept).toBe(IAM_PROPAGATION_WINDOW_MS);
|
|
342
|
+
expect(r.ok).toBe(false);
|
|
343
|
+
if (!r.ok) {
|
|
344
|
+
expect(r.error).toContain("The grant exists; nothing was changed: re-run upgrade");
|
|
345
|
+
expect(r.error).toContain("after 7 minutes");
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("fails at once when nothing was just granted: the grant is missing", async () => {
|
|
350
|
+
const net = propagating(Number.POSITIVE_INFINITY);
|
|
351
|
+
let slept = 0;
|
|
352
|
+
const probe = createIapHealthProbe(FIREBASE, {
|
|
353
|
+
run: gcloudOk,
|
|
354
|
+
http: net.http,
|
|
355
|
+
sleep: async (ms) => {
|
|
356
|
+
slept += ms;
|
|
357
|
+
},
|
|
358
|
+
accessJustChanged: false,
|
|
359
|
+
});
|
|
360
|
+
const r = await probe.baseline();
|
|
361
|
+
expect(slept).toBe(0);
|
|
362
|
+
expect(net.signs()).toBe(1);
|
|
363
|
+
expect(r.ok).toBe(false);
|
|
364
|
+
if (!r.ok) {
|
|
365
|
+
expect(r.error).toContain("it lacks roles/iam.serviceAccountTokenCreator");
|
|
366
|
+
expect(r.error).toContain("run upgrade as an allowlisted account");
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("never waits out a failure that is not a refusal", async () => {
|
|
371
|
+
const http: HttpFetch = async () => {
|
|
372
|
+
throw new Error("ENOTFOUND");
|
|
373
|
+
};
|
|
374
|
+
let slept = 0;
|
|
375
|
+
const probe = createIapHealthProbe(FIREBASE, {
|
|
376
|
+
run: gcloudOk,
|
|
377
|
+
http,
|
|
378
|
+
sleep: async (ms) => {
|
|
379
|
+
slept += ms;
|
|
380
|
+
},
|
|
381
|
+
accessJustChanged: true,
|
|
382
|
+
});
|
|
383
|
+
expect((await probe.baseline()).ok).toBe(false);
|
|
384
|
+
expect(slept).toBe(0);
|
|
385
|
+
});
|
|
263
386
|
});
|
|
264
387
|
|
|
265
388
|
it("a transport failure while signing is a failed check, not a throw", async () => {
|
|
@@ -270,6 +393,7 @@ describe("createIapHealthProbe", () => {
|
|
|
270
393
|
run: gcloudOk,
|
|
271
394
|
http,
|
|
272
395
|
sleep: async () => {},
|
|
396
|
+
accessJustChanged: false,
|
|
273
397
|
attempts: 2,
|
|
274
398
|
});
|
|
275
399
|
const r = await probe.deep({ version: null });
|