@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
|
@@ -6,7 +6,7 @@ import type { HttpFetch } from "../runtime-secrets.js";
|
|
|
6
6
|
import {
|
|
7
7
|
createIapHealthProbe,
|
|
8
8
|
evaluateAdminDeepHealth,
|
|
9
|
-
|
|
9
|
+
evaluatePublicServiceDeepHealth,
|
|
10
10
|
type HealthTarget,
|
|
11
11
|
healthOrigins,
|
|
12
12
|
IAM_PROPAGATION_WINDOW_MS,
|
|
@@ -72,27 +72,29 @@ describe("evaluateAdminDeepHealth", () => {
|
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
describe("
|
|
76
|
-
it("accepts a healthy gateway
|
|
77
|
-
expect(
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
describe("evaluatePublicServiceDeepHealth", () => {
|
|
76
|
+
it("accepts a healthy gateway; its version is not public any more (#1326)", () => {
|
|
77
|
+
expect(evaluatePublicServiceDeepHealth("gateway", 200, JSON.stringify({ ok: true }))).toEqual({
|
|
78
|
+
ok: true,
|
|
79
|
+
detail: "gateway healthy",
|
|
80
|
+
});
|
|
80
81
|
});
|
|
81
82
|
|
|
82
|
-
it("
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
it("ignores a version an older gateway still reports", () => {
|
|
84
|
+
expect(
|
|
85
|
+
evaluatePublicServiceDeepHealth(
|
|
86
|
+
"gateway",
|
|
87
|
+
200,
|
|
88
|
+
JSON.stringify({ ok: true, version: "0.10.0" }),
|
|
89
|
+
).ok,
|
|
90
|
+
).toBe(true);
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
it("refuses a gateway that can't reach its database", () => {
|
|
92
|
-
const r =
|
|
94
|
+
const r = evaluatePublicServiceDeepHealth(
|
|
95
|
+
"gateway",
|
|
93
96
|
503,
|
|
94
|
-
JSON.stringify({ ok: false,
|
|
95
|
-
"0.11.0",
|
|
97
|
+
JSON.stringify({ ok: false, error: { kind: "DbUnreachable" } }),
|
|
96
98
|
);
|
|
97
99
|
expect(r).toEqual({ ok: false, error: "gateway reports unhealthy (HTTP 503, DbUnreachable)" });
|
|
98
100
|
});
|
|
@@ -103,7 +105,8 @@ const FIREBASE: HealthTarget = {
|
|
|
103
105
|
projectId: "acme",
|
|
104
106
|
domain: "acme.com",
|
|
105
107
|
adminServiceUrl: "https://caelo-production-admin-aaa-xyz.a.run.app",
|
|
106
|
-
gatewayServiceUrl: "https://caelo-production-gateway-
|
|
108
|
+
gatewayServiceUrl: "https://caelo-production-data-gateway-ddd-xyz.a.run.app",
|
|
109
|
+
mcpAuthProxyServiceUrl: "https://caelo-production-gateway-bbb-xyz.a.run.app",
|
|
107
110
|
};
|
|
108
111
|
|
|
109
112
|
describe("healthOrigins", () => {
|
|
@@ -111,13 +114,15 @@ describe("healthOrigins", () => {
|
|
|
111
114
|
expect(healthOrigins({ ...FIREBASE, provider: "gcp" })).toEqual({
|
|
112
115
|
admin: "https://admin.acme.com",
|
|
113
116
|
gateway: "https://acme.com",
|
|
117
|
+
mcpAuthProxy: "https://acme.com",
|
|
114
118
|
});
|
|
115
119
|
});
|
|
116
120
|
|
|
117
121
|
it("uses the run.app URLs on gcp-firebase", () => {
|
|
118
122
|
expect(healthOrigins(FIREBASE)).toEqual({
|
|
119
123
|
admin: "https://caelo-production-admin-aaa-xyz.a.run.app",
|
|
120
|
-
gateway: "https://caelo-production-gateway-
|
|
124
|
+
gateway: "https://caelo-production-data-gateway-ddd-xyz.a.run.app",
|
|
125
|
+
mcpAuthProxy: "https://caelo-production-gateway-bbb-xyz.a.run.app",
|
|
121
126
|
});
|
|
122
127
|
});
|
|
123
128
|
});
|
|
@@ -130,8 +135,12 @@ describe("createIapHealthProbe", () => {
|
|
|
130
135
|
exitCode: 0,
|
|
131
136
|
});
|
|
132
137
|
|
|
133
|
-
/** A fake network: signJwt + the
|
|
134
|
-
function network(answers: {
|
|
138
|
+
/** A fake network: signJwt + the three health endpoints (the proxy answers healthy unless given). */
|
|
139
|
+
function network(answers: {
|
|
140
|
+
admin: () => [number, string];
|
|
141
|
+
gateway: () => [number, string];
|
|
142
|
+
proxy?: () => [number, string];
|
|
143
|
+
}) {
|
|
135
144
|
const requests: { url: string; headers: Record<string, string>; body?: string }[] = [];
|
|
136
145
|
const http: HttpFetch = async (url, init) => {
|
|
137
146
|
requests.push({ url, headers: init.headers, ...(init.body ? { body: init.body } : {}) });
|
|
@@ -139,7 +148,9 @@ describe("createIapHealthProbe", () => {
|
|
|
139
148
|
? [200, JSON.stringify({ signedJwt: "signed.jwt" })]
|
|
140
149
|
: url.includes("admin")
|
|
141
150
|
? answers.admin()
|
|
142
|
-
:
|
|
151
|
+
: url.endsWith("/api/mcp/health")
|
|
152
|
+
? (answers.proxy ?? (() => [200, JSON.stringify({ ok: true, version: "0.11.0" })]))()
|
|
153
|
+
: answers.gateway();
|
|
143
154
|
return { ok: status < 400, status, text: async () => text };
|
|
144
155
|
};
|
|
145
156
|
return { http, requests };
|
|
@@ -148,7 +159,7 @@ describe("createIapHealthProbe", () => {
|
|
|
148
159
|
it("signs an IAP credential for the admin origin and sends it to the admin only", async () => {
|
|
149
160
|
const net = network({
|
|
150
161
|
admin: () => [200, adminBody()],
|
|
151
|
-
gateway: () => [200, JSON.stringify({ ok: true
|
|
162
|
+
gateway: () => [200, JSON.stringify({ ok: true })],
|
|
152
163
|
});
|
|
153
164
|
const probe = createIapHealthProbe(FIREBASE, {
|
|
154
165
|
run: gcloudOk,
|
|
@@ -160,7 +171,7 @@ describe("createIapHealthProbe", () => {
|
|
|
160
171
|
expect(await probe.deep({ version: "0.11.0" })).toEqual({
|
|
161
172
|
ok: true,
|
|
162
173
|
detail:
|
|
163
|
-
"admin 0.11.0, migrations at 0262_tool_approval_branch.sql / 0040_x.sql; gateway
|
|
174
|
+
"admin 0.11.0, migrations at 0262_tool_approval_branch.sql / 0040_x.sql; gateway healthy; MCP-Auth-Proxy healthy",
|
|
164
175
|
});
|
|
165
176
|
const sign = net.requests[0];
|
|
166
177
|
expect(sign?.url).toBe(
|
|
@@ -180,9 +191,36 @@ describe("createIapHealthProbe", () => {
|
|
|
180
191
|
expect(admin?.headers.Authorization).toBe("Bearer signed.jwt");
|
|
181
192
|
const gateway = net.requests.find((r) => r.url.endsWith("/api/_caelo/health?deep=1"));
|
|
182
193
|
expect(gateway?.url).toBe(
|
|
183
|
-
"https://caelo-production-gateway-
|
|
194
|
+
"https://caelo-production-data-gateway-ddd-xyz.a.run.app/api/_caelo/health?deep=1",
|
|
184
195
|
);
|
|
185
196
|
expect(gateway?.headers.Authorization).toBeUndefined();
|
|
197
|
+
// #1328 — the proxy, on the service that was the gateway, by version.
|
|
198
|
+
const proxy = net.requests.find((r) => r.url.endsWith("/api/mcp/health"));
|
|
199
|
+
expect(proxy?.url).toBe("https://caelo-production-gateway-bbb-xyz.a.run.app/api/mcp/health");
|
|
200
|
+
expect(proxy?.headers.Authorization).toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("#1328 — fails the deep check while the MCP-Auth-Proxy is unhealthy or does not answer", async () => {
|
|
204
|
+
const deepWith = (proxy: () => [number, string]) =>
|
|
205
|
+
createIapHealthProbe(FIREBASE, {
|
|
206
|
+
run: gcloudOk,
|
|
207
|
+
http: network({
|
|
208
|
+
admin: () => [200, adminBody()],
|
|
209
|
+
gateway: () => [200, JSON.stringify({ ok: true, version: "0.11.0" })],
|
|
210
|
+
proxy,
|
|
211
|
+
}).http,
|
|
212
|
+
sleep: async () => {},
|
|
213
|
+
accessJustChanged: false,
|
|
214
|
+
attempts: 1,
|
|
215
|
+
}).deep({ version: "0.11.0" });
|
|
216
|
+
expect(await deepWith(() => [503, JSON.stringify({ ok: false })])).toEqual({
|
|
217
|
+
ok: false,
|
|
218
|
+
error: "MCP-Auth-Proxy reports unhealthy (HTTP 503, no reason)",
|
|
219
|
+
});
|
|
220
|
+
expect(await deepWith(() => [404, "<html>Not Found</html>"])).toEqual({
|
|
221
|
+
ok: false,
|
|
222
|
+
error: "MCP-Auth-Proxy answered HTTP 404 without JSON",
|
|
223
|
+
});
|
|
186
224
|
});
|
|
187
225
|
|
|
188
226
|
it("retries while the new revisions warm up, then gives the last error", async () => {
|
|
@@ -194,7 +232,7 @@ describe("createIapHealthProbe", () => {
|
|
|
194
232
|
? [503, adminBody({ ok: false, migrations: migrations(1) })]
|
|
195
233
|
: [200, adminBody()];
|
|
196
234
|
},
|
|
197
|
-
gateway: () => [200, JSON.stringify({ ok: true
|
|
235
|
+
gateway: () => [200, JSON.stringify({ ok: true })],
|
|
198
236
|
});
|
|
199
237
|
const probe = createIapHealthProbe(FIREBASE, {
|
|
200
238
|
run: gcloudOk,
|
package/src/upgrade/health.ts
CHANGED
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
* - admin `GET /_caelo/health?deep=1` → `{ ok, version, migrations }`:
|
|
12
12
|
* both databases reachable as the expected roles, and every
|
|
13
13
|
* migration the image ships recorded as applied.
|
|
14
|
-
* - gateway `GET /api/_caelo/health?deep=1` → `{ ok
|
|
15
|
-
* database
|
|
14
|
+
* - gateway `GET /api/_caelo/health?deep=1` → `{ ok }` (no version since
|
|
15
|
+
* #1326): its database login works. Under `/api/` because that is the only
|
|
16
16
|
* path the gcp load balancer routes to the gateway.
|
|
17
|
+
* - MCP-Auth-Proxy `GET /api/mcp/health` → `{ ok }` (#1328, no version
|
|
18
|
+
* either): it has no database, so it answers once it runs; under
|
|
19
|
+
* `/api/mcp/`, the prefix the gcp load balancer routes to it.
|
|
17
20
|
*
|
|
18
21
|
* Both answer with no secrets and no raw database errors. The admin sits
|
|
19
22
|
* behind IAP, so the probe signs an IAP credential as the install's MCP
|
|
@@ -134,29 +137,41 @@ export function evaluateAdminDeepHealth(
|
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
137
|
-
/**
|
|
138
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Judge a public service's deep health answer: the gateway's, or the
|
|
142
|
+
* MCP-Auth-Proxy's (#1328). Since #1326 the public services report no
|
|
143
|
+
* release version (a public version string names the flaws to try), so
|
|
144
|
+
* this proves only that the service answers healthy — for the gateway,
|
|
145
|
+
* that its database login works. Which release it runs the roll itself
|
|
146
|
+
* establishes: `update --image` with the pinned digest succeeded and
|
|
147
|
+
* traffic moved to that revision (app-roll.ts), or the roll already failed.
|
|
148
|
+
*
|
|
149
|
+
* @param service how the errors name it (`gateway`, `MCP-Auth-Proxy`)
|
|
150
|
+
*/
|
|
151
|
+
export function evaluatePublicServiceDeepHealth(
|
|
152
|
+
service: string,
|
|
139
153
|
status: number,
|
|
140
154
|
text: string,
|
|
141
|
-
expectedVersion: string,
|
|
142
155
|
): HealthOutcome {
|
|
143
|
-
const body = parseJson<{ ok?: boolean;
|
|
144
|
-
if (!body) return { ok: false, error:
|
|
156
|
+
const body = parseJson<{ ok?: boolean; error?: { kind?: string } }>(text);
|
|
157
|
+
if (!body) return { ok: false, error: `${service} answered HTTP ${status} without JSON` };
|
|
145
158
|
if (status !== 200 || body.ok !== true) {
|
|
146
159
|
return {
|
|
147
160
|
ok: false,
|
|
148
|
-
error:
|
|
161
|
+
error: `${service} reports unhealthy (HTTP ${status}, ${body.error?.kind ?? "no reason"})`,
|
|
149
162
|
};
|
|
150
163
|
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
ok: false,
|
|
154
|
-
error: `gateway runs ${body.version ?? "an unknown version"}, the admin runs ${expectedVersion}`,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return { ok: true, detail: `gateway ${body.version}` };
|
|
164
|
+
return { ok: true, detail: `${service} healthy` };
|
|
158
165
|
}
|
|
159
166
|
|
|
167
|
+
/**
|
|
168
|
+
* The MCP-Auth-Proxy's health path (apps/mcp-auth-proxy server.ts). Under
|
|
169
|
+
* `/api/mcp/` because on gcp that is the prefix the load balancer routes to
|
|
170
|
+
* the proxy; it answers before any other routing, also while remote MCP is
|
|
171
|
+
* off.
|
|
172
|
+
*/
|
|
173
|
+
export const MCP_AUTH_PROXY_HEALTH_PATH = "/api/mcp/health";
|
|
174
|
+
|
|
160
175
|
/** Where the probe reaches the install. */
|
|
161
176
|
export interface HealthTarget {
|
|
162
177
|
readonly provider: "gcp" | "gcp-firebase";
|
|
@@ -166,20 +181,32 @@ export interface HealthTarget {
|
|
|
166
181
|
readonly adminServiceUrl: string;
|
|
167
182
|
/** The gateway service's `run.app` URL (public on gcp-firebase). */
|
|
168
183
|
readonly gatewayServiceUrl: string;
|
|
184
|
+
/** The MCP-Auth-Proxy service's `run.app` URL (public on gcp-firebase). */
|
|
185
|
+
readonly mcpAuthProxyServiceUrl: string;
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
/**
|
|
172
189
|
* The origins the probe calls. On gcp the admin's IAP lives on the load
|
|
173
|
-
* balancer (`admin.<domain>`) and the gateway's ingress is
|
|
174
|
-
* (`<domain>/api/*`); on
|
|
175
|
-
*
|
|
190
|
+
* balancer (`admin.<domain>`), and the gateway's and the proxy's ingress is
|
|
191
|
+
* load-balancer-only (`<domain>/api/*`, `<domain>/api/mcp/*`); on
|
|
192
|
+
* gcp-firebase every `run.app` URL is reachable — the admin's through Cloud
|
|
193
|
+
* Run's own IAP, the others publicly.
|
|
176
194
|
*/
|
|
177
|
-
export function healthOrigins(target: HealthTarget): {
|
|
195
|
+
export function healthOrigins(target: HealthTarget): {
|
|
196
|
+
admin: string;
|
|
197
|
+
gateway: string;
|
|
198
|
+
mcpAuthProxy: string;
|
|
199
|
+
} {
|
|
178
200
|
return target.provider === "gcp"
|
|
179
|
-
? {
|
|
201
|
+
? {
|
|
202
|
+
admin: `https://admin.${target.domain}`,
|
|
203
|
+
gateway: `https://${target.domain}`,
|
|
204
|
+
mcpAuthProxy: `https://${target.domain}`,
|
|
205
|
+
}
|
|
180
206
|
: {
|
|
181
207
|
admin: new URL(target.adminServiceUrl).origin,
|
|
182
208
|
gateway: new URL(target.gatewayServiceUrl).origin,
|
|
209
|
+
mcpAuthProxy: new URL(target.mcpAuthProxyServiceUrl).origin,
|
|
183
210
|
};
|
|
184
211
|
}
|
|
185
212
|
|
|
@@ -437,7 +464,12 @@ export function createIapHealthProbe(target: HealthTarget, deps: HealthProbeDeps
|
|
|
437
464
|
if (!a.ok) return a;
|
|
438
465
|
const gateway = await get(`${origins.gateway}/api/_caelo/health?deep=1`, {});
|
|
439
466
|
if (!gateway.reached) return { ok: false, error: `gateway unreachable: ${gateway.error}` };
|
|
440
|
-
const g =
|
|
441
|
-
|
|
467
|
+
const g = evaluatePublicServiceDeepHealth("gateway", gateway.status, gateway.text);
|
|
468
|
+
if (!g.ok) return g;
|
|
469
|
+
// #1328 — the proxy holds no database login: healthy once it answers.
|
|
470
|
+
const proxy = await get(`${origins.mcpAuthProxy}${MCP_AUTH_PROXY_HEALTH_PATH}`, {});
|
|
471
|
+
if (!proxy.reached) return { ok: false, error: `MCP-Auth-Proxy unreachable: ${proxy.error}` };
|
|
472
|
+
const p = evaluatePublicServiceDeepHealth("MCP-Auth-Proxy", proxy.status, proxy.text);
|
|
473
|
+
return p.ok ? { ok: true, detail: `${a.detail}; ${g.detail}; ${p.detail}` } : p;
|
|
442
474
|
}
|
|
443
475
|
}
|
|
@@ -184,6 +184,40 @@ describe("platformDigestsOf", () => {
|
|
|
184
184
|
expect(calls).toEqual([]);
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
it("#1328: carries the MCP-Auth-Proxy's signed form, and skips a proxy digest install.json lacks", async () => {
|
|
188
|
+
const calls: string[] = [];
|
|
189
|
+
const forms = createReleaseImageForms(fakeRegistryFetch(calls));
|
|
190
|
+
const m = {
|
|
191
|
+
version: "0.12.0",
|
|
192
|
+
digests: { admin: D("a"), gateway: D("b"), ops: D("c"), "mcp-auth-proxy": D("d") },
|
|
193
|
+
platformDigests: { admin: D("1"), gateway: D("2"), ops: D("3"), "mcp-auth-proxy": D("4") },
|
|
194
|
+
};
|
|
195
|
+
expect(
|
|
196
|
+
await platformDigestsOf(
|
|
197
|
+
{ admin: D("a"), gateway: D("b"), "mcp-auth-proxy": D("d") },
|
|
198
|
+
forms,
|
|
199
|
+
m,
|
|
200
|
+
),
|
|
201
|
+
).toEqual({
|
|
202
|
+
ok: true,
|
|
203
|
+
platformDigests: { admin: D("1"), gateway: D("2"), "mcp-auth-proxy": D("4") },
|
|
204
|
+
});
|
|
205
|
+
// Recorded before the proxy existed: no proxy form, and nothing looked up for it.
|
|
206
|
+
expect(await platformDigestsOf({ admin: D("a"), gateway: D("b") }, forms, m)).toEqual({
|
|
207
|
+
ok: true,
|
|
208
|
+
platformDigests: { admin: D("1"), gateway: D("2") },
|
|
209
|
+
});
|
|
210
|
+
expect(calls).toEqual([]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("#1328: a release from before the proxy ships no proxy image form", async () => {
|
|
214
|
+
const forms = createReleaseImageForms(fakeRegistryFetch([]));
|
|
215
|
+
const m = { version: "0.11.0", digests: { admin: D("a"), gateway: D("b"), ops: D("c") } };
|
|
216
|
+
const r = await forms.of(m, "mcp-auth-proxy");
|
|
217
|
+
expect(r).toMatchObject({ ok: false, transient: false });
|
|
218
|
+
expect(r.ok ? "" : r.message).toContain("ships no mcp-auth-proxy image");
|
|
219
|
+
});
|
|
220
|
+
|
|
187
221
|
it("says why when an index cannot be read", async () => {
|
|
188
222
|
const forms = createReleaseImageForms(fakeRegistryFetch([], { status: 503 }));
|
|
189
223
|
const r = await platformDigestsOf({ admin: ADMIN_INDEX, gateway: GATEWAY_INDEX }, forms);
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
import { z } from "zod";
|
|
31
31
|
import type { ImageDigests } from "../install-state.js";
|
|
32
32
|
import type { ReleaseManifest } from "../release-manifest.js";
|
|
33
|
-
import type
|
|
33
|
+
import { ROLL_ORDER, type RollSlug } from "./app-roll.js";
|
|
34
34
|
import { fetchRegistryManifest, IMAGE_DIGEST, RELEASE_REGISTRY } from "./release-images.js";
|
|
35
35
|
|
|
36
36
|
/** The digests one release image is known by. */
|
|
@@ -178,12 +178,20 @@ export function createReleaseImageForms(fetchImpl: typeof fetch = fetch): Releas
|
|
|
178
178
|
return {
|
|
179
179
|
ofIndex,
|
|
180
180
|
of: async (m, slug) => {
|
|
181
|
+
const index = m.digests[slug];
|
|
182
|
+
if (!index) {
|
|
183
|
+
// #1328 — a release from before the MCP-Auth-Proxy signs no image of it.
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
transient: false,
|
|
187
|
+
message: `release ${m.version} ships no ${slug} image`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
181
190
|
const signed = m.platformDigests?.[slug];
|
|
182
191
|
if (signed) {
|
|
183
|
-
const index = m.digests[slug];
|
|
184
192
|
return { ok: true, digests: [index, ...(signed === index ? [] : [signed])], amd64: signed };
|
|
185
193
|
}
|
|
186
|
-
return ofIndex(slug,
|
|
194
|
+
return ofIndex(slug, index);
|
|
187
195
|
},
|
|
188
196
|
};
|
|
189
197
|
}
|
|
@@ -205,22 +213,29 @@ export async function platformDigestsOf(
|
|
|
205
213
|
| { readonly ok: false; readonly message: string }
|
|
206
214
|
> {
|
|
207
215
|
const out: Partial<Record<RollSlug, string>> = {};
|
|
208
|
-
for (const slug of
|
|
216
|
+
for (const slug of ROLL_ORDER) {
|
|
217
|
+
const index = digests[slug];
|
|
218
|
+
// #1328 — digests recorded before the MCP-Auth-Proxy existed carry none.
|
|
219
|
+
if (!index) continue;
|
|
209
220
|
const found =
|
|
210
|
-
manifest && manifest.digests[slug] ===
|
|
221
|
+
manifest && manifest.digests[slug] === index
|
|
211
222
|
? await forms.of(manifest, slug)
|
|
212
|
-
: await forms.ofIndex(slug,
|
|
223
|
+
: await forms.ofIndex(slug, index);
|
|
213
224
|
if (!found.ok) return { ok: false, message: found.message };
|
|
214
225
|
if (!found.amd64) {
|
|
215
226
|
return {
|
|
216
227
|
ok: false,
|
|
217
|
-
message: `the ${slug} image index ${
|
|
228
|
+
message: `the ${slug} image index ${index} lists no single linux/amd64 manifest`,
|
|
218
229
|
};
|
|
219
230
|
}
|
|
220
231
|
out[slug] = found.amd64;
|
|
221
232
|
}
|
|
222
233
|
const { admin, gateway } = out;
|
|
234
|
+
const proxy = out["mcp-auth-proxy"];
|
|
223
235
|
return admin && gateway
|
|
224
|
-
? {
|
|
236
|
+
? {
|
|
237
|
+
ok: true,
|
|
238
|
+
platformDigests: { admin, gateway, ...(proxy ? { "mcp-auth-proxy": proxy } : {}) },
|
|
239
|
+
}
|
|
225
240
|
: { ok: false, message: "the platform digests could not be read" };
|
|
226
241
|
}
|
|
@@ -70,7 +70,11 @@ describe("resolveRegistryDigest", () => {
|
|
|
70
70
|
const missing = fakeFetch(() => new Response(null, { status: 404, statusText: "Not Found" }));
|
|
71
71
|
expect(
|
|
72
72
|
await resolveRegistryDigest("ghcr.io", "caelo-cms/admin", "9.9.9", missing.impl),
|
|
73
|
-
).toEqual({ ok: false, reason: "HTTP 404 Not Found" });
|
|
73
|
+
).toEqual({ ok: false, reason: "HTTP 404 Not Found", notFound: true });
|
|
74
|
+
// Only a 404 is the verdict "no such tag" (#1328); an outage is not.
|
|
75
|
+
const down = fakeFetch(() => new Response(null, { status: 503 }));
|
|
76
|
+
const outage = await resolveRegistryDigest("ghcr.io", "caelo-cms/admin", "9.9.9", down.impl);
|
|
77
|
+
expect(outage.ok === false && "notFound" in outage).toBe(false);
|
|
74
78
|
const bad = fakeFetch(
|
|
75
79
|
() => new Response(null, { status: 200, headers: { "docker-content-digest": "md5:x" } }),
|
|
76
80
|
);
|
|
@@ -17,11 +17,15 @@ import { IMAGE_DIGEST } from "@caelo-cms/shared";
|
|
|
17
17
|
import { confirm, isCancel, log, spinner } from "@clack/prompts";
|
|
18
18
|
import { green, red } from "kleur/colors";
|
|
19
19
|
|
|
20
|
-
/** The
|
|
21
|
-
export type ReleaseService = "admin" | "gateway";
|
|
20
|
+
/** The Caelo services shipped as release images (#1328 added the MCP-Auth-Proxy). */
|
|
21
|
+
export type ReleaseService = "admin" | "gateway" | "mcp-auth-proxy";
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The services every install runs, in roll order: the admin first, then the
|
|
25
|
+
* gateway, then the MCP-Auth-Proxy (its own service since #1328, so a hole in
|
|
26
|
+
* a visitor handler never shares a process with the operators' tokens).
|
|
27
|
+
*/
|
|
28
|
+
export const RELEASE_SERVICES: readonly ReleaseService[] = ["admin", "gateway", "mcp-auth-proxy"];
|
|
25
29
|
|
|
26
30
|
/** GHCR — the canonical registry (release-images.yml), used by self-hosted Compose. */
|
|
27
31
|
export const GHCR_REGISTRY = "ghcr.io";
|
|
@@ -116,7 +120,13 @@ export function releaseTag(opts: {
|
|
|
116
120
|
return "latest";
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
|
|
123
|
+
/**
|
|
124
|
+
* `notFound`: the registry answered that the tag does not exist (HTTP 404)
|
|
125
|
+
* — a verdict, unlike an outage, a refused token or a malformed answer.
|
|
126
|
+
*/
|
|
127
|
+
export type DigestResult =
|
|
128
|
+
| { ok: true; digest: string }
|
|
129
|
+
| { ok: false; reason: string; notFound?: true };
|
|
120
130
|
|
|
121
131
|
/** Manifest media types: single-arch manifests and multi-arch indexes, Docker + OCI. */
|
|
122
132
|
const MANIFEST_ACCEPT = [
|
|
@@ -155,7 +165,13 @@ export async function resolveRegistryDigest(
|
|
|
155
165
|
headers: { Accept: MANIFEST_ACCEPT, Authorization: `Bearer ${token.token}` },
|
|
156
166
|
});
|
|
157
167
|
}
|
|
158
|
-
if (!res.ok)
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
reason: `HTTP ${res.status} ${res.statusText}`,
|
|
172
|
+
...(res.status === 404 ? { notFound: true as const } : {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
159
175
|
const header = res.headers.get("docker-content-digest");
|
|
160
176
|
const digest = header ? canonicalDigest(header) : null;
|
|
161
177
|
if (!digest) {
|