@caelo-cms/provisioning 0.10.55 → 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 +76 -19
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +4 -1
- 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 +9 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +13 -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 +22 -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/release-contract.d.ts +3 -2
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +5 -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 +167 -3
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +171 -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/upgrade/app-roll.d.ts +36 -7
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +53 -19
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +111 -9
- 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 +123 -11
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +11 -3
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +99 -4
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/agent/run.test.ts +14 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +4 -1
- 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 +15 -0
- 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 +22 -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/release-contract.ts +5 -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 +30 -2
- package/src/stack-contract.ts +291 -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/upgrade/app-roll.ts +96 -21
- package/src/upgrade/converge.ts +130 -8
- 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 +203 -3
- package/src/upgrade/upgrade.ts +120 -24
- package/src/upgrader-assess.test.ts +92 -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 +586 -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 +3 -3
- package/src/upgrader-setup.ts +128 -11
- package/src/wizards/gcp-cost.ts +12 -3
- package/src/wizards/gcp.ts +125 -4
- package/stacks/aws/index.ts +10 -0
- package/stacks/azure/index.ts +9 -0
- package/stacks/gcp/index.ts +46 -7
- package/stacks/gcp-firebase/index.ts +37 -6
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Set up (and converge) the converge job on a Google Cloud install (`gcp`,
|
|
4
|
+
* `gcp-firebase`), #819. Run by the wizard (laptop, Cloud Shell or the
|
|
5
|
+
* install job) after migrations and by every converge — `cms-provision
|
|
6
|
+
* upgrade` and the converge job itself — so the job always runs the ops
|
|
7
|
+
* image of the release the install last converged to.
|
|
8
|
+
*
|
|
9
|
+
* What it is. A release that raises the infrastructure contract
|
|
10
|
+
* (infra-contract.ts) needs rights the upgrader job deliberately lacks:
|
|
11
|
+
* IAM bindings, service accounts, secrets. Until #819 that meant the Owner
|
|
12
|
+
* ran the upgrade from a terminal with their own Google account. The
|
|
13
|
+
* converge job runs the very same upgrade (upgrade/upgrade.ts: backup,
|
|
14
|
+
* converge, roll, health check, rollback, records) from inside the
|
|
15
|
+
* install's project, as the install's existing `caelo-provisioner` service
|
|
16
|
+
* account — the account the install job already runs `pulumi up` as and
|
|
17
|
+
* that already holds every right a converge needs (gcloud.ts
|
|
18
|
+
* PROVISIONER_ROLES). No new privileged identity is created.
|
|
19
|
+
*
|
|
20
|
+
* Trust boundary — the same shape as the upgrader job (upgrader-setup.ts)
|
|
21
|
+
* and the operator-access job. The admin's runtime service account gets
|
|
22
|
+
* exactly two roles, both on this one job:
|
|
23
|
+
*
|
|
24
|
+
* - `roles/run.jobsExecutor`: `run.jobs.run` WITHOUT
|
|
25
|
+
* `run.jobs.runWithOverrides` — it may start the job, never change its
|
|
26
|
+
* image, command, args or env (cloud.google.com/run/docs/reference/iam/roles);
|
|
27
|
+
* - `roles/run.viewer`: read its executions (the page's "Continue" for a
|
|
28
|
+
* run that died, and the dead-run check).
|
|
29
|
+
*
|
|
30
|
+
* The job takes no input but the database: its only input is the request
|
|
31
|
+
* row's target version. It fetches that release's manifest from the FIXED
|
|
32
|
+
* registry, verifies its signature with cosign, refuses a downgrade or a
|
|
33
|
+
* release below `minFromVersion`, and then runs only that release's signed
|
|
34
|
+
* ops image, pinned by the digest the manifest names (converge-job.ts). So a
|
|
35
|
+
* compromised admin can at most make the install converge to a newer
|
|
36
|
+
* signed Caelo release — what the Owner's click does anyway.
|
|
37
|
+
*
|
|
38
|
+
* What the provisioner gets here, on top of what it holds since install:
|
|
39
|
+
*
|
|
40
|
+
* - {@link INSTALL_JOB_ROLES} (the operator-access step of a converge
|
|
41
|
+
* creates a custom role and a Cloud Scheduler job, and the job's lines
|
|
42
|
+
* must reach Cloud Logging) — what an install-job install already
|
|
43
|
+
* grants; not an escalation, the provisioner holds projectIamAdmin;
|
|
44
|
+
* - a Cloud SQL IAM database login that is a member of `platform_updater`
|
|
45
|
+
* only (migration 0267): it reads its request and writes the timeline,
|
|
46
|
+
* nothing else in the database.
|
|
47
|
+
*/
|
|
48
|
+
import { ensureCloudSqlIamUser } from "./cloudsql-iam-user.js";
|
|
49
|
+
import { gcloud as defaultGcloud } from "./gcloud.js";
|
|
50
|
+
import { realSleep, runWithRetry } from "./gcloud-retry.js";
|
|
51
|
+
import { convergeJobName, runServiceAccountEmail } from "./gcp-names.js";
|
|
52
|
+
import { INSTALL_JOB_ROLES } from "./install-job.js";
|
|
53
|
+
import { provisionerServiceAccountEmail } from "./provisioner-identity.js";
|
|
54
|
+
import { opsBucketName } from "./state-backend.js";
|
|
55
|
+
import { UPGRADER_DB_ROLE } from "./upgrader-setup.js";
|
|
56
|
+
/**
|
|
57
|
+
* The job's task timeout in minutes, and the lease on the state lock it
|
|
58
|
+
* holds (state-lock-lease.ts): after it, Cloud Run has killed the run and a
|
|
59
|
+
* new execution resumes from the checkpoint. A converge plus a roll takes
|
|
60
|
+
* 10–20 minutes; the live 0.10.51 run needed 7 before its roll.
|
|
61
|
+
*/
|
|
62
|
+
export const CONVERGE_TASK_TIMEOUT_MINUTES = 60;
|
|
63
|
+
/**
|
|
64
|
+
* Exactly what the admin's runtime service account may do on the converge
|
|
65
|
+
* job — start it (no overrides) and read its runs. The setup grants these
|
|
66
|
+
* two and nothing else; the tests assert it.
|
|
67
|
+
*/
|
|
68
|
+
export const CONVERGE_ADMIN_ROLES = ["roles/run.jobsExecutor", "roles/run.viewer"];
|
|
69
|
+
/**
|
|
70
|
+
* The job's entry point and its one argument. `caelo-converge-job`
|
|
71
|
+
* (packages/provisioning/ops/caelo-converge-job, installed by
|
|
72
|
+
* Dockerfile.ops) refuses every other argument and starts the job with a
|
|
73
|
+
* scrubbed environment, so even an execution override could not turn the
|
|
74
|
+
* provisioner's job into `destroy` or point gcloud/Pulumi elsewhere
|
|
75
|
+
* (#883 review). Nobody holds run.jobs.runWithOverrides on it anyway.
|
|
76
|
+
*/
|
|
77
|
+
export const CONVERGE_JOB_ENTRYPOINT = "caelo-converge-job";
|
|
78
|
+
export const CONVERGE_JOB_COMMAND = "converge-job";
|
|
79
|
+
/** Resources: the converge runs gcloud + Pulumi's Node language host for the stack pin. */
|
|
80
|
+
const JOB_CPU = "2";
|
|
81
|
+
const JOB_MEMORY = "4Gi";
|
|
82
|
+
/**
|
|
83
|
+
* The `gcloud run jobs deploy` argv of the converge job: the ops image by
|
|
84
|
+
* digest, the provisioner as its identity, no env and no secrets, the one
|
|
85
|
+
* fixed subcommand. Exported for the tests that pin the boundary.
|
|
86
|
+
*/
|
|
87
|
+
export function convergeJobDeployArgs(target) {
|
|
88
|
+
return [
|
|
89
|
+
"run",
|
|
90
|
+
"jobs",
|
|
91
|
+
"deploy",
|
|
92
|
+
convergeJobName(target.env),
|
|
93
|
+
`--image=${target.opsImageRef}`,
|
|
94
|
+
`--region=${target.region}`,
|
|
95
|
+
`--project=${target.projectId}`,
|
|
96
|
+
`--service-account=${provisionerServiceAccountEmail(target.projectId)}`,
|
|
97
|
+
`--network=${target.network}`,
|
|
98
|
+
`--subnet=${target.subnet}`,
|
|
99
|
+
"--vpc-egress=private-ranges-only",
|
|
100
|
+
`--command=${CONVERGE_JOB_ENTRYPOINT}`,
|
|
101
|
+
`--args=${CONVERGE_JOB_COMMAND}`,
|
|
102
|
+
// No env and no secrets on the job, whatever an earlier deploy set.
|
|
103
|
+
"--clear-env-vars",
|
|
104
|
+
"--clear-secrets",
|
|
105
|
+
"--tasks=1",
|
|
106
|
+
// A retry would start a second converge inside the same execution; a
|
|
107
|
+
// killed run is resumed by starting the job again (checkpointed).
|
|
108
|
+
"--max-retries=0",
|
|
109
|
+
`--task-timeout=${CONVERGE_TASK_TIMEOUT_MINUTES}m`,
|
|
110
|
+
`--cpu=${JOB_CPU}`,
|
|
111
|
+
`--memory=${JOB_MEMORY}`,
|
|
112
|
+
"--labels=caelo-role=converge",
|
|
113
|
+
"--quiet",
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
/** Ensure the converge job and everything it needs. Never throws for a gcloud failure. */
|
|
117
|
+
export async function ensureConvergeJob(target, deps = {}) {
|
|
118
|
+
const run = deps.run ?? defaultGcloud;
|
|
119
|
+
const sleep = deps.sleep ?? realSleep;
|
|
120
|
+
const retry = (args) => runWithRetry(run, sleep, args);
|
|
121
|
+
const { projectId, region, env } = target;
|
|
122
|
+
const project = `--project=${projectId}`;
|
|
123
|
+
const provisioner = provisionerServiceAccountEmail(projectId);
|
|
124
|
+
const member = `serviceAccount:${provisioner}`;
|
|
125
|
+
const adminMember = `serviceAccount:${runServiceAccountEmail(projectId, env)}`;
|
|
126
|
+
const job = convergeJobName(env);
|
|
127
|
+
const bucket = opsBucketName(projectId);
|
|
128
|
+
const done = [];
|
|
129
|
+
const fail = (step, r) => ({
|
|
130
|
+
ok: false,
|
|
131
|
+
done,
|
|
132
|
+
error: `${step}: ${r.stderr.trim()}`,
|
|
133
|
+
});
|
|
134
|
+
if (!/@sha256:[0-9a-f]{64}$/.test(target.opsImageRef)) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
done,
|
|
138
|
+
error: `the converge job's image must be pinned by digest, got ${target.opsImageRef}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// The job attaches the install from its ops bucket and keeps its
|
|
142
|
+
// checkpoints there; an install whose state is on a laptop has neither.
|
|
143
|
+
const bucketCheck = await run([
|
|
144
|
+
"storage",
|
|
145
|
+
"buckets",
|
|
146
|
+
"describe",
|
|
147
|
+
`gs://${bucket}`,
|
|
148
|
+
project,
|
|
149
|
+
"--format=value(name)",
|
|
150
|
+
]);
|
|
151
|
+
if (!bucketCheck.ok) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
done,
|
|
155
|
+
error: `the ops bucket gs://${bucket} is not there (${bucketCheck.stderr.trim()}). Move the install's state into its own project first: cms-provision state migrate`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// The job's identity is the install's provisioner — never created here:
|
|
159
|
+
// an install without one (installed before it existed) keeps converging
|
|
160
|
+
// from a terminal.
|
|
161
|
+
const describe = await run([
|
|
162
|
+
"iam",
|
|
163
|
+
"service-accounts",
|
|
164
|
+
"describe",
|
|
165
|
+
provisioner,
|
|
166
|
+
project,
|
|
167
|
+
"--format=value(email)",
|
|
168
|
+
]);
|
|
169
|
+
if (!describe.ok) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
done,
|
|
173
|
+
reason: "no-provisioner",
|
|
174
|
+
error: `this install has no ${provisioner} service account (${describe.stderr.trim()}), which the converge job runs as. Updates that change the cloud setup keep running from a terminal (Settings → Updates shows the command).`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// 1. What a converge run as the provisioner needs beyond the install roles.
|
|
178
|
+
for (const role of INSTALL_JOB_ROLES) {
|
|
179
|
+
const r = await retry([
|
|
180
|
+
"projects",
|
|
181
|
+
"add-iam-policy-binding",
|
|
182
|
+
projectId,
|
|
183
|
+
`--member=${member}`,
|
|
184
|
+
`--role=${role}`,
|
|
185
|
+
"--condition=None",
|
|
186
|
+
"--quiet",
|
|
187
|
+
"--format=none",
|
|
188
|
+
]);
|
|
189
|
+
if (!r.ok)
|
|
190
|
+
return fail(`grant the provisioner ${role}`, r);
|
|
191
|
+
done.push(`provisioner: ${role} on the project`);
|
|
192
|
+
}
|
|
193
|
+
// 2. Its database login: the request row and the timeline, nothing else.
|
|
194
|
+
const sqlUser = await ensureCloudSqlIamUser(run, {
|
|
195
|
+
projectId,
|
|
196
|
+
env,
|
|
197
|
+
serviceAccountEmail: provisioner,
|
|
198
|
+
databaseRole: UPGRADER_DB_ROLE,
|
|
199
|
+
});
|
|
200
|
+
done.push(...sqlUser.done);
|
|
201
|
+
if (!sqlUser.ok)
|
|
202
|
+
return { ok: false, done, error: sqlUser.error };
|
|
203
|
+
// 3. The job itself.
|
|
204
|
+
const deploy = await run(convergeJobDeployArgs(target));
|
|
205
|
+
if (!deploy.ok)
|
|
206
|
+
return fail(`deploy job ${job}`, deploy);
|
|
207
|
+
done.push(`job ${job} (${target.opsImageRef.split("@")[1]?.slice(0, 19)}...)`);
|
|
208
|
+
// 4. The admin may start it (no overrides) and read its runs — nothing else.
|
|
209
|
+
for (const role of CONVERGE_ADMIN_ROLES) {
|
|
210
|
+
const r = await retry([
|
|
211
|
+
"run",
|
|
212
|
+
"jobs",
|
|
213
|
+
"add-iam-policy-binding",
|
|
214
|
+
job,
|
|
215
|
+
`--region=${region}`,
|
|
216
|
+
project,
|
|
217
|
+
`--member=${adminMember}`,
|
|
218
|
+
`--role=${role}`,
|
|
219
|
+
"--quiet",
|
|
220
|
+
"--format=none",
|
|
221
|
+
]);
|
|
222
|
+
if (!r.ok)
|
|
223
|
+
return fail(`grant the admin ${role} on ${job}`, r);
|
|
224
|
+
done.push(`admin: ${role} on ${job}`);
|
|
225
|
+
}
|
|
226
|
+
return { ok: true, done };
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=converge-job-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converge-job-setup.js","sourceRoot":"","sources":["../src/converge-job-setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The converge job (#819): installs a signed Caelo release that changes the
|
|
3
|
+
* install's cloud setup (raises the infrastructure contract), from inside
|
|
4
|
+
* the install's own project, started by the Owner's Update + password in
|
|
5
|
+
* Settings → Updates. It runs as the install's `caelo-provisioner` account
|
|
6
|
+
* (converge-job-setup.ts has the trust boundary); the upgrader job, which
|
|
7
|
+
* only rolls images, keeps every other release.
|
|
8
|
+
*
|
|
9
|
+
* One job, two stages, told apart by the release this code belongs to:
|
|
10
|
+
*
|
|
11
|
+
* Stage 1 — the job still runs the ops image of the INSTALLED release.
|
|
12
|
+
* It claims the request, reads install.json from the ops bucket,
|
|
13
|
+
* fetches the target's release manifest from the FIXED registry and
|
|
14
|
+
* verifies its signature (release-manifest.ts), and decides
|
|
15
|
+
* (upgrader-assess.ts, `route: "converge"`): no downgrade, nothing
|
|
16
|
+
* below minFromVersion, no automatic converge. Then it verifies the
|
|
17
|
+
* target's ops image, points this job at it BY DIGEST and starts a new
|
|
18
|
+
* execution, handing the request over to it. It changes nothing else:
|
|
19
|
+
* the converge logic always comes from the release it installs.
|
|
20
|
+
*
|
|
21
|
+
* Stage 2 — the job runs the TARGET release's ops image (its own
|
|
22
|
+
* version is the request's). It verifies the manifest again, checks
|
|
23
|
+
* that the image it runs is the one the manifest signs, takes the
|
|
24
|
+
* install's state lock (#815; the upgrader job and the terminal
|
|
25
|
+
* `upgrade` take the same one, so they exclude each other), and runs
|
|
26
|
+
* the very upgrade the terminal runs (upgrade/upgrade.ts): backup →
|
|
27
|
+
* converge + app-roll → deep health check, rollback on failure →
|
|
28
|
+
* install.json + the infrastructure contract → the request's end.
|
|
29
|
+
*
|
|
30
|
+
* Resume. Every stage-2 fact a successor must not recompute (the backup,
|
|
31
|
+
* the revisions that served before, the release it judges from) is in the
|
|
32
|
+
* checkpoint (converge-checkpoint.ts). A killed execution leaves its
|
|
33
|
+
* request `running`; starting the job again (the page's "Continue", or
|
|
34
|
+
* any new request for the same release) adopts it once Cloud Run says the
|
|
35
|
+
* old execution has ended, and resumes from the checkpoint.
|
|
36
|
+
*
|
|
37
|
+
* Inputs. The job takes no argument and no env: the request row (a
|
|
38
|
+
* version), install.json in the ops bucket (which the admin cannot write)
|
|
39
|
+
* and the signed manifest decide everything. All side effects come in
|
|
40
|
+
* through {@link ConvergeJobDeps}, so the run is tested against fakes;
|
|
41
|
+
* converge-job-gcp.ts wires the real ones and `@caelo-cms/platform-updater`
|
|
42
|
+
* the database (`caelo-ops converge-job`).
|
|
43
|
+
*/
|
|
44
|
+
import type { ConvergeCheckpoint } from "./converge-checkpoint.js";
|
|
45
|
+
import { type ExecutionState } from "./install-job.js";
|
|
46
|
+
import type { InstallMetadata } from "./install-state.js";
|
|
47
|
+
import { type ReleaseManifest, type ReleaseManifestResult } from "./release-manifest.js";
|
|
48
|
+
import type { UpgradeCheckpoint } from "./upgrade/upgrade.js";
|
|
49
|
+
import { type ClaimedUpdate, type UpdateQueue, type UpgraderRunResult } from "./upgrader-job.js";
|
|
50
|
+
import { type EventReporter, type JobLog } from "./upgrader-reporter.js";
|
|
51
|
+
/** What the converge claim found. */
|
|
52
|
+
export interface ConvergeClaim {
|
|
53
|
+
readonly request: ClaimedUpdate;
|
|
54
|
+
/**
|
|
55
|
+
* The request was already running: stage 1 handed it to this run, or a
|
|
56
|
+
* run that died left it. Fresh claims (pending → running) are false.
|
|
57
|
+
*/
|
|
58
|
+
readonly resume: boolean;
|
|
59
|
+
/** The execution the request names now (after a fresh claim: this one). */
|
|
60
|
+
readonly execution: string | null;
|
|
61
|
+
}
|
|
62
|
+
/** The converge job's view of the database (the platform_updater role). */
|
|
63
|
+
export interface ConvergeQueue extends Omit<UpdateQueue, "claim"> {
|
|
64
|
+
/**
|
|
65
|
+
* End runs that died long ago (stale), then return the running converge
|
|
66
|
+
* request, or claim the oldest pending one.
|
|
67
|
+
*/
|
|
68
|
+
claimConverge(execution: string | null): Promise<ConvergeClaim | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Point running request `requestId` at execution `to` — or at none
|
|
71
|
+
* (`null`: released for the next run to adopt) — only while it still
|
|
72
|
+
* names `from`. Resolves false when another run got there first.
|
|
73
|
+
*/
|
|
74
|
+
handover(requestId: string, from: string | null, to: string | null): Promise<boolean>;
|
|
75
|
+
}
|
|
76
|
+
/** How stage 2's upgrade ended (upgrade/upgrade.ts, seen from the job). */
|
|
77
|
+
export type ConvergeUpgradeOutcome = {
|
|
78
|
+
readonly ok: true;
|
|
79
|
+
/** Every converge step succeeded and the new contract is recorded. */
|
|
80
|
+
readonly converged: boolean;
|
|
81
|
+
} | {
|
|
82
|
+
readonly ok: false;
|
|
83
|
+
/** Traffic went back to the revisions that served before. */
|
|
84
|
+
readonly rolledBack: boolean;
|
|
85
|
+
/** The new release serves but install.json could not record it (#860). */
|
|
86
|
+
readonly unrecorded: boolean;
|
|
87
|
+
};
|
|
88
|
+
export interface ConvergeJobDeps {
|
|
89
|
+
readonly queue: ConvergeQueue;
|
|
90
|
+
/** From the metadata server — never from env or argv. */
|
|
91
|
+
readonly projectId: string;
|
|
92
|
+
/** This Cloud Run execution; null outside Cloud Run. */
|
|
93
|
+
readonly execution: string | null;
|
|
94
|
+
/** The Caelo release this code belongs to (CAELO_VERSION of the image). */
|
|
95
|
+
readonly ownVersion: string;
|
|
96
|
+
/** fetchVerifiedReleaseManifest against the fixed registry. */
|
|
97
|
+
readonly fetchManifest: (version: string) => Promise<ReleaseManifestResult>;
|
|
98
|
+
/** install.json as the ops bucket holds it NOW (null when absent) — never cached. */
|
|
99
|
+
readonly readInstall: () => InstallMetadata | null;
|
|
100
|
+
/** Verify the cosign signature of the release image `slug@digest`. */
|
|
101
|
+
readonly verifyImage: (slug: "ops", digest: string) => Promise<boolean>;
|
|
102
|
+
/** The digest of the image this execution runs (from Cloud Run); null when unreadable. */
|
|
103
|
+
readonly ownImageDigest: () => Promise<string | null>;
|
|
104
|
+
/** Point this job at the ops image with `digest` (its identity, command and args stay). */
|
|
105
|
+
readonly pointJobAt: (digest: string) => Promise<{
|
|
106
|
+
readonly ok: true;
|
|
107
|
+
} | {
|
|
108
|
+
readonly ok: false;
|
|
109
|
+
readonly error: string;
|
|
110
|
+
}>;
|
|
111
|
+
/** Start a new execution of this job; returns its name. */
|
|
112
|
+
readonly startJob: () => Promise<{
|
|
113
|
+
readonly ok: true;
|
|
114
|
+
readonly execution: string;
|
|
115
|
+
} | {
|
|
116
|
+
readonly ok: false;
|
|
117
|
+
readonly error: string;
|
|
118
|
+
}>;
|
|
119
|
+
/** Where an execution of this job stands (Cloud Run). */
|
|
120
|
+
readonly executionState: (execution: string) => Promise<ExecutionState>;
|
|
121
|
+
/**
|
|
122
|
+
* Take the install's state lock (#815); returns its release. Throws
|
|
123
|
+
* StateLockHeldError when a live run holds it, any other error when it
|
|
124
|
+
* cannot be taken or judged.
|
|
125
|
+
*/
|
|
126
|
+
readonly lockState: (onBreak: (message: string) => void) => () => void;
|
|
127
|
+
readonly readCheckpoint: () => ConvergeCheckpoint | null;
|
|
128
|
+
readonly writeCheckpoint: (checkpoint: ConvergeCheckpoint) => void;
|
|
129
|
+
/** Stage 2: the terminal's upgrade (upgrade/upgrade.ts) to `manifest`, as this account. */
|
|
130
|
+
readonly upgrade: (input: {
|
|
131
|
+
readonly manifest: ReleaseManifest;
|
|
132
|
+
readonly reporter: EventReporter;
|
|
133
|
+
readonly resume: UpgradeCheckpoint | undefined;
|
|
134
|
+
readonly onCheckpoint: (checkpoint: UpgradeCheckpoint) => void;
|
|
135
|
+
}) => Promise<ConvergeUpgradeOutcome>;
|
|
136
|
+
readonly now: () => Date;
|
|
137
|
+
readonly log: JobLog;
|
|
138
|
+
}
|
|
139
|
+
/** Run the converge job once. Never throws; see the file comment. */
|
|
140
|
+
export declare function runConvergeJob(deps: ConvergeJobDeps): Promise<UpgraderRunResult>;
|
|
141
|
+
//# sourceMappingURL=converge-job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converge-job.d.ts","sourceRoot":"","sources":["../src/converge-job.ts"],"names":[],"mappings":"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"}
|