@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
|
@@ -32,14 +32,20 @@ import {
|
|
|
32
32
|
import { databaseUrls } from "./stack-contract.js";
|
|
33
33
|
|
|
34
34
|
const FAKE_PW = "fakePw0rdForTests123";
|
|
35
|
-
const legacyAdminUrl = `postgresql://admin_role:${FAKE_PW}@
|
|
36
|
-
const legacyPublicUrl = `postgresql://public_role:${FAKE_PW}@
|
|
35
|
+
const legacyAdminUrl = `postgresql://admin_role:${FAKE_PW}@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem`;
|
|
36
|
+
const legacyPublicUrl = `postgresql://public_role:${FAKE_PW}@db1.p-label.europe-west1.sql-psa.goog:5432/cms_public?sslmode=verify-full&sslrootcert=/etc/caelo/db-ca/cloudsql-global.pem`;
|
|
37
37
|
const setupLink = "https://admin.acme.test/setup?token=fakeSetupToken0123456789";
|
|
38
38
|
|
|
39
39
|
/** What `pulumi up` returns on the current stacks, secret flags included. */
|
|
40
40
|
const stackOutputs = {
|
|
41
|
-
adminDatabaseUrlOut: {
|
|
42
|
-
|
|
41
|
+
adminDatabaseUrlOut: {
|
|
42
|
+
value: databaseUrls("db1.p-label.europe-west1.sql-psa.goog").admin,
|
|
43
|
+
secret: false,
|
|
44
|
+
},
|
|
45
|
+
publicDatabaseUrlOut: {
|
|
46
|
+
value: databaseUrls("db1.p-label.europe-west1.sql-psa.goog").public,
|
|
47
|
+
secret: false,
|
|
48
|
+
},
|
|
43
49
|
bootstrapUrlOut: { value: setupLink, secret: false },
|
|
44
50
|
bootstrapTokenExpiresAtOut: { value: "2026-10-13T00:00:00.000Z", secret: false },
|
|
45
51
|
providerOut: { value: "gcp", secret: false },
|
|
@@ -97,7 +103,7 @@ describe("findSecrets", () => {
|
|
|
97
103
|
});
|
|
98
104
|
|
|
99
105
|
it("does not flag the password-less database URLs the stacks export today", () => {
|
|
100
|
-
expect(findSecrets(databaseUrls("
|
|
106
|
+
expect(findSecrets(databaseUrls("db1.p-label.europe-west1.sql-psa.goog"))).toEqual([]);
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
it("flags secret-named keys holding a value, not markers or look-alikes", () => {
|
|
@@ -314,7 +320,9 @@ describe("redactCredentials", () => {
|
|
|
314
320
|
it("masks URL passwords and leaves the rest readable", () => {
|
|
315
321
|
const out = redactCredentials(`connect failed: ${legacyAdminUrl} (timeout)`);
|
|
316
322
|
expect(out).not.toContain(FAKE_PW);
|
|
317
|
-
expect(out).toContain(
|
|
323
|
+
expect(out).toContain(
|
|
324
|
+
"postgresql://admin_role:***@db1.p-label.europe-west1.sql-psa.goog:5432/cms_admin",
|
|
325
|
+
);
|
|
318
326
|
expect(redactCredentials("https://admin.acme.test/x")).toBe("https://admin.acme.test/x");
|
|
319
327
|
});
|
|
320
328
|
});
|
package/src/cli-routing.test.ts
CHANGED
|
@@ -61,7 +61,7 @@ describe("initDelegatesToWizard", () => {
|
|
|
61
61
|
describe("CLI_COMMANDS / cliUsage", () => {
|
|
62
62
|
it("prints the usage line operators already know, built from the catalogue", () => {
|
|
63
63
|
expect(cliUsage().split("\n")[0]).toBe(
|
|
64
|
-
"Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r>] [--local|--install-job] | <init|up|migrate|status|upgrade|backup|restore|rotate-secret|truncate|destroy|regenerate-caddy|admin-domain|staging-domain|owner-reset|state migrate|attach|pulumi-output-sync|version> [options]",
|
|
64
|
+
"Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r> [--scheduler-interval-minutes 1-15]] [--local|--install-job] | <init|up|migrate|status|upgrade|backup|restore|rotate-secret|truncate|destroy|regenerate-caddy|admin-domain|staging-domain|owner-reset|state migrate|attach|pulumi-output-sync|version> [options]",
|
|
65
65
|
);
|
|
66
66
|
});
|
|
67
67
|
|
package/src/cli-routing.ts
CHANGED
|
@@ -174,7 +174,7 @@ export const CLI_COMMANDS: readonly CliCommandDoc[] = [
|
|
|
174
174
|
/** The text `cms-provision --help` prints, built from {@link CLI_COMMANDS}. */
|
|
175
175
|
export function cliUsage(): string {
|
|
176
176
|
return (
|
|
177
|
-
`Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r>] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
|
|
177
|
+
`Usage: cms-provision [wizard] [--provider <name> --domain <d> [--staging-domain <host>] --owner-email <e> --region <r> [--scheduler-interval-minutes 1-15]] [--local|--install-job] | <${CLI_COMMANDS.map((c) => c.name).join("|")}> [options]\n` +
|
|
178
178
|
"With several installs on this machine, pick one with --install <install-id or domain> (status, upgrade and backup also take --install all); in a terminal you are asked otherwise.\n" +
|
|
179
179
|
"Pass --no-wizard with no sub-command to print this usage instead of the wizard."
|
|
180
180
|
);
|
package/src/cli.ts
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
BOOTSTRAP_DIR,
|
|
64
64
|
COMPOSE_ADMIN_UPSTREAM,
|
|
65
65
|
COMPOSE_GATEWAY_UPSTREAM,
|
|
66
|
+
COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
66
67
|
type ComposeImages,
|
|
67
68
|
composeProjectName,
|
|
68
69
|
generateDockerCompose,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
} from "./compose.js";
|
|
76
77
|
import { tightenPrivateTree, writePrivateFile } from "./private-file.js";
|
|
77
78
|
import { selfCommand } from "./self-command.js";
|
|
79
|
+
import { ensureSelfHostedDbTls, selfHostedDbTlsServerCertSha256 } from "./self-hosted-db-tls.js";
|
|
78
80
|
import { ensureInternalSecret, writeInternalSecret } from "./self-hosted-internal-secret.js";
|
|
79
81
|
import {
|
|
80
82
|
bunHostRunner,
|
|
@@ -84,10 +86,12 @@ import {
|
|
|
84
86
|
resolveVerifiedRelease,
|
|
85
87
|
} from "./self-hosted-release.js";
|
|
86
88
|
import {
|
|
89
|
+
applyPreMigrationRoles,
|
|
87
90
|
convergeSelfHostedRoles,
|
|
88
91
|
decideUp,
|
|
89
92
|
selfHostedBootstrapScript,
|
|
90
93
|
} from "./self-hosted-roles.js";
|
|
94
|
+
import { parseSchedulerIntervalMinutes } from "./stack-contract.js";
|
|
91
95
|
|
|
92
96
|
interface CaeloConfig {
|
|
93
97
|
domain: string;
|
|
@@ -150,6 +154,12 @@ interface CaeloConfig {
|
|
|
150
154
|
* carry it).
|
|
151
155
|
*/
|
|
152
156
|
composeProject?: string;
|
|
157
|
+
/**
|
|
158
|
+
* #1140 — the `schedulerIntervalMinutes` knob (`init
|
|
159
|
+
* --scheduler-interval-minutes`, 1–15): how often the scheduler container
|
|
160
|
+
* calls the admin's tick. Absent: the default (5).
|
|
161
|
+
*/
|
|
162
|
+
schedulerIntervalMinutes?: number;
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
const REPO_ROOT = resolve(import.meta.dir, "../../..");
|
|
@@ -252,6 +262,15 @@ function loadConfig(): CaeloConfig | null {
|
|
|
252
262
|
cfg.composeProject ??= "caelo";
|
|
253
263
|
saveConfig(cfg);
|
|
254
264
|
}
|
|
265
|
+
// #1329 — the staging gateway's login came with the staging database. An
|
|
266
|
+
// install from before gets its own password now; the next `up` or
|
|
267
|
+
// upgrade creates the role with it (self-hosted-roles.ts) before anything
|
|
268
|
+
// uses it, so the roles count as not converged until then.
|
|
269
|
+
if (cfg.rolePasswords && !cfg.rolePasswords.publicStaging) {
|
|
270
|
+
cfg.rolePasswords = { ...cfg.rolePasswords, publicStaging: randomSecret(32) };
|
|
271
|
+
cfg.rolesConverged = false;
|
|
272
|
+
saveConfig(cfg);
|
|
273
|
+
}
|
|
255
274
|
return cfg;
|
|
256
275
|
}
|
|
257
276
|
|
|
@@ -266,9 +285,9 @@ function arg(name: string): string | undefined {
|
|
|
266
285
|
return process.argv[idx + 1];
|
|
267
286
|
}
|
|
268
287
|
|
|
269
|
-
/** Fresh passwords for the
|
|
288
|
+
/** Fresh passwords for the application roles. */
|
|
270
289
|
function newRolePasswords(): RolePasswords {
|
|
271
|
-
return { admin: randomSecret(32), public: randomSecret(32) };
|
|
290
|
+
return { admin: randomSecret(32), public: randomSecret(32), publicStaging: randomSecret(32) };
|
|
272
291
|
}
|
|
273
292
|
|
|
274
293
|
function emitConfig(cfg: CaeloConfig, extraDomains: CaddyDomainSpec[] = []): void {
|
|
@@ -305,6 +324,7 @@ function writeCaddyfile(cfg: CaeloConfig, extraDomains: CaddyDomainSpec[]): void
|
|
|
305
324
|
// localhost (#707).
|
|
306
325
|
adminUpstream: COMPOSE_ADMIN_UPSTREAM,
|
|
307
326
|
gatewayUpstream: COMPOSE_GATEWAY_UPSTREAM,
|
|
327
|
+
mcpAuthProxyUpstream: COMPOSE_MCP_AUTH_PROXY_UPSTREAM,
|
|
308
328
|
domains: dedupeDomains([
|
|
309
329
|
...selfHostedSeedDomains(cfg.domain, cfg.stagingDomain),
|
|
310
330
|
...extraDomains,
|
|
@@ -344,6 +364,9 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
|
|
|
344
364
|
"config has no ports / composeProject — they are back-filled on load; this is a bug",
|
|
345
365
|
);
|
|
346
366
|
}
|
|
367
|
+
// #1338 — the install's database CA + server certificate the Compose file
|
|
368
|
+
// mounts (created once, the server certificate renewed when due).
|
|
369
|
+
for (const line of ensureSelfHostedDbTls(CAELO_DIR)) console.log(` database TLS: ${line}`);
|
|
347
370
|
return generateDockerCompose({
|
|
348
371
|
domain: cfg.domain,
|
|
349
372
|
postgresPassword: cfg.postgresPassword,
|
|
@@ -351,6 +374,7 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
|
|
|
351
374
|
caeloSecretKek: cfg.caeloSecretKek,
|
|
352
375
|
// #770 — every render (up, upgrade, agent apply) hands the admin the
|
|
353
376
|
// secret host-side commands sign their /api/internal/* tokens with.
|
|
377
|
+
dbTlsServerCertSha256: selfHostedDbTlsServerCertSha256(CAELO_DIR),
|
|
354
378
|
internalSecret: ensureInternalSecret(CAELO_DIR, {
|
|
355
379
|
randomSecret: () => randomSecret(48),
|
|
356
380
|
report: (line) => console.log(line),
|
|
@@ -361,6 +385,9 @@ function renderCompose(cfg: CaeloConfig, images: ComposeImages): string {
|
|
|
361
385
|
images,
|
|
362
386
|
ports: cfg.ports,
|
|
363
387
|
project: cfg.composeProject,
|
|
388
|
+
...(cfg.schedulerIntervalMinutes !== undefined
|
|
389
|
+
? { schedulerIntervalMinutes: cfg.schedulerIntervalMinutes }
|
|
390
|
+
: {}),
|
|
364
391
|
...(activeAgent(cfg) ? { agent: activeAgent(cfg) } : {}),
|
|
365
392
|
});
|
|
366
393
|
}
|
|
@@ -397,6 +424,20 @@ function agentContext(cfg: CaeloConfig): AgentCliContext {
|
|
|
397
424
|
...(activeAgent(cfg) ? { agentPassword: activeAgent(cfg)?.dbPassword } : {}),
|
|
398
425
|
});
|
|
399
426
|
},
|
|
427
|
+
applyPreMigrationRoles: () => {
|
|
428
|
+
if (!cfg.rolePasswords) {
|
|
429
|
+
return Promise.resolve({
|
|
430
|
+
ok: false as const,
|
|
431
|
+
reachable: true,
|
|
432
|
+
error: "config.json has no role passwords — run `cms-provision up` first",
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return applyPreMigrationRoles({
|
|
436
|
+
probe: postgresIsUp,
|
|
437
|
+
psql: superuserPsql,
|
|
438
|
+
stagingPassword: cfg.rolePasswords.publicStaging,
|
|
439
|
+
});
|
|
440
|
+
},
|
|
400
441
|
renderCompose: (images) => renderCompose(cfg, images),
|
|
401
442
|
refreshCaddy: () => refreshCaddyfile(cfg),
|
|
402
443
|
randomSecret: () => randomSecret(32),
|
|
@@ -552,7 +593,7 @@ async function init(): Promise<void> {
|
|
|
552
593
|
}
|
|
553
594
|
if (!domain || !ownerEmail) {
|
|
554
595
|
console.error(
|
|
555
|
-
"Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443]\n" +
|
|
596
|
+
"Usage: cms-provision init [--provider self-hosted] --domain example.com [--staging-domain staging.example.com] --owner-email me@example.com [--version x.y.z] [--skip-verify] [--dev] [--tls acme|internal] [--http-port 80] [--https-port 443] [--scheduler-interval-minutes 5]\n" +
|
|
556
597
|
"Cloud providers (gcp | gcp-firebase | aws | azure) run the wizard: cms-provision --provider <name>",
|
|
557
598
|
);
|
|
558
599
|
process.exit(2);
|
|
@@ -567,6 +608,17 @@ async function init(): Promise<void> {
|
|
|
567
608
|
console.error("--http-port and --https-port must differ");
|
|
568
609
|
process.exit(2);
|
|
569
610
|
}
|
|
611
|
+
const schedulerArg = arg("scheduler-interval-minutes");
|
|
612
|
+
let schedulerIntervalMinutes: number | undefined;
|
|
613
|
+
try {
|
|
614
|
+
schedulerIntervalMinutes =
|
|
615
|
+
schedulerArg === undefined
|
|
616
|
+
? undefined
|
|
617
|
+
: parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
|
|
618
|
+
} catch (e) {
|
|
619
|
+
console.error((e as Error).message);
|
|
620
|
+
process.exit(2);
|
|
621
|
+
}
|
|
570
622
|
const stagingArg = arg("staging-domain");
|
|
571
623
|
const stagingProblem = stagingArg
|
|
572
624
|
? stagingHostProblem(stagingArg, {
|
|
@@ -635,6 +687,7 @@ async function init(): Promise<void> {
|
|
|
635
687
|
tls,
|
|
636
688
|
ports,
|
|
637
689
|
composeProject: composeProjectName(domain),
|
|
690
|
+
...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
|
|
638
691
|
};
|
|
639
692
|
saveConfig(cfg);
|
|
640
693
|
// P15.1 — the shared HMAC secret for /api/internal/* endpoints, read by
|
|
@@ -1019,7 +1072,7 @@ async function upgradeSelfHostedCommand(opts: {
|
|
|
1019
1072
|
console.error(
|
|
1020
1073
|
"This is a DEV MODE install: the admin + gateway run your Caelo checkout, so there are\n" +
|
|
1021
1074
|
"no images to upgrade. Update the checkout (git pull), then `bun packages/provisioning/src/cli.ts migrate` and\n" +
|
|
1022
|
-
`\`docker compose -f ${COMPOSE_PATH} restart caelo-admin caelo-gateway\`.`,
|
|
1075
|
+
`\`docker compose -f ${COMPOSE_PATH} restart caelo-admin caelo-gateway caelo-mcp-auth-proxy\`.`,
|
|
1023
1076
|
);
|
|
1024
1077
|
process.exit(2);
|
|
1025
1078
|
}
|
|
@@ -1047,6 +1100,7 @@ async function upgradeSelfHostedCommand(opts: {
|
|
|
1047
1100
|
runner: bunHostRunner,
|
|
1048
1101
|
renderCompose: (images) => renderCompose(cfg, images),
|
|
1049
1102
|
convergeRoles: agentContext(cfg).convergeRoles,
|
|
1103
|
+
applyPreMigrationRoles: agentContext(cfg).applyPreMigrationRoles,
|
|
1050
1104
|
refreshCaddy: () => refreshCaddyfile(cfg),
|
|
1051
1105
|
log: {
|
|
1052
1106
|
step: (m) => console.log(`\n==> ${m}`),
|
|
@@ -1614,13 +1668,25 @@ async function waitForDomainMappingCert(target: {
|
|
|
1614
1668
|
* §11.C — interactive wizard. Loaded lazily so the bare-CLI startup
|
|
1615
1669
|
* cost stays small (clack + kleur are pulled in only when needed).
|
|
1616
1670
|
* Reads `--provider`, `--domain`, `--owner-email`, `--project-id`, `--region`,
|
|
1617
|
-
* `--non-interactive` and the run mode (`--local` / `--install-job`, default:
|
|
1671
|
+
* `--scheduler-interval-minutes` (#1140), `--non-interactive` and the run mode (`--local` / `--install-job`, default:
|
|
1618
1672
|
* the install job in Cloud Shell, local elsewhere — run-mode.ts) from argv.
|
|
1619
1673
|
*/
|
|
1620
1674
|
async function wizardCommand(): Promise<void> {
|
|
1621
1675
|
const { runWizard } = await import("./wizard.js");
|
|
1622
1676
|
const { chooseRunMode } = await import("./run-mode.js");
|
|
1677
|
+
const schedulerArg = arg("scheduler-interval-minutes");
|
|
1678
|
+
let schedulerIntervalMinutes: number | undefined;
|
|
1679
|
+
try {
|
|
1680
|
+
schedulerIntervalMinutes =
|
|
1681
|
+
schedulerArg === undefined
|
|
1682
|
+
? undefined
|
|
1683
|
+
: parseSchedulerIntervalMinutes(schedulerArg, "--scheduler-interval-minutes");
|
|
1684
|
+
} catch (e) {
|
|
1685
|
+
console.error((e as Error).message);
|
|
1686
|
+
process.exit(2);
|
|
1687
|
+
}
|
|
1623
1688
|
await runWizard({
|
|
1689
|
+
...(schedulerIntervalMinutes !== undefined ? { schedulerIntervalMinutes } : {}),
|
|
1624
1690
|
runMode: chooseRunMode(process.argv, process.env),
|
|
1625
1691
|
nonInteractive: process.argv.includes("--non-interactive"),
|
|
1626
1692
|
provider: arg("provider") as Provider | undefined,
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import {
|
|
5
|
+
dialsIpAddress,
|
|
6
|
+
ensureCloudSqlVerifyFull,
|
|
7
|
+
moveServicesToDnsHost,
|
|
8
|
+
urlOnDnsHost,
|
|
9
|
+
} from "./cloudsql-tls.js";
|
|
10
|
+
import type { GcloudResult } from "./gcloud.js";
|
|
11
|
+
import type { HttpFetch } from "./runtime-secrets.js";
|
|
12
|
+
|
|
13
|
+
const DNS = "abc123.p-label.europe-west1.sql-psa.goog";
|
|
14
|
+
const TARGET = { projectId: "p", env: "production", sqlInstance: "caelo-production-pg-1" };
|
|
15
|
+
|
|
16
|
+
const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
17
|
+
const notFound = (): GcloudResult => ({
|
|
18
|
+
ok: false,
|
|
19
|
+
stdout: "",
|
|
20
|
+
stderr: "ERROR: (gcloud) NOT_FOUND",
|
|
21
|
+
exitCode: 1,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** An instance as `gcloud sql instances describe --format=json` prints it. */
|
|
25
|
+
function instance(opts: { caMode: string; sslMode: string; dns: boolean }): string {
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
settings: {
|
|
28
|
+
ipConfiguration: {
|
|
29
|
+
serverCaMode: opts.caMode,
|
|
30
|
+
sslMode: opts.sslMode,
|
|
31
|
+
privateNetwork: "projects/p/global/networks/caelo-production-vpc-1",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
ipAddresses: [{ type: "PRIVATE", ipAddress: "10.20.0.3" }],
|
|
35
|
+
dnsNames: opts.dns
|
|
36
|
+
? [{ name: `${DNS}.`, connectionType: "PRIVATE_SERVICES_ACCESS", dnsScope: "INSTANCE" }]
|
|
37
|
+
: [],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A fake gcloud: `answer` per call, every call recorded. */
|
|
42
|
+
function fake(answer: (args: string[]) => GcloudResult) {
|
|
43
|
+
const calls: string[] = [];
|
|
44
|
+
return {
|
|
45
|
+
calls,
|
|
46
|
+
run: async (args: string[]) => {
|
|
47
|
+
calls.push(args.join(" "));
|
|
48
|
+
return answer(args);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The Cloud SQL Admin API listServerCertificates answer. */
|
|
54
|
+
function certs(body: object): HttpFetch {
|
|
55
|
+
return async () => ({ ok: true, status: 200, text: async () => JSON.stringify(body) });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("ensureCloudSqlVerifyFull", () => {
|
|
59
|
+
it("moves a per-instance-CA instance to the shared CA, rotates, and makes its name resolve", async () => {
|
|
60
|
+
let described = 0;
|
|
61
|
+
const g = fake((a) => {
|
|
62
|
+
const line = a.join(" ");
|
|
63
|
+
if (line.startsWith("sql instances describe")) {
|
|
64
|
+
described += 1;
|
|
65
|
+
return ok(
|
|
66
|
+
instance(
|
|
67
|
+
described === 1
|
|
68
|
+
? {
|
|
69
|
+
caMode: "GOOGLE_MANAGED_INTERNAL_CA",
|
|
70
|
+
sslMode: "ALLOW_UNENCRYPTED_AND_ENCRYPTED",
|
|
71
|
+
dns: false,
|
|
72
|
+
}
|
|
73
|
+
: { caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true },
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (line.startsWith("auth print-access-token")) return ok("token");
|
|
78
|
+
if (line.includes(" describe ")) return notFound();
|
|
79
|
+
return ok();
|
|
80
|
+
});
|
|
81
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
82
|
+
run: g.run,
|
|
83
|
+
http: certs({ serverCerts: [] }),
|
|
84
|
+
sleep: async () => {},
|
|
85
|
+
});
|
|
86
|
+
expect(r).toMatchObject({ ok: true, host: DNS });
|
|
87
|
+
const writes = g.calls.filter((c) => !c.includes(" describe ") && !c.startsWith("auth"));
|
|
88
|
+
expect(writes).toEqual([
|
|
89
|
+
"sql instances patch caelo-production-pg-1 --project=p --server-ca-mode=GOOGLE_MANAGED_CAS_CA --ssl-mode=ENCRYPTED_ONLY --quiet",
|
|
90
|
+
"sql ssl server-certs create --instance=caelo-production-pg-1 --project=p",
|
|
91
|
+
"sql ssl server-certs rotate --instance=caelo-production-pg-1 --project=p --quiet",
|
|
92
|
+
`dns managed-zones create caelo-production-pg-dns --project=p --dns-name=${DNS}. --visibility=private --networks=caelo-production-vpc-1 --description=Caelo production: the Cloud SQL instance's DNS name inside the VPC (#1338)`,
|
|
93
|
+
`dns record-sets create ${DNS}. --zone=caelo-production-pg-dns --type=A --project=p --ttl=300 --rrdatas=10.20.0.3`,
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("reads the DNS name after the move when the shared CA's certificate is active at once", async () => {
|
|
98
|
+
// searchviu-website, v0.11.10: the patch made the shared CA's certificate
|
|
99
|
+
// the active one, so no rotation re-read the instance, and the
|
|
100
|
+
// description from before the patch named no DNS host.
|
|
101
|
+
let described = 0;
|
|
102
|
+
const g = fake((a) => {
|
|
103
|
+
const line = a.join(" ");
|
|
104
|
+
if (line.startsWith("sql instances describe")) {
|
|
105
|
+
described += 1;
|
|
106
|
+
return ok(
|
|
107
|
+
instance(
|
|
108
|
+
described === 1
|
|
109
|
+
? { caMode: "GOOGLE_MANAGED_INTERNAL_CA", sslMode: "ENCRYPTED_ONLY", dns: false }
|
|
110
|
+
: { caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true },
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (line.startsWith("auth print-access-token")) return ok("token");
|
|
115
|
+
if (line.includes(" describe ")) return notFound();
|
|
116
|
+
return ok();
|
|
117
|
+
});
|
|
118
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
119
|
+
run: g.run,
|
|
120
|
+
http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
|
|
121
|
+
sleep: async () => {},
|
|
122
|
+
});
|
|
123
|
+
expect(r).toMatchObject({ ok: true, host: DNS });
|
|
124
|
+
expect(g.calls.filter((c) => c.startsWith("sql ssl"))).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("changes nothing on an instance already in shape", async () => {
|
|
128
|
+
const g = fake((a) => {
|
|
129
|
+
const line = a.join(" ");
|
|
130
|
+
if (line.startsWith("sql instances describe")) {
|
|
131
|
+
return ok(
|
|
132
|
+
instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (line.startsWith("auth")) return ok("token");
|
|
136
|
+
if (line.startsWith("dns managed-zones describe"))
|
|
137
|
+
return ok(JSON.stringify({ dnsName: `${DNS}.` }));
|
|
138
|
+
if (line.startsWith("dns record-sets describe"))
|
|
139
|
+
return ok(JSON.stringify({ rrdatas: ["10.20.0.3"] }));
|
|
140
|
+
return ok();
|
|
141
|
+
});
|
|
142
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
143
|
+
run: g.run,
|
|
144
|
+
http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
|
|
145
|
+
});
|
|
146
|
+
expect(r).toEqual({ ok: true, host: DNS, done: [] });
|
|
147
|
+
expect(g.calls.every((c) => c.includes(" describe ") || c.startsWith("auth"))).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("finishes an interrupted rotation without creating a second certificate", async () => {
|
|
151
|
+
const g = fake((a) => {
|
|
152
|
+
const line = a.join(" ");
|
|
153
|
+
if (line.startsWith("sql instances describe")) {
|
|
154
|
+
return ok(
|
|
155
|
+
instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (line.startsWith("auth")) return ok("token");
|
|
159
|
+
if (line.startsWith("dns managed-zones describe"))
|
|
160
|
+
return ok(JSON.stringify({ dnsName: `${DNS}.` }));
|
|
161
|
+
if (line.startsWith("dns record-sets describe"))
|
|
162
|
+
return ok(JSON.stringify({ rrdatas: ["10.9.9.9"] }));
|
|
163
|
+
return ok();
|
|
164
|
+
});
|
|
165
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
166
|
+
run: g.run,
|
|
167
|
+
http: certs({
|
|
168
|
+
activeVersion: "old-per-instance",
|
|
169
|
+
serverCerts: [{ sha1Fingerprint: "upcoming" }],
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
expect(r.ok).toBe(true);
|
|
173
|
+
expect(g.calls.some((c) => c.startsWith("sql ssl server-certs create"))).toBe(false);
|
|
174
|
+
expect(g.calls.some((c) => c.startsWith("sql ssl server-certs rotate"))).toBe(true);
|
|
175
|
+
expect(
|
|
176
|
+
g.calls.some(
|
|
177
|
+
(c) => c.startsWith("dns record-sets update") && c.endsWith("--rrdatas=10.20.0.3"),
|
|
178
|
+
),
|
|
179
|
+
).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("refuses a zone that serves another name instead of repointing it", async () => {
|
|
183
|
+
const g = fake((a) => {
|
|
184
|
+
const line = a.join(" ");
|
|
185
|
+
if (line.startsWith("sql instances describe")) {
|
|
186
|
+
return ok(
|
|
187
|
+
instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: true }),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (line.startsWith("auth")) return ok("token");
|
|
191
|
+
if (line.startsWith("dns managed-zones describe")) {
|
|
192
|
+
return ok(JSON.stringify({ dnsName: "old.p-label.europe-west1.sql-psa.goog." }));
|
|
193
|
+
}
|
|
194
|
+
return ok();
|
|
195
|
+
});
|
|
196
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
197
|
+
run: g.run,
|
|
198
|
+
http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
|
|
199
|
+
});
|
|
200
|
+
expect(r).toMatchObject({ ok: false });
|
|
201
|
+
if (!r.ok) expect(r.error).toContain("serves old.p-label");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("fails loudly when the instance names no private DNS name", async () => {
|
|
205
|
+
const g = fake((a) =>
|
|
206
|
+
a.join(" ").startsWith("sql instances describe")
|
|
207
|
+
? ok(instance({ caMode: "GOOGLE_MANAGED_CAS_CA", sslMode: "ENCRYPTED_ONLY", dns: false }))
|
|
208
|
+
: ok("token"),
|
|
209
|
+
);
|
|
210
|
+
const r = await ensureCloudSqlVerifyFull(TARGET, {
|
|
211
|
+
run: g.run,
|
|
212
|
+
http: certs({ activeVersion: "f1", serverCerts: [{ sha1Fingerprint: "f1" }] }),
|
|
213
|
+
});
|
|
214
|
+
expect(r).toMatchObject({ ok: false });
|
|
215
|
+
if (!r.ok) expect(r.error).toContain("PRIVATE_SERVICES_ACCESS DNS name");
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("moveServicesToDnsHost", () => {
|
|
220
|
+
it("switches only the URLs that dial an IP, keeping their sslmode", async () => {
|
|
221
|
+
const g = fake(() => ok());
|
|
222
|
+
const r = await moveServicesToDnsHost(
|
|
223
|
+
{ projectId: "p", region: "europe-west1" },
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
serviceName: "caelo-production-admin-1",
|
|
227
|
+
liveUrls: {
|
|
228
|
+
ADMIN_DATABASE_URL: "postgresql://admin_role@10.20.0.3:5432/cms_admin?sslmode=require",
|
|
229
|
+
PUBLIC_ADMIN_DATABASE_URL:
|
|
230
|
+
"postgresql://admin_role@10.20.0.3:5432/cms_public?sslmode=require",
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
serviceName: "caelo-production-gateway-1",
|
|
235
|
+
liveUrls: { PUBLIC_DATABASE_URL: `postgresql://public_role@${DNS}:5432/cms_public` },
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
DNS,
|
|
239
|
+
{ run: g.run, sleep: async () => {} },
|
|
240
|
+
);
|
|
241
|
+
expect(r.ok).toBe(true);
|
|
242
|
+
expect(g.calls).toEqual([
|
|
243
|
+
`run services update caelo-production-admin-1 --region europe-west1 --project=p --update-env-vars=^|^ADMIN_DATABASE_URL=postgresql://admin_role@${DNS}:5432/cms_admin?sslmode=require|PUBLIC_ADMIN_DATABASE_URL=postgresql://admin_role@${DNS}:5432/cms_public?sslmode=require --quiet`,
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("urlOnDnsHost / dialsIpAddress", () => {
|
|
249
|
+
it("tells IP-dialling URLs apart and moves only those", () => {
|
|
250
|
+
expect(dialsIpAddress("postgresql://a@10.0.0.3:5432/d")).toBe(true);
|
|
251
|
+
expect(dialsIpAddress(`postgresql://a@${DNS}:5432/d`)).toBe(false);
|
|
252
|
+
expect(dialsIpAddress("not a url")).toBe(false);
|
|
253
|
+
expect(urlOnDnsHost(`postgresql://a@${DNS}:5432/d`, "x")).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
});
|