@caelo-cms/provisioning 0.11.9 → 0.11.11
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/adapter.d.ts +6 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/admin-domain.d.ts +10 -1
- package/dist/admin-domain.d.ts.map +1 -1
- package/dist/admin-domain.js +13 -3
- package/dist/admin-domain.js.map +1 -1
- package/dist/adopt-existing.d.ts +12 -3
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +143 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +23 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +32 -9
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/run.d.ts +2 -0
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +5 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +69 -10
- package/dist/aws-task-roles.d.ts +4 -2
- package/dist/aws-task-roles.d.ts.map +1 -1
- package/dist/aws-task-roles.js +4 -1
- package/dist/aws-task-roles.js.map +1 -1
- package/dist/caddy.d.ts +21 -4
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +27 -3
- 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 +68 -7
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +93 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +295 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +43 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +148 -23
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +1 -0
- package/dist/converge-checkpoint.d.ts.map +1 -1
- package/dist/converge-checkpoint.js +8 -1
- package/dist/converge-checkpoint.js.map +1 -1
- package/dist/converge-db-trust.d.ts +44 -0
- package/dist/converge-db-trust.d.ts.map +1 -0
- package/dist/converge-db-trust.js +158 -0
- package/dist/converge-db-trust.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +5 -2
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +11 -13
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +8 -0
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job.d.ts +17 -0
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +12 -0
- package/dist/converge-job.js.map +1 -1
- package/dist/deployed-release.d.ts +4 -1
- package/dist/deployed-release.d.ts.map +1 -1
- package/dist/deployed-release.js +18 -8
- package/dist/deployed-release.js.map +1 -1
- package/dist/gcp-names.d.ts +34 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +45 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/gcp-url-map.d.ts.map +1 -1
- package/dist/gcp-url-map.js +6 -2
- package/dist/gcp-url-map.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- 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 +45 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +4 -2
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +4 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +13 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +17 -4
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +20 -8
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +48 -0
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
- package/dist/mcp-auth-proxy-setup.js +280 -0
- package/dist/mcp-auth-proxy-setup.js.map +1 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +8 -3
- package/dist/migration-runner.js.map +1 -1
- package/dist/migration-targets.d.ts +11 -0
- package/dist/migration-targets.d.ts.map +1 -0
- package/dist/migration-targets.js +12 -0
- package/dist/migration-targets.js.map +1 -0
- package/dist/operator-access.d.ts +10 -0
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +5 -0
- package/dist/operator-access.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +13 -1
- package/dist/pool-budget-stack.d.ts.map +1 -1
- package/dist/pool-budget-stack.js +11 -1
- package/dist/pool-budget-stack.js.map +1 -1
- package/dist/public-role-isolation.d.ts +7 -3
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +25 -13
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +3 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/release-manifest.d.ts +25 -1
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +36 -3
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp.d.ts +55 -18
- package/dist/remote-mcp.d.ts.map +1 -1
- package/dist/remote-mcp.js +307 -77
- package/dist/remote-mcp.js.map +1 -1
- package/dist/runtime-secrets.d.ts +11 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +40 -25
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/scheduler-setup.d.ts +75 -0
- package/dist/scheduler-setup.d.ts.map +1 -0
- package/dist/scheduler-setup.js +331 -0
- package/dist/scheduler-setup.js.map +1 -0
- package/dist/seal-key-rotation.d.ts +7 -7
- package/dist/seal-key-rotation.d.ts.map +1 -1
- package/dist/seal-key-rotation.js +22 -22
- package/dist/seal-key-rotation.js.map +1 -1
- package/dist/self-hosted-db-tls.d.ts +37 -0
- package/dist/self-hosted-db-tls.d.ts.map +1 -0
- package/dist/self-hosted-db-tls.js +220 -0
- package/dist/self-hosted-db-tls.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 +33 -9
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +43 -2
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +87 -1
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-scheduler-interval.d.ts +21 -0
- package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
- package/dist/self-hosted-scheduler-interval.js +68 -0
- package/dist/self-hosted-scheduler-interval.js.map +1 -0
- package/dist/self-hosted-start.d.ts +0 -21
- package/dist/self-hosted-start.d.ts.map +1 -1
- package/dist/self-hosted-start.js +39 -1
- package/dist/self-hosted-start.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +19 -7
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +78 -25
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +234 -28
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +397 -39
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +31 -0
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +71 -30
- package/dist/stack-converge.js.map +1 -1
- package/dist/staging-database.d.ts +22 -0
- package/dist/staging-database.d.ts.map +1 -0
- package/dist/staging-database.js +117 -0
- package/dist/staging-database.js.map +1 -0
- package/dist/upgrade/app-roll.d.ts +25 -4
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +63 -17
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/converge.d.ts +5 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +207 -20
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -7
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +41 -17
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/image-forms.d.ts +1 -1
- package/dist/upgrade/image-forms.d.ts.map +1 -1
- package/dist/upgrade/image-forms.js +24 -7
- package/dist/upgrade/image-forms.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -3
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +13 -4
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +19 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +37 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +13 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +50 -1
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +6 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +20 -14
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +1 -1
- package/dist/upgrader-records.d.ts.map +1 -1
- package/dist/upgrader-records.js +26 -7
- package/dist/upgrader-records.js.map +1 -1
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +10 -5
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizard.d.ts +7 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +11 -0
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts +31 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +103 -18
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +129 -27
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +6 -0
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +2 -1
- package/src/adopt-existing.test.ts +85 -0
- package/src/adopt-existing.ts +141 -3
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +39 -9
- package/src/agent/run.test.ts +19 -0
- package/src/agent/run.ts +11 -2
- package/src/aws-task-roles.ts +5 -2
- package/src/caddy-redirects.docker.test.ts +2 -0
- package/src/caddy-redirects.test.ts +4 -1
- package/src/caddy-validate.test.ts +7 -1
- package/src/caddy.test.ts +39 -1
- package/src/caddy.ts +36 -4
- package/src/checkpoint-secrets.test.ts +14 -6
- package/src/cli-routing.test.ts +1 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +71 -5
- package/src/cloudsql-tls.test.ts +255 -0
- package/src/cloudsql-tls.ts +359 -0
- package/src/compose.ts +193 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-db-trust.test.ts +237 -0
- package/src/converge-db-trust.ts +212 -0
- package/src/converge-job-gcp.ts +11 -12
- package/src/converge-job-preflight.test.ts +5 -0
- package/src/converge-job-preflight.ts +8 -0
- package/src/converge-job.test.ts +70 -0
- package/src/converge-job.ts +32 -0
- package/src/database-tls-builders.test.ts +56 -0
- package/src/deployed-release.test.ts +107 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +50 -12
- package/src/gcp-names.ts +56 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +10 -4
- package/src/index.ts +7 -0
- package/src/infra-contract.test.ts +7 -3
- package/src/infra-contract.ts +45 -1
- package/src/install-job.ts +4 -1
- package/src/install-state.ts +33 -4
- package/src/lifecycle.ts +23 -11
- package/src/mcp-auth-proxy-setup.test.ts +305 -0
- package/src/mcp-auth-proxy-setup.ts +374 -0
- package/src/mcp-iap-stacks.test.ts +1 -1
- package/src/media-volume.test.ts +1 -1
- package/src/migration-runner.ts +14 -9
- package/src/migration-targets.ts +12 -0
- package/src/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +20 -4
- package/src/operator-access.ts +14 -0
- package/src/pool-budget-stack.ts +15 -0
- package/src/pool-budget-stacks.test.ts +39 -14
- package/src/public-role-isolation.test.ts +18 -8
- package/src/public-role-isolation.ts +31 -17
- package/src/release-contract.ts +3 -0
- package/src/release-manifest.test.ts +25 -0
- package/src/release-manifest.ts +59 -4
- package/src/remote-mcp-stack-config.test.ts +19 -9
- package/src/remote-mcp.test.ts +252 -57
- package/src/remote-mcp.ts +336 -77
- package/src/runtime-secrets.test.ts +37 -14
- package/src/runtime-secrets.ts +87 -27
- package/src/scheduler-setup.test.ts +201 -0
- package/src/scheduler-setup.ts +375 -0
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-backup.test.ts +1 -0
- package/src/self-hosted-compose.test.ts +180 -6
- package/src/self-hosted-db-tls.test.ts +111 -0
- package/src/self-hosted-db-tls.ts +287 -0
- package/src/self-hosted-release-resolve.test.ts +71 -0
- package/src/self-hosted-release.ts +41 -11
- package/src/self-hosted-roles.integration.test.ts +2 -0
- package/src/self-hosted-roles.test.ts +87 -3
- package/src/self-hosted-roles.ts +101 -3
- package/src/self-hosted-scheduler-interval.test.ts +66 -0
- package/src/self-hosted-scheduler-interval.ts +79 -0
- package/src/self-hosted-start.test.ts +35 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +104 -14
- package/src/self-hosted-upgrade.ts +123 -31
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +199 -31
- package/src/stack-contract.ts +472 -45
- package/src/stack-converge-secrets.test.ts +28 -13
- package/src/stack-converge.test.ts +203 -28
- package/src/stack-converge.ts +92 -30
- package/src/staging-database.test.ts +114 -0
- package/src/staging-database.ts +148 -0
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +249 -19
- package/src/upgrade/health.test.ts +63 -25
- package/src/upgrade/health.ts +54 -22
- package/src/upgrade/image-forms.test.ts +34 -0
- package/src/upgrade/image-forms.ts +23 -8
- package/src/upgrade/release-images.test.ts +5 -1
- package/src/upgrade/release-images.ts +22 -6
- package/src/upgrade/upgrade.test.ts +438 -46
- package/src/upgrade/upgrade.ts +64 -6
- package/src/upgrader-gcp.ts +66 -3
- package/src/upgrader-iam-preflight.test.ts +19 -0
- package/src/upgrader-iam-preflight.ts +6 -0
- package/src/upgrader-job.test.ts +101 -27
- package/src/upgrader-job.ts +27 -14
- package/src/upgrader-records.test.ts +97 -12
- package/src/upgrader-records.ts +28 -11
- package/src/upgrader-setup.test.ts +2 -0
- package/src/upgrader-setup.ts +10 -4
- package/src/wizard.ts +19 -0
- package/src/wizards/gcp-cost.test.ts +103 -0
- package/src/wizards/gcp-cost.ts +125 -18
- package/src/wizards/gcp.ts +164 -22
- package/stacks/aws/Pulumi.yaml +1 -1
- package/stacks/aws/index.ts +72 -14
- package/stacks/azure/index.ts +108 -21
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -0
- package/stacks/gcp/index.ts +133 -24
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +212 -34
- package/stacks/self-hosted/Pulumi.yaml +3 -0
- package/stacks/self-hosted/README.md +6 -1
- package/stacks/self-hosted/index.ts +40 -5
package/src/upgrade/app-roll.ts
CHANGED
|
@@ -25,15 +25,23 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import type { GcloudRunner, Sleep } from "../gcloud-retry.js";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
gatewayServiceAccountEmail,
|
|
30
|
+
mcpAuthProxyServiceAccountEmail,
|
|
31
|
+
runServiceAccountEmail,
|
|
32
|
+
} from "../gcp-names.js";
|
|
29
33
|
import type { ImageDigests } from "../install-state.js";
|
|
30
34
|
import {
|
|
31
35
|
adminMediaVolume,
|
|
36
|
+
CLOUD_RUN_SLUGS,
|
|
32
37
|
type CloudRunEnvVar,
|
|
33
|
-
|
|
38
|
+
type CloudRunSlug,
|
|
39
|
+
cloudRunServicePrefix,
|
|
40
|
+
MCP_AUTH_PROXY_TIMEOUT_SECONDS,
|
|
34
41
|
RETIRED_SECRET_FILES,
|
|
35
42
|
SERVICE_CPU_IDLE,
|
|
36
43
|
SERVICE_SECRET_FILES,
|
|
44
|
+
SERVICE_VPC_EGRESS,
|
|
37
45
|
} from "../stack-contract.js";
|
|
38
46
|
import {
|
|
39
47
|
type DeployedService,
|
|
@@ -44,12 +52,14 @@ import {
|
|
|
44
52
|
liveContainerEnv,
|
|
45
53
|
liveContainerMemory,
|
|
46
54
|
liveCpuIdle,
|
|
55
|
+
liveDirectVpc,
|
|
47
56
|
liveTimeoutSeconds,
|
|
48
57
|
liveVolumes,
|
|
49
58
|
planAdminMemory,
|
|
50
59
|
planContractEnv,
|
|
51
60
|
planCpuIdle,
|
|
52
61
|
planMediaVolume,
|
|
62
|
+
planNoVpc,
|
|
53
63
|
planRequestTimeout,
|
|
54
64
|
planSecretFiles,
|
|
55
65
|
rollService,
|
|
@@ -69,10 +79,27 @@ import {
|
|
|
69
79
|
import type { HealthProbe } from "./health.js";
|
|
70
80
|
import type { UpgradeReporter } from "./reporter.js";
|
|
71
81
|
|
|
72
|
-
export type RollSlug =
|
|
82
|
+
export type RollSlug = CloudRunSlug;
|
|
73
83
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The roll order: the gateway follows the admin, never the other way round,
|
|
86
|
+
* and the MCP-Auth-Proxy comes last (#1328) — on gcp-firebase it forwards
|
|
87
|
+
* the visitor traffic still addressed to it to the gateway, which must
|
|
88
|
+
* already serve the new release by then.
|
|
89
|
+
*/
|
|
90
|
+
export const ROLL_ORDER: readonly RollSlug[] = CLOUD_RUN_SLUGS;
|
|
91
|
+
|
|
92
|
+
/** The service account each service's new revision runs as. */
|
|
93
|
+
export function rollServiceAccount(slug: RollSlug, projectId: string, env: string): string {
|
|
94
|
+
switch (slug) {
|
|
95
|
+
case "admin":
|
|
96
|
+
return runServiceAccountEmail(projectId, env);
|
|
97
|
+
case "gateway":
|
|
98
|
+
return gatewayServiceAccountEmail(projectId, env);
|
|
99
|
+
case "mcp-auth-proxy":
|
|
100
|
+
return mcpAuthProxyServiceAccountEmail(projectId, env);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
76
103
|
|
|
77
104
|
/** The install an app-roll acts on. */
|
|
78
105
|
export interface AppRollInstall {
|
|
@@ -110,6 +137,8 @@ export interface ServicePlan {
|
|
|
110
137
|
readonly liveTimeout: number | null;
|
|
111
138
|
/** Whether the service gets CPU only during requests now. */
|
|
112
139
|
readonly liveCpuIdle: boolean;
|
|
140
|
+
/** Whether the service egresses into the install's VPC now. */
|
|
141
|
+
readonly liveDirectVpc: boolean;
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
/** A service plan plus the changes its roll applies. */
|
|
@@ -139,6 +168,13 @@ export interface AppRollPlan {
|
|
|
139
168
|
readonly services: readonly RollPlan[];
|
|
140
169
|
/** Notes for the operator (e.g. a memory limit left as is). */
|
|
141
170
|
readonly warnings: readonly string[];
|
|
171
|
+
/**
|
|
172
|
+
* #1338 — the services dial the Cloud SQL instance's IP, which no
|
|
173
|
+
* verify-full connection can use, so their database URLs are not planned.
|
|
174
|
+
* A converge moves them onto the instance's DNS name
|
|
175
|
+
* ({@link EnvAdjustments.databaseHost}); a roll refuses while this holds.
|
|
176
|
+
*/
|
|
177
|
+
readonly databaseHostPending?: boolean;
|
|
142
178
|
}
|
|
143
179
|
|
|
144
180
|
export type PlanResult =
|
|
@@ -166,7 +202,7 @@ export async function planAppRoll(
|
|
|
166
202
|
): Promise<PlanResult> {
|
|
167
203
|
const services: ServicePlan[] = [];
|
|
168
204
|
for (const slug of ROLL_ORDER) {
|
|
169
|
-
const prefix =
|
|
205
|
+
const prefix = cloudRunServicePrefix(install.provider, install.env, slug);
|
|
170
206
|
const lookup = await resolveGcpResourceName(
|
|
171
207
|
run,
|
|
172
208
|
"run-service",
|
|
@@ -210,6 +246,7 @@ export async function planAppRoll(
|
|
|
210
246
|
liveVolumes: liveVolumes(json),
|
|
211
247
|
liveTimeout: liveTimeoutSeconds(json),
|
|
212
248
|
liveCpuIdle: liveCpuIdle(json),
|
|
249
|
+
liveDirectVpc: liveDirectVpc(json),
|
|
213
250
|
});
|
|
214
251
|
}
|
|
215
252
|
const env = planEnv(install, services, {});
|
|
@@ -265,29 +302,45 @@ export async function planAppRoll(
|
|
|
265
302
|
// stack's memory default (never lowered; an unparsable value is kept).
|
|
266
303
|
const memory = s.slug === "admin" ? planAdminMemory(s.liveMemory) : null;
|
|
267
304
|
if (memory && !memory.ok) warnings.push(`admin memory left as is: ${memory.error}`);
|
|
268
|
-
// #806 — the
|
|
269
|
-
//
|
|
305
|
+
// #806 — the MCP-Auth-Proxy streams remote-MCP chat turns
|
|
306
|
+
// (stack-contract.ts MCP_AUTH_PROXY_TIMEOUT_SECONDS). The other services'
|
|
307
|
+
// timeouts are the stacks' alone.
|
|
270
308
|
const timeout =
|
|
271
|
-
s.slug === "
|
|
309
|
+
s.slug === "mcp-auth-proxy"
|
|
310
|
+
? planRequestTimeout(s.liveTimeout, MCP_AUTH_PROXY_TIMEOUT_SECONDS)
|
|
311
|
+
: null;
|
|
272
312
|
const memoryFlags = memory?.ok ? memory.flags : [];
|
|
273
313
|
const timeoutFlags = timeout?.ok ? timeout.flags : [];
|
|
274
314
|
const cpu = planCpuIdle(s.liveCpuIdle, SERVICE_CPU_IDLE[s.slug]);
|
|
275
315
|
const cpuFlags = cpu.ok ? cpu.flags : [];
|
|
316
|
+
// #1328 — the gcp-firebase proxy is the former gateway service: it
|
|
317
|
+
// leaves the VPC in the same revision that drops its database env.
|
|
318
|
+
const vpc = SERVICE_VPC_EGRESS[s.slug] ? null : planNoVpc(s.liveDirectVpc);
|
|
319
|
+
const vpcFlags = vpc?.ok ? vpc.flags : [];
|
|
276
320
|
return {
|
|
277
321
|
...s,
|
|
278
322
|
envFlags: env.services[s.slug].flags,
|
|
279
323
|
envChanges: env.services[s.slug].changes,
|
|
280
|
-
resourceFlags: [...memoryFlags, ...timeoutFlags, ...cpuFlags],
|
|
324
|
+
resourceFlags: [...memoryFlags, ...timeoutFlags, ...cpuFlags, ...vpcFlags],
|
|
281
325
|
resourceChanges: [
|
|
282
326
|
...memoryFlags.map((f) => `${s.liveMemory ?? "(default)"} → ${f}`),
|
|
283
327
|
...(timeout?.ok ? timeout.changes : []),
|
|
284
328
|
...(cpu.ok ? cpu.changes : []),
|
|
329
|
+
...(vpc?.ok ? vpc.changes : []),
|
|
285
330
|
],
|
|
286
331
|
volumeFlags: [...(s.slug === "admin" ? media.flags : []), ...secretFiles[s.slug].flags],
|
|
287
332
|
volumeChanges: [...(s.slug === "admin" ? media.changes : []), ...secretFiles[s.slug].changes],
|
|
288
333
|
};
|
|
289
334
|
});
|
|
290
|
-
|
|
335
|
+
if (env.databaseHostPending) {
|
|
336
|
+
warnings.push(
|
|
337
|
+
"the services still dial the Cloud SQL instance's IP; the converge moves them onto its DNS name for verify-full TLS (#1338) before anything rolls",
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
plan: { install, services: rolls, warnings, databaseHostPending: env.databaseHostPending },
|
|
343
|
+
};
|
|
291
344
|
}
|
|
292
345
|
|
|
293
346
|
/** Env the converge half adds to, or keeps out of, the rolls ({@link AppRollHooks.afterMigrations}). */
|
|
@@ -299,6 +352,11 @@ export interface EnvAdjustments {
|
|
|
299
352
|
* identity env, which only the converge (operator identity) can resolve.
|
|
300
353
|
*/
|
|
301
354
|
readonly extraEnv?: Readonly<Partial<Record<RollSlug, readonly CloudRunEnvVar[]>>>;
|
|
355
|
+
/**
|
|
356
|
+
* #1338 — the database host the converge moved the services onto (the
|
|
357
|
+
* Cloud SQL instance's DNS name, cloudsql-tls.ts).
|
|
358
|
+
*/
|
|
359
|
+
readonly databaseHost?: string;
|
|
302
360
|
}
|
|
303
361
|
|
|
304
362
|
function planEnv(
|
|
@@ -322,6 +380,7 @@ function planEnv(
|
|
|
322
380
|
{
|
|
323
381
|
leaveUntouched: adjust.leaveUntouched ?? [],
|
|
324
382
|
...(adjust.extraEnv ? { extraEnv: adjust.extraEnv } : {}),
|
|
383
|
+
...(adjust.databaseHost ? { databaseHost: adjust.databaseHost } : {}),
|
|
325
384
|
},
|
|
326
385
|
);
|
|
327
386
|
}
|
|
@@ -333,11 +392,12 @@ function planEnv(
|
|
|
333
392
|
*/
|
|
334
393
|
export function adjustingEnv(plan: AppRollPlan, adjust: EnvAdjustments): AppRollPlan {
|
|
335
394
|
const extra = Object.values(adjust.extraEnv ?? {}).some((v) => (v?.length ?? 0) > 0);
|
|
336
|
-
if ((adjust.leaveUntouched?.length ?? 0) === 0 && !extra) return plan;
|
|
395
|
+
if ((adjust.leaveUntouched?.length ?? 0) === 0 && !extra && !adjust.databaseHost) return plan;
|
|
337
396
|
const env = planEnv(plan.install, plan.services, adjust);
|
|
338
397
|
if (!env.ok) return plan;
|
|
339
398
|
return {
|
|
340
399
|
...plan,
|
|
400
|
+
databaseHostPending: env.databaseHostPending,
|
|
341
401
|
services: plan.services.map((s) => ({
|
|
342
402
|
...s,
|
|
343
403
|
envFlags: env.services[s.slug].flags,
|
|
@@ -370,7 +430,7 @@ export interface AppRollHooks {
|
|
|
370
430
|
afterMigrations?(): Promise<
|
|
371
431
|
| ({ readonly ok: true; readonly leaveUntouched: readonly string[] } & Pick<
|
|
372
432
|
EnvAdjustments,
|
|
373
|
-
"extraEnv"
|
|
433
|
+
"extraEnv" | "databaseHost"
|
|
374
434
|
>)
|
|
375
435
|
| { readonly ok: false }
|
|
376
436
|
>;
|
|
@@ -470,6 +530,17 @@ export async function executeAppRoll(
|
|
|
470
530
|
r.warn("--skip-health-check set — the new revisions get Cloud Run's readiness check only.");
|
|
471
531
|
}
|
|
472
532
|
|
|
533
|
+
// #1338 — the new images refuse a database URL without verify-full, and
|
|
534
|
+
// only a converge can move the services off the instance's IP. Without
|
|
535
|
+
// one (the in-cloud upgrader) the roll stops before it changes anything.
|
|
536
|
+
if (plan.databaseHostPending && !hooks.afterMigrations) {
|
|
537
|
+
r.error(
|
|
538
|
+
"The services still dial the Cloud SQL instance's IP address. This release connects with verify-full TLS, which needs the instance's DNS name: run the converge first (Settings → Updates installs it as a cloud-setup release, or `cms-provision upgrade` from a terminal).",
|
|
539
|
+
);
|
|
540
|
+
r.warn("No migrations ran and no traffic moved.");
|
|
541
|
+
return fail("migrations", false);
|
|
542
|
+
}
|
|
543
|
+
|
|
473
544
|
// Migrations BEFORE any traffic moves, with the NEW admin image: the old
|
|
474
545
|
// image does not carry the release's migration files (v0.9.2).
|
|
475
546
|
const admin = plan.services.find((s) => s.slug === "admin") as RollPlan;
|
|
@@ -490,8 +561,15 @@ export async function executeAppRoll(
|
|
|
490
561
|
plan = adjustingEnv(plan, {
|
|
491
562
|
leaveUntouched: converged.leaveUntouched,
|
|
492
563
|
...(converged.extraEnv ? { extraEnv: converged.extraEnv } : {}),
|
|
564
|
+
...(converged.databaseHost ? { databaseHost: converged.databaseHost } : {}),
|
|
493
565
|
});
|
|
494
566
|
}
|
|
567
|
+
if (plan.databaseHostPending) {
|
|
568
|
+
r.error(
|
|
569
|
+
"The converge did not move the services onto the Cloud SQL instance's DNS name, so the new revisions could not reach the database with verify-full TLS (#1338). Nothing rolled; fix the database TLS step above and re-run.",
|
|
570
|
+
);
|
|
571
|
+
return fail("converge", true);
|
|
572
|
+
}
|
|
495
573
|
|
|
496
574
|
const rolled: RollPlan[] = [];
|
|
497
575
|
/** Jobs this roll moved onto the admin's image (#875, #1217), with their prior image. */
|
|
@@ -540,10 +618,7 @@ export async function executeAppRoll(
|
|
|
540
618
|
region: install.region,
|
|
541
619
|
projectId: install.projectId,
|
|
542
620
|
imageRef: p.imageRef,
|
|
543
|
-
serviceAccount:
|
|
544
|
-
p.slug === "admin"
|
|
545
|
-
? runServiceAccountEmail(install.projectId, install.env)
|
|
546
|
-
: gatewayServiceAccountEmail(install.projectId, install.env),
|
|
621
|
+
serviceAccount: rollServiceAccount(p.slug, install.projectId, install.env),
|
|
547
622
|
envFlags: p.envFlags,
|
|
548
623
|
resourceFlags: p.resourceFlags,
|
|
549
624
|
volumeFlags: p.volumeFlags,
|
|
@@ -637,9 +712,12 @@ export async function executeAppRoll(
|
|
|
637
712
|
s.done(`Healthy: ${deep.detail}`);
|
|
638
713
|
}
|
|
639
714
|
|
|
715
|
+
const digestOf = (slug: RollSlug) =>
|
|
716
|
+
(plan.services.find((s) => s.slug === slug) as RollPlan).digest;
|
|
640
717
|
const digests: ImageDigests = {
|
|
641
|
-
admin: (
|
|
642
|
-
gateway: (
|
|
718
|
+
admin: digestOf("admin"),
|
|
719
|
+
gateway: digestOf("gateway"),
|
|
720
|
+
"mcp-auth-proxy": digestOf("mcp-auth-proxy"),
|
|
643
721
|
};
|
|
644
722
|
await deps.recordDigests(digests);
|
|
645
723
|
return { ok: true, digests };
|
package/src/upgrade/converge.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* service accounts, secrets or other cloud resources so the install matches
|
|
6
6
|
* what the release's stacks declare — the gateway's run SA and the
|
|
7
7
|
* CLI-generated runtime secrets, the stack's IAM/CDN/firewall invariants,
|
|
8
|
-
* MCP access through IAP,
|
|
8
|
+
* MCP access through IAP, the staging database and its gateway login
|
|
9
|
+
* (#1329), the gateway logins' Cloud SQL role memberships (#633),
|
|
9
10
|
* the gateway's and public_role's database logins, the operator-access sync
|
|
10
11
|
* job, the quality-audit job (#875), the remote-MCP load-balancer rules and
|
|
11
12
|
* identity env (#806), and dropping bindings nothing reads any more.
|
|
@@ -30,10 +31,16 @@
|
|
|
30
31
|
|
|
31
32
|
import { ensureAuditJob, resolveAdminImage } from "../audit-job-setup.js";
|
|
32
33
|
import { ensureCaptureJob } from "../capture-job-setup.js";
|
|
34
|
+
import {
|
|
35
|
+
dialsIpAddress,
|
|
36
|
+
ensureCloudSqlVerifyFull,
|
|
37
|
+
liveDatabaseUrls,
|
|
38
|
+
moveServicesToDnsHost,
|
|
39
|
+
} from "../cloudsql-tls.js";
|
|
33
40
|
import { ensureConvergeJob } from "../converge-job-setup.js";
|
|
34
41
|
import { findSqlInstance, planPublicRoleSwitch } from "../gateway-credentials.js";
|
|
35
42
|
import type { GcloudRunner, Sleep } from "../gcloud-retry.js";
|
|
36
|
-
import { upgraderServiceAccountEmail } from "../gcp-names.js";
|
|
43
|
+
import { schedulerServiceAccountEmail, upgraderServiceAccountEmail } from "../gcp-names.js";
|
|
37
44
|
import type { InstallMetadata } from "../install-state.js";
|
|
38
45
|
import { ensureMcpIapAccess, type IapResource } from "../mcp-iap.js";
|
|
39
46
|
import { ensureOperatorAccessSync, resolveOperatorAccessTarget } from "../operator-access.js";
|
|
@@ -46,6 +53,7 @@ import {
|
|
|
46
53
|
plainGeneratedSecretSeed,
|
|
47
54
|
readSecretReplication,
|
|
48
55
|
} from "../runtime-secrets.js";
|
|
56
|
+
import { ensureSchedulerTick } from "../scheduler-setup.js";
|
|
49
57
|
import { selfCommand } from "../self-command.js";
|
|
50
58
|
import {
|
|
51
59
|
AUDIT_JOB_ENV_VAR,
|
|
@@ -54,9 +62,13 @@ import {
|
|
|
54
62
|
CAPTURE_JOB_ENV_VAR,
|
|
55
63
|
type CloudRunEnvVar,
|
|
56
64
|
CONVERGE_JOB_ENV_VAR,
|
|
65
|
+
DATABASE_URL_VARS,
|
|
66
|
+
INTERIM_FORWARD_ORIGIN_ENV,
|
|
57
67
|
MCP_ENV_VAR,
|
|
58
68
|
OPERATOR_ACCESS_JOB_ENV_VAR,
|
|
69
|
+
parseSchedulerIntervalMinutes,
|
|
59
70
|
remoteMcpIdentityEnv,
|
|
71
|
+
SCHEDULER_SERVICE_ACCOUNT_ENV_VAR,
|
|
60
72
|
UPGRADER_JOB_ENV_VAR,
|
|
61
73
|
} from "../stack-contract.js";
|
|
62
74
|
import {
|
|
@@ -65,10 +77,11 @@ import {
|
|
|
65
77
|
liveEnvHasInlinePassword,
|
|
66
78
|
removeRetiredIamBindings,
|
|
67
79
|
} from "../stack-converge.js";
|
|
80
|
+
import { ensureStagingDatabase } from "../staging-database.js";
|
|
68
81
|
import { ensureUpgraderJob, resolveUpgraderTarget } from "../upgrader-setup.js";
|
|
69
82
|
import { ensureUrlMapRedirectsAccess } from "../url-map-redirects-access.js";
|
|
70
83
|
import type { AppRollHooks, AppRollPlan, RollPlan, RollSlug } from "./app-roll.js";
|
|
71
|
-
import { lookupFailure } from "./gcp-lookup.js";
|
|
84
|
+
import { findServiceUrl, lookupFailure } from "./gcp-lookup.js";
|
|
72
85
|
import type { UpgradeReporter } from "./reporter.js";
|
|
73
86
|
|
|
74
87
|
/**
|
|
@@ -102,6 +115,11 @@ export interface ConvergeDeps {
|
|
|
102
115
|
* then fails and the converge stays incomplete.
|
|
103
116
|
*/
|
|
104
117
|
readonly opsImageRef?: string | null;
|
|
118
|
+
/**
|
|
119
|
+
* #1140 — the install's `schedulerIntervalMinutes` knob (install.json);
|
|
120
|
+
* absent: the default. The scheduler step refuses an out-of-range value.
|
|
121
|
+
*/
|
|
122
|
+
readonly schedulerIntervalMinutes?: number;
|
|
105
123
|
}
|
|
106
124
|
|
|
107
125
|
export interface ConvergeSession {
|
|
@@ -139,7 +157,11 @@ export function startConverge(
|
|
|
139
157
|
const { reporter: r, run } = deps;
|
|
140
158
|
const runDeps = { run, ...(deps.sleep ? { sleep: deps.sleep } : {}) };
|
|
141
159
|
const service = (slug: RollSlug) => plan.services.find((s) => s.slug === slug) as RollPlan;
|
|
142
|
-
const services = {
|
|
160
|
+
const services = {
|
|
161
|
+
admin: service("admin").serviceName,
|
|
162
|
+
gateway: service("gateway").serviceName,
|
|
163
|
+
"mcp-auth-proxy": service("mcp-auth-proxy").serviceName,
|
|
164
|
+
};
|
|
143
165
|
const target = {
|
|
144
166
|
provider: install.provider,
|
|
145
167
|
projectId: install.projectId,
|
|
@@ -222,22 +244,48 @@ export function startConverge(
|
|
|
222
244
|
return true;
|
|
223
245
|
}
|
|
224
246
|
|
|
247
|
+
async function stagingDatabase(): Promise<boolean> {
|
|
248
|
+
// #1329 — staging's own database and its gateway login, on an install
|
|
249
|
+
// from before them. Before migrations: the cms_public_staging run and
|
|
250
|
+
// migration 0530 need both, and the membership revoke below needs the
|
|
251
|
+
// user. A failure aborts — the new admin refuses to start without the
|
|
252
|
+
// staging database.
|
|
253
|
+
const s = r.step("Ensuring the staging database (cms_public_staging) and its gateway login...");
|
|
254
|
+
const outcomes = await ensureStagingDatabase(
|
|
255
|
+
{ projectId: install.projectId, env: install.env, credentials: install.credentials },
|
|
256
|
+
{ ...runDeps, ...(deps.http ? { http: deps.http } : {}) },
|
|
257
|
+
);
|
|
258
|
+
const failed = outcomes.filter((o) => o.status === "failed");
|
|
259
|
+
if (failed.length > 0) {
|
|
260
|
+
s.failed("The staging database could not be set up. Aborting upgrade.");
|
|
261
|
+
for (const o of failed) r.error(` FAILED: ${o.id}\n ${o.error ?? ""}`);
|
|
262
|
+
r.warn("No traffic was shifted and no migrations ran. Fix the above and re-run.");
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
s.done("Staging database and its gateway login ready");
|
|
266
|
+
for (const o of outcomes.filter((o) => o.status === "applied")) {
|
|
267
|
+
r.info(` ${o.id}: ${o.detail ?? "created"}`);
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
225
272
|
async function publicRoleIsolation(): Promise<boolean> {
|
|
226
|
-
// #633 — the Admin API made public_role
|
|
227
|
-
// Revoked here, before migrations,
|
|
228
|
-
// audit is what verifies it and fails
|
|
229
|
-
|
|
273
|
+
// #633 — the Admin API made public_role (and, #1329, public_staging_role)
|
|
274
|
+
// a member of cloudsqlsuperuser. Revoked here, before migrations,
|
|
275
|
+
// because the migration job's role audit is what verifies it and fails
|
|
276
|
+
// the upgrade while it remains.
|
|
277
|
+
const s = r.step("Removing the gateway logins from the Cloud SQL superuser group...");
|
|
230
278
|
const outcome = await revokePublicRoleMemberships(
|
|
231
279
|
{ projectId: install.projectId, env: install.env, credentials: install.credentials },
|
|
232
280
|
runDeps,
|
|
233
281
|
);
|
|
234
282
|
if (outcome.status === "failed") {
|
|
235
|
-
s.failed("
|
|
283
|
+
s.failed("The gateway logins' role memberships could not be revoked. Aborting upgrade.");
|
|
236
284
|
r.error(` ${outcome.error ?? ""}`);
|
|
237
285
|
r.warn("No traffic was shifted and no migrations ran. Fix the above and re-run.");
|
|
238
286
|
return false;
|
|
239
287
|
}
|
|
240
|
-
s.done(`
|
|
288
|
+
s.done(`The gateway logins hold no database roles (${outcome.detail ?? "revoked"})`);
|
|
241
289
|
return true;
|
|
242
290
|
}
|
|
243
291
|
|
|
@@ -371,33 +419,53 @@ export function startConverge(
|
|
|
371
419
|
}
|
|
372
420
|
|
|
373
421
|
async function remoteMcpEdge(): Promise<void> {
|
|
374
|
-
// #806 — on `gcp` the load balancer routes the remote-MCP
|
|
375
|
-
//
|
|
376
|
-
// /api/mcp/* (remote-mcp-edge.ts). Only remote
|
|
377
|
-
// failure warns.
|
|
422
|
+
// #806 / #1328 — on `gcp` the load balancer routes the remote-MCP paths
|
|
423
|
+
// to the MCP-Auth-Proxy's own backend, whose Cloud Armor policy lets
|
|
424
|
+
// agent traffic through on /api/mcp/* (remote-mcp-edge.ts). Only remote
|
|
425
|
+
// MCP depends on it, so a failure warns. The gateway being rolled in
|
|
426
|
+
// this run no longer answers those paths, so until this succeeds remote
|
|
427
|
+
// MCP is down on the site's domain — said below.
|
|
378
428
|
if (install.provider !== "gcp") return;
|
|
379
|
-
const s = r.step("Ensuring the
|
|
429
|
+
const s = r.step("Ensuring the MCP-Auth-Proxy's load-balancer route and Cloud Armor rule...");
|
|
380
430
|
const edge = await ensureRemoteMcpEdge(
|
|
381
431
|
{
|
|
382
432
|
provider: install.provider,
|
|
383
433
|
projectId: install.projectId,
|
|
434
|
+
region: install.region,
|
|
384
435
|
env: install.env,
|
|
385
436
|
domain: install.domain,
|
|
437
|
+
mcpAuthProxyImageRef: service("mcp-auth-proxy").imageRef,
|
|
386
438
|
},
|
|
387
439
|
{ ...runDeps, ...(deps.http ? { http: deps.http } : {}) },
|
|
388
440
|
);
|
|
389
441
|
if (edge.ok) {
|
|
390
442
|
for (const line of edge.done) r.info(` ${line}`);
|
|
391
|
-
s.done("Remote-MCP load-balancer
|
|
443
|
+
s.done("Remote-MCP load-balancer route ready");
|
|
392
444
|
return;
|
|
393
445
|
}
|
|
394
|
-
s.warned(`Remote-MCP load-balancer
|
|
446
|
+
s.warned(`Remote-MCP load-balancer route not set up: ${edge.error}`);
|
|
395
447
|
r.warn(
|
|
396
448
|
"The upgrade continues; remote MCP (Settings → MCP → Remote) cannot be reached through the site's domain until this succeeds. Re-run upgrade once the cause is fixed.",
|
|
397
449
|
);
|
|
398
450
|
incomplete = true;
|
|
399
451
|
}
|
|
400
452
|
|
|
453
|
+
async function interimForwardEnv(): Promise<CloudRunEnvVar[]> {
|
|
454
|
+
// #1328 — on gcp-firebase the proxy is the service the current live
|
|
455
|
+
// release's Firebase rewrite and the staging domain mapping still
|
|
456
|
+
// point at; it forwards that traffic to the data gateway, whose URL is
|
|
457
|
+
// its origin (stack-contract.ts INTERIM_FORWARD_ORIGIN_ENV). Without it
|
|
458
|
+
// the proxy answers the live site's /api/** with 404 until the next
|
|
459
|
+
// Publish live, and the staging host with 404 — so this aborts.
|
|
460
|
+
if (install.provider !== "gcp-firebase") return [];
|
|
461
|
+
const url = await findServiceUrl(run, install.projectId, install.region, services.gateway);
|
|
462
|
+
if (!url) throw new Error(`could not read the URL of ${services.gateway}`);
|
|
463
|
+
return [{ name: INTERIM_FORWARD_ORIGIN_ENV, value: new URL(url).origin }];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** The admin's origin behind IAP, once {@link remoteMcpIdentity} read it. */
|
|
467
|
+
let resolvedAdminUrl: string | null = null;
|
|
468
|
+
|
|
401
469
|
async function remoteMcpIdentity(): Promise<Record<RollSlug, CloudRunEnvVar[]> | null> {
|
|
402
470
|
// #806 — CAELO_ADMIN_URL / CAELO_MCP_PUBLIC_URL / CAELO_IAP_AUDIENCE
|
|
403
471
|
// name identities the env contract cannot (stack-contract.ts
|
|
@@ -420,6 +488,7 @@ export function startConverge(
|
|
|
420
488
|
runDeps,
|
|
421
489
|
);
|
|
422
490
|
if (resolved.ok) {
|
|
491
|
+
resolvedAdminUrl = resolved.identity.adminUrl;
|
|
423
492
|
s.done(
|
|
424
493
|
`Google sign-in and remote MCP can be set up under Settings → Google sign-in (${resolved.identity.mcpPublicUrl})`,
|
|
425
494
|
);
|
|
@@ -459,6 +528,71 @@ export function startConverge(
|
|
|
459
528
|
return name ? { kind: "backend-services", service: name } : null;
|
|
460
529
|
}
|
|
461
530
|
|
|
531
|
+
/** #1338 — the host the database TLS step moved the services onto. */
|
|
532
|
+
let databaseHost: string | null = null;
|
|
533
|
+
|
|
534
|
+
async function databaseTls(): Promise<boolean> {
|
|
535
|
+
// #1338 — verify-full TLS needs the shared server CA, the instance's
|
|
536
|
+
// DNS name resolving in the VPC, and the services dialling that name
|
|
537
|
+
// (cloudsql-tls.ts). Before migrations: the migration job reads its host
|
|
538
|
+
// from the admin's live URL, and the new images refuse anything less
|
|
539
|
+
// than verify-full. A failure aborts — the new revisions could not
|
|
540
|
+
// reach the database.
|
|
541
|
+
const s = r.step("Setting up verify-full TLS to the database (shared CA, private DNS name)...");
|
|
542
|
+
const liveUrls = plan.services.map((p) => ({
|
|
543
|
+
serviceName: p.serviceName,
|
|
544
|
+
liveUrls: liveDatabaseUrls(p.liveEnv, DATABASE_URL_VARS[p.slug]),
|
|
545
|
+
}));
|
|
546
|
+
// Only this step moves the services off the instance's IP, and only
|
|
547
|
+
// after the instance, its certificate and the DNS name are in shape —
|
|
548
|
+
// so services that dial a host name already went through it.
|
|
549
|
+
const onIp = liveUrls.some((u) => Object.values(u.liveUrls).some((v) => dialsIpAddress(v)));
|
|
550
|
+
if (!onIp) {
|
|
551
|
+
s.done("Database TLS ready: the services already dial the instance's DNS name");
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
const lookup = await findSqlInstance(install, { run });
|
|
555
|
+
if (lookup.status !== "found") {
|
|
556
|
+
s.failed(
|
|
557
|
+
`${lookupFailure(lookup, "Cloud SQL instance", install.credentials)}. Aborting upgrade.`,
|
|
558
|
+
);
|
|
559
|
+
r.warn(
|
|
560
|
+
"No traffic was shifted and no migrations ran. Re-run upgrade once the instance is found.",
|
|
561
|
+
);
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
const tlsDeps = { ...runDeps, ...(deps.http ? { http: deps.http } : {}) };
|
|
565
|
+
const tls = await ensureCloudSqlVerifyFull(
|
|
566
|
+
{ projectId: install.projectId, env: install.env, sqlInstance: lookup.name },
|
|
567
|
+
tlsDeps,
|
|
568
|
+
);
|
|
569
|
+
for (const line of tls.done) r.info(` ${line}`);
|
|
570
|
+
if (!tls.ok) {
|
|
571
|
+
s.failed(`Database TLS could not be set up: ${tls.error}. Aborting upgrade.`);
|
|
572
|
+
r.warn("No traffic was shifted and no migrations ran. Fix the above and re-run.");
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
const moved = await moveServicesToDnsHost(
|
|
576
|
+
{ projectId: install.projectId, region: install.region },
|
|
577
|
+
liveUrls,
|
|
578
|
+
tls.host,
|
|
579
|
+
tlsDeps,
|
|
580
|
+
);
|
|
581
|
+
for (const line of moved.done) r.info(` ${line}`);
|
|
582
|
+
if (!moved.ok) {
|
|
583
|
+
s.failed(
|
|
584
|
+
`The services could not be moved onto ${tls.host}: ${moved.error}. Aborting upgrade.`,
|
|
585
|
+
);
|
|
586
|
+
r.warn("No migrations ran. Fix the above and re-run.");
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
databaseHost = tls.host;
|
|
590
|
+
s.done(
|
|
591
|
+
`Database TLS ready: the services dial ${tls.host} (verify-full from the new revisions)`,
|
|
592
|
+
);
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
|
|
462
596
|
async function databaseLogins(): Promise<boolean> {
|
|
463
597
|
// #613 — public_role moves onto its own password right before the
|
|
464
598
|
// gateway's roll. The gateway has no other database login (#635).
|
|
@@ -578,6 +712,77 @@ export function startConverge(
|
|
|
578
712
|
leaveUntouched.push(CONVERGE_JOB_ENV_VAR);
|
|
579
713
|
}
|
|
580
714
|
|
|
715
|
+
let schedulerReady = false;
|
|
716
|
+
/** The scheduler's SA exists from an earlier run though this one failed. */
|
|
717
|
+
let schedulerAccountExists = false;
|
|
718
|
+
|
|
719
|
+
async function setUpSchedulerTick(): Promise<
|
|
720
|
+
| { ok: true; done: string[]; intervalMinutes: number }
|
|
721
|
+
| { ok: false; done: string[]; error: string }
|
|
722
|
+
> {
|
|
723
|
+
let intervalMinutes: number;
|
|
724
|
+
try {
|
|
725
|
+
intervalMinutes = parseSchedulerIntervalMinutes(
|
|
726
|
+
deps.schedulerIntervalMinutes,
|
|
727
|
+
"install.json schedulerIntervalMinutes",
|
|
728
|
+
);
|
|
729
|
+
} catch (e) {
|
|
730
|
+
return { ok: false, done: [], error: (e as Error).message };
|
|
731
|
+
}
|
|
732
|
+
const resource = await adminIapResource();
|
|
733
|
+
if (!resource) return { ok: false, done: [], error: "the admin's IAP resource was not found" };
|
|
734
|
+
if (!resolvedAdminUrl)
|
|
735
|
+
return { ok: false, done: [], error: "the admin's URL could not be read" };
|
|
736
|
+
const st = await ensureSchedulerTick(
|
|
737
|
+
{
|
|
738
|
+
projectId: install.projectId,
|
|
739
|
+
region: install.region,
|
|
740
|
+
env: install.env,
|
|
741
|
+
adminUrl: resolvedAdminUrl,
|
|
742
|
+
resource,
|
|
743
|
+
intervalMinutes,
|
|
744
|
+
},
|
|
745
|
+
runDeps,
|
|
746
|
+
);
|
|
747
|
+
return st.ok ? { ok: true, done: st.done, intervalMinutes } : st;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function schedulerTick(): Promise<void> {
|
|
751
|
+
// #1140 — the one Cloud Scheduler job that calls the admin's tick
|
|
752
|
+
// (scheduler-setup.ts). After migrations and before the operator-access
|
|
753
|
+
// step, which must keep the scheduler's account on IAP. The schedule
|
|
754
|
+
// starts right away: until the roll below finishes, a tick reaches the
|
|
755
|
+
// previous admin, which has no such endpoint (404) — that execution
|
|
756
|
+
// fails and nothing runs; the first tick after the roll does the work.
|
|
757
|
+
// A failure costs the periodic work (plugin workers, GC, release check),
|
|
758
|
+
// not the site, so it warns and the admin keeps its current env.
|
|
759
|
+
const s = r.step("Ensuring the scheduler tick (periodic work and plugin workers)...");
|
|
760
|
+
const st = await setUpSchedulerTick();
|
|
761
|
+
for (const step of st.done) r.info(` ${step}`);
|
|
762
|
+
if (st.ok) {
|
|
763
|
+
schedulerReady = true;
|
|
764
|
+
s.done(`Scheduler tick ready (every ${st.intervalMinutes} min)`);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
s.warned(`Scheduler tick not set up: ${st.error}`);
|
|
768
|
+
// An account set up by an earlier run keeps its IAP access, or the next
|
|
769
|
+
// sync would cut off a tick that still works.
|
|
770
|
+
const exists = await run([
|
|
771
|
+
"iam",
|
|
772
|
+
"service-accounts",
|
|
773
|
+
"describe",
|
|
774
|
+
schedulerServiceAccountEmail(install.projectId, install.env),
|
|
775
|
+
`--project=${install.projectId}`,
|
|
776
|
+
"--format=value(email)",
|
|
777
|
+
]);
|
|
778
|
+
schedulerAccountExists = exists.ok;
|
|
779
|
+
r.warn(
|
|
780
|
+
"The upgrade continues; plugin workers, update checks and clean-up run late or not at all until this succeeds. Re-run upgrade once the cause is fixed.",
|
|
781
|
+
);
|
|
782
|
+
incomplete = true;
|
|
783
|
+
leaveUntouched.push(SCHEDULER_SERVICE_ACCOUNT_ENV_VAR);
|
|
784
|
+
}
|
|
785
|
+
|
|
581
786
|
async function operatorAccess(): Promise<void> {
|
|
582
787
|
// The job that keeps IAP in step with the user list (operator-access.ts).
|
|
583
788
|
// After migrations: its database user gets the role migration 0239
|
|
@@ -605,6 +810,13 @@ export function startConverge(
|
|
|
605
810
|
],
|
|
606
811
|
}
|
|
607
812
|
: {}),
|
|
813
|
+
...(schedulerReady || schedulerAccountExists
|
|
814
|
+
? {
|
|
815
|
+
iapServiceAccounts: [
|
|
816
|
+
schedulerServiceAccountEmail(install.projectId, install.env),
|
|
817
|
+
],
|
|
818
|
+
}
|
|
819
|
+
: {}),
|
|
608
820
|
},
|
|
609
821
|
runDeps,
|
|
610
822
|
)
|
|
@@ -627,7 +839,9 @@ export function startConverge(
|
|
|
627
839
|
if (deps.provisionerIdentity && !(await deps.provisionerIdentity())) return false;
|
|
628
840
|
if (!(await runtimeIdentities())) return false;
|
|
629
841
|
if (!(await stackInvariants())) return false;
|
|
842
|
+
if (!(await stagingDatabase())) return false;
|
|
630
843
|
if (!(await publicRoleIsolation())) return false;
|
|
844
|
+
if (!(await databaseTls())) return false;
|
|
631
845
|
await mcpAccess();
|
|
632
846
|
await urlMapRedirectsAccess();
|
|
633
847
|
await auditJob();
|
|
@@ -639,14 +853,30 @@ export function startConverge(
|
|
|
639
853
|
hooks: {
|
|
640
854
|
async afterMigrations() {
|
|
641
855
|
if (!(await databaseLogins())) return { ok: false };
|
|
856
|
+
// Read-only, first: the scheduler tick needs the admin's URL.
|
|
857
|
+
const identity = await remoteMcpIdentity();
|
|
642
858
|
await upgraderJob();
|
|
643
859
|
await convergeJob();
|
|
860
|
+
await schedulerTick();
|
|
644
861
|
await operatorAccess();
|
|
645
|
-
|
|
862
|
+
let forward: CloudRunEnvVar[];
|
|
863
|
+
try {
|
|
864
|
+
forward = await interimForwardEnv();
|
|
865
|
+
} catch (e) {
|
|
866
|
+
r.error(
|
|
867
|
+
`The MCP-Auth-Proxy cannot be pointed at the data gateway: ${e instanceof Error ? e.message : String(e)}. Nothing was rolled; re-run upgrade once it is fixed.`,
|
|
868
|
+
);
|
|
869
|
+
return { ok: false };
|
|
870
|
+
}
|
|
871
|
+
const extraEnv: Partial<Record<RollSlug, CloudRunEnvVar[]>> = {
|
|
872
|
+
...(identity ?? {}),
|
|
873
|
+
"mcp-auth-proxy": [...(identity?.["mcp-auth-proxy"] ?? []), ...forward],
|
|
874
|
+
};
|
|
646
875
|
return {
|
|
647
876
|
ok: true,
|
|
648
877
|
leaveUntouched: [...leaveUntouched],
|
|
649
|
-
|
|
878
|
+
extraEnv,
|
|
879
|
+
...(databaseHost ? { databaseHost } : {}),
|
|
650
880
|
};
|
|
651
881
|
},
|
|
652
882
|
async beforeRoll(slug) {
|