@caelo-cms/provisioning 0.10.53 → 0.10.57
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/dist/agent-db.js +272 -128
- package/dist/audit-job-setup.d.ts +69 -0
- package/dist/audit-job-setup.d.ts.map +1 -0
- package/dist/audit-job-setup.js +317 -0
- package/dist/audit-job-setup.js.map +1 -0
- package/dist/caddy.d.ts +9 -0
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +38 -0
- 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 +36 -9
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +5 -0
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +66 -0
- package/dist/converge-checkpoint.d.ts.map +1 -0
- package/dist/converge-checkpoint.js +61 -0
- package/dist/converge-checkpoint.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +46 -0
- package/dist/converge-job-gcp.d.ts.map +1 -0
- package/dist/converge-job-gcp.js +368 -0
- package/dist/converge-job-gcp.js.map +1 -0
- package/dist/converge-job-setup.d.ts +62 -0
- package/dist/converge-job-setup.d.ts.map +1 -0
- package/dist/converge-job-setup.js +228 -0
- package/dist/converge-job-setup.js.map +1 -0
- package/dist/converge-job.d.ts +141 -0
- package/dist/converge-job.d.ts.map +1 -0
- package/dist/converge-job.js +325 -0
- package/dist/converge-job.js.map +1 -0
- package/dist/gcp-names.d.ts +25 -1
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +36 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.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 +28 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +11 -3
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +22 -12
- package/dist/install-job.js.map +1 -1
- package/dist/lifecycle.d.ts +13 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +115 -58
- package/dist/lifecycle.js.map +1 -1
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +13 -3
- package/dist/operator-access.js.map +1 -1
- package/dist/private-file.d.ts +26 -0
- package/dist/private-file.d.ts.map +1 -0
- package/dist/private-file.js +49 -0
- package/dist/private-file.js.map +1 -0
- package/dist/release-contract.d.ts +3 -2
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +8 -2
- package/dist/release-contract.js.map +1 -1
- package/dist/release-manifest.d.ts +2 -0
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp-edge.d.ts +114 -0
- package/dist/remote-mcp-edge.d.ts.map +1 -0
- package/dist/remote-mcp-edge.js +116 -0
- package/dist/remote-mcp-edge.js.map +1 -0
- package/dist/remote-mcp.d.ts +117 -0
- package/dist/remote-mcp.d.ts.map +1 -0
- package/dist/remote-mcp.js +379 -0
- package/dist/remote-mcp.js.map +1 -0
- package/dist/runtime-secrets.d.ts +12 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +32 -4
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/seal-key-rotation.d.ts +38 -0
- package/dist/seal-key-rotation.d.ts.map +1 -0
- package/dist/seal-key-rotation.js +206 -0
- package/dist/seal-key-rotation.js.map +1 -0
- package/dist/stack-contract.d.ts +175 -3
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +183 -3
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +38 -1
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +74 -4
- package/dist/stack-converge.js.map +1 -1
- package/dist/state-lock-lease.d.ts +142 -0
- package/dist/state-lock-lease.d.ts.map +1 -0
- package/dist/state-lock-lease.js +347 -0
- package/dist/state-lock-lease.js.map +1 -0
- package/dist/state-store.d.ts +66 -9
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +249 -30
- package/dist/state-store.js.map +1 -1
- package/dist/tar-entries.d.ts.map +1 -1
- package/dist/tar-entries.js +84 -9
- package/dist/tar-entries.js.map +1 -1
- package/dist/upgrade/app-roll.d.ts +36 -7
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +88 -19
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/audit-job-roll.d.ts +38 -0
- package/dist/upgrade/audit-job-roll.d.ts.map +1 -0
- package/dist/upgrade/audit-job-roll.js +43 -0
- package/dist/upgrade/audit-job-roll.js.map +1 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +141 -8
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +27 -10
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +46 -7
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/record-rolled.d.ts +40 -0
- package/dist/upgrade/record-rolled.d.ts.map +1 -0
- package/dist/upgrade/record-rolled.js +46 -0
- package/dist/upgrade/record-rolled.js.map +1 -0
- package/dist/upgrade/upgrade.d.ts +45 -1
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +60 -17
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-assess.d.ts +15 -1
- package/dist/upgrader-assess.d.ts.map +1 -1
- package/dist/upgrader-assess.js +36 -6
- package/dist/upgrader-assess.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +15 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +42 -7
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts +123 -0
- package/dist/upgrader-iam-preflight.d.ts.map +1 -0
- package/dist/upgrader-iam-preflight.js +414 -0
- package/dist/upgrader-iam-preflight.js.map +1 -0
- package/dist/upgrader-job.d.ts +33 -9
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +169 -27
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +58 -0
- package/dist/upgrader-records.d.ts.map +1 -0
- package/dist/upgrader-records.js +87 -0
- package/dist/upgrader-records.js.map +1 -0
- package/dist/upgrader-reporter.d.ts +3 -2
- package/dist/upgrader-reporter.d.ts.map +1 -1
- package/dist/upgrader-reporter.js +10 -2
- package/dist/upgrader-reporter.js.map +1 -1
- package/dist/upgrader-setup.d.ts +25 -3
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +132 -14
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +14 -6
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +6 -0
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +7 -0
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +130 -4
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +4 -4
- package/src/agent/run.test.ts +15 -1
- package/src/audit-job-setup.test.ts +234 -0
- package/src/audit-job-setup.ts +360 -0
- package/src/caddy-redirects.docker.test.ts +19 -6
- package/src/caddy.test.ts +30 -0
- package/src/caddy.ts +43 -0
- package/src/cli-init-validation.test.ts +44 -0
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +40 -8
- package/src/compose.ts +5 -0
- package/src/converge-checkpoint.ts +75 -0
- package/src/converge-entrypoint.test.ts +213 -0
- package/src/converge-job-gcp.test.ts +222 -0
- package/src/converge-job-gcp.ts +443 -0
- package/src/converge-job-setup.test.ts +152 -0
- package/src/converge-job-setup.ts +268 -0
- package/src/converge-job.test.ts +486 -0
- package/src/converge-job.ts +542 -0
- package/src/gateway-env-isolation.test.ts +8 -1
- package/src/gcp-names.ts +43 -1
- package/src/gcp-url-map.test.ts +9 -1
- package/src/index.ts +8 -0
- package/src/infra-contract.test.ts +7 -4
- package/src/infra-contract.ts +28 -1
- package/src/install-job.test.ts +3 -1
- package/src/install-job.ts +31 -14
- package/src/lifecycle.ts +137 -71
- package/src/operator-access.test.ts +14 -0
- package/src/operator-access.ts +13 -3
- package/src/private-file.test.ts +80 -0
- package/src/private-file.ts +50 -0
- package/src/release-contract.ts +8 -2
- package/src/release-manifest.test.ts +42 -0
- package/src/release-manifest.ts +2 -0
- package/src/remote-mcp-edge.ts +137 -0
- package/src/remote-mcp.test.ts +517 -0
- package/src/remote-mcp.ts +460 -0
- package/src/runtime-secrets.test.ts +31 -6
- package/src/runtime-secrets.ts +41 -3
- package/src/seal-key-rotation.test.ts +131 -0
- package/src/seal-key-rotation.ts +258 -0
- package/src/stack-contract-parity.test.ts +20 -3
- package/src/stack-contract.test.ts +34 -2
- package/src/stack-contract.ts +305 -3
- package/src/stack-converge-secrets.test.ts +3 -0
- package/src/stack-converge.test.ts +21 -5
- package/src/stack-converge.ts +101 -7
- package/src/state-attach.test.ts +7 -1
- package/src/state-lock-lease.test.ts +302 -0
- package/src/state-lock-lease.ts +423 -0
- package/src/state-lock.test.ts +568 -0
- package/src/state-store.ts +347 -27
- package/src/tar-entries.test.ts +149 -7
- package/src/tar-entries.ts +90 -9
- package/src/upgrade/app-roll.ts +135 -21
- package/src/upgrade/audit-job-roll.ts +68 -0
- package/src/upgrade/converge.ts +170 -7
- package/src/upgrade/gcp-lookup.test.ts +80 -0
- package/src/upgrade/gcp-lookup.ts +65 -8
- package/src/upgrade/record-rolled.test.ts +105 -0
- package/src/upgrade/record-rolled.ts +91 -0
- package/src/upgrade/upgrade.test.ts +279 -5
- package/src/upgrade/upgrade.ts +120 -24
- package/src/upgrader-assess.test.ts +93 -3
- package/src/upgrader-assess.ts +58 -9
- package/src/upgrader-gcp.ts +47 -8
- package/src/upgrader-iam-preflight.test.ts +393 -0
- package/src/upgrader-iam-preflight.ts +510 -0
- package/src/upgrader-job.test.ts +587 -15
- package/src/upgrader-job.ts +237 -53
- package/src/upgrader-records.ts +151 -0
- package/src/upgrader-reporter.test.ts +6 -0
- package/src/upgrader-reporter.ts +7 -2
- package/src/upgrader-setup.test.ts +4 -3
- package/src/upgrader-setup.ts +137 -13
- package/src/wizards/gcp-cost.ts +15 -6
- package/src/wizards/gcp-pulumi.ts +7 -0
- package/src/wizards/gcp.ts +162 -4
- package/stacks/aws/index.ts +18 -0
- package/stacks/azure/index.ts +18 -0
- package/stacks/gcp/index.ts +59 -7
- package/stacks/gcp-firebase/index.ts +50 -6
|
@@ -30,6 +30,7 @@ const POLICY: StoredUpdatePolicy = {
|
|
|
30
30
|
autoSecurity: true,
|
|
31
31
|
autoPatch: true,
|
|
32
32
|
autoMinor: true,
|
|
33
|
+
autoConverge: false,
|
|
33
34
|
channel: "stable",
|
|
34
35
|
windowStart: null,
|
|
35
36
|
windowEnd: null,
|
|
@@ -53,7 +54,11 @@ const verified = (m: Partial<ReleaseManifest> = {}): ReleaseManifestResult => ({
|
|
|
53
54
|
});
|
|
54
55
|
const broken = (
|
|
55
56
|
code: "registry" | "signature" | "schema" | "blob-digest" | "version-mismatch" | "artifact-shape",
|
|
56
|
-
|
|
57
|
+
transient?: true,
|
|
58
|
+
): ReleaseManifestResult => ({
|
|
59
|
+
ok: false,
|
|
60
|
+
error: { code, message: `${code} problem`, ...(transient ? { transient } : {}) },
|
|
61
|
+
});
|
|
57
62
|
|
|
58
63
|
interface Row {
|
|
59
64
|
readonly name: string;
|
|
@@ -72,9 +77,16 @@ const ROWS: readonly Row[] = [
|
|
|
72
77
|
{ name: "a signed, newer, app-only release", expect: "proceed" },
|
|
73
78
|
{
|
|
74
79
|
name: "registry unreachable",
|
|
75
|
-
manifest: broken("registry"),
|
|
80
|
+
manifest: broken("registry", true),
|
|
76
81
|
expect: "failed/registry",
|
|
77
|
-
reasonContains: "
|
|
82
|
+
reasonContains: "try again later",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
// Regression #865: a 404 never heals, so it must not end retryable.
|
|
86
|
+
name: "no signed manifest for the target (404)",
|
|
87
|
+
manifest: broken("registry"),
|
|
88
|
+
expect: "refused/manifest-missing",
|
|
89
|
+
reasonContains: "has no signed release manifest",
|
|
78
90
|
},
|
|
79
91
|
{ name: "bad signature", manifest: broken("signature"), expect: "refused/signature" },
|
|
80
92
|
{ name: "tampered blob", manifest: broken("blob-digest"), expect: "refused/signature" },
|
|
@@ -94,6 +106,7 @@ const ROWS: readonly Row[] = [
|
|
|
94
106
|
name: "same version",
|
|
95
107
|
installed: { version: "0.11.0", infraContractVersion: 2 },
|
|
96
108
|
expect: "refused/not-newer",
|
|
109
|
+
reasonContains: "v0.11.0 already serves on this install",
|
|
97
110
|
},
|
|
98
111
|
{
|
|
99
112
|
name: "downgrade",
|
|
@@ -326,3 +339,80 @@ describe("assessUpgradeRequest — self-hosted (the host agent, #696 Phase 4c)",
|
|
|
326
339
|
).toBe("refused/contract-migrations");
|
|
327
340
|
});
|
|
328
341
|
});
|
|
342
|
+
|
|
343
|
+
describe("assessUpgradeRequest — the converge route (#819)", () => {
|
|
344
|
+
const assess = (row: {
|
|
345
|
+
manifest?: ReleaseManifestResult;
|
|
346
|
+
installed?: InstalledState;
|
|
347
|
+
trigger?: "manual" | "auto";
|
|
348
|
+
policy?: Partial<StoredUpdatePolicy>;
|
|
349
|
+
}) =>
|
|
350
|
+
assessUpgradeRequest({
|
|
351
|
+
request: { targetVersion: "0.11.0", trigger: row.trigger ?? "manual" },
|
|
352
|
+
manifest: row.manifest ?? verified({ infraContractVersion: 3 }),
|
|
353
|
+
installed: row.installed ?? INSTALLED,
|
|
354
|
+
policy: { ...POLICY, ...row.policy },
|
|
355
|
+
now: NOW,
|
|
356
|
+
route: "converge",
|
|
357
|
+
});
|
|
358
|
+
const code = (d: ReturnType<typeof assess>) =>
|
|
359
|
+
d.kind === "stop" ? `${d.status}/${d.codes.join(",")}` : d.kind;
|
|
360
|
+
|
|
361
|
+
it("installs a release that raises the infrastructure contract: that is its job", () => {
|
|
362
|
+
expect(code(assess({}))).toBe("proceed");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("the contract bump is no reason to warn about destructive migrations", () => {
|
|
366
|
+
const d = assess({});
|
|
367
|
+
expect(d.kind === "proceed" ? d.warnings : ["stopped"]).toEqual([]);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("converges automatically only with the Owner's autoConverge (#864), default off", () => {
|
|
371
|
+
expect(code(assess({ trigger: "auto" }))).toBe("refused/policy");
|
|
372
|
+
const off = assess({ trigger: "auto" });
|
|
373
|
+
expect(off.kind === "stop" ? off.reason : "").toContain("Settings → Updates");
|
|
374
|
+
expect(code(assess({ trigger: "auto", policy: { autoConverge: true } }))).toBe("proceed");
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("autoConverge never widens the rest of the policy: kinds, mode and pause still decide", () => {
|
|
378
|
+
const on = { autoConverge: true };
|
|
379
|
+
// 0.10.45 → 0.11.0 is a minor release.
|
|
380
|
+
expect(code(assess({ trigger: "auto", policy: { ...on, autoMinor: false } }))).toBe(
|
|
381
|
+
"refused/policy",
|
|
382
|
+
);
|
|
383
|
+
expect(code(assess({ trigger: "auto", policy: { ...on, mode: "notify" } }))).toBe(
|
|
384
|
+
"refused/policy",
|
|
385
|
+
);
|
|
386
|
+
expect(code(assess({ trigger: "auto", policy: { ...on, paused: true } }))).toBe(
|
|
387
|
+
"refused/policy",
|
|
388
|
+
);
|
|
389
|
+
// The Owner's click needs no autoConverge.
|
|
390
|
+
expect(code(assess({ trigger: "manual" }))).toBe("proceed");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("keeps every other refusal: signature, downgrade, minFromVersion, unknown version", () => {
|
|
394
|
+
expect(code(assess({ manifest: broken("signature") }))).toBe("refused/signature");
|
|
395
|
+
expect(code(assess({ manifest: broken("blob-digest") }))).toBe("refused/signature");
|
|
396
|
+
expect(code(assess({ installed: { version: "0.11.0", infraContractVersion: 2 } }))).toBe(
|
|
397
|
+
"refused/not-newer",
|
|
398
|
+
);
|
|
399
|
+
expect(
|
|
400
|
+
code(assess({ manifest: verified({ infraContractVersion: 3, minFromVersion: "0.10.50" }) })),
|
|
401
|
+
).toBe("refused/min-from-version");
|
|
402
|
+
expect(code(assess({ installed: { version: null, infraContractVersion: 2 } }))).toBe(
|
|
403
|
+
"needs_cloud_shell/install-version-unknown",
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it("the upgrader's own route still hands the contract bump on, naming the converge job first", () => {
|
|
408
|
+
const d = assessUpgradeRequest({
|
|
409
|
+
request: { targetVersion: "0.11.0", trigger: "manual" },
|
|
410
|
+
manifest: verified({ infraContractVersion: 3 }),
|
|
411
|
+
installed: INSTALLED,
|
|
412
|
+
policy: POLICY,
|
|
413
|
+
now: NOW,
|
|
414
|
+
});
|
|
415
|
+
expect(code(d)).toBe("needs_cloud_shell/infra-contract");
|
|
416
|
+
expect(d.kind === "stop" ? d.reason : "").toContain("converge job");
|
|
417
|
+
});
|
|
418
|
+
});
|
package/src/upgrader-assess.ts
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* | target not newer than installed (downgrade/same) | refused |
|
|
28
28
|
* | installed below the release's minFromVersion | refused |
|
|
29
29
|
* | release raises the infrastructure contract | needs_cloud_shell |
|
|
30
|
+
* | … on the converge route (#819, the converge job) | proceed |
|
|
31
|
+
* | … converge route, automatic, autoConverge off | refused |
|
|
30
32
|
* | contract (destructive) migrations, automatic run | refused |
|
|
31
33
|
* | contract migrations, Owner's click | proceed + warning |
|
|
32
34
|
* | automatic run the update policy does not allow | refused |
|
|
@@ -38,6 +40,13 @@
|
|
|
38
40
|
* that rolls the images. So for them `installed.infraContractVersion` is
|
|
39
41
|
* null and the infra-contract row never applies; every "needs_cloud_shell"
|
|
40
42
|
* stop names the upgrade command on the server instead of Cloud Shell.
|
|
43
|
+
*
|
|
44
|
+
* The converge job (#819, converge-job.ts) asks with `route: "converge"`:
|
|
45
|
+
* it runs as the install's provisioner, so an infrastructure change is what
|
|
46
|
+
* it is for — every other row still applies. An automatic run on the
|
|
47
|
+
* converge route needs the Owner's `autoConverge` (#864) on top of
|
|
48
|
+
* everything an automatic install needs; the scheduler queues one only
|
|
49
|
+
* then, and the job re-checks it here against the policy it claimed.
|
|
41
50
|
*/
|
|
42
51
|
|
|
43
52
|
import {
|
|
@@ -60,6 +69,7 @@ import { needsConverge } from "./upgrade/converge.js";
|
|
|
60
69
|
/** Machine-readable reason codes (`platform_update_requests.refusal_codes`). */
|
|
61
70
|
export type RefusalCode =
|
|
62
71
|
| "registry"
|
|
72
|
+
| "manifest-missing"
|
|
63
73
|
| "signature"
|
|
64
74
|
| "manifest-unreadable"
|
|
65
75
|
| "install-version-unknown"
|
|
@@ -134,6 +144,11 @@ export function assessUpgradeRequest(input: {
|
|
|
134
144
|
readonly now: Date;
|
|
135
145
|
/** Default `cloud-shell` (the GCP upgrader job). */
|
|
136
146
|
readonly manualPath?: ManualUpgradePath;
|
|
147
|
+
/**
|
|
148
|
+
* Which job asks (#819). `converge` = the converge job: an infrastructure
|
|
149
|
+
* contract bump is installed, not handed to Cloud Shell. Default `app_roll`.
|
|
150
|
+
*/
|
|
151
|
+
readonly route?: "app_roll" | "converge";
|
|
137
152
|
}): RequestDecision {
|
|
138
153
|
const { request, installed } = input;
|
|
139
154
|
const by = manualRun(input.manualPath ?? "cloud-shell");
|
|
@@ -142,10 +157,20 @@ export function assessUpgradeRequest(input: {
|
|
|
142
157
|
const e = input.manifest.error;
|
|
143
158
|
switch (e.code) {
|
|
144
159
|
case "registry":
|
|
160
|
+
// Only an outage is worth retrying: a manifest the registry says
|
|
161
|
+
// does not exist (404 / other 4xx) never appears, so retrying would
|
|
162
|
+
// loop on "try again later" forever.
|
|
163
|
+
if (e.transient) {
|
|
164
|
+
return stop(
|
|
165
|
+
"failed",
|
|
166
|
+
"registry",
|
|
167
|
+
`Could not reach the Caelo release registry to verify v${target} (${e.message}). Nothing was changed; try again later.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
145
170
|
return stop(
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
`
|
|
171
|
+
"refused",
|
|
172
|
+
"manifest-missing",
|
|
173
|
+
`v${target} has no signed release manifest in the Caelo release registry, so it cannot be verified and will not be installed. Nothing was changed; retrying will not help. (${e.message})`,
|
|
149
174
|
);
|
|
150
175
|
case "schema":
|
|
151
176
|
return stop(
|
|
@@ -170,7 +195,18 @@ export function assessUpgradeRequest(input: {
|
|
|
170
195
|
manifest,
|
|
171
196
|
);
|
|
172
197
|
}
|
|
173
|
-
|
|
198
|
+
const order = compareSemver(target, installed.version);
|
|
199
|
+
if (order === 0) {
|
|
200
|
+
// Reached after a run that rolled but could not record it (#860): the
|
|
201
|
+
// next run re-records the serving release, which then is the target.
|
|
202
|
+
return stop(
|
|
203
|
+
"refused",
|
|
204
|
+
"not-newer",
|
|
205
|
+
`v${target} already serves on this install, so there is nothing to install. Nothing was changed.`,
|
|
206
|
+
manifest,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (order < 0) {
|
|
174
210
|
return stop(
|
|
175
211
|
"refused",
|
|
176
212
|
"not-newer",
|
|
@@ -188,20 +224,33 @@ export function assessUpgradeRequest(input: {
|
|
|
188
224
|
}
|
|
189
225
|
// Self-hosted: the target's own CLI renders the host setup in this run.
|
|
190
226
|
const infraContractVersion = installed.infraContractVersion ?? manifest.infraContractVersion;
|
|
191
|
-
|
|
227
|
+
const converge = input.route === "converge";
|
|
228
|
+
const infraChange =
|
|
192
229
|
installed.infraContractVersion !== null &&
|
|
193
|
-
needsConverge({ infraContractVersion }, manifest.infraContractVersion)
|
|
194
|
-
) {
|
|
230
|
+
needsConverge({ infraContractVersion }, manifest.infraContractVersion);
|
|
231
|
+
if (infraChange && !converge) {
|
|
195
232
|
return stop(
|
|
196
233
|
"needs_cloud_shell",
|
|
197
234
|
"infra-contract",
|
|
198
|
-
`v${target} changes this install's cloud setup (infrastructure contract ${infraContractVersion} → ${manifest.infraContractVersion}: ${infraStepsText(infraContractVersion, manifest.infraContractVersion)}). The
|
|
235
|
+
`v${target} changes this install's cloud setup (infrastructure contract ${infraContractVersion} → ${manifest.infraContractVersion}: ${infraStepsText(infraContractVersion, manifest.infraContractVersion)}). The upgrader may not change cloud permissions, so Caelo installs this release with its converge job: start it again from Settings → Updates. On an install without the converge job, install it once with your own Google account in Cloud Shell.`,
|
|
236
|
+
manifest,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (converge && request.trigger === "auto" && !input.policy.autoConverge) {
|
|
240
|
+
return stop(
|
|
241
|
+
"refused",
|
|
242
|
+
"policy",
|
|
243
|
+
`Not installed automatically: v${target} changes this install's cloud setup, and the update policy does not install those automatically. Install it from Settings → Updates.`,
|
|
199
244
|
manifest,
|
|
200
245
|
);
|
|
201
246
|
}
|
|
247
|
+
// On the converge route the contract bump is the job's work, not a
|
|
248
|
+
// reason for a manual install: only the migrations decide below.
|
|
202
249
|
const assessment = assessAutoInstall(manifest, {
|
|
203
250
|
version: installed.version,
|
|
204
|
-
infraContractVersion
|
|
251
|
+
infraContractVersion: converge
|
|
252
|
+
? Math.max(infraContractVersion, manifest.infraContractVersion)
|
|
253
|
+
: infraContractVersion,
|
|
205
254
|
});
|
|
206
255
|
const destructive = assessment.mode === "manual";
|
|
207
256
|
const warnings: string[] = [];
|
package/src/upgrader-gcp.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* The real side effects of the upgrader job on Google Cloud (epic #696):
|
|
5
5
|
* gcloud as the job's own service account, the install's ops bucket for
|
|
6
|
-
* install.json, the fixed release registry, the migration job
|
|
7
|
-
* health probe. upgrader-job.ts takes them as {@link UpgraderJobDeps}; the
|
|
6
|
+
* install.json, the fixed release registry, the migration job, the IAP
|
|
7
|
+
* health probe and the IAM self-check (#818). upgrader-job.ts takes them as {@link UpgraderJobDeps}; the
|
|
8
8
|
* database queue and the job log come from `@caelo-cms/platform-updater`,
|
|
9
9
|
* which owns the Query API connection.
|
|
10
10
|
*
|
|
@@ -24,12 +24,14 @@ import { readAdminConfig, runMigrationsViaCloudRunJob } from "./migration-runner
|
|
|
24
24
|
import { fetchVerifiedReleaseManifest } from "./release-manifest.js";
|
|
25
25
|
import type { HttpFetch } from "./runtime-secrets.js";
|
|
26
26
|
import { opsBucketName } from "./state-backend.js";
|
|
27
|
-
import { acquireBucketStateLock, GcsStateStore } from "./state-store.js";
|
|
27
|
+
import { acquireBucketStateLock, GcsStateStore, realSyncGcloud } from "./state-store.js";
|
|
28
28
|
import type { AppRollPlan } from "./upgrade/app-roll.js";
|
|
29
29
|
import { findServiceUrl } from "./upgrade/gcp-lookup.js";
|
|
30
30
|
import { createIapHealthProbe } from "./upgrade/health.js";
|
|
31
|
+
import { restIamTester } from "./upgrader-iam-preflight.js";
|
|
31
32
|
import type { UpdateQueue, UpgraderJobDeps } from "./upgrader-job.js";
|
|
32
33
|
import type { JobLog } from "./upgrader-reporter.js";
|
|
34
|
+
import { UPGRADER_TASK_TIMEOUT_MINUTES } from "./upgrader-setup.js";
|
|
33
35
|
|
|
34
36
|
/** Where the job runs, from the metadata server. */
|
|
35
37
|
export interface UpgraderPlacement {
|
|
@@ -55,8 +57,11 @@ export async function upgraderDatabaseHost(
|
|
|
55
57
|
return cfg.databaseHost;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
/**
|
|
59
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The IAP-backed deep health probe for `plan`'s install (the upgrader job;
|
|
62
|
+
* the converge job, #819, too).
|
|
63
|
+
*/
|
|
64
|
+
export async function healthProbeFor(run: GcloudRunner, http: HttpFetch, plan: AppRollPlan) {
|
|
60
65
|
const { install } = plan;
|
|
61
66
|
const urls: Partial<Record<"admin" | "gateway", string>> = {};
|
|
62
67
|
for (const s of plan.services) {
|
|
@@ -102,12 +107,46 @@ export function gcpUpgraderDeps(
|
|
|
102
107
|
region: placement.region,
|
|
103
108
|
env: GCP_STACK_ENV,
|
|
104
109
|
execution: placement.execution,
|
|
110
|
+
// #818 — the IAM self-check asks as the job's own account (gcloud's
|
|
111
|
+
// token from the metadata server), so it sees exactly the job's grants.
|
|
112
|
+
iam: restIamTester({
|
|
113
|
+
http,
|
|
114
|
+
token: async () => {
|
|
115
|
+
const t = await run(["auth", "print-access-token"]);
|
|
116
|
+
if (!t.ok) throw new Error(`gcloud auth print-access-token: ${t.stderr.trim()}`);
|
|
117
|
+
return t.stdout.trim();
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
105
120
|
// The default deps: the fixed registry and signer, never overridden.
|
|
106
121
|
fetchManifest: (version) => fetchVerifiedReleaseManifest(version),
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
// #815 — the lock names this execution, so a later run can break it
|
|
123
|
+
// once Cloud Run says this one has ended (killed at its task timeout,
|
|
124
|
+
// OOM). Outside Cloud Run there is no execution to vouch for it.
|
|
125
|
+
lockState: (onBreak) =>
|
|
126
|
+
acquireBucketStateLock(
|
|
127
|
+
opsBucketName(placement.projectId),
|
|
128
|
+
"caelo upgrader job",
|
|
129
|
+
realSyncGcloud,
|
|
130
|
+
{
|
|
131
|
+
onBreak,
|
|
132
|
+
...(placement.execution
|
|
133
|
+
? {
|
|
134
|
+
holder: {
|
|
135
|
+
execution: {
|
|
136
|
+
projectId: placement.projectId,
|
|
137
|
+
region: placement.region,
|
|
138
|
+
name: placement.execution,
|
|
139
|
+
},
|
|
140
|
+
maxRuntimeMs: UPGRADER_TASK_TIMEOUT_MINUTES * 60_000,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
: {}),
|
|
144
|
+
},
|
|
145
|
+
),
|
|
146
|
+
// Uncached on purpose: the job re-reads install.json once it holds the
|
|
147
|
+
// lock to see whether another writer changed it meanwhile (#815).
|
|
109
148
|
readInstall: () => {
|
|
110
|
-
const text = store.
|
|
149
|
+
const text = store.peek("install.json");
|
|
111
150
|
return text === null ? null : (JSON.parse(text) as InstallMetadata);
|
|
112
151
|
},
|
|
113
152
|
writeInstall: (meta) => store.write("install.json", meta),
|