@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
|
@@ -29,6 +29,17 @@ function fakeGcloud(answers: Record<string, GcloudResult[]>) {
|
|
|
29
29
|
return { run, calls };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A gcp-firebase install since #1328: the admin, the data gateway and the
|
|
34
|
+
* MCP-Auth-Proxy — the service that was the gateway (E13 a).
|
|
35
|
+
*/
|
|
36
|
+
const FIREBASE = { provider: "gcp-firebase" as const, projectId: "p", region: "europe-west1" };
|
|
37
|
+
const firebaseLists = () => ({
|
|
38
|
+
"metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
|
|
39
|
+
"metadata.name~^caelo-production-data-gateway": [ok("caelo-production-data-gateway\n")],
|
|
40
|
+
"metadata.name~^caelo-production-gateway": [ok("caelo-production-gateway-def\n")],
|
|
41
|
+
});
|
|
42
|
+
|
|
32
43
|
describe("digestFromImageRef", () => {
|
|
33
44
|
it("extracts the pinned digest", () => {
|
|
34
45
|
expect(digestFromImageRef(`${REG}/admin@${A}\n`)).toBe(A);
|
|
@@ -39,16 +50,43 @@ describe("digestFromImageRef", () => {
|
|
|
39
50
|
});
|
|
40
51
|
|
|
41
52
|
describe("readDeployedImageDigests", () => {
|
|
42
|
-
it("reads
|
|
53
|
+
it("reads the three services' digests, each from the service playing its role", async () => {
|
|
43
54
|
const { run, calls } = fakeGcloud({
|
|
55
|
+
...firebaseLists(),
|
|
56
|
+
"describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
|
|
57
|
+
"describe caelo-production-data-gateway": [ok(`${REG}/gateway@${B}`)],
|
|
58
|
+
"describe caelo-production-gateway-def": [ok(`${REG}/mcp-auth-proxy@${C}`)],
|
|
59
|
+
});
|
|
60
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, run });
|
|
61
|
+
expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B, "mcp-auth-proxy": C } });
|
|
62
|
+
expect(calls.every((c) => c.includes("--project=p"))).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("finds the gcp proxy under its own name", async () => {
|
|
66
|
+
const { run } = fakeGcloud({
|
|
44
67
|
"metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
|
|
45
68
|
"metadata.name~^caelo-production-gateway": [ok("caelo-production-gateway-def\n")],
|
|
69
|
+
"metadata.name~^caelo-production-mcp-auth-proxy": [ok("caelo-production-mcp-auth-proxy\n")],
|
|
46
70
|
"describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
|
|
47
71
|
"describe caelo-production-gateway-def": [ok(`${REG}/gateway@${B}`)],
|
|
72
|
+
"describe caelo-production-mcp-auth-proxy": [ok(`${REG}/mcp-auth-proxy@${C}`)],
|
|
48
73
|
});
|
|
49
|
-
const r = await readDeployedImageDigests({
|
|
50
|
-
expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B } });
|
|
51
|
-
|
|
74
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, provider: "gcp", run });
|
|
75
|
+
expect(r).toEqual({ ok: true, digests: { admin: A, gateway: B, "mcp-auth-proxy": C } });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("sends an install without its #1328 services to upgrade", async () => {
|
|
79
|
+
const { run } = fakeGcloud({
|
|
80
|
+
"metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
|
|
81
|
+
"metadata.name~^caelo-production-data-gateway": [ok("")],
|
|
82
|
+
"describe caelo-production-admin-abc": [ok(`${REG}/admin@${A}`)],
|
|
83
|
+
});
|
|
84
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, run });
|
|
85
|
+
expect(r.ok).toBe(false);
|
|
86
|
+
if (!r.ok) {
|
|
87
|
+
expect(r.error).toContain("no caelo-production-data-gateway* Cloud Run service");
|
|
88
|
+
expect(r.error).toContain("Run `upgrade` first");
|
|
89
|
+
}
|
|
52
90
|
});
|
|
53
91
|
|
|
54
92
|
it("refuses an ambiguous service match", async () => {
|
|
@@ -57,7 +95,7 @@ describe("readDeployedImageDigests", () => {
|
|
|
57
95
|
ok("caelo-production-admin-a\ncaelo-production-admin-b\n"),
|
|
58
96
|
],
|
|
59
97
|
});
|
|
60
|
-
const r = await readDeployedImageDigests({
|
|
98
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, run });
|
|
61
99
|
expect(r.ok).toBe(false);
|
|
62
100
|
if (!r.ok)
|
|
63
101
|
expect(r.error).toContain("found caelo-production-admin-a, caelo-production-admin-b");
|
|
@@ -68,14 +106,14 @@ describe("readDeployedImageDigests", () => {
|
|
|
68
106
|
"metadata.name~^caelo-production-admin": [ok("caelo-production-admin-abc\n")],
|
|
69
107
|
"describe caelo-production-admin-abc": [ok(`${REG}/admin:latest`)],
|
|
70
108
|
});
|
|
71
|
-
const r = await readDeployedImageDigests({
|
|
109
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, run });
|
|
72
110
|
expect(r.ok).toBe(false);
|
|
73
111
|
if (!r.ok) expect(r.error).toContain("not pinned to a digest");
|
|
74
112
|
});
|
|
75
113
|
|
|
76
114
|
it("surfaces a gcloud failure", async () => {
|
|
77
115
|
const { run } = fakeGcloud({ "services list": [fail("PERMISSION_DENIED")] });
|
|
78
|
-
const r = await readDeployedImageDigests({
|
|
116
|
+
const r = await readDeployedImageDigests({ ...FIREBASE, run });
|
|
79
117
|
expect(r).toEqual({ ok: false, error: "list Cloud Run services: PERMISSION_DENIED" });
|
|
80
118
|
});
|
|
81
119
|
});
|
|
@@ -87,40 +125,53 @@ describe("checkDeployedRuntimeEnv", () => {
|
|
|
87
125
|
name,
|
|
88
126
|
valueFrom: { secretKeyRef: { name: `caelo-production-${id}`, key: "latest" } },
|
|
89
127
|
});
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
128
|
+
const adminOk = svc([
|
|
129
|
+
secret("ADMIN_DATABASE_PASSWORD"),
|
|
130
|
+
secret("PUBLIC_ADMIN_DATABASE_PASSWORD"),
|
|
131
|
+
// #1329 — the staging database's pool: a release from before it is
|
|
132
|
+
// refused here too and goes through `upgrade` first.
|
|
133
|
+
secret("PUBLIC_STAGING_ADMIN_DATABASE_PASSWORD"),
|
|
134
|
+
]);
|
|
135
|
+
const gatewayOk = svc([secret("PUBLIC_DATABASE_PASSWORD", "public-role-password")]);
|
|
136
|
+
const proxyOk = svc([secret("CAELO_MCP_OAUTH_SEAL_KEY", "mcp-oauth-seal-key")]);
|
|
94
137
|
|
|
95
|
-
it("passes once
|
|
138
|
+
it("passes once every service runs the current env", async () => {
|
|
96
139
|
const { run } = fakeGcloud({
|
|
97
|
-
...
|
|
98
|
-
"describe caelo-production-admin-abc": [
|
|
99
|
-
|
|
100
|
-
],
|
|
101
|
-
"describe caelo-production-gateway-def": [
|
|
102
|
-
svc([secret("PUBLIC_DATABASE_PASSWORD", "public-role-password")]),
|
|
103
|
-
],
|
|
140
|
+
...firebaseLists(),
|
|
141
|
+
"describe caelo-production-admin-abc": [adminOk],
|
|
142
|
+
"describe caelo-production-data-gateway": [gatewayOk],
|
|
143
|
+
"describe caelo-production-gateway-def": [proxyOk],
|
|
104
144
|
});
|
|
105
|
-
expect(await checkDeployedRuntimeEnv({
|
|
106
|
-
|
|
145
|
+
expect(await checkDeployedRuntimeEnv({ ...FIREBASE, run })).toEqual({ ok: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("#1328 — stops a wizard re-run while the proxy is still the old gateway (database env)", async () => {
|
|
149
|
+
const { run } = fakeGcloud({
|
|
150
|
+
...firebaseLists(),
|
|
151
|
+
"describe caelo-production-admin-abc": [adminOk],
|
|
152
|
+
"describe caelo-production-data-gateway": [gatewayOk],
|
|
153
|
+
"describe caelo-production-gateway-def": [gatewayOk],
|
|
107
154
|
});
|
|
155
|
+
const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
|
|
156
|
+
expect(r.ok).toBe(false);
|
|
157
|
+
if (!r.ok) {
|
|
158
|
+
expect(r.error).toContain("PUBLIC_DATABASE_PASSWORD");
|
|
159
|
+
expect(r.error).toContain("Run `upgrade` first");
|
|
160
|
+
}
|
|
108
161
|
});
|
|
109
162
|
|
|
110
163
|
it("stops a wizard re-run on a gateway from before #635 (it needs its gateway_role pool)", async () => {
|
|
111
164
|
const { run } = fakeGcloud({
|
|
112
|
-
...
|
|
113
|
-
"describe caelo-production-admin-abc": [
|
|
114
|
-
|
|
115
|
-
],
|
|
116
|
-
"describe caelo-production-gateway-def": [
|
|
165
|
+
...firebaseLists(),
|
|
166
|
+
"describe caelo-production-admin-abc": [adminOk],
|
|
167
|
+
"describe caelo-production-data-gateway": [
|
|
117
168
|
svc([
|
|
118
169
|
secret("GATEWAY_DATABASE_PASSWORD", "gateway-role-password"),
|
|
119
170
|
secret("PUBLIC_DATABASE_PASSWORD", "public-role-password"),
|
|
120
171
|
]),
|
|
121
172
|
],
|
|
122
173
|
});
|
|
123
|
-
const r = await checkDeployedRuntimeEnv({
|
|
174
|
+
const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
|
|
124
175
|
expect(r.ok).toBe(false);
|
|
125
176
|
if (!r.ok) {
|
|
126
177
|
expect(r.error).toContain("GATEWAY_DATABASE_PASSWORD");
|
|
@@ -130,15 +181,13 @@ describe("checkDeployedRuntimeEnv", () => {
|
|
|
130
181
|
|
|
131
182
|
it("stops a wizard re-run on a gateway from before #613 (it needs the admin_role pool)", async () => {
|
|
132
183
|
const { run } = fakeGcloud({
|
|
133
|
-
...
|
|
134
|
-
"describe caelo-production-admin-abc": [
|
|
135
|
-
|
|
136
|
-
],
|
|
137
|
-
"describe caelo-production-gateway-def": [
|
|
184
|
+
...firebaseLists(),
|
|
185
|
+
"describe caelo-production-admin-abc": [adminOk],
|
|
186
|
+
"describe caelo-production-data-gateway": [
|
|
138
187
|
svc([secret("ADMIN_DATABASE_PASSWORD"), secret("PUBLIC_DATABASE_PASSWORD")]),
|
|
139
188
|
],
|
|
140
189
|
});
|
|
141
|
-
const r = await checkDeployedRuntimeEnv({
|
|
190
|
+
const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
|
|
142
191
|
expect(r.ok).toBe(false);
|
|
143
192
|
if (!r.ok) {
|
|
144
193
|
expect(r.error).toContain("ADMIN_DATABASE_PASSWORD");
|
|
@@ -148,14 +197,17 @@ describe("checkDeployedRuntimeEnv", () => {
|
|
|
148
197
|
|
|
149
198
|
it("stops a wizard re-run on an install upgrade hasn't moved over (password in the URL)", async () => {
|
|
150
199
|
const { run } = fakeGcloud({
|
|
151
|
-
...
|
|
200
|
+
...firebaseLists(),
|
|
152
201
|
"describe caelo-production-admin-abc": [
|
|
153
202
|
svc([
|
|
154
|
-
{
|
|
203
|
+
{
|
|
204
|
+
name: "ADMIN_DATABASE_URL",
|
|
205
|
+
value: "postgres://admin_role:pw@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin",
|
|
206
|
+
},
|
|
155
207
|
]),
|
|
156
208
|
],
|
|
157
209
|
});
|
|
158
|
-
const r = await checkDeployedRuntimeEnv({
|
|
210
|
+
const r = await checkDeployedRuntimeEnv({ ...FIREBASE, run });
|
|
159
211
|
expect(r.ok).toBe(false);
|
|
160
212
|
if (!r.ok) {
|
|
161
213
|
expect(r.error).toContain("Run `upgrade` first");
|
|
@@ -165,29 +217,44 @@ describe("checkDeployedRuntimeEnv", () => {
|
|
|
165
217
|
});
|
|
166
218
|
|
|
167
219
|
describe("chooseImageDigests", () => {
|
|
168
|
-
const latest = { admin: C, gateway: C };
|
|
220
|
+
const latest = { admin: C, gateway: C, "mcp-auth-proxy": C };
|
|
221
|
+
const recorded = { admin: A, gateway: B, "mcp-auth-proxy": C };
|
|
169
222
|
const never = () => {
|
|
170
223
|
throw new Error("must not be called");
|
|
171
224
|
};
|
|
172
225
|
|
|
173
226
|
it("keeps the recorded release", async () => {
|
|
227
|
+
const r = await chooseImageDigests({
|
|
228
|
+
recorded,
|
|
229
|
+
deployed: true,
|
|
230
|
+
readLive: never,
|
|
231
|
+
resolveLatest: never,
|
|
232
|
+
});
|
|
233
|
+
expect(r).toEqual({ ok: true, source: "recorded", digests: recorded });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("#1328 — sends a record from before the MCP-Auth-Proxy to upgrade instead of mixing releases", async () => {
|
|
174
237
|
const r = await chooseImageDigests({
|
|
175
238
|
recorded: { admin: A, gateway: B },
|
|
176
239
|
deployed: true,
|
|
177
240
|
readLive: never,
|
|
178
241
|
resolveLatest: never,
|
|
179
242
|
});
|
|
180
|
-
expect(r).
|
|
243
|
+
expect(r.ok).toBe(false);
|
|
244
|
+
if (!r.ok) {
|
|
245
|
+
expect(r.error).toContain("records no mcp-auth-proxy image");
|
|
246
|
+
expect(r.error).toContain("Run `upgrade` first");
|
|
247
|
+
}
|
|
181
248
|
});
|
|
182
249
|
|
|
183
250
|
it("keeps what a provisioned install without a record runs (no :latest roll)", async () => {
|
|
184
251
|
const r = await chooseImageDigests({
|
|
185
252
|
recorded: null,
|
|
186
253
|
deployed: true,
|
|
187
|
-
readLive: async () => ({ ok: true, digests:
|
|
254
|
+
readLive: async () => ({ ok: true, digests: recorded }),
|
|
188
255
|
resolveLatest: never,
|
|
189
256
|
});
|
|
190
|
-
expect(r).toEqual({ ok: true, source: "live", digests:
|
|
257
|
+
expect(r).toEqual({ ok: true, source: "live", digests: recorded });
|
|
191
258
|
});
|
|
192
259
|
|
|
193
260
|
it("stops and points at upgrade when the running release can't be read", async () => {
|
package/src/deployed-release.ts
CHANGED
|
@@ -15,7 +15,14 @@ import { gcloud as defaultGcloud } from "./gcloud.js";
|
|
|
15
15
|
import type { GcloudRunner } from "./gcloud-retry.js";
|
|
16
16
|
import { GCP_STACK_ENV, gcpNamePrefix } from "./gcp-names.js";
|
|
17
17
|
import type { ImageDigests } from "./install-state.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
CLOUD_RUN_SLUGS,
|
|
20
|
+
type CloudRunSlug,
|
|
21
|
+
cloudRunServicePrefix,
|
|
22
|
+
type GcpProvider,
|
|
23
|
+
RETIRED_SERVICE_ENV,
|
|
24
|
+
SERVICE_SECRET_ENV,
|
|
25
|
+
} from "./stack-contract.js";
|
|
19
26
|
import { liveContainerEnv } from "./stack-converge.js";
|
|
20
27
|
|
|
21
28
|
/** The `sha256:…` digest an image reference pins, or null for a tag reference. */
|
|
@@ -23,13 +30,13 @@ export function digestFromImageRef(ref: string): string | null {
|
|
|
23
30
|
return ref.trim().match(/@(sha256:[0-9a-f]{64})$/)?.[1] ?? null;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
/** The single deployed
|
|
33
|
+
/** The single deployed Cloud Run service playing `slug` (stack-contract.ts cloudRunServicePrefix). */
|
|
27
34
|
async function findDeployedService(
|
|
28
35
|
run: GcloudRunner,
|
|
29
|
-
opts: { projectId: string; region: string },
|
|
36
|
+
opts: { provider: GcpProvider; projectId: string; region: string },
|
|
30
37
|
slug: CloudRunSlug,
|
|
31
38
|
): Promise<{ ok: true; name: string } | { ok: false; error: string }> {
|
|
32
|
-
const prefix =
|
|
39
|
+
const prefix = cloudRunServicePrefix(opts.provider, GCP_STACK_ENV, slug);
|
|
33
40
|
const list = await run([
|
|
34
41
|
"run",
|
|
35
42
|
"services",
|
|
@@ -47,7 +54,10 @@ async function findDeployedService(
|
|
|
47
54
|
if (names.length !== 1) {
|
|
48
55
|
return {
|
|
49
56
|
ok: false,
|
|
50
|
-
error:
|
|
57
|
+
error:
|
|
58
|
+
names.length === 0
|
|
59
|
+
? `no ${prefix}* Cloud Run service: the install runs a release from before it (the ${slug} became its own service in #1328). Run \`upgrade\` first: it creates the service and moves the release over.`
|
|
60
|
+
: `expected one ${prefix}* Cloud Run service, found ${names.join(", ")}`,
|
|
51
61
|
};
|
|
52
62
|
}
|
|
53
63
|
return { ok: true, name: names[0] as string };
|
|
@@ -68,12 +78,13 @@ async function findDeployedService(
|
|
|
68
78
|
* and the env in one revision.
|
|
69
79
|
*/
|
|
70
80
|
export async function checkDeployedRuntimeEnv(opts: {
|
|
81
|
+
provider: GcpProvider;
|
|
71
82
|
projectId: string;
|
|
72
83
|
region: string;
|
|
73
84
|
run?: GcloudRunner;
|
|
74
85
|
}): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
75
86
|
const run = opts.run ?? defaultGcloud;
|
|
76
|
-
for (const slug of
|
|
87
|
+
for (const slug of CLOUD_RUN_SLUGS) {
|
|
77
88
|
const found = await findDeployedService(run, opts, slug);
|
|
78
89
|
if (!found.ok) return found;
|
|
79
90
|
const describe = await run([
|
|
@@ -109,18 +120,19 @@ export async function checkDeployedRuntimeEnv(opts: {
|
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/**
|
|
112
|
-
* Read the image digests the
|
|
123
|
+
* Read the image digests the Cloud Run services run. Fails, naming
|
|
113
124
|
* what is missing, when a service can't be found unambiguously or doesn't
|
|
114
125
|
* run a digest-pinned image.
|
|
115
126
|
*/
|
|
116
127
|
export async function readDeployedImageDigests(opts: {
|
|
128
|
+
provider: GcpProvider;
|
|
117
129
|
projectId: string;
|
|
118
130
|
region: string;
|
|
119
131
|
run?: GcloudRunner;
|
|
120
132
|
}): Promise<{ ok: true; digests: ImageDigests } | { ok: false; error: string }> {
|
|
121
133
|
const run = opts.run ?? defaultGcloud;
|
|
122
|
-
const digests = { admin: "", gateway: "" };
|
|
123
|
-
for (const slug of
|
|
134
|
+
const digests: Record<CloudRunSlug, string> = { admin: "", gateway: "", "mcp-auth-proxy": "" };
|
|
135
|
+
for (const slug of CLOUD_RUN_SLUGS) {
|
|
124
136
|
const found = await findDeployedService(run, opts, slug);
|
|
125
137
|
if (!found.ok) return found;
|
|
126
138
|
const image = await run([
|
|
@@ -170,6 +182,15 @@ export async function chooseImageDigests(opts: {
|
|
|
170
182
|
readonly readLive: () => ReturnType<typeof readDeployedImageDigests>;
|
|
171
183
|
readonly resolveLatest: () => Promise<ImageDigests>;
|
|
172
184
|
}): Promise<DigestChoice> {
|
|
185
|
+
if (opts.recorded && !opts.recorded["mcp-auth-proxy"]) {
|
|
186
|
+
// #1328 — the release it records predates the MCP-Auth-Proxy: this
|
|
187
|
+
// installer's stack would deploy a proxy image of another release.
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
error:
|
|
191
|
+
"This install runs a release from before the MCP-Auth-Proxy (its install.json records no mcp-auth-proxy image). Run `upgrade` first — it sets the proxy up and records the release — then re-run the installer.",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
173
194
|
if (opts.recorded) return { ok: true, source: "recorded", digests: opts.recorded };
|
|
174
195
|
if (!opts.deployed) return { ok: true, source: "latest", digests: await opts.resolveLatest() };
|
|
175
196
|
const live = await opts.readLive();
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
* self-hosted the generated compose gateway service: env, secrets,
|
|
17
17
|
* mounts
|
|
18
18
|
*
|
|
19
|
+
* Issue #1329 — the same crosswise between the two public databases: the
|
|
20
|
+
* live gateway's env names nothing of the staging database
|
|
21
|
+
* (cms_public_staging, public_staging_role, its password secret), and its
|
|
22
|
+
* identity cannot read the staging login's password, on every adapter. The
|
|
23
|
+
* staging gateway (#1330) gets the mirror-image checks with its service.
|
|
24
|
+
*
|
|
19
25
|
* The gateway itself refuses to boot on such an env
|
|
20
26
|
* (apps/api-gateway `adminDatabaseAccessInEnv`); this catches it at review
|
|
21
27
|
* time, before a release can ship it.
|
|
@@ -42,6 +48,12 @@ const ADMIN_ACCESS = /cms_admin|admin_role|gateway_role|ADMIN_DATABASE|GATEWAY_D
|
|
|
42
48
|
/** Secrets that unlock a cms_admin login. */
|
|
43
49
|
const ADMIN_SECRETS = ["postgres-password", "gateway-role-password"];
|
|
44
50
|
|
|
51
|
+
/** #1329 — anything in the live gateway's env that reaches the staging database. */
|
|
52
|
+
const STAGING_ACCESS = /cms_public_staging|public_staging_role|PUBLIC_STAGING/;
|
|
53
|
+
|
|
54
|
+
/** #1329 — the secret that unlocks the staging gateway's login. */
|
|
55
|
+
const STAGING_SECRETS = ["public-staging-role-password"];
|
|
56
|
+
|
|
45
57
|
/** The secrets the gateway reads on GCP: its own login and the staging gate key. */
|
|
46
58
|
const GCP_GATEWAY_SECRETS = [
|
|
47
59
|
"public-role-password",
|
|
@@ -65,26 +77,39 @@ describe("gcp + gcp-firebase: the gateway env contract (#635)", () => {
|
|
|
65
77
|
domain: "example.com",
|
|
66
78
|
region: "europe-west1",
|
|
67
79
|
stagingHost: "staging.example.com",
|
|
68
|
-
databaseUrls: databaseUrls("
|
|
80
|
+
databaseUrls: databaseUrls("db1.p-label.europe-west1.sql-psa.goog"),
|
|
69
81
|
});
|
|
70
82
|
|
|
71
|
-
it("carries no cms_admin URL, role or credential name", () => {
|
|
83
|
+
it("carries no cms_admin URL, role or credential name, and nothing of the staging database (#1329)", () => {
|
|
72
84
|
for (const v of env) {
|
|
73
85
|
expect(v.name).not.toMatch(ADMIN_ACCESS);
|
|
74
|
-
|
|
86
|
+
expect(v.name).not.toMatch(STAGING_ACCESS);
|
|
87
|
+
if ("value" in v && typeof v.value === "string") {
|
|
88
|
+
expect(v.value).not.toMatch(ADMIN_ACCESS);
|
|
89
|
+
expect(v.value).not.toMatch(STAGING_ACCESS);
|
|
90
|
+
}
|
|
75
91
|
}
|
|
92
|
+
expect(env.find((v) => v.name === "CAELO_GATEWAY_ENVIRONMENT")).toEqual({
|
|
93
|
+
name: "CAELO_GATEWAY_ENVIRONMENT",
|
|
94
|
+
value: "live",
|
|
95
|
+
});
|
|
76
96
|
expect(env.find((v) => v.name === "PUBLIC_DATABASE_URL")).toEqual({
|
|
77
97
|
name: "PUBLIC_DATABASE_URL",
|
|
78
|
-
value:
|
|
98
|
+
value:
|
|
99
|
+
"postgresql://public_role@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem",
|
|
79
100
|
});
|
|
80
101
|
});
|
|
81
102
|
|
|
82
|
-
it("references no secret that unlocks a cms_admin login", () => {
|
|
103
|
+
it("references no secret that unlocks a cms_admin or the staging login", () => {
|
|
83
104
|
for (const secret of Object.values(SERVICE_SECRET_ENV.gateway)) {
|
|
84
105
|
expect(ADMIN_SECRETS).not.toContain(secret);
|
|
106
|
+
expect(STAGING_SECRETS).not.toContain(secret);
|
|
85
107
|
}
|
|
86
108
|
const gatewayBindings = runtimeSecretBindings().filter((b) => b.service === "gateway");
|
|
87
|
-
for (const b of gatewayBindings)
|
|
109
|
+
for (const b of gatewayBindings) {
|
|
110
|
+
expect(ADMIN_SECRETS).not.toContain(b.secret);
|
|
111
|
+
expect(STAGING_SECRETS).not.toContain(b.secret);
|
|
112
|
+
}
|
|
88
113
|
});
|
|
89
114
|
|
|
90
115
|
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
@@ -133,8 +158,9 @@ describe("self-hosted: the generated compose gateway service (#635)", () => {
|
|
|
133
158
|
const out = generateDockerCompose({
|
|
134
159
|
domain: "example.com",
|
|
135
160
|
postgresPassword: "superpw",
|
|
136
|
-
rolePasswords: { admin: "adminpw", public: "publicpw" },
|
|
161
|
+
rolePasswords: { admin: "adminpw", public: "publicpw", publicStaging: "stagingpw" },
|
|
137
162
|
caeloSecretKek: "0".repeat(64),
|
|
163
|
+
dbTlsServerCertSha256: "abababababababababababababababababababababababababababababababab",
|
|
138
164
|
internalSecret: "i".repeat(48),
|
|
139
165
|
diskSize: "20Gi",
|
|
140
166
|
images: {
|
|
@@ -150,15 +176,22 @@ describe("self-hosted: the generated compose gateway service (#635)", () => {
|
|
|
150
176
|
const gateway = out.slice(start, end === -1 ? undefined : end);
|
|
151
177
|
expect(gateway).toContain("PUBLIC_DATABASE_URL:");
|
|
152
178
|
expect(gateway).not.toMatch(ADMIN_ACCESS);
|
|
153
|
-
expect(gateway).not.toMatch(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
179
|
+
expect(gateway).not.toMatch(STAGING_ACCESS);
|
|
180
|
+
expect(gateway).not.toMatch(/superpw|adminpw|gatewaypw|stagingpw/);
|
|
181
|
+
expect(gateway).toContain('CAELO_GATEWAY_ENVIRONMENT: "live"');
|
|
182
|
+
// No other way in: no Compose secrets, no env file, and exactly one
|
|
183
|
+
// mount — the database CA certificate (#1338), public, no credential.
|
|
184
|
+
expect(gateway).not.toMatch(/^ {4}(secrets|env_file):/m);
|
|
185
|
+
expect(gateway).toContain(
|
|
186
|
+
" volumes:\n - ./postgres-tls/ca.crt:/etc/caelo/db-ca/self-hosted-ca.pem:ro\n restart:",
|
|
187
|
+
);
|
|
157
188
|
const env = gateway.slice(gateway.indexOf(" environment:"));
|
|
158
189
|
const keys = [...env.matchAll(/^ {6}([A-Z_][A-Z0-9_]*):/gm)].map((m) => m[1]);
|
|
159
190
|
expect(keys.sort()).toEqual([
|
|
160
191
|
// #1254 — its cms_public pool size (pool-budget-stack.ts).
|
|
161
192
|
"CAELO_DB_PUBLIC_POOL_MAX",
|
|
193
|
+
// #1329 — the live site's gateway.
|
|
194
|
+
"CAELO_GATEWAY_ENVIRONMENT",
|
|
162
195
|
"CAELO_TRUSTED_PROXY_HOPS",
|
|
163
196
|
"GATEWAY_PORT",
|
|
164
197
|
"NODE_ENV",
|
|
@@ -176,6 +209,9 @@ describe("azure: the gateway container app (#635)", () => {
|
|
|
176
209
|
const block = src.slice(start, src.indexOf("});", start));
|
|
177
210
|
expect(block).toContain('{ name: "PUBLIC_DATABASE_URL", value: publicDatabaseUrl }');
|
|
178
211
|
expect(block).not.toMatch(/adminDatabase|gatewayDatabase|ADMIN_DATABASE|GATEWAY_DATABASE/);
|
|
212
|
+
// #1329 — nothing of the staging database, and it says it is the live one.
|
|
213
|
+
expect(block).not.toMatch(/publicStaging|PUBLIC_STAGING/);
|
|
214
|
+
expect(block).toContain('{ name: GATEWAY_ENVIRONMENT_ENV, value: "live" }');
|
|
179
215
|
});
|
|
180
216
|
|
|
181
217
|
it("builds no cms_admin URL for any role but the admin's", () => {
|
|
@@ -201,7 +237,9 @@ describe("aws: the stack (#635)", () => {
|
|
|
201
237
|
|
|
202
238
|
it("the gateway's task role reads only the public_role password (review finding)", () => {
|
|
203
239
|
expect(AWS_SERVICE_SECRETS.gateway).toEqual(["public-role-password"]);
|
|
204
|
-
for (const secret of ADMIN_SECRETS)
|
|
240
|
+
for (const secret of [...ADMIN_SECRETS, ...STAGING_SECRETS]) {
|
|
241
|
+
expect(AWS_SERVICE_SECRETS.gateway).not.toContain(secret);
|
|
242
|
+
}
|
|
205
243
|
});
|
|
206
244
|
|
|
207
245
|
it("each service gets its own task role whose policy is built from that table", () => {
|
package/src/gcp-names.ts
CHANGED
|
@@ -56,6 +56,37 @@ export function gatewayServiceAccountEmail(projectId: string, env: string): stri
|
|
|
56
56
|
return `${gatewayServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* #1328 — account id of the service account the MCP-Auth-Proxy runs as. It
|
|
61
|
+
* may read the Google sign-in client and the broker's seal key and nothing
|
|
62
|
+
* else: no database password, no project role (stack-contract.ts
|
|
63
|
+
* `SERVICE_SECRET_ENV` / `SERVICE_SECRET_FILES`). Shortened from the
|
|
64
|
+
* service's name: account ids max out at 30 characters.
|
|
65
|
+
*/
|
|
66
|
+
export function mcpAuthProxyServiceAccountId(env: string): string {
|
|
67
|
+
return `${gcpNamePrefix(env)}-mcp-proxy-sa`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* #1328 — the `gcp` MCP-Auth-Proxy's load-balancer resources, CLI-owned
|
|
72
|
+
* (remote-mcp.ts ensureRemoteMcpEdge): its serverless NEG, its backend
|
|
73
|
+
* service and its own Cloud Armor policy. None matches a prefix the other
|
|
74
|
+
* lookups use (`…-waf`, `…-admin-backend`, `…-gateway-backend`).
|
|
75
|
+
*/
|
|
76
|
+
export function mcpAuthProxyEdgeNames(env: string): {
|
|
77
|
+
readonly neg: string;
|
|
78
|
+
readonly backend: string;
|
|
79
|
+
readonly policy: string;
|
|
80
|
+
} {
|
|
81
|
+
const prefix = `${gcpNamePrefix(env)}-mcp-auth-proxy`;
|
|
82
|
+
return { neg: `${prefix}-neg`, backend: `${prefix}-backend`, policy: `${prefix}-armor` };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Email of the service account the MCP-Auth-Proxy runs as. */
|
|
86
|
+
export function mcpAuthProxyServiceAccountEmail(projectId: string, env: string): string {
|
|
87
|
+
return `${mcpAuthProxyServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
|
|
88
|
+
}
|
|
89
|
+
|
|
59
90
|
/**
|
|
60
91
|
* Account id of the `gcp` stack's static-publisher service account. Account
|
|
61
92
|
* ids max out at 30 chars, so the env is shortened.
|
|
@@ -190,6 +221,31 @@ export function captureServiceAccountEmail(projectId: string, env: string): stri
|
|
|
190
221
|
return `${captureServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
|
|
191
222
|
}
|
|
192
223
|
|
|
224
|
+
/**
|
|
225
|
+
* #1140 — account id of the scheduler tick's service account: signs its own
|
|
226
|
+
* IAP credential and calls the admin's tick endpoint, nothing else
|
|
227
|
+
* (scheduler-setup.ts). `caelo-production-scheduler` is 26 of the 30
|
|
228
|
+
* characters an account id may have.
|
|
229
|
+
*/
|
|
230
|
+
export function schedulerServiceAccountId(env: string): string {
|
|
231
|
+
return `${gcpNamePrefix(env)}-scheduler`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Email of the scheduler tick's service account. */
|
|
235
|
+
export function schedulerServiceAccountEmail(projectId: string, env: string): string {
|
|
236
|
+
return `${schedulerServiceAccountId(env)}@${projectId}.iam.gserviceaccount.com`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** #1140 — the Cloud Workflows workflow one scheduler tick runs (scheduler-setup.ts). */
|
|
240
|
+
export function schedulerWorkflowName(env: string): string {
|
|
241
|
+
return `${gcpNamePrefix(env)}-scheduler-tick`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** #1140 — the Cloud Scheduler job that starts the tick workflow every N minutes. */
|
|
245
|
+
export function schedulerJobName(env: string): string {
|
|
246
|
+
return `${gcpNamePrefix(env)}-scheduler-tick`;
|
|
247
|
+
}
|
|
248
|
+
|
|
193
249
|
/**
|
|
194
250
|
* Account id of the upgrader job's own service account: may roll the two
|
|
195
251
|
* Cloud Run services, run the migration job and back up Cloud SQL, never
|
package/src/gcp-url-map.test.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { beforeAll, describe, expect, it } from "bun:test";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
|
-
import {
|
|
12
|
+
import { GCP_GATEWAY_PATHS, isGcpRedirectRule, withGcpRedirects } from "@caelo-cms/shared";
|
|
13
13
|
import * as pulumi from "@pulumi/pulumi";
|
|
14
14
|
import { URL_MAP_REDIRECTS_PERMISSIONS, urlMapRedirectsRoleId } from "./gcp-names.js";
|
|
15
15
|
|
|
@@ -66,7 +66,8 @@ describe("gcp stack URL map (Pulumi mocks)", () => {
|
|
|
66
66
|
{
|
|
67
67
|
name: PUBLIC_PATH_MATCHER,
|
|
68
68
|
defaultService: "bucket-id",
|
|
69
|
-
|
|
69
|
+
// #1328 — only /api/* here: the MCP-Auth-Proxy's rule is the CLI's.
|
|
70
|
+
pathRules: [{ paths: [...GCP_GATEWAY_PATHS], service: "gateway-id" }],
|
|
70
71
|
},
|
|
71
72
|
{ name: "admin", defaultService: "admin-id" },
|
|
72
73
|
]);
|
|
@@ -102,12 +103,9 @@ describe("gcp stack URL map (Pulumi mocks)", () => {
|
|
|
102
103
|
);
|
|
103
104
|
expect(withRedirects.pathRules?.filter((r) => !isGcpRedirectRule(r))).toEqual([
|
|
104
105
|
{
|
|
105
|
-
paths
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"/.well-known/oauth-protected-resource/*",
|
|
109
|
-
"/.well-known/oauth-authorization-server/*",
|
|
110
|
-
],
|
|
106
|
+
// #1328 — the remote-MCP discovery paths go to the MCP-Auth-Proxy's
|
|
107
|
+
// own rule, which the CLI adds (remote-mcp.ts planUrlMapPaths).
|
|
108
|
+
paths: ["/api/*"],
|
|
111
109
|
service: "gateway-id",
|
|
112
110
|
},
|
|
113
111
|
]);
|
package/src/gcp-url-map.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Imported by the stack from `dist/`; keep it free of CLI dependencies.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {
|
|
22
|
+
import { GCP_GATEWAY_PATHS } from "@caelo-cms/shared";
|
|
23
23
|
import * as gcp from "@pulumi/gcp";
|
|
24
24
|
import type * as pulumi from "@pulumi/pulumi";
|
|
25
25
|
|
|
@@ -58,7 +58,11 @@ export function siteUrlMapArgs(i: SiteUrlMapInputs): gcp.compute.URLMapArgs {
|
|
|
58
58
|
{
|
|
59
59
|
name: PUBLIC_PATH_MATCHER,
|
|
60
60
|
defaultService: i.staticBackend,
|
|
61
|
-
|
|
61
|
+
// #1328 — the MCP-Auth-Proxy's rule (GCP_MCP_AUTH_PROXY_PATHS) is the
|
|
62
|
+
// CLI's: it creates the proxy's backend after the first `up`
|
|
63
|
+
// (remote-mcp.ts ensureRemoteMcpEdge), and the stack ignores
|
|
64
|
+
// `pathRules` after creation anyway.
|
|
65
|
+
pathRules: [{ paths: [...GCP_GATEWAY_PATHS], service: i.gatewayBackend }],
|
|
62
66
|
},
|
|
63
67
|
{
|
|
64
68
|
name: "admin",
|