@caelo-cms/provisioning 0.11.9 → 0.11.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +6 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/admin-domain.d.ts +10 -1
- package/dist/admin-domain.d.ts.map +1 -1
- package/dist/admin-domain.js +13 -3
- package/dist/admin-domain.js.map +1 -1
- package/dist/adopt-existing.d.ts +12 -3
- package/dist/adopt-existing.d.ts.map +1 -1
- package/dist/adopt-existing.js +143 -0
- package/dist/adopt-existing.js.map +1 -1
- package/dist/agent/command.d.ts +23 -0
- package/dist/agent/command.d.ts.map +1 -1
- package/dist/agent/command.js +32 -9
- package/dist/agent/command.js.map +1 -1
- package/dist/agent/run.d.ts +2 -0
- package/dist/agent/run.d.ts.map +1 -1
- package/dist/agent/run.js +5 -1
- package/dist/agent/run.js.map +1 -1
- package/dist/agent-db.js +69 -10
- package/dist/aws-task-roles.d.ts +4 -2
- package/dist/aws-task-roles.d.ts.map +1 -1
- package/dist/aws-task-roles.js +4 -1
- package/dist/aws-task-roles.js.map +1 -1
- package/dist/caddy.d.ts +21 -4
- package/dist/caddy.d.ts.map +1 -1
- package/dist/caddy.js +27 -3
- package/dist/caddy.js.map +1 -1
- package/dist/cli-routing.js +1 -1
- package/dist/cli-routing.js.map +1 -1
- package/dist/cli.js +68 -7
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-tls.d.ts +93 -0
- package/dist/cloudsql-tls.d.ts.map +1 -0
- package/dist/cloudsql-tls.js +295 -0
- package/dist/cloudsql-tls.js.map +1 -0
- package/dist/compose.d.ts +43 -6
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +148 -23
- package/dist/compose.js.map +1 -1
- package/dist/converge-checkpoint.d.ts +1 -0
- package/dist/converge-checkpoint.d.ts.map +1 -1
- package/dist/converge-checkpoint.js +8 -1
- package/dist/converge-checkpoint.js.map +1 -1
- package/dist/converge-db-trust.d.ts +44 -0
- package/dist/converge-db-trust.d.ts.map +1 -0
- package/dist/converge-db-trust.js +158 -0
- package/dist/converge-db-trust.js.map +1 -0
- package/dist/converge-job-gcp.d.ts +5 -2
- package/dist/converge-job-gcp.d.ts.map +1 -1
- package/dist/converge-job-gcp.js +11 -13
- package/dist/converge-job-gcp.js.map +1 -1
- package/dist/converge-job-preflight.d.ts.map +1 -1
- package/dist/converge-job-preflight.js +8 -0
- package/dist/converge-job-preflight.js.map +1 -1
- package/dist/converge-job.d.ts +17 -0
- package/dist/converge-job.d.ts.map +1 -1
- package/dist/converge-job.js +12 -0
- package/dist/converge-job.js.map +1 -1
- package/dist/deployed-release.d.ts +4 -1
- package/dist/deployed-release.d.ts.map +1 -1
- package/dist/deployed-release.js +18 -8
- package/dist/deployed-release.js.map +1 -1
- package/dist/gcp-names.d.ts +34 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +45 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/gcp-url-map.d.ts.map +1 -1
- package/dist/gcp-url-map.js +6 -2
- package/dist/gcp-url-map.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.d.ts.map +1 -1
- package/dist/infra-contract.js +45 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job.d.ts +4 -2
- package/dist/install-job.d.ts.map +1 -1
- package/dist/install-job.js +4 -1
- package/dist/install-job.js.map +1 -1
- package/dist/install-state.d.ts +13 -0
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +17 -4
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +20 -8
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-auth-proxy-setup.d.ts +48 -0
- package/dist/mcp-auth-proxy-setup.d.ts.map +1 -0
- package/dist/mcp-auth-proxy-setup.js +280 -0
- package/dist/mcp-auth-proxy-setup.js.map +1 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +8 -3
- package/dist/migration-runner.js.map +1 -1
- package/dist/migration-targets.d.ts +11 -0
- package/dist/migration-targets.d.ts.map +1 -0
- package/dist/migration-targets.js +12 -0
- package/dist/migration-targets.js.map +1 -0
- package/dist/operator-access.d.ts +10 -0
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +5 -0
- package/dist/operator-access.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +13 -1
- package/dist/pool-budget-stack.d.ts.map +1 -1
- package/dist/pool-budget-stack.js +11 -1
- package/dist/pool-budget-stack.js.map +1 -1
- package/dist/public-role-isolation.d.ts +7 -3
- package/dist/public-role-isolation.d.ts.map +1 -1
- package/dist/public-role-isolation.js +25 -13
- package/dist/public-role-isolation.js.map +1 -1
- package/dist/release-contract.d.ts.map +1 -1
- package/dist/release-contract.js +3 -0
- package/dist/release-contract.js.map +1 -1
- package/dist/release-manifest.d.ts +25 -1
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +36 -3
- package/dist/release-manifest.js.map +1 -1
- package/dist/remote-mcp.d.ts +55 -18
- package/dist/remote-mcp.d.ts.map +1 -1
- package/dist/remote-mcp.js +307 -77
- package/dist/remote-mcp.js.map +1 -1
- package/dist/runtime-secrets.d.ts +11 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +40 -25
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/scheduler-setup.d.ts +75 -0
- package/dist/scheduler-setup.d.ts.map +1 -0
- package/dist/scheduler-setup.js +331 -0
- package/dist/scheduler-setup.js.map +1 -0
- package/dist/seal-key-rotation.d.ts +7 -7
- package/dist/seal-key-rotation.d.ts.map +1 -1
- package/dist/seal-key-rotation.js +22 -22
- package/dist/seal-key-rotation.js.map +1 -1
- package/dist/self-hosted-db-tls.d.ts +37 -0
- package/dist/self-hosted-db-tls.d.ts.map +1 -0
- package/dist/self-hosted-db-tls.js +220 -0
- package/dist/self-hosted-db-tls.js.map +1 -0
- package/dist/self-hosted-release.d.ts +3 -1
- package/dist/self-hosted-release.d.ts.map +1 -1
- package/dist/self-hosted-release.js +33 -9
- package/dist/self-hosted-release.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +43 -2
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +87 -1
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-scheduler-interval.d.ts +21 -0
- package/dist/self-hosted-scheduler-interval.d.ts.map +1 -0
- package/dist/self-hosted-scheduler-interval.js +68 -0
- package/dist/self-hosted-scheduler-interval.js.map +1 -0
- package/dist/self-hosted-start.d.ts +0 -21
- package/dist/self-hosted-start.d.ts.map +1 -1
- package/dist/self-hosted-start.js +39 -1
- package/dist/self-hosted-start.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +19 -7
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +78 -25
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +234 -28
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +397 -39
- package/dist/stack-contract.js.map +1 -1
- package/dist/stack-converge.d.ts +31 -0
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +71 -30
- package/dist/stack-converge.js.map +1 -1
- package/dist/staging-database.d.ts +22 -0
- package/dist/staging-database.d.ts.map +1 -0
- package/dist/staging-database.js +117 -0
- package/dist/staging-database.js.map +1 -0
- package/dist/upgrade/app-roll.d.ts +25 -4
- package/dist/upgrade/app-roll.d.ts.map +1 -1
- package/dist/upgrade/app-roll.js +63 -17
- package/dist/upgrade/app-roll.js.map +1 -1
- package/dist/upgrade/converge.d.ts +5 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +207 -20
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/health.d.ts +31 -7
- package/dist/upgrade/health.d.ts.map +1 -1
- package/dist/upgrade/health.js +41 -17
- package/dist/upgrade/health.js.map +1 -1
- package/dist/upgrade/image-forms.d.ts +1 -1
- package/dist/upgrade/image-forms.d.ts.map +1 -1
- package/dist/upgrade/image-forms.js +24 -7
- package/dist/upgrade/image-forms.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -3
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +13 -4
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +19 -3
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +37 -2
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-gcp.d.ts +13 -0
- package/dist/upgrader-gcp.d.ts.map +1 -1
- package/dist/upgrader-gcp.js +50 -1
- package/dist/upgrader-gcp.js.map +1 -1
- package/dist/upgrader-iam-preflight.d.ts.map +1 -1
- package/dist/upgrader-iam-preflight.js +6 -1
- package/dist/upgrader-iam-preflight.js.map +1 -1
- package/dist/upgrader-job.d.ts +1 -1
- package/dist/upgrader-job.d.ts.map +1 -1
- package/dist/upgrader-job.js +20 -14
- package/dist/upgrader-job.js.map +1 -1
- package/dist/upgrader-records.d.ts +1 -1
- package/dist/upgrader-records.d.ts.map +1 -1
- package/dist/upgrader-records.js +26 -7
- package/dist/upgrader-records.js.map +1 -1
- package/dist/upgrader-setup.d.ts.map +1 -1
- package/dist/upgrader-setup.js +10 -5
- package/dist/upgrader-setup.js.map +1 -1
- package/dist/wizard.d.ts +7 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +11 -0
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts +31 -0
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +103 -18
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +129 -27
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +6 -0
- package/src/admin-domain.test.ts +9 -8
- package/src/admin-domain.ts +13 -3
- package/src/admin-memory.test.ts +2 -1
- package/src/adopt-existing.test.ts +85 -0
- package/src/adopt-existing.ts +141 -3
- package/src/agent/command.test.ts +49 -1
- package/src/agent/command.ts +39 -9
- package/src/agent/run.test.ts +19 -0
- package/src/agent/run.ts +11 -2
- package/src/aws-task-roles.ts +5 -2
- package/src/caddy-redirects.docker.test.ts +2 -0
- package/src/caddy-redirects.test.ts +4 -1
- package/src/caddy-validate.test.ts +7 -1
- package/src/caddy.test.ts +39 -1
- package/src/caddy.ts +36 -4
- package/src/checkpoint-secrets.test.ts +14 -6
- package/src/cli-routing.test.ts +1 -1
- package/src/cli-routing.ts +1 -1
- package/src/cli.ts +71 -5
- package/src/cloudsql-tls.test.ts +255 -0
- package/src/cloudsql-tls.ts +359 -0
- package/src/compose.ts +193 -24
- package/src/converge-checkpoint.ts +8 -1
- package/src/converge-db-trust.test.ts +237 -0
- package/src/converge-db-trust.ts +212 -0
- package/src/converge-job-gcp.ts +11 -12
- package/src/converge-job-preflight.test.ts +5 -0
- package/src/converge-job-preflight.ts +8 -0
- package/src/converge-job.test.ts +70 -0
- package/src/converge-job.ts +32 -0
- package/src/database-tls-builders.test.ts +56 -0
- package/src/deployed-release.test.ts +107 -40
- package/src/deployed-release.ts +30 -9
- package/src/gateway-env-isolation.test.ts +50 -12
- package/src/gcp-names.ts +56 -0
- package/src/gcp-url-map.test.ts +6 -8
- package/src/gcp-url-map.ts +6 -2
- package/src/index.test.ts +10 -4
- package/src/index.ts +7 -0
- package/src/infra-contract.test.ts +7 -3
- package/src/infra-contract.ts +45 -1
- package/src/install-job.ts +4 -1
- package/src/install-state.ts +33 -4
- package/src/lifecycle.ts +23 -11
- package/src/mcp-auth-proxy-setup.test.ts +305 -0
- package/src/mcp-auth-proxy-setup.ts +374 -0
- package/src/mcp-iap-stacks.test.ts +1 -1
- package/src/media-volume.test.ts +1 -1
- package/src/migration-runner.ts +14 -9
- package/src/migration-targets.ts +12 -0
- package/src/operator-access-stacks.test.ts +1 -1
- package/src/operator-access.test.ts +20 -4
- package/src/operator-access.ts +14 -0
- package/src/pool-budget-stack.ts +15 -0
- package/src/pool-budget-stacks.test.ts +39 -14
- package/src/public-role-isolation.test.ts +18 -8
- package/src/public-role-isolation.ts +31 -17
- package/src/release-contract.ts +3 -0
- package/src/release-manifest.test.ts +25 -0
- package/src/release-manifest.ts +59 -4
- package/src/remote-mcp-stack-config.test.ts +19 -9
- package/src/remote-mcp.test.ts +252 -57
- package/src/remote-mcp.ts +336 -77
- package/src/runtime-secrets.test.ts +37 -14
- package/src/runtime-secrets.ts +87 -27
- package/src/scheduler-setup.test.ts +201 -0
- package/src/scheduler-setup.ts +375 -0
- package/src/seal-key-rotation.test.ts +2 -2
- package/src/seal-key-rotation.ts +25 -25
- package/src/self-hosted-backup.test.ts +1 -0
- package/src/self-hosted-compose.test.ts +180 -6
- package/src/self-hosted-db-tls.test.ts +111 -0
- package/src/self-hosted-db-tls.ts +287 -0
- package/src/self-hosted-release-resolve.test.ts +71 -0
- package/src/self-hosted-release.ts +41 -11
- package/src/self-hosted-roles.integration.test.ts +2 -0
- package/src/self-hosted-roles.test.ts +87 -3
- package/src/self-hosted-roles.ts +101 -3
- package/src/self-hosted-scheduler-interval.test.ts +66 -0
- package/src/self-hosted-scheduler-interval.ts +79 -0
- package/src/self-hosted-start.test.ts +35 -2
- package/src/self-hosted-start.ts +20 -2
- package/src/self-hosted-upgrade.test.ts +104 -14
- package/src/self-hosted-upgrade.ts +123 -31
- package/src/stack-contract-parity.test.ts +79 -12
- package/src/stack-contract.test.ts +199 -31
- package/src/stack-contract.ts +472 -45
- package/src/stack-converge-secrets.test.ts +28 -13
- package/src/stack-converge.test.ts +203 -28
- package/src/stack-converge.ts +92 -30
- package/src/staging-database.test.ts +114 -0
- package/src/staging-database.ts +148 -0
- package/src/state-store.test.ts +2 -1
- package/src/upgrade/app-roll.ts +97 -19
- package/src/upgrade/converge.ts +249 -19
- package/src/upgrade/health.test.ts +63 -25
- package/src/upgrade/health.ts +54 -22
- package/src/upgrade/image-forms.test.ts +34 -0
- package/src/upgrade/image-forms.ts +23 -8
- package/src/upgrade/release-images.test.ts +5 -1
- package/src/upgrade/release-images.ts +22 -6
- package/src/upgrade/upgrade.test.ts +438 -46
- package/src/upgrade/upgrade.ts +64 -6
- package/src/upgrader-gcp.ts +66 -3
- package/src/upgrader-iam-preflight.test.ts +19 -0
- package/src/upgrader-iam-preflight.ts +6 -0
- package/src/upgrader-job.test.ts +101 -27
- package/src/upgrader-job.ts +27 -14
- package/src/upgrader-records.test.ts +97 -12
- package/src/upgrader-records.ts +28 -11
- package/src/upgrader-setup.test.ts +2 -0
- package/src/upgrader-setup.ts +10 -4
- package/src/wizard.ts +19 -0
- package/src/wizards/gcp-cost.test.ts +103 -0
- package/src/wizards/gcp-cost.ts +125 -18
- package/src/wizards/gcp.ts +164 -22
- package/stacks/aws/Pulumi.yaml +1 -1
- package/stacks/aws/index.ts +72 -14
- package/stacks/azure/index.ts +108 -21
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -0
- package/stacks/gcp/index.ts +133 -24
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +212 -34
- package/stacks/self-hosted/Pulumi.yaml +3 -0
- package/stacks/self-hosted/README.md +6 -1
- package/stacks/self-hosted/index.ts +40 -5
package/src/upgrader-job.ts
CHANGED
|
@@ -56,15 +56,21 @@ import type {
|
|
|
56
56
|
StoredUpdatePolicy,
|
|
57
57
|
} from "@caelo-cms/shared";
|
|
58
58
|
import type { GcloudRunner, Sleep } from "./gcloud-retry.js";
|
|
59
|
-
import {
|
|
59
|
+
import { upgraderJobName } from "./gcp-names.js";
|
|
60
60
|
import type { InstallMetadata } from "./install-state.js";
|
|
61
|
-
import
|
|
61
|
+
import {
|
|
62
|
+
manifestServiceImages,
|
|
63
|
+
type ReleaseManifest,
|
|
64
|
+
type ReleaseManifestResult,
|
|
65
|
+
} from "./release-manifest.js";
|
|
66
|
+
import { cloudRunServicePrefix } from "./stack-contract.js";
|
|
62
67
|
import { StateLockHeldError } from "./state-store.js";
|
|
63
68
|
import {
|
|
64
69
|
type AppRollPlan,
|
|
65
70
|
executeAppRoll,
|
|
66
71
|
planAppRoll,
|
|
67
72
|
type ReleaseImage,
|
|
73
|
+
ROLL_ORDER,
|
|
68
74
|
type RollSlug,
|
|
69
75
|
} from "./upgrade/app-roll.js";
|
|
70
76
|
import { restoreCommand, takeSqlBackup } from "./upgrade/backup.js";
|
|
@@ -389,7 +395,7 @@ export async function runUpgraderJob(deps: UpgraderJobDeps): Promise<UpgraderRun
|
|
|
389
395
|
// install.json must describe what actually serves: if a previous run
|
|
390
396
|
// rolled but could not record it, the recorded version is stale and
|
|
391
397
|
// the downgrade / minFromVersion checks would judge against it.
|
|
392
|
-
const serving = await servingDigests(deps);
|
|
398
|
+
const serving = await servingDigests(deps, meta.provider);
|
|
393
399
|
if (!serving.ok) {
|
|
394
400
|
return stopWith("failed", ["lookup"], `Nothing was changed: ${serving.error}.`);
|
|
395
401
|
}
|
|
@@ -424,6 +430,9 @@ export async function runUpgraderJob(deps: UpgraderJobDeps): Promise<UpgraderRun
|
|
|
424
430
|
imageDigests: {
|
|
425
431
|
admin: found.manifest.digests.admin,
|
|
426
432
|
gateway: found.manifest.digests.gateway,
|
|
433
|
+
...(found.manifest.digests["mcp-auth-proxy"]
|
|
434
|
+
? { "mcp-auth-proxy": found.manifest.digests["mcp-auth-proxy"] }
|
|
435
|
+
: {}),
|
|
427
436
|
},
|
|
428
437
|
...(found.platformDigests ? { platformDigests: found.platformDigests } : {}),
|
|
429
438
|
releaseVersion: found.manifest.version,
|
|
@@ -500,13 +509,10 @@ export async function runUpgraderJob(deps: UpgraderJobDeps): Promise<UpgraderRun
|
|
|
500
509
|
r.info("The upgrader has every permission this update needs.");
|
|
501
510
|
|
|
502
511
|
const m = decision.manifest;
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
imageRef: releaseImageRef("gateway", m.digests.gateway),
|
|
508
|
-
},
|
|
509
|
-
};
|
|
512
|
+
const resolved = manifestServiceImages(m);
|
|
513
|
+
if (!resolved.ok)
|
|
514
|
+
return stopWith("failed", ["manifest"], `Nothing was changed: ${resolved.error}.`);
|
|
515
|
+
const images: Record<RollSlug, ReleaseImage> = resolved.images;
|
|
510
516
|
const planned = await planAppRoll(
|
|
511
517
|
{
|
|
512
518
|
provider: meta.provider,
|
|
@@ -694,7 +700,10 @@ export async function recordUpdateEnd(
|
|
|
694
700
|
}
|
|
695
701
|
|
|
696
702
|
/** Every digest the revisions serving traffic are known by, per service. */
|
|
697
|
-
async function servingDigests(
|
|
703
|
+
async function servingDigests(
|
|
704
|
+
deps: UpgraderJobDeps,
|
|
705
|
+
provider: InstallMetadata["provider"],
|
|
706
|
+
): Promise<
|
|
698
707
|
| {
|
|
699
708
|
ok: true;
|
|
700
709
|
digests: ServingDigests;
|
|
@@ -705,8 +714,12 @@ async function servingDigests(deps: UpgraderJobDeps): Promise<
|
|
|
705
714
|
> {
|
|
706
715
|
const digests: Partial<Record<RollSlug, readonly string[]>> = {};
|
|
707
716
|
const services: Partial<Record<RollSlug, string>> = {};
|
|
708
|
-
|
|
709
|
-
|
|
717
|
+
if (provider !== "gcp" && provider !== "gcp-firebase") {
|
|
718
|
+
return { ok: false, error: `the upgrader runs on Google Cloud installs only, not ${provider}` };
|
|
719
|
+
}
|
|
720
|
+
for (const slug of ROLL_ORDER) {
|
|
721
|
+
// #1328 — which service plays which role differs per provider.
|
|
722
|
+
const prefix = cloudRunServicePrefix(provider, deps.env, slug);
|
|
710
723
|
const service = await resolveGcpResourceName(
|
|
711
724
|
deps.run,
|
|
712
725
|
"run-service",
|
|
@@ -739,7 +752,7 @@ async function templateDigests(
|
|
|
739
752
|
services: Readonly<Record<RollSlug, string>>,
|
|
740
753
|
): Promise<ServingDigests> {
|
|
741
754
|
const out: Partial<Record<RollSlug, readonly string[]>> = {};
|
|
742
|
-
for (const slug of
|
|
755
|
+
for (const slug of ROLL_ORDER) {
|
|
743
756
|
const d = await findTemplateImageDigest(deps.run, deps.projectId, deps.region, services[slug]);
|
|
744
757
|
out[slug] = d ? [d] : [];
|
|
745
758
|
}
|
|
@@ -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),
|
package/src/wizard.ts
CHANGED
|
@@ -72,6 +72,13 @@ export interface WizardOptions {
|
|
|
72
72
|
* install a different value is refused.
|
|
73
73
|
*/
|
|
74
74
|
region?: string;
|
|
75
|
+
/**
|
|
76
|
+
* #1140 — `--scheduler-interval-minutes` (1–15, already validated): how
|
|
77
|
+
* often the scheduler tick calls the admin. Recorded in install.json, so
|
|
78
|
+
* every later `upgrade` keeps it; omitted keeps what is recorded (or the
|
|
79
|
+
* default, 5).
|
|
80
|
+
*/
|
|
81
|
+
schedulerIntervalMinutes?: number;
|
|
75
82
|
/**
|
|
76
83
|
* #696 — where `pulumi up` and the rest of a GCP install run
|
|
77
84
|
* (run-mode.ts): `job` (default in Cloud Shell) or `local`.
|
|
@@ -192,6 +199,18 @@ export async function runWizard(opts: WizardOptions = {}): Promise<void> {
|
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
202
|
+
// #1140 — the scheduler knob, on a new install and on a resumed one alike
|
|
203
|
+
// (every later `upgrade` reads it from install.json).
|
|
204
|
+
if (opts.schedulerIntervalMinutes !== undefined) {
|
|
205
|
+
const recorded = readMetadata(installId);
|
|
206
|
+
if (recorded) {
|
|
207
|
+
writeMetadata(installId, {
|
|
208
|
+
...recorded,
|
|
209
|
+
schedulerIntervalMinutes: opts.schedulerIntervalMinutes,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
195
214
|
// 3.5. #656 — a re-run of an install an earlier CLI set up: drop the
|
|
196
215
|
// secrets that CLI checkpointed (database passwords, the setup link)
|
|
197
216
|
// before any step writes progress.json again, and say what to rotate.
|
|
@@ -0,0 +1,103 @@
|
|
|
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 { SERVICE_CPU_IDLE } from "../stack-contract.js";
|
|
11
|
+
import {
|
|
12
|
+
adminKeptWarmByTicks,
|
|
13
|
+
alwaysOnAdminInstanceUsd,
|
|
14
|
+
type CostEstimateInputs,
|
|
15
|
+
estimateGcpCost,
|
|
16
|
+
schedulerTickMonthlyUsd,
|
|
17
|
+
} from "./gcp-cost.js";
|
|
18
|
+
|
|
19
|
+
const inputs: CostEstimateInputs = {
|
|
20
|
+
cloudSqlTier: "db-f1-micro",
|
|
21
|
+
cloudSqlHa: false,
|
|
22
|
+
adminMinInstances: 0,
|
|
23
|
+
gatewayMinInstances: 0,
|
|
24
|
+
wafAdaptiveProtection: false,
|
|
25
|
+
region: "europe-west1",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function names(provider: "gcp" | "gcp-firebase"): string[] {
|
|
29
|
+
return estimateGcpCost({ ...inputs, provider }).lines.map((l) => l.name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("cost table: MCP-Auth-Proxy (#1328)", () => {
|
|
33
|
+
it("lists the proxy's Cloud Run service at $0 idle on both variants", () => {
|
|
34
|
+
for (const provider of ["gcp", "gcp-firebase"] as const) {
|
|
35
|
+
const line = estimateGcpCost({ ...inputs, provider }).lines.find(
|
|
36
|
+
(l) => l.name === "Cloud Run MCP-Auth-Proxy",
|
|
37
|
+
);
|
|
38
|
+
expect(line?.monthlyUsd).toBe(0);
|
|
39
|
+
expect(line?.notes).toContain("scale-to-zero");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("prices the proxy's own Cloud Armor policy on gcp only (policy + 2 rules)", () => {
|
|
44
|
+
const gcp = estimateGcpCost({ ...inputs, provider: "gcp" }).lines.find((l) =>
|
|
45
|
+
l.name.startsWith("MCP-Auth-Proxy backend"),
|
|
46
|
+
);
|
|
47
|
+
expect(gcp?.monthlyUsd).toBe(7);
|
|
48
|
+
expect(names("gcp-firebase").some((n) => n.startsWith("MCP-Auth-Proxy backend"))).toBe(false);
|
|
49
|
+
// The throttle moved off the gateway's policy: no separate rule line.
|
|
50
|
+
expect(names("gcp")).not.toContain("Cloud Armor rule: remote MCP throttle");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("names the gcp-firebase visitor gateway as the data gateway", () => {
|
|
54
|
+
expect(names("gcp-firebase")).toContain("Cloud Run data gateway");
|
|
55
|
+
expect(names("gcp")).toContain("Cloud Run gateway");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("cost table: scheduler tick (#1140)", () => {
|
|
60
|
+
it("lists the tick at the default 5 minutes: well under a dollar", () => {
|
|
61
|
+
const line = estimateGcpCost(inputs).lines.find((l) => l.name.startsWith("Scheduler tick"));
|
|
62
|
+
expect(line?.name).toBe("Scheduler tick (Cloud Scheduler + Workflows, every 5 min)");
|
|
63
|
+
// 8,640 ticks: 25,920 internal steps (20,920 billed) + 8,640 external
|
|
64
|
+
// (6,640 billed) + the scheduler job.
|
|
65
|
+
expect(line?.monthlyUsd).toBe(0.48);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("follows the knob: one minute stays a few dollars, fifteen little more than the job", () => {
|
|
69
|
+
expect(schedulerTickMonthlyUsd(1)).toBe(2.38);
|
|
70
|
+
expect(schedulerTickMonthlyUsd(15)).toBe(0.16);
|
|
71
|
+
const fast = estimateGcpCost({ ...inputs, schedulerIntervalMinutes: 1 }).lines.find((l) =>
|
|
72
|
+
l.name.startsWith("Scheduler tick"),
|
|
73
|
+
);
|
|
74
|
+
expect(fast?.name).toContain("every 1 min");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("prices the admin kept up around the clock while its CPU is always allocated (#1361)", () => {
|
|
78
|
+
// The tick (at most every 15 minutes) beats Cloud Run's 15-minute idle
|
|
79
|
+
// shutdown, so a scale-to-zero admin never stops.
|
|
80
|
+
expect(SERVICE_CPU_IDLE.admin).toBe(false);
|
|
81
|
+
expect(adminKeptWarmByTicks(0)).toBe(true);
|
|
82
|
+
// 2,592,000 s x (1 vCPU x $0.000018 + 2 GiB x $0.000002).
|
|
83
|
+
expect(alwaysOnAdminInstanceUsd(2)).toBe(57);
|
|
84
|
+
const admin = estimateGcpCost(inputs).lines.find((l) => l.name.startsWith("Cloud Run admin"));
|
|
85
|
+
expect(admin?.monthlyUsd).toBe(57);
|
|
86
|
+
expect(admin?.notes).toContain("around the clock");
|
|
87
|
+
expect(admin?.notes).toContain("#1361");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("adds nothing on the admin with a min instance, which is up anyway", () => {
|
|
91
|
+
expect(adminKeptWarmByTicks(1)).toBe(false);
|
|
92
|
+
const admin = estimateGcpCost({ ...inputs, adminMinInstances: 1 }).lines.find((l) =>
|
|
93
|
+
l.name.startsWith("Cloud Run admin"),
|
|
94
|
+
);
|
|
95
|
+
expect(admin?.notes).toContain("the scheduler tick adds nothing");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("refuses an interval outside 1-15 minutes", () => {
|
|
99
|
+
expect(() => estimateGcpCost({ ...inputs, schedulerIntervalMinutes: 20 })).toThrow(
|
|
100
|
+
"between 1 and 15",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|