@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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Set up (and converge) the converge job on a Google Cloud install (`gcp`,
|
|
5
|
+
* `gcp-firebase`), #819. Run by the wizard (laptop, Cloud Shell or the
|
|
6
|
+
* install job) after migrations and by every converge — `cms-provision
|
|
7
|
+
* upgrade` and the converge job itself — so the job always runs the ops
|
|
8
|
+
* image of the release the install last converged to.
|
|
9
|
+
*
|
|
10
|
+
* What it is. A release that raises the infrastructure contract
|
|
11
|
+
* (infra-contract.ts) needs rights the upgrader job deliberately lacks:
|
|
12
|
+
* IAM bindings, service accounts, secrets. Until #819 that meant the Owner
|
|
13
|
+
* ran the upgrade from a terminal with their own Google account. The
|
|
14
|
+
* converge job runs the very same upgrade (upgrade/upgrade.ts: backup,
|
|
15
|
+
* converge, roll, health check, rollback, records) from inside the
|
|
16
|
+
* install's project, as the install's existing `caelo-provisioner` service
|
|
17
|
+
* account — the account the install job already runs `pulumi up` as and
|
|
18
|
+
* that already holds every right a converge needs (gcloud.ts
|
|
19
|
+
* PROVISIONER_ROLES). No new privileged identity is created.
|
|
20
|
+
*
|
|
21
|
+
* Trust boundary — the same shape as the upgrader job (upgrader-setup.ts)
|
|
22
|
+
* and the operator-access job. The admin's runtime service account gets
|
|
23
|
+
* exactly two roles, both on this one job:
|
|
24
|
+
*
|
|
25
|
+
* - `roles/run.jobsExecutor`: `run.jobs.run` WITHOUT
|
|
26
|
+
* `run.jobs.runWithOverrides` — it may start the job, never change its
|
|
27
|
+
* image, command, args or env (cloud.google.com/run/docs/reference/iam/roles);
|
|
28
|
+
* - `roles/run.viewer`: read its executions (the page's "Continue" for a
|
|
29
|
+
* run that died, and the dead-run check).
|
|
30
|
+
*
|
|
31
|
+
* The job takes no input but the database: its only input is the request
|
|
32
|
+
* row's target version. It fetches that release's manifest from the FIXED
|
|
33
|
+
* registry, verifies its signature with cosign, refuses a downgrade or a
|
|
34
|
+
* release below `minFromVersion`, and then runs only that release's signed
|
|
35
|
+
* ops image, pinned by the digest the manifest names (converge-job.ts). So a
|
|
36
|
+
* compromised admin can at most make the install converge to a newer
|
|
37
|
+
* signed Caelo release — what the Owner's click does anyway.
|
|
38
|
+
*
|
|
39
|
+
* What the provisioner gets here, on top of what it holds since install:
|
|
40
|
+
*
|
|
41
|
+
* - {@link INSTALL_JOB_ROLES} (the operator-access step of a converge
|
|
42
|
+
* creates a custom role and a Cloud Scheduler job, and the job's lines
|
|
43
|
+
* must reach Cloud Logging) — what an install-job install already
|
|
44
|
+
* grants; not an escalation, the provisioner holds projectIamAdmin;
|
|
45
|
+
* - a Cloud SQL IAM database login that is a member of `platform_updater`
|
|
46
|
+
* only (migration 0267): it reads its request and writes the timeline,
|
|
47
|
+
* nothing else in the database.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import { ensureCloudSqlIamUser } from "./cloudsql-iam-user.js";
|
|
51
|
+
import { gcloud as defaultGcloud, type GcloudResult } from "./gcloud.js";
|
|
52
|
+
import { type GcloudRunner, realSleep, runWithRetry, type Sleep } from "./gcloud-retry.js";
|
|
53
|
+
import { convergeJobName, runServiceAccountEmail } from "./gcp-names.js";
|
|
54
|
+
import { INSTALL_JOB_ROLES } from "./install-job.js";
|
|
55
|
+
import { provisionerServiceAccountEmail } from "./provisioner-identity.js";
|
|
56
|
+
import { opsBucketName } from "./state-backend.js";
|
|
57
|
+
import { UPGRADER_DB_ROLE } from "./upgrader-setup.js";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The job's task timeout in minutes, and the lease on the state lock it
|
|
61
|
+
* holds (state-lock-lease.ts): after it, Cloud Run has killed the run and a
|
|
62
|
+
* new execution resumes from the checkpoint. A converge plus a roll takes
|
|
63
|
+
* 10–20 minutes; the live 0.10.51 run needed 7 before its roll.
|
|
64
|
+
*/
|
|
65
|
+
export const CONVERGE_TASK_TIMEOUT_MINUTES = 60;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Exactly what the admin's runtime service account may do on the converge
|
|
69
|
+
* job — start it (no overrides) and read its runs. The setup grants these
|
|
70
|
+
* two and nothing else; the tests assert it.
|
|
71
|
+
*/
|
|
72
|
+
export const CONVERGE_ADMIN_ROLES = ["roles/run.jobsExecutor", "roles/run.viewer"] as const;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The job's entry point and its one argument. `caelo-converge-job`
|
|
76
|
+
* (packages/provisioning/ops/caelo-converge-job, installed by
|
|
77
|
+
* Dockerfile.ops) refuses every other argument and starts the job with a
|
|
78
|
+
* scrubbed environment, so even an execution override could not turn the
|
|
79
|
+
* provisioner's job into `destroy` or point gcloud/Pulumi elsewhere
|
|
80
|
+
* (#883 review). Nobody holds run.jobs.runWithOverrides on it anyway.
|
|
81
|
+
*/
|
|
82
|
+
export const CONVERGE_JOB_ENTRYPOINT = "caelo-converge-job";
|
|
83
|
+
export const CONVERGE_JOB_COMMAND = "converge-job";
|
|
84
|
+
|
|
85
|
+
/** Resources: the converge runs gcloud + Pulumi's Node language host for the stack pin. */
|
|
86
|
+
const JOB_CPU = "2";
|
|
87
|
+
const JOB_MEMORY = "4Gi";
|
|
88
|
+
|
|
89
|
+
export interface ConvergeSetupTarget {
|
|
90
|
+
readonly projectId: string;
|
|
91
|
+
readonly region: string;
|
|
92
|
+
/** Pulumi stack name (`production`). */
|
|
93
|
+
readonly env: string;
|
|
94
|
+
/** The caelo-ops image to run, digest-pinned (`…/ops@sha256:…`). */
|
|
95
|
+
readonly opsImageRef: string;
|
|
96
|
+
/** VPC the admin uses to reach Cloud SQL's private IP (the job's queue does too). */
|
|
97
|
+
readonly network: string;
|
|
98
|
+
readonly subnet: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type ConvergeSetupResult =
|
|
102
|
+
| { readonly ok: true; readonly done: string[] }
|
|
103
|
+
| {
|
|
104
|
+
readonly ok: false;
|
|
105
|
+
readonly done: string[];
|
|
106
|
+
readonly error: string;
|
|
107
|
+
/**
|
|
108
|
+
* `no-provisioner`: this install has no caelo-provisioner, which the
|
|
109
|
+
* job would run as. Not a failure of the converge (#883 review): such
|
|
110
|
+
* an install keeps installing cloud-setup releases from a terminal,
|
|
111
|
+
* and its admin, without CAELO_CONVERGE_JOB, says so.
|
|
112
|
+
*/
|
|
113
|
+
readonly reason?: "no-provisioner";
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The `gcloud run jobs deploy` argv of the converge job: the ops image by
|
|
118
|
+
* digest, the provisioner as its identity, no env and no secrets, the one
|
|
119
|
+
* fixed subcommand. Exported for the tests that pin the boundary.
|
|
120
|
+
*/
|
|
121
|
+
export function convergeJobDeployArgs(target: ConvergeSetupTarget): string[] {
|
|
122
|
+
return [
|
|
123
|
+
"run",
|
|
124
|
+
"jobs",
|
|
125
|
+
"deploy",
|
|
126
|
+
convergeJobName(target.env),
|
|
127
|
+
`--image=${target.opsImageRef}`,
|
|
128
|
+
`--region=${target.region}`,
|
|
129
|
+
`--project=${target.projectId}`,
|
|
130
|
+
`--service-account=${provisionerServiceAccountEmail(target.projectId)}`,
|
|
131
|
+
`--network=${target.network}`,
|
|
132
|
+
`--subnet=${target.subnet}`,
|
|
133
|
+
"--vpc-egress=private-ranges-only",
|
|
134
|
+
`--command=${CONVERGE_JOB_ENTRYPOINT}`,
|
|
135
|
+
`--args=${CONVERGE_JOB_COMMAND}`,
|
|
136
|
+
// No env and no secrets on the job, whatever an earlier deploy set.
|
|
137
|
+
"--clear-env-vars",
|
|
138
|
+
"--clear-secrets",
|
|
139
|
+
"--tasks=1",
|
|
140
|
+
// A retry would start a second converge inside the same execution; a
|
|
141
|
+
// killed run is resumed by starting the job again (checkpointed).
|
|
142
|
+
"--max-retries=0",
|
|
143
|
+
`--task-timeout=${CONVERGE_TASK_TIMEOUT_MINUTES}m`,
|
|
144
|
+
`--cpu=${JOB_CPU}`,
|
|
145
|
+
`--memory=${JOB_MEMORY}`,
|
|
146
|
+
"--labels=caelo-role=converge",
|
|
147
|
+
"--quiet",
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Ensure the converge job and everything it needs. Never throws for a gcloud failure. */
|
|
152
|
+
export async function ensureConvergeJob(
|
|
153
|
+
target: ConvergeSetupTarget,
|
|
154
|
+
deps: { run?: GcloudRunner; sleep?: Sleep } = {},
|
|
155
|
+
): Promise<ConvergeSetupResult> {
|
|
156
|
+
const run = deps.run ?? defaultGcloud;
|
|
157
|
+
const sleep = deps.sleep ?? realSleep;
|
|
158
|
+
const retry = (args: string[]) => runWithRetry(run, sleep, args);
|
|
159
|
+
const { projectId, region, env } = target;
|
|
160
|
+
const project = `--project=${projectId}`;
|
|
161
|
+
const provisioner = provisionerServiceAccountEmail(projectId);
|
|
162
|
+
const member = `serviceAccount:${provisioner}`;
|
|
163
|
+
const adminMember = `serviceAccount:${runServiceAccountEmail(projectId, env)}`;
|
|
164
|
+
const job = convergeJobName(env);
|
|
165
|
+
const bucket = opsBucketName(projectId);
|
|
166
|
+
const done: string[] = [];
|
|
167
|
+
const fail = (step: string, r: GcloudResult): ConvergeSetupResult => ({
|
|
168
|
+
ok: false,
|
|
169
|
+
done,
|
|
170
|
+
error: `${step}: ${r.stderr.trim()}`,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!/@sha256:[0-9a-f]{64}$/.test(target.opsImageRef)) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
done,
|
|
177
|
+
error: `the converge job's image must be pinned by digest, got ${target.opsImageRef}`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The job attaches the install from its ops bucket and keeps its
|
|
182
|
+
// checkpoints there; an install whose state is on a laptop has neither.
|
|
183
|
+
const bucketCheck = await run([
|
|
184
|
+
"storage",
|
|
185
|
+
"buckets",
|
|
186
|
+
"describe",
|
|
187
|
+
`gs://${bucket}`,
|
|
188
|
+
project,
|
|
189
|
+
"--format=value(name)",
|
|
190
|
+
]);
|
|
191
|
+
if (!bucketCheck.ok) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
done,
|
|
195
|
+
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`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// The job's identity is the install's provisioner — never created here:
|
|
200
|
+
// an install without one (installed before it existed) keeps converging
|
|
201
|
+
// from a terminal.
|
|
202
|
+
const describe = await run([
|
|
203
|
+
"iam",
|
|
204
|
+
"service-accounts",
|
|
205
|
+
"describe",
|
|
206
|
+
provisioner,
|
|
207
|
+
project,
|
|
208
|
+
"--format=value(email)",
|
|
209
|
+
]);
|
|
210
|
+
if (!describe.ok) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
done,
|
|
214
|
+
reason: "no-provisioner",
|
|
215
|
+
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).`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 1. What a converge run as the provisioner needs beyond the install roles.
|
|
220
|
+
for (const role of INSTALL_JOB_ROLES) {
|
|
221
|
+
const r = await retry([
|
|
222
|
+
"projects",
|
|
223
|
+
"add-iam-policy-binding",
|
|
224
|
+
projectId,
|
|
225
|
+
`--member=${member}`,
|
|
226
|
+
`--role=${role}`,
|
|
227
|
+
"--condition=None",
|
|
228
|
+
"--quiet",
|
|
229
|
+
"--format=none",
|
|
230
|
+
]);
|
|
231
|
+
if (!r.ok) return fail(`grant the provisioner ${role}`, r);
|
|
232
|
+
done.push(`provisioner: ${role} on the project`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 2. Its database login: the request row and the timeline, nothing else.
|
|
236
|
+
const sqlUser = await ensureCloudSqlIamUser(run, {
|
|
237
|
+
projectId,
|
|
238
|
+
env,
|
|
239
|
+
serviceAccountEmail: provisioner,
|
|
240
|
+
databaseRole: UPGRADER_DB_ROLE,
|
|
241
|
+
});
|
|
242
|
+
done.push(...sqlUser.done);
|
|
243
|
+
if (!sqlUser.ok) return { ok: false, done, error: sqlUser.error };
|
|
244
|
+
|
|
245
|
+
// 3. The job itself.
|
|
246
|
+
const deploy = await run(convergeJobDeployArgs(target));
|
|
247
|
+
if (!deploy.ok) return fail(`deploy job ${job}`, deploy);
|
|
248
|
+
done.push(`job ${job} (${target.opsImageRef.split("@")[1]?.slice(0, 19)}...)`);
|
|
249
|
+
|
|
250
|
+
// 4. The admin may start it (no overrides) and read its runs — nothing else.
|
|
251
|
+
for (const role of CONVERGE_ADMIN_ROLES) {
|
|
252
|
+
const r = await retry([
|
|
253
|
+
"run",
|
|
254
|
+
"jobs",
|
|
255
|
+
"add-iam-policy-binding",
|
|
256
|
+
job,
|
|
257
|
+
`--region=${region}`,
|
|
258
|
+
project,
|
|
259
|
+
`--member=${adminMember}`,
|
|
260
|
+
`--role=${role}`,
|
|
261
|
+
"--quiet",
|
|
262
|
+
"--format=none",
|
|
263
|
+
]);
|
|
264
|
+
if (!r.ok) return fail(`grant the admin ${role} on ${job}`, r);
|
|
265
|
+
done.push(`admin: ${role} on ${job}`);
|
|
266
|
+
}
|
|
267
|
+
return { ok: true, done };
|
|
268
|
+
}
|