@caelo-cms/provisioning 0.11.9 → 0.11.10
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/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 +6 -1
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +86 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +20 -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 +44 -2
- 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.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +63 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +289 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +29 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +95 -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-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +2 -11
- package/dist/converge-job-gcp.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 +21 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +24 -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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -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 +24 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-state.d.ts +6 -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 +17 -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/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 +9 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +21 -10
- package/dist/runtime-secrets.js.map +1 -1
- 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 +34 -0
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +52 -0
- package/dist/self-hosted-roles.js.map +1 -1
- 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 +15 -6
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +72 -23
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +170 -25
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +314 -37
- 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/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.map +1 -1
- package/dist/upgrade/converge.js +99 -12
- 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 +13 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +34 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +12 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +49 -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/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +22 -7
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +43 -17
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +1 -0
- package/src/adopt-existing.test.ts +39 -0
- package/src/adopt-existing.ts +83 -1
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +36 -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 +3 -0
- 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.ts +12 -1
- package/src/cloudsql-tls.test.ts +225 -0
- package/src/cloudsql-tls.ts +352 -0
- package/src/compose.ts +125 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-job-gcp.ts +2 -11
- package/src/database-tls-builders.test.ts +55 -0
- package/src/deployed-release.test.ts +104 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +10 -5
- package/src/gcp-names.ts +31 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +7 -1
- package/src/index.ts +1 -0
- package/src/infra-contract.test.ts +5 -3
- package/src/infra-contract.ts +24 -1
- package/src/install-state.ts +26 -4
- package/src/lifecycle.ts +20 -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/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +5 -4
- package/src/pool-budget-stacks.test.ts +1 -1
- 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 +20 -7
- package/src/runtime-secrets.ts +50 -9
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-compose.test.ts +108 -5
- 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.test.ts +54 -0
- package/src/self-hosted-roles.ts +58 -0
- package/src/self-hosted-start.test.ts +34 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +103 -14
- package/src/self-hosted-upgrade.ts +115 -29
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +169 -29
- package/src/stack-contract.ts +362 -42
- package/src/stack-converge-secrets.test.ts +26 -13
- package/src/stack-converge.test.ts +199 -26
- package/src/stack-converge.ts +92 -30
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +120 -11
- 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 +353 -44
- package/src/upgrade/upgrade.ts +55 -6
- package/src/upgrader-gcp.ts +65 -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/wizards/gcp-cost.test.ts +50 -0
- package/src/wizards/gcp-cost.ts +24 -7
- package/src/wizards/gcp.ts +55 -14
- package/stacks/aws/index.ts +32 -14
- package/stacks/azure/index.ts +57 -18
- package/stacks/gcp/index.ts +83 -23
- package/stacks/gcp-firebase/index.ts +162 -33
- package/stacks/self-hosted/README.md +2 -1
- package/stacks/self-hosted/index.ts +11 -1
|
@@ -29,13 +29,21 @@ import {
|
|
|
29
29
|
} from "./upgrader-records.js";
|
|
30
30
|
|
|
31
31
|
const D = (c: string) => `sha256:${c.repeat(64)}`;
|
|
32
|
+
// #1328 — the MCP-Auth-Proxy's index; serving revisions name it as is here,
|
|
33
|
+
// so it matches without a registry lookup.
|
|
34
|
+
const PROXY_INDEX = `sha256:${"e".repeat(64)}`;
|
|
32
35
|
const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
33
36
|
|
|
34
37
|
const V064: ReleaseManifest = {
|
|
35
38
|
manifestVersion: 1,
|
|
36
39
|
version: "0.10.64",
|
|
37
40
|
channel: "stable",
|
|
38
|
-
digests: {
|
|
41
|
+
digests: {
|
|
42
|
+
admin: ADMIN_INDEX,
|
|
43
|
+
gateway: GATEWAY_INDEX,
|
|
44
|
+
ops: D("c"),
|
|
45
|
+
"mcp-auth-proxy": PROXY_INDEX,
|
|
46
|
+
},
|
|
39
47
|
minFromVersion: "0.10.0",
|
|
40
48
|
infraContractVersion: 13,
|
|
41
49
|
migrations: { contract: false, lastContractVersion: null, head: "0338_x" },
|
|
@@ -51,13 +59,17 @@ const META: InstallMetadata = {
|
|
|
51
59
|
ownerEmail: "owner@acme.com",
|
|
52
60
|
region: "europe-west1",
|
|
53
61
|
createdAt: "2026-01-01T00:00:00Z",
|
|
54
|
-
imageDigests: { admin: ADMIN_INDEX, gateway: GATEWAY_INDEX },
|
|
62
|
+
imageDigests: { admin: ADMIN_INDEX, gateway: GATEWAY_INDEX, "mcp-auth-proxy": PROXY_INDEX },
|
|
55
63
|
releaseVersion: "0.10.64",
|
|
56
64
|
infraContractVersion: 13,
|
|
57
65
|
};
|
|
58
66
|
|
|
59
67
|
/** What the serving revisions read back as on searchviu-website (spec = status). */
|
|
60
|
-
const LIVE_SERVING: ServingDigests = {
|
|
68
|
+
const LIVE_SERVING: ServingDigests = {
|
|
69
|
+
admin: [ADMIN_AMD64],
|
|
70
|
+
gateway: [GATEWAY_AMD64],
|
|
71
|
+
"mcp-auth-proxy": [PROXY_INDEX],
|
|
72
|
+
};
|
|
61
73
|
|
|
62
74
|
function deps(
|
|
63
75
|
opts: {
|
|
@@ -112,16 +124,38 @@ describe("recordMatchesServing", () => {
|
|
|
112
124
|
const both = { ...META, platformDigests: { admin: ADMIN_AMD64, gateway: GATEWAY_AMD64 } };
|
|
113
125
|
expect(recordMatchesServing(both, LIVE_SERVING)).toBe(true);
|
|
114
126
|
// Still by the index when a revision names it.
|
|
115
|
-
expect(
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
expect(
|
|
128
|
+
recordMatchesServing(META, {
|
|
129
|
+
admin: [ADMIN_INDEX],
|
|
130
|
+
gateway: [GATEWAY_INDEX],
|
|
131
|
+
"mcp-auth-proxy": [PROXY_INDEX],
|
|
132
|
+
}),
|
|
133
|
+
).toBe(true);
|
|
118
134
|
});
|
|
119
135
|
|
|
120
136
|
it("both services must match, and no record never matches", () => {
|
|
121
137
|
const both = { ...META, platformDigests: { admin: ADMIN_AMD64, gateway: GATEWAY_AMD64 } };
|
|
122
|
-
expect(
|
|
138
|
+
expect(
|
|
139
|
+
recordMatchesServing(both, {
|
|
140
|
+
admin: [ADMIN_AMD64],
|
|
141
|
+
gateway: [D("6")],
|
|
142
|
+
"mcp-auth-proxy": [PROXY_INDEX],
|
|
143
|
+
}),
|
|
144
|
+
).toBe(false);
|
|
123
145
|
expect(recordMatchesServing({}, LIVE_SERVING)).toBe(false);
|
|
124
146
|
});
|
|
147
|
+
|
|
148
|
+
it("#1328: an install.json from before the MCP-Auth-Proxy (no proxy digest) never matches", () => {
|
|
149
|
+
const { "mcp-auth-proxy": _none, ...old } = META.imageDigests as NonNullable<
|
|
150
|
+
InstallMetadata["imageDigests"]
|
|
151
|
+
>;
|
|
152
|
+
const both = {
|
|
153
|
+
...META,
|
|
154
|
+
imageDigests: old,
|
|
155
|
+
platformDigests: { admin: ADMIN_AMD64, gateway: GATEWAY_AMD64 },
|
|
156
|
+
};
|
|
157
|
+
expect(recordMatchesServing(both, LIVE_SERVING)).toBe(false);
|
|
158
|
+
});
|
|
125
159
|
});
|
|
126
160
|
|
|
127
161
|
describe("findServingRelease", () => {
|
|
@@ -141,12 +175,12 @@ describe("findServingRelease", () => {
|
|
|
141
175
|
it("an attestation manifest of the index is not a serving image of the release", async () => {
|
|
142
176
|
const r = await findServingRelease(
|
|
143
177
|
META,
|
|
144
|
-
{ admin: [ADMIN_ATTESTATION], gateway: [GATEWAY_AMD64] },
|
|
178
|
+
{ admin: [ADMIN_ATTESTATION], gateway: [GATEWAY_AMD64], "mcp-auth-proxy": [PROXY_INDEX] },
|
|
145
179
|
deps(),
|
|
146
180
|
);
|
|
147
181
|
expect(r.ok).toBe(false);
|
|
148
182
|
expect(r.ok ? "" : r.reason).toContain(
|
|
149
|
-
"The gateway
|
|
183
|
+
"The gateway and the MCP-Auth-Proxy serve v0.10.64's image, the admin does not.",
|
|
150
184
|
);
|
|
151
185
|
});
|
|
152
186
|
|
|
@@ -166,7 +200,11 @@ describe("findServingRelease", () => {
|
|
|
166
200
|
});
|
|
167
201
|
|
|
168
202
|
it("a digest in no signed index is still refused with the current message", async () => {
|
|
169
|
-
const r = await findServingRelease(
|
|
203
|
+
const r = await findServingRelease(
|
|
204
|
+
META,
|
|
205
|
+
{ admin: [D("7")], gateway: [D("6")], "mcp-auth-proxy": [PROXY_INDEX] },
|
|
206
|
+
deps(),
|
|
207
|
+
);
|
|
170
208
|
expect(r).toMatchObject({ ok: false, retryable: false });
|
|
171
209
|
expect(r.ok ? "" : r.reason).toContain(
|
|
172
210
|
"are not the ones install.json names, and they do not belong to one signed Caelo release",
|
|
@@ -190,13 +228,60 @@ describe("findServingRelease", () => {
|
|
|
190
228
|
|
|
191
229
|
it("finds the release by the template's index when install.json names none", async () => {
|
|
192
230
|
const { releaseVersion: _none, ...unversioned } = META;
|
|
193
|
-
const meta = {
|
|
231
|
+
const meta = {
|
|
232
|
+
...unversioned,
|
|
233
|
+
imageDigests: { admin: D("9"), gateway: D("8"), "mcp-auth-proxy": D("5") },
|
|
234
|
+
};
|
|
194
235
|
const d = deps({
|
|
195
236
|
tags: { admin: { "0.10.64": ADMIN_INDEX } },
|
|
196
|
-
hints: { admin: [ADMIN_INDEX], gateway: [GATEWAY_INDEX] },
|
|
237
|
+
hints: { admin: [ADMIN_INDEX], gateway: [GATEWAY_INDEX], "mcp-auth-proxy": [PROXY_INDEX] },
|
|
197
238
|
});
|
|
198
239
|
const r = await findServingRelease(meta, LIVE_SERVING, d);
|
|
199
240
|
expect(r.ok && r.manifest.version).toBe("0.10.64");
|
|
200
241
|
expect(d.fetched).toEqual(["0.10.64"]);
|
|
201
242
|
});
|
|
202
243
|
});
|
|
244
|
+
|
|
245
|
+
describe("findServingRelease — the MCP-Auth-Proxy (#1328)", () => {
|
|
246
|
+
it("records the proxy's signed platform form with the others", async () => {
|
|
247
|
+
const PROXY_AMD64 = `sha256:${"f".repeat(64)}`;
|
|
248
|
+
const signed: ReleaseManifest = {
|
|
249
|
+
...V064,
|
|
250
|
+
platformDigests: {
|
|
251
|
+
admin: ADMIN_AMD64,
|
|
252
|
+
gateway: GATEWAY_AMD64,
|
|
253
|
+
ops: D("3"),
|
|
254
|
+
"mcp-auth-proxy": PROXY_AMD64,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
const r = await findServingRelease(
|
|
258
|
+
META,
|
|
259
|
+
{ ...LIVE_SERVING, "mcp-auth-proxy": [PROXY_AMD64] },
|
|
260
|
+
deps({ manifests: { "0.10.64": signed } }),
|
|
261
|
+
);
|
|
262
|
+
expect(r).toMatchObject({
|
|
263
|
+
ok: true,
|
|
264
|
+
platformDigests: {
|
|
265
|
+
admin: ADMIN_AMD64,
|
|
266
|
+
gateway: GATEWAY_AMD64,
|
|
267
|
+
"mcp-auth-proxy": PROXY_AMD64,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("a release from before the proxy cannot be what serves (its manifest names no proxy)", async () => {
|
|
273
|
+
const { "mcp-auth-proxy": _none, ...digests } = V064.digests;
|
|
274
|
+
const old: ReleaseManifest = { ...V064, digests };
|
|
275
|
+
const r = await findServingRelease(META, LIVE_SERVING, deps({ manifests: { "0.10.64": old } }));
|
|
276
|
+
expect(r.ok).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("a proxy of another release is refused", async () => {
|
|
280
|
+
const r = await findServingRelease(
|
|
281
|
+
META,
|
|
282
|
+
{ ...LIVE_SERVING, "mcp-auth-proxy": [D("4")] },
|
|
283
|
+
deps(),
|
|
284
|
+
);
|
|
285
|
+
expect(r.ok).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
});
|
package/src/upgrader-records.ts
CHANGED
|
@@ -30,14 +30,16 @@
|
|
|
30
30
|
import type { GcloudRunner } from "./gcloud-retry.js";
|
|
31
31
|
import type { ImageDigests, InstallMetadata } from "./install-state.js";
|
|
32
32
|
import type { ReleaseManifest, ReleaseManifestResult } from "./release-manifest.js";
|
|
33
|
-
import type
|
|
33
|
+
import { ROLL_ORDER, type RollSlug } from "./upgrade/app-roll.js";
|
|
34
34
|
import type { ReleaseImageForms } from "./upgrade/image-forms.js";
|
|
35
35
|
import { releaseVersionForDigest } from "./upgrade/release-images.js";
|
|
36
36
|
|
|
37
37
|
/** Every digest each serving image is known by (gcp-lookup.ts servingImageDigests). */
|
|
38
38
|
export type ServingDigests = Readonly<Record<RollSlug, readonly string[]>>;
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// #1328 — the MCP-Auth-Proxy serves on every install this code runs on (the
|
|
41
|
+
// upgrader runs only on installs converged to its contract).
|
|
42
|
+
const SLUGS: readonly RollSlug[] = ROLL_ORDER;
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Whether install.json names the images that serve: per service, the
|
|
@@ -50,11 +52,14 @@ export function recordMatchesServing(
|
|
|
50
52
|
): boolean {
|
|
51
53
|
const { imageDigests, platformDigests } = recorded;
|
|
52
54
|
if (!imageDigests) return false;
|
|
53
|
-
return SLUGS.every(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
return SLUGS.every((slug) => {
|
|
56
|
+
const index = imageDigests[slug];
|
|
57
|
+
const platform = platformDigests?.[slug];
|
|
58
|
+
return (
|
|
59
|
+
(index !== undefined && serving[slug].includes(index)) ||
|
|
60
|
+
(platform !== undefined && serving[slug].includes(platform))
|
|
61
|
+
);
|
|
62
|
+
});
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
/** What {@link findServingRelease} learned. */
|
|
@@ -139,7 +144,7 @@ export async function findServingRelease(
|
|
|
139
144
|
if (judged.matching.length > 0) {
|
|
140
145
|
const other = SLUGS.filter((s) => !judged.matching.includes(s));
|
|
141
146
|
partial.push(
|
|
142
|
-
|
|
147
|
+
`${serviceList(judged.matching)} ${judged.matching.length > 1 ? "serve" : "serves"} v${m.version}'s image, ${serviceList(other)} ${other.length > 1 ? "do" : "does"} not`,
|
|
143
148
|
);
|
|
144
149
|
}
|
|
145
150
|
}
|
|
@@ -162,7 +167,7 @@ export async function findServingRelease(
|
|
|
162
167
|
...(unverified.length > 0
|
|
163
168
|
? [`These release records could not be verified: ${unverified.join("; ")}.`]
|
|
164
169
|
: []),
|
|
165
|
-
"Nothing was changed. Check who deployed these images (the Cloud Run revision history of
|
|
170
|
+
"Nothing was changed. Check who deployed these images (the Cloud Run revision history of the services); an upgrade with Cloud Shell puts a signed release back.",
|
|
166
171
|
].join(" "),
|
|
167
172
|
};
|
|
168
173
|
}
|
|
@@ -189,7 +194,10 @@ async function judgeRelease(
|
|
|
189
194
|
const matching: RollSlug[] = [];
|
|
190
195
|
const platform: Partial<Record<RollSlug, string>> = {};
|
|
191
196
|
for (const slug of SLUGS) {
|
|
192
|
-
|
|
197
|
+
const index = m.digests[slug];
|
|
198
|
+
// #1328 — a release from before the proxy cannot be what serves here.
|
|
199
|
+
if (!index) continue;
|
|
200
|
+
if (serving[slug].includes(index)) {
|
|
193
201
|
matching.push(slug);
|
|
194
202
|
const signed = m.platformDigests?.[slug];
|
|
195
203
|
if (signed) platform[slug] = signed;
|
|
@@ -205,10 +213,12 @@ async function judgeRelease(
|
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
const { admin, gateway } = platform;
|
|
216
|
+
const proxy = platform["mcp-auth-proxy"];
|
|
208
217
|
return {
|
|
209
218
|
kind: "judged",
|
|
210
219
|
matching,
|
|
211
|
-
platformDigests:
|
|
220
|
+
platformDigests:
|
|
221
|
+
admin && gateway ? { admin, gateway, ...(proxy ? { "mcp-auth-proxy": proxy } : {}) } : null,
|
|
212
222
|
};
|
|
213
223
|
}
|
|
214
224
|
|
|
@@ -245,3 +255,10 @@ async function* candidateVersions(
|
|
|
245
255
|
function capitalize(s: string): string {
|
|
246
256
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
247
257
|
}
|
|
258
|
+
|
|
259
|
+
/** `the admin`, `the gateway and the MCP-Auth-Proxy`, … for the refusal text. */
|
|
260
|
+
function serviceList(slugs: readonly RollSlug[]): string {
|
|
261
|
+
const names = slugs.map((s) => (s === "mcp-auth-proxy" ? "the MCP-Auth-Proxy" : `the ${s}`));
|
|
262
|
+
if (names.length <= 1) return names[0] ?? "";
|
|
263
|
+
return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
264
|
+
}
|
|
@@ -81,6 +81,8 @@ describe("ensureUpgraderJob", () => {
|
|
|
81
81
|
"projects add-iam-policy-binding acme → projects/acme/roles/caeloUpgraderSqlBackup",
|
|
82
82
|
"iam service-accounts add-iam-policy-binding caelo-production-run-sa@acme.iam.gserviceaccount.com → roles/iam.serviceAccountUser",
|
|
83
83
|
"iam service-accounts add-iam-policy-binding caelo-production-gateway-sa@acme.iam.gserviceaccount.com → roles/iam.serviceAccountUser",
|
|
84
|
+
// #1328 — the MCP-Auth-Proxy's revisions run as its own account.
|
|
85
|
+
"iam service-accounts add-iam-policy-binding caelo-production-mcp-proxy-sa@acme.iam.gserviceaccount.com → roles/iam.serviceAccountUser",
|
|
84
86
|
"iam service-accounts add-iam-policy-binding caelo-production-upgrader@acme.iam.gserviceaccount.com → roles/iam.serviceAccountUser",
|
|
85
87
|
"iam service-accounts add-iam-policy-binding caelo-mcp@acme.iam.gserviceaccount.com → roles/iam.serviceAccountTokenCreator",
|
|
86
88
|
"iam service-accounts add-iam-policy-binding caelo-production-audit@acme.iam.gserviceaccount.com → roles/iam.serviceAccountUser",
|
package/src/upgrader-setup.ts
CHANGED
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
* PULUMI_*) and so act as the provisioner (#883 review). The custom role
|
|
28
28
|
* has no `runWithOverrides` and no `*.setIamPolicy`. Updating another
|
|
29
29
|
* job (the converge job's image, say) additionally needs `actAs` on its
|
|
30
|
-
* service account, which the upgrader holds only on the admin, gateway
|
|
31
|
-
* and its own. A grant of `roles/run.developer` left by an earlier
|
|
30
|
+
* service account, which the upgrader holds only on the admin, gateway,
|
|
31
|
+
* MCP-Auth-Proxy and its own. A grant of `roles/run.developer` left by an earlier
|
|
32
32
|
* setup is removed.
|
|
33
|
-
* - `roles/iam.serviceAccountUser` on exactly the admin's
|
|
34
|
-
* gateway's runtime service accounts (a revision runs as them), on
|
|
33
|
+
* - `roles/iam.serviceAccountUser` on exactly the admin's, the
|
|
34
|
+
* gateway's and the MCP-Auth-Proxy's (#1328) runtime service accounts (a revision runs as them), on
|
|
35
35
|
* itself (its own job) and on the quality-audit and capture jobs'
|
|
36
36
|
* accounts (the app-roll moves those jobs' images, #875, #1217) — not
|
|
37
37
|
* on the project.
|
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
auditServiceAccountEmail,
|
|
80
80
|
captureServiceAccountEmail,
|
|
81
81
|
gatewayServiceAccountEmail,
|
|
82
|
+
mcpAuthProxyServiceAccountEmail,
|
|
82
83
|
mcpIapServiceAccountEmail,
|
|
83
84
|
runServiceAccountEmail,
|
|
84
85
|
upgraderJobName,
|
|
@@ -367,6 +368,11 @@ export async function ensureUpgraderJob(
|
|
|
367
368
|
role: "roles/iam.serviceAccountUser",
|
|
368
369
|
why: "gateway revisions run as it",
|
|
369
370
|
},
|
|
371
|
+
{
|
|
372
|
+
account: mcpAuthProxyServiceAccountEmail(projectId, env),
|
|
373
|
+
role: "roles/iam.serviceAccountUser",
|
|
374
|
+
why: "MCP-Auth-Proxy revisions run as it (#1328)",
|
|
375
|
+
},
|
|
370
376
|
{ account: sa, role: "roles/iam.serviceAccountUser", why: "the job updates its own image" },
|
|
371
377
|
{
|
|
372
378
|
account: auditServiceAccountEmail(projectId, env),
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* #1328 — the pre-flight cost table shows the MCP-Auth-Proxy on both GCP
|
|
5
|
+
* variants, its own Cloud Armor policy on `gcp`, and the gcp-firebase data
|
|
6
|
+
* gateway (CLAUDE.md §11.C: every billable resource has a cost line).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "bun:test";
|
|
10
|
+
import { type CostEstimateInputs, estimateGcpCost } from "./gcp-cost.js";
|
|
11
|
+
|
|
12
|
+
const inputs: CostEstimateInputs = {
|
|
13
|
+
cloudSqlTier: "db-f1-micro",
|
|
14
|
+
cloudSqlHa: false,
|
|
15
|
+
adminMinInstances: 0,
|
|
16
|
+
gatewayMinInstances: 0,
|
|
17
|
+
wafAdaptiveProtection: false,
|
|
18
|
+
region: "europe-west1",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function names(provider: "gcp" | "gcp-firebase"): string[] {
|
|
22
|
+
return estimateGcpCost({ ...inputs, provider }).lines.map((l) => l.name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("cost table: MCP-Auth-Proxy (#1328)", () => {
|
|
26
|
+
it("lists the proxy's Cloud Run service at $0 idle on both variants", () => {
|
|
27
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
28
|
+
const line = estimateGcpCost({ ...inputs, provider }).lines.find(
|
|
29
|
+
(l) => l.name === "Cloud Run MCP-Auth-Proxy",
|
|
30
|
+
);
|
|
31
|
+
expect(line?.monthlyUsd).toBe(0);
|
|
32
|
+
expect(line?.notes).toContain("scale-to-zero");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("prices the proxy's own Cloud Armor policy on gcp only (policy + 2 rules)", () => {
|
|
37
|
+
const gcp = estimateGcpCost({ ...inputs, provider: "gcp" }).lines.find((l) =>
|
|
38
|
+
l.name.startsWith("MCP-Auth-Proxy backend"),
|
|
39
|
+
);
|
|
40
|
+
expect(gcp?.monthlyUsd).toBe(7);
|
|
41
|
+
expect(names("gcp-firebase").some((n) => n.startsWith("MCP-Auth-Proxy backend"))).toBe(false);
|
|
42
|
+
// The throttle moved off the gateway's policy: no separate rule line.
|
|
43
|
+
expect(names("gcp")).not.toContain("Cloud Armor rule: remote MCP throttle");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("names the gcp-firebase visitor gateway as the data gateway", () => {
|
|
47
|
+
expect(names("gcp-firebase")).toContain("Cloud Run data gateway");
|
|
48
|
+
expect(names("gcp")).toContain("Cloud Run gateway");
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/wizards/gcp-cost.ts
CHANGED
|
@@ -116,13 +116,24 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
116
116
|
"one short container per server-side screenshot or page render (~20-40 s at 1 vCPU / 2 GiB): ~$0.001 each, a 50-page site import ~$0.05; mostly in the free tier; capture bucket ~$0",
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
|
-
|
|
119
|
+
// #1328 — on gcp-firebase the visitor gateway is the new
|
|
120
|
+
// `caelo-<env>-data-gateway` service; same billing either way.
|
|
121
|
+
name: provider === "gcp" ? "Cloud Run gateway" : "Cloud Run data gateway",
|
|
120
122
|
monthlyUsd: inputs.gatewayMinInstances === 0 ? 1 : 1 + inputs.gatewayMinInstances * 15,
|
|
121
123
|
notes:
|
|
122
124
|
inputs.gatewayMinInstances === 0
|
|
123
125
|
? "scale-to-zero; ~$1/mo light traffic"
|
|
124
126
|
: `${inputs.gatewayMinInstances} min-instance${inputs.gatewayMinInstances > 1 ? "s" : ""}`,
|
|
125
127
|
},
|
|
128
|
+
{
|
|
129
|
+
// #1328 — remote MCP's sign-in service, its own service on every
|
|
130
|
+
// install (E14 a of #763): always provisioned, zero minimum instances,
|
|
131
|
+
// CPU only during requests, so it costs nothing while nobody connects.
|
|
132
|
+
name: "Cloud Run MCP-Auth-Proxy",
|
|
133
|
+
monthlyUsd: 0,
|
|
134
|
+
notes:
|
|
135
|
+
"scale-to-zero, request-based CPU; only external AI apps' sign-in and MCP calls wake it, mostly in the free tier",
|
|
136
|
+
},
|
|
126
137
|
// v0.3.3 — LB / Cloud CDN / Cloud Armor lines apply only to the
|
|
127
138
|
// gcp variant. gcp-firebase has no LB (Firebase Hosting is the
|
|
128
139
|
// edge); no Cloud CDN line (Firebase Hosting has its own CDN
|
|
@@ -148,12 +159,18 @@ export function estimateGcpCost(inputs: CostEstimateInputs): {
|
|
|
148
159
|
: "Free tier — rate limit + OWASP basic rules",
|
|
149
160
|
},
|
|
150
161
|
{
|
|
151
|
-
// #
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
// #1328 — the MCP-Auth-Proxy's own backend (serverless NEG +
|
|
163
|
+
// backend service, no extra LB fee: same URL map) with its own
|
|
164
|
+
// Cloud Armor policy carrying the remote-MCP throttle
|
|
165
|
+
// (src/remote-mcp-edge.ts) and the default rule. Cloud Armor
|
|
166
|
+
// Standard: $0.006849315/policy-hour (~$5/mo) and
|
|
167
|
+
// $0.001369863/rule-hour (~$1/mo), plus $0.75 per million
|
|
168
|
+
// requests on a global policy — cloud.google.com/armor/pricing,
|
|
169
|
+
// checked 2026-11-02.
|
|
170
|
+
name: "MCP-Auth-Proxy backend + Cloud Armor policy",
|
|
171
|
+
monthlyUsd: 7,
|
|
172
|
+
notes:
|
|
173
|
+
"own policy ($5) with 2 rules ($2): per-IP throttle for remote MCP clients; requests ~$0",
|
|
157
174
|
},
|
|
158
175
|
] satisfies CostLine[])
|
|
159
176
|
: ([
|
package/src/wizards/gcp.ts
CHANGED
|
@@ -30,6 +30,7 @@ import { randomBytes } from "node:crypto";
|
|
|
30
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
+
import { CLOUD_SQL_CA_BUNDLE, verifyFullQuery } from "@caelo-cms/shared";
|
|
33
34
|
import { cancel, confirm, isCancel, log, note, select, spinner } from "@clack/prompts";
|
|
34
35
|
import { bold, cyan, dim, green, red, yellow } from "kleur/colors";
|
|
35
36
|
import { checkAdcImpersonation } from "../adc-impersonation.js";
|
|
@@ -118,6 +119,7 @@ import type { RunMode } from "../run-mode.js";
|
|
|
118
119
|
import { selfCommand } from "../self-command.js";
|
|
119
120
|
import {
|
|
120
121
|
ABSENT_DEFAULT_FIREWALL_RULES,
|
|
122
|
+
cloudSqlPrivateDnsName,
|
|
121
123
|
type RemoteMcpIdentity,
|
|
122
124
|
sameRemoteMcpIdentity,
|
|
123
125
|
} from "../stack-contract.js";
|
|
@@ -134,6 +136,7 @@ import {
|
|
|
134
136
|
import { acquireStateLock, GcsStateStore } from "../state-store.js";
|
|
135
137
|
import { createReleaseImageForms, platformDigestsOf } from "../upgrade/image-forms.js";
|
|
136
138
|
import {
|
|
139
|
+
releaseImageRef,
|
|
137
140
|
releaseVersionForDigest,
|
|
138
141
|
resolveOpsImageRef,
|
|
139
142
|
verifyCosignAll,
|
|
@@ -357,7 +360,7 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
357
360
|
const deployed = isStepDone(installId, `pulumi-up-${projectId}`);
|
|
358
361
|
if (deployed) {
|
|
359
362
|
// The kept release must be able to use the env the stack deploys.
|
|
360
|
-
const runtimeEnv = await checkDeployedRuntimeEnv({ projectId, region });
|
|
363
|
+
const runtimeEnv = await checkDeployedRuntimeEnv({ provider, projectId, region });
|
|
361
364
|
if (!runtimeEnv.ok) {
|
|
362
365
|
cancel(runtimeEnv.error);
|
|
363
366
|
process.exit(1);
|
|
@@ -366,7 +369,7 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
366
369
|
const choice = await chooseImageDigests({
|
|
367
370
|
recorded: recordedImageDigests(meta),
|
|
368
371
|
deployed,
|
|
369
|
-
readLive: () => readDeployedImageDigests({ projectId, region }),
|
|
372
|
+
readLive: () => readDeployedImageDigests({ provider, projectId, region }),
|
|
370
373
|
resolveLatest: resolveImageDigests,
|
|
371
374
|
});
|
|
372
375
|
if (!choice.ok) {
|
|
@@ -531,7 +534,14 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
531
534
|
|
|
532
535
|
// === 11.6. Remote MCP (#806): LB rules (gcp) + a check of the identity env ===
|
|
533
536
|
// The env itself came with `pulumi up` (step 10.05, #1082).
|
|
534
|
-
const remoteMcpReady = await stepRemoteMcp(
|
|
537
|
+
const remoteMcpReady = await stepRemoteMcp(
|
|
538
|
+
projectId,
|
|
539
|
+
region,
|
|
540
|
+
provider,
|
|
541
|
+
domain,
|
|
542
|
+
identity,
|
|
543
|
+
imageDigests,
|
|
544
|
+
);
|
|
535
545
|
|
|
536
546
|
// A fresh install ran every converge step above, so it is at this CLI's
|
|
537
547
|
// infrastructure contract — recorded only when the upgrader and the
|
|
@@ -603,8 +613,12 @@ async function resolveImageDigests(): Promise<ImageDigests> {
|
|
|
603
613
|
const project = "caelo-website";
|
|
604
614
|
const registryRegion = "europe-west1";
|
|
605
615
|
const repo = "caelo-cms-images";
|
|
606
|
-
const out
|
|
607
|
-
|
|
616
|
+
const out: Record<"admin" | "gateway" | "mcp-auth-proxy", string> = {
|
|
617
|
+
admin: "",
|
|
618
|
+
gateway: "",
|
|
619
|
+
"mcp-auth-proxy": "",
|
|
620
|
+
};
|
|
621
|
+
for (const service of ["admin", "gateway", "mcp-auth-proxy"] as const) {
|
|
608
622
|
const s = spinner();
|
|
609
623
|
s.start(`Resolving ${service}:latest → digest...`);
|
|
610
624
|
// gcloud's --filter='tag=latest' is in a transitional state (warns +
|
|
@@ -1309,9 +1323,11 @@ async function stepResolveRemoteMcpIdentity(
|
|
|
1309
1323
|
*/
|
|
1310
1324
|
async function stepRemoteMcp(
|
|
1311
1325
|
projectId: string,
|
|
1326
|
+
region: string,
|
|
1312
1327
|
provider: "gcp" | "gcp-firebase",
|
|
1313
1328
|
domain: string,
|
|
1314
1329
|
identity: ResolvedRemoteMcpIdentity | null,
|
|
1330
|
+
imageDigests: ImageDigests,
|
|
1315
1331
|
): Promise<boolean> {
|
|
1316
1332
|
const s = spinner();
|
|
1317
1333
|
s.start(
|
|
@@ -1325,11 +1341,22 @@ async function stepRemoteMcp(
|
|
|
1325
1341
|
return false;
|
|
1326
1342
|
};
|
|
1327
1343
|
if (!identity) return failed("its URLs and IAP audience could not be read (see above)");
|
|
1328
|
-
const
|
|
1344
|
+
const proxyDigest = imageDigests["mcp-auth-proxy"];
|
|
1345
|
+
if (!proxyDigest) return failed("the release records no MCP-Auth-Proxy image");
|
|
1346
|
+
const edge = await ensureRemoteMcpEdge({
|
|
1347
|
+
provider,
|
|
1348
|
+
projectId,
|
|
1349
|
+
region,
|
|
1350
|
+
env: GCP_STACK_ENV,
|
|
1351
|
+
domain,
|
|
1352
|
+
mcpAuthProxyImageRef: releaseImageRef("mcp-auth-proxy", proxyDigest),
|
|
1353
|
+
});
|
|
1329
1354
|
if (!edge.ok) return failed(edge.error);
|
|
1330
1355
|
const check = await checkRemoteMcpIdentityEnv(identity.target, identity.identity);
|
|
1331
1356
|
if (!check.ok) return failed(check.error);
|
|
1332
|
-
const missing =
|
|
1357
|
+
const missing = Object.values(check.drift)
|
|
1358
|
+
.flat()
|
|
1359
|
+
.map((c) => c.name);
|
|
1333
1360
|
if (missing.length > 0) {
|
|
1334
1361
|
return failed(`pulumi up left ${[...new Set(missing)].join(", ")} out of the services' env`);
|
|
1335
1362
|
}
|
|
@@ -1905,8 +1932,10 @@ async function stepDrainLegacyCaeloAdminUser(projectId: string, region: string):
|
|
|
1905
1932
|
pgPass,
|
|
1906
1933
|
]);
|
|
1907
1934
|
|
|
1908
|
-
// Resolve the SQL instance's
|
|
1909
|
-
// Pulumi outputs from inside the wizard).
|
|
1935
|
+
// Resolve the SQL instance's DNS name from gcloud (avoids reading
|
|
1936
|
+
// Pulumi outputs from inside the wizard). #1338: the job connects with
|
|
1937
|
+
// verify-full, and the shared-CA certificate names the DNS name, not the
|
|
1938
|
+
// private IP.
|
|
1910
1939
|
const sqlDescr = await gcloud([
|
|
1911
1940
|
"sql",
|
|
1912
1941
|
"instances",
|
|
@@ -1914,16 +1943,28 @@ async function stepDrainLegacyCaeloAdminUser(projectId: string, region: string):
|
|
|
1914
1943
|
"caelo-production-pg-1d65811",
|
|
1915
1944
|
"--project",
|
|
1916
1945
|
projectId,
|
|
1917
|
-
"--format=
|
|
1946
|
+
"--format=json(dnsNames)",
|
|
1918
1947
|
]);
|
|
1919
|
-
|
|
1948
|
+
let pgHost: string | null = null;
|
|
1949
|
+
try {
|
|
1950
|
+
pgHost = cloudSqlPrivateDnsName(
|
|
1951
|
+
(
|
|
1952
|
+
JSON.parse(sqlDescr.stdout || "{}") as {
|
|
1953
|
+
dnsNames?: { name?: string; connectionType?: string }[];
|
|
1954
|
+
}
|
|
1955
|
+
).dnsNames,
|
|
1956
|
+
);
|
|
1957
|
+
} catch {
|
|
1958
|
+
pgHost = null;
|
|
1959
|
+
}
|
|
1920
1960
|
if (!pgHost) {
|
|
1921
|
-
s.stop(yellow("Could not resolve Cloud SQL
|
|
1961
|
+
s.stop(yellow("Could not resolve the Cloud SQL instance's DNS name — skipping drain"));
|
|
1922
1962
|
return;
|
|
1923
1963
|
}
|
|
1924
1964
|
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
1965
|
+
const tls = verifyFullQuery(CLOUD_SQL_CA_BUNDLE);
|
|
1966
|
+
const adminPg = `postgres://postgres:${pgPass}@${pgHost}:5432/cms_admin?${tls}`;
|
|
1967
|
+
const publicPg = `postgres://postgres:${pgPass}@${pgHost}:5432/cms_public?${tls}`;
|
|
1927
1968
|
|
|
1928
1969
|
// The cleanup script — uses bun's globalThis.Bun.SQL (already proven
|
|
1929
1970
|
// pattern in hooks.server.ts + the migration runner). DO block makes
|