@caelo-cms/provisioning 0.10.46 → 0.10.48
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/adc-impersonation.d.ts.map +1 -1
- package/dist/adc-impersonation.js +23 -1
- package/dist/adc-impersonation.js.map +1 -1
- package/dist/agent/apply.d.ts +45 -0
- package/dist/agent/apply.d.ts.map +1 -0
- package/dist/agent/apply.js +83 -0
- package/dist/agent/apply.js.map +1 -0
- package/dist/agent/cli-provenance.d.ts +42 -0
- package/dist/agent/cli-provenance.d.ts.map +1 -0
- package/dist/agent/cli-provenance.js +138 -0
- package/dist/agent/cli-provenance.js.map +1 -0
- package/dist/agent/command.d.ts +79 -0
- package/dist/agent/command.d.ts.map +1 -0
- package/dist/agent/command.js +454 -0
- package/dist/agent/command.js.map +1 -0
- package/dist/agent/config.d.ts +30 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +60 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/lock.d.ts +13 -0
- package/dist/agent/lock.d.ts.map +1 -0
- package/dist/agent/lock.js +100 -0
- package/dist/agent/lock.js.map +1 -0
- package/dist/agent/queue.d.ts +35 -0
- package/dist/agent/queue.d.ts.map +1 -0
- package/dist/agent/queue.js +50 -0
- package/dist/agent/queue.js.map +1 -0
- package/dist/agent/run.d.ts +97 -0
- package/dist/agent/run.d.ts.map +1 -0
- package/dist/agent/run.js +198 -0
- package/dist/agent/run.js.map +1 -0
- package/dist/agent/systemd.d.ts +55 -0
- package/dist/agent/systemd.d.ts.map +1 -0
- package/dist/agent/systemd.js +171 -0
- package/dist/agent/systemd.js.map +1 -0
- package/dist/agent/units.d.ts +44 -0
- package/dist/agent/units.d.ts.map +1 -0
- package/dist/agent/units.js +127 -0
- package/dist/agent/units.js.map +1 -0
- package/dist/agent-db.js +18362 -0
- package/dist/cli.js +122 -16
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-iam-user.d.ts +39 -0
- package/dist/cloudsql-iam-user.d.ts.map +1 -0
- package/dist/cloudsql-iam-user.js +119 -0
- package/dist/cloudsql-iam-user.js.map +1 -0
- package/dist/compose.d.ts +16 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +26 -1
- package/dist/compose.js.map +1 -1
- package/dist/dns/index.d.ts +2 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +3 -2
- package/dist/dns/index.js.map +1 -1
- package/dist/dns/manual.d.ts +5 -0
- package/dist/dns/manual.d.ts.map +1 -1
- package/dist/dns/manual.js +28 -12
- package/dist/dns/manual.js.map +1 -1
- package/dist/gcloud.d.ts +2 -0
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +1 -1
- package/dist/gcloud.js.map +1 -1
- package/dist/gcp-names.d.ts +15 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +23 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.js +7 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job-runner.d.ts +13 -0
- package/dist/install-job-runner.d.ts.map +1 -0
- package/dist/install-job-runner.js +155 -0
- package/dist/install-job-runner.js.map +1 -0
- package/dist/install-job.d.ts +210 -0
- package/dist/install-job.d.ts.map +1 -0
- package/dist/install-job.js +449 -0
- package/dist/install-job.js.map +1 -0
- package/dist/install-state.d.ts +19 -1
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +31 -3
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +24 -5
- package/dist/lifecycle.js.map +1 -1
- package/dist/migration-runner.d.ts +5 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +1 -1
- package/dist/migration-runner.js.map +1 -1
- package/dist/operator-access.d.ts +14 -8
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +16 -102
- package/dist/operator-access.js.map +1 -1
- package/dist/run-mode.d.ts +27 -0
- package/dist/run-mode.d.ts.map +1 -0
- package/dist/run-mode.js +22 -0
- package/dist/run-mode.js.map +1 -0
- package/dist/runtime-secrets.d.ts +19 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +44 -0
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +22 -1
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +81 -12
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +17 -0
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +16 -1
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +6 -0
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +10 -1
- package/dist/stack-contract.js.map +1 -1
- package/dist/state-attach.d.ts +23 -0
- package/dist/state-attach.d.ts.map +1 -0
- package/dist/state-attach.js +112 -0
- package/dist/state-attach.js.map +1 -0
- package/dist/state-backend.d.ts +11 -0
- package/dist/state-backend.d.ts.map +1 -1
- package/dist/state-backend.js +20 -0
- package/dist/state-backend.js.map +1 -1
- package/dist/state-command.d.ts +20 -0
- package/dist/state-command.d.ts.map +1 -0
- package/dist/state-command.js +237 -0
- package/dist/state-command.js.map +1 -0
- package/dist/state-migrate.d.ts +121 -0
- package/dist/state-migrate.d.ts.map +1 -0
- package/dist/state-migrate.js +290 -0
- package/dist/state-migrate.js.map +1 -0
- package/dist/state-pulumi.d.ts +17 -0
- package/dist/state-pulumi.d.ts.map +1 -0
- package/dist/state-pulumi.js +218 -0
- package/dist/state-pulumi.js.map +1 -0
- package/dist/state-store.d.ts +27 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +65 -0
- package/dist/state-store.js.map +1 -1
- package/dist/upgrade/converge.d.ts +6 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +59 -2
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +6 -0
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +25 -0
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +31 -0
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +5 -0
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +28 -1
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-assess.d.ts +82 -0
- package/dist/upgrader-assess.d.ts.map +1 -0
- package/dist/upgrader-assess.js +116 -0
- package/dist/upgrader-assess.js.map +1 -0
- package/dist/upgrader-gcp.d.ts +17 -0
- package/dist/upgrader-gcp.d.ts.map +1 -0
- package/dist/upgrader-gcp.js +87 -0
- package/dist/upgrader-gcp.js.map +1 -0
- package/dist/upgrader-job.d.ts +131 -0
- package/dist/upgrader-job.d.ts.map +1 -0
- package/dist/upgrader-job.js +259 -0
- package/dist/upgrader-job.js.map +1 -0
- package/dist/upgrader-reporter.d.ts +35 -0
- package/dist/upgrader-reporter.d.ts.map +1 -0
- package/dist/upgrader-reporter.js +79 -0
- package/dist/upgrader-reporter.js.map +1 -0
- package/dist/upgrader-setup.d.ts +59 -0
- package/dist/upgrader-setup.d.ts.map +1 -0
- package/dist/upgrader-setup.js +308 -0
- package/dist/upgrader-setup.js.map +1 -0
- package/dist/wizard.d.ts +6 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +32 -4
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-firebase.d.ts +3 -0
- package/dist/wizards/gcp-firebase.d.ts.map +1 -1
- package/dist/wizards/gcp-firebase.js +1 -0
- package/dist/wizards/gcp-firebase.js.map +1 -1
- package/dist/wizards/gcp-install-job.d.ts +74 -0
- package/dist/wizards/gcp-install-job.d.ts.map +1 -0
- package/dist/wizards/gcp-install-job.js +398 -0
- package/dist/wizards/gcp-install-job.js.map +1 -0
- package/dist/wizards/gcp-prompts.d.ts +3 -0
- package/dist/wizards/gcp-prompts.d.ts.map +1 -0
- package/dist/wizards/gcp-prompts.js +25 -0
- package/dist/wizards/gcp-prompts.js.map +1 -0
- package/dist/wizards/gcp-pulumi.d.ts +9 -2
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +8 -6
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts +40 -0
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +291 -83
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +6 -4
- package/src/adapter.ts +97 -0
- package/src/adc-impersonation.ts +176 -0
- package/src/admin-domain.test.ts +254 -0
- package/src/admin-domain.ts +245 -0
- package/src/admin-memory.test.ts +151 -0
- package/src/agent/apply.test.ts +185 -0
- package/src/agent/apply.ts +124 -0
- package/src/agent/cli-provenance.test.ts +159 -0
- package/src/agent/cli-provenance.ts +191 -0
- package/src/agent/command.test.ts +77 -0
- package/src/agent/command.ts +564 -0
- package/src/agent/config.ts +86 -0
- package/src/agent/lock-config.test.ts +97 -0
- package/src/agent/lock.ts +110 -0
- package/src/agent/queue.test.ts +10 -0
- package/src/agent/queue.ts +91 -0
- package/src/agent/run.test.ts +382 -0
- package/src/agent/run.ts +295 -0
- package/src/agent/systemd.ts +209 -0
- package/src/agent/units.test.ts +223 -0
- package/src/agent/units.ts +163 -0
- package/src/azure-front-door.test.ts +119 -0
- package/src/azure-front-door.ts +56 -0
- package/src/bootstrap-token.ts +20 -0
- package/src/caddy.ts +112 -0
- package/src/cdn-copy.ts +91 -0
- package/src/cert-wait.test.ts +194 -0
- package/src/cert-wait.ts +163 -0
- package/src/checkpoint-secrets.test.ts +380 -0
- package/src/checkpoint-secrets.ts +283 -0
- package/src/cli-routing.test.ts +45 -0
- package/src/cli-routing.ts +43 -0
- package/src/cli.ts +1491 -0
- package/src/cloudsql-iam-user.ts +147 -0
- package/src/compose.ts +334 -0
- package/src/default-firewall.test.ts +411 -0
- package/src/default-firewall.ts +342 -0
- package/src/deployed-release.test.ts +231 -0
- package/src/deployed-release.ts +220 -0
- package/src/dns/cloudflare.ts +182 -0
- package/src/dns/groups.test.ts +76 -0
- package/src/dns/groups.ts +86 -0
- package/src/dns/index.ts +56 -0
- package/src/dns/manual.ts +130 -0
- package/src/dns/types.ts +27 -0
- package/src/gateway-credentials.test.ts +180 -0
- package/src/gateway-credentials.ts +144 -0
- package/src/gcloud-components.test.ts +76 -0
- package/src/gcloud-components.ts +85 -0
- package/src/gcloud-retry.ts +41 -0
- package/src/gcloud-runner.test.ts +61 -0
- package/src/gcloud.test.ts +99 -0
- package/src/gcloud.ts +322 -0
- package/src/gcp-names.ts +135 -0
- package/src/index.test.ts +332 -0
- package/src/index.ts +114 -0
- package/src/infra-contract.ts +32 -0
- package/src/install-job-runner.ts +166 -0
- package/src/install-job.test.ts +566 -0
- package/src/install-job.ts +542 -0
- package/src/install-state.test.ts +239 -0
- package/src/install-state.ts +583 -0
- package/src/internal-token.ts +35 -0
- package/src/lifecycle.ts +900 -0
- package/src/mcp-iap-stacks.test.ts +58 -0
- package/src/mcp-iap.test.ts +149 -0
- package/src/mcp-iap.ts +131 -0
- package/src/media-volume.test.ts +296 -0
- package/src/migration-runner.ts +365 -0
- package/src/operator-access-stacks.test.ts +55 -0
- package/src/operator-access.test.ts +332 -0
- package/src/operator-access.ts +554 -0
- package/src/ops-backend.test.ts +320 -0
- package/src/ops-backend.ts +414 -0
- package/src/provisioner-identity.test.ts +482 -0
- package/src/provisioner-identity.ts +407 -0
- package/src/redirects-emit.ts +175 -0
- package/src/region-data.ts +428 -0
- package/src/regions.test.ts +277 -0
- package/src/regions.ts +370 -0
- package/src/release-contract.ts +57 -0
- package/src/release-manifest.test.ts +293 -0
- package/src/release-manifest.ts +430 -0
- package/src/run-mode.ts +40 -0
- package/src/runtime-secrets.test.ts +627 -0
- package/src/runtime-secrets.ts +754 -0
- package/src/self-command.test.ts +14 -0
- package/src/self-command.ts +21 -0
- package/src/self-hosted-compose.test.ts +177 -0
- package/src/self-hosted-release.ts +240 -0
- package/src/self-hosted-roles.integration.test.ts +161 -0
- package/src/self-hosted-roles.test.ts +124 -0
- package/src/self-hosted-roles.ts +203 -0
- package/src/self-hosted-upgrade.test.ts +394 -0
- package/src/self-hosted-upgrade.ts +548 -0
- package/src/stack-contract-parity.test.ts +199 -0
- package/src/stack-contract.test.ts +391 -0
- package/src/stack-contract.ts +888 -0
- package/src/stack-converge-secrets.test.ts +421 -0
- package/src/stack-converge.test.ts +833 -0
- package/src/stack-converge.ts +909 -0
- package/src/staging-admin.test.ts +246 -0
- package/src/staging-admin.ts +324 -0
- package/src/state-attach.test.ts +211 -0
- package/src/state-attach.ts +132 -0
- package/src/state-backend.test.ts +145 -0
- package/src/state-backend.ts +262 -0
- package/src/state-command.ts +293 -0
- package/src/state-migrate.test.ts +479 -0
- package/src/state-migrate.ts +434 -0
- package/src/state-pulumi.ts +289 -0
- package/src/state-store.test.ts +212 -0
- package/src/state-store.ts +243 -0
- package/src/upgrade/app-roll.ts +486 -0
- package/src/upgrade/backup.ts +109 -0
- package/src/upgrade/converge.ts +437 -0
- package/src/upgrade/gcp-lookup.ts +167 -0
- package/src/upgrade/health.test.ts +279 -0
- package/src/upgrade/health.ts +305 -0
- package/src/upgrade/release-images.test.ts +119 -0
- package/src/upgrade/release-images.ts +391 -0
- package/src/upgrade/reporter.ts +46 -0
- package/src/upgrade/upgrade.test.ts +626 -0
- package/src/upgrade/upgrade.ts +249 -0
- package/src/upgrader-assess.test.ts +265 -0
- package/src/upgrader-assess.ts +233 -0
- package/src/upgrader-gcp.ts +123 -0
- package/src/upgrader-job.test.ts +408 -0
- package/src/upgrader-job.ts +499 -0
- package/src/upgrader-reporter.test.ts +46 -0
- package/src/upgrader-reporter.ts +105 -0
- package/src/upgrader-setup.test.ts +169 -0
- package/src/upgrader-setup.ts +355 -0
- package/src/wizard.ts +425 -0
- package/src/wizards/gcp-cost.ts +188 -0
- package/src/wizards/gcp-firebase.ts +72 -0
- package/src/wizards/gcp-install-job.ts +506 -0
- package/src/wizards/gcp-prompts.ts +26 -0
- package/src/wizards/gcp-pulumi.test.ts +246 -0
- package/src/wizards/gcp-pulumi.ts +516 -0
- package/src/wizards/gcp.ts +1935 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The public site on every provider is object storage behind the provider's
|
|
5
|
+
* CDN, with no edge compute on the request path (CLAUDE.md §11.B Tier 1).
|
|
6
|
+
* Epic #676 removed the per-provider A/B edge routers; these checks keep them
|
|
7
|
+
* from coming back and pin the Azure primary domain to its Front Door
|
|
8
|
+
* endpoint. Stack checks are pure-string, like the other stack tests.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from "bun:test";
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { azureFrontDoorDnsRecords } from "./azure-front-door.js";
|
|
15
|
+
|
|
16
|
+
const stackDir = (provider: string) => resolve(import.meta.dir, `../stacks/${provider}`);
|
|
17
|
+
const stack = (provider: string) => readFileSync(`${stackDir(provider)}/index.ts`, "utf8");
|
|
18
|
+
|
|
19
|
+
describe("azureFrontDoorDnsRecords", () => {
|
|
20
|
+
const records = azureFrontDoorDnsRecords({
|
|
21
|
+
domain: "example.com",
|
|
22
|
+
endpointHostName: "caeloproduction-site-abc123.z01.azurefd.net",
|
|
23
|
+
validationToken: "tok-123",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("points the primary domain at the Front Door endpoint", () => {
|
|
27
|
+
expect(records).toContainEqual(
|
|
28
|
+
expect.objectContaining({
|
|
29
|
+
hostname: "example.com",
|
|
30
|
+
type: "CNAME",
|
|
31
|
+
value: "caeloproduction-site-abc123.z01.azurefd.net",
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("publishes the managed-certificate validation token", () => {
|
|
37
|
+
expect(records).toContainEqual(
|
|
38
|
+
expect.objectContaining({ hostname: "_dnsauth.example.com", type: "TXT", value: "tok-123" }),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("refuses an endpoint without a hostname instead of emitting a dangling record", () => {
|
|
43
|
+
expect(() =>
|
|
44
|
+
azureFrontDoorDnsRecords({
|
|
45
|
+
domain: "example.com",
|
|
46
|
+
endpointHostName: "",
|
|
47
|
+
validationToken: "t",
|
|
48
|
+
}),
|
|
49
|
+
).toThrow(/no hostName/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("refuses a custom domain without a validation token", () => {
|
|
53
|
+
expect(() =>
|
|
54
|
+
azureFrontDoorDnsRecords({
|
|
55
|
+
domain: "example.com",
|
|
56
|
+
endpointHostName: "x.azurefd.net",
|
|
57
|
+
validationToken: "",
|
|
58
|
+
}),
|
|
59
|
+
).toThrow(/no validation token/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("azure stack — static site behind Front Door", () => {
|
|
64
|
+
const src = stack("azure");
|
|
65
|
+
|
|
66
|
+
it("builds its DNS records from the Front Door endpoint and custom domain", () => {
|
|
67
|
+
expect(src).toContain("new azure.cdn.AFDEndpoint(");
|
|
68
|
+
expect(src).toContain("new azure.cdn.AFDCustomDomain(");
|
|
69
|
+
expect(src).toMatch(
|
|
70
|
+
/pulumi\s*\.all\(\[frontDoorEndpoint\.hostName, siteDomain\.validationProperties\]\)[\s\S]*?azureFrontDoorDnsRecords\(/,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("routes the custom domain to the static-website origin", () => {
|
|
75
|
+
expect(src).toContain("new azure.storage.StorageAccountStaticWebsite(");
|
|
76
|
+
expect(src).toMatch(
|
|
77
|
+
/new azure\.cdn\.Route\([\s\S]*?customDomains: \[\{ id: siteDomain\.id \}\]/,
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("keeps owning the published $web container and edge-caches the route", () => {
|
|
82
|
+
expect(src).toMatch(/new azure\.storage\.BlobContainer\([\s\S]*?containerName: "\$web"/);
|
|
83
|
+
expect(src).toMatch(/new azure\.cdn\.Route\([\s\S]*?cacheConfiguration: \{/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("exposes no Container App to the public internet", () => {
|
|
87
|
+
expect(src).toContain("external: false");
|
|
88
|
+
expect(src).not.toMatch(/external:\s*(true|args\.)/);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("aws stack — edge-log sink", () => {
|
|
93
|
+
const src = stack("aws");
|
|
94
|
+
|
|
95
|
+
it("delivers CloudFront access logs to the log group it reports as the sink", () => {
|
|
96
|
+
expect(src).toMatch(
|
|
97
|
+
/new aws\.cloudwatch\.LogDeliverySource\([\s\S]*?logType: "ACCESS_LOGS",\s*resourceArn: distribution\.arn/,
|
|
98
|
+
);
|
|
99
|
+
expect(src).toContain("destinationResourceArn: edgeLogGroup.arn");
|
|
100
|
+
expect(src).toMatch(/cloudwatch:\/\/us-east-1\/\$\{edgeLogGroup\.name\}/);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe.each(["aws", "azure", "gcp", "gcp-firebase", "self-hosted"])(
|
|
105
|
+
"%s stack — no edge router",
|
|
106
|
+
(provider) => {
|
|
107
|
+
const src = stack(provider);
|
|
108
|
+
|
|
109
|
+
it("runs no edge compute for the public site", () => {
|
|
110
|
+
expect(src).not.toContain("edge-router");
|
|
111
|
+
expect(src).not.toContain("lambdaFunctionAssociations");
|
|
112
|
+
expect(src).not.toContain("ab-routing.json");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("ships no edge handler source", () => {
|
|
116
|
+
expect(existsSync(`${stackDir(provider)}/edge-handler.ts`)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DNS records the Azure stack asks the operator's registrar to hold.
|
|
5
|
+
*
|
|
6
|
+
* The public site is served by Azure Front Door (CLAUDE.md §11.B Tier 1:
|
|
7
|
+
* object storage + edge CDN + managed TLS, no compute on the request path),
|
|
8
|
+
* so the primary domain points at the Front Door endpoint, and Front Door's
|
|
9
|
+
* managed certificate needs the `_dnsauth` TXT record to prove domain
|
|
10
|
+
* ownership before it issues.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { DnsRecord } from "./adapter.js";
|
|
14
|
+
|
|
15
|
+
/** Inputs the Azure stack resolves from its Front Door resources. */
|
|
16
|
+
export interface AzureFrontDoorDnsInputs {
|
|
17
|
+
/** The site's primary domain (`caelo-azure:domain`). */
|
|
18
|
+
readonly domain: string;
|
|
19
|
+
/** `AFDEndpoint.hostName`, e.g. `caelo-prod-abc123.z01.azurefd.net`. */
|
|
20
|
+
readonly endpointHostName: string;
|
|
21
|
+
/** `AFDCustomDomain.validationProperties.validationToken`. */
|
|
22
|
+
readonly validationToken: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The records for the primary domain: a CNAME to the Front Door endpoint and
|
|
27
|
+
* the TXT record that validates the custom domain for its managed certificate.
|
|
28
|
+
*
|
|
29
|
+
* @throws when the endpoint hostname or validation token is empty — a record
|
|
30
|
+
* pointing at nothing would read fine in the DNS guidance page and then fail
|
|
31
|
+
* at the registrar (no fallbacks pre-1.0, CLAUDE.md §2).
|
|
32
|
+
*/
|
|
33
|
+
export function azureFrontDoorDnsRecords(input: AzureFrontDoorDnsInputs): DnsRecord[] {
|
|
34
|
+
if (input.endpointHostName.length === 0) {
|
|
35
|
+
throw new Error("azure-front-door: the Front Door endpoint has no hostName yet");
|
|
36
|
+
}
|
|
37
|
+
if (input.validationToken.length === 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`azure-front-door: the custom domain ${input.domain} has no validation token yet`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
hostname: input.domain,
|
|
45
|
+
type: "CNAME",
|
|
46
|
+
value: input.endpointHostName,
|
|
47
|
+
purpose: "Primary domain → Azure Front Door endpoint (static site)",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
hostname: `_dnsauth.${input.domain}`,
|
|
51
|
+
type: "TXT",
|
|
52
|
+
value: input.validationToken,
|
|
53
|
+
purpose: "Validates the primary domain for Front Door's managed TLS certificate",
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* P14 — owner bootstrap token. cms-provision generates one at first
|
|
5
|
+
* `up`; the operator visits /setup?token=<…> to create the first
|
|
6
|
+
* Owner. Single-use, 24h TTL.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface BootstrapToken {
|
|
10
|
+
readonly token: string;
|
|
11
|
+
readonly expiresAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function generateBootstrapToken(): BootstrapToken {
|
|
15
|
+
const bytes = new Uint8Array(32);
|
|
16
|
+
crypto.getRandomValues(bytes);
|
|
17
|
+
const token = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
18
|
+
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
19
|
+
return { token, expiresAt };
|
|
20
|
+
}
|
package/src/caddy.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* P14 — Caddyfile generator.
|
|
5
|
+
*
|
|
6
|
+
* Reads the `domains` table + the deploy targets and emits a
|
|
7
|
+
* deterministic Caddyfile string. The CLI's `regenerate-caddy`
|
|
8
|
+
* sub-command writes this to `/etc/caddy/Caddyfile` and runs
|
|
9
|
+
* `caddy reload`.
|
|
10
|
+
*
|
|
11
|
+
* Per-vhost shape:
|
|
12
|
+
* <hostname> {
|
|
13
|
+
* # admin → reverse_proxy localhost:5173
|
|
14
|
+
* # public → root + try_files (static), with /api/* → gateway
|
|
15
|
+
* tls <ownerEmail>
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Staging vhosts force `X-Robots-Tag: noindex`.
|
|
19
|
+
*
|
|
20
|
+
* Public vhosts mark content-hashed build outputs (fonts, plugin
|
|
21
|
+
* bundles — `CONTENT_HASHED_PATH_PATTERN` in @caelo-cms/shared)
|
|
22
|
+
* `immutable` for a year. Everything else keeps Caddy's file_server
|
|
23
|
+
* default (no Cache-Control; ETag + Last-Modified for revalidation),
|
|
24
|
+
* so pages and slug-addressed media are never pinned in caches. The
|
|
25
|
+
* matcher also requires the file to exist (`file`), so a 404 for a
|
|
26
|
+
* hashed URL (an old page requesting an asset the new build dropped)
|
|
27
|
+
* is never cached for a year.
|
|
28
|
+
*
|
|
29
|
+
* `.ico` files (theme favicons) are served as `image/x-icon`, the one
|
|
30
|
+
* icon MIME the media library stores and `<link rel="icon" type>`
|
|
31
|
+
* declares. file_server only fills Content-Type when no earlier handler
|
|
32
|
+
* set it, so the `header` line wins over the host's mime.types name.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { CONTENT_HASHED_PATH_PATTERN, IMMUTABLE_CACHE_CONTROL } from "@caelo-cms/shared";
|
|
36
|
+
|
|
37
|
+
export interface CaddyDomainSpec {
|
|
38
|
+
readonly hostname: string;
|
|
39
|
+
readonly kind: "admin" | "public";
|
|
40
|
+
readonly env: "production" | "staging";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CaddyfileSpec {
|
|
44
|
+
readonly ownerEmail: string;
|
|
45
|
+
readonly publicSiteRoot: string; // absolute path on disk
|
|
46
|
+
readonly stagingSiteRoot: string;
|
|
47
|
+
readonly adminPort: number;
|
|
48
|
+
readonly gatewayPort: number;
|
|
49
|
+
readonly domains: ReadonlyArray<CaddyDomainSpec>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function generateCaddyfile(spec: CaddyfileSpec): string {
|
|
53
|
+
const blocks: string[] = [];
|
|
54
|
+
|
|
55
|
+
// Global options.
|
|
56
|
+
blocks.push(`{
|
|
57
|
+
email ${spec.ownerEmail}
|
|
58
|
+
}
|
|
59
|
+
`);
|
|
60
|
+
|
|
61
|
+
for (const d of spec.domains) {
|
|
62
|
+
blocks.push(vhost(d, spec));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Localhost dev fallback when no domains are configured (so a fresh
|
|
66
|
+
// `cms-provision` produces a working Caddyfile even pre-DNS).
|
|
67
|
+
if (spec.domains.length === 0) {
|
|
68
|
+
blocks.push(`# No domains configured yet — cms-provision regenerate-caddy
|
|
69
|
+
# will overwrite this file when you add one at /settings/domains.
|
|
70
|
+
:8081 {
|
|
71
|
+
reverse_proxy localhost:${spec.adminPort}
|
|
72
|
+
}
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return blocks.join("\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function vhost(d: CaddyDomainSpec, spec: CaddyfileSpec): string {
|
|
80
|
+
const noindex = d.env === "staging" ? `\n header X-Robots-Tag "noindex"` : "";
|
|
81
|
+
if (d.kind === "admin") {
|
|
82
|
+
return `${d.hostname} {${noindex}
|
|
83
|
+
reverse_proxy localhost:${spec.adminPort}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
// public — API routes go to the gateway,
|
|
88
|
+
// the rest serves static files. Locale variants serve from a
|
|
89
|
+
const root = d.env === "staging" ? spec.stagingSiteRoot : spec.publicSiteRoot;
|
|
90
|
+
return `${d.hostname} {${noindex}
|
|
91
|
+
root * ${root}
|
|
92
|
+
@content_hashed {
|
|
93
|
+
path_regexp content_hashed ${CONTENT_HASHED_PATH_PATTERN}
|
|
94
|
+
file
|
|
95
|
+
}
|
|
96
|
+
header @content_hashed Cache-Control "${IMMUTABLE_CACHE_CONTROL}"
|
|
97
|
+
@ico path *.ico
|
|
98
|
+
header @ico Content-Type "image/x-icon"
|
|
99
|
+
handle /api/* {
|
|
100
|
+
reverse_proxy localhost:${spec.gatewayPort}
|
|
101
|
+
}
|
|
102
|
+
handle /admin/* {
|
|
103
|
+
reverse_proxy localhost:${spec.adminPort}
|
|
104
|
+
}
|
|
105
|
+
handle {
|
|
106
|
+
file_server {
|
|
107
|
+
try_files {path} {path}/index.html /index.html
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
package/src/cdn-copy.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* P15 — per-provider CDN-copy adapter contract.
|
|
5
|
+
*
|
|
6
|
+
* P7 ships the `media_assets.usage_count` threshold + the `media.set_cdn`
|
|
7
|
+
* op that flips a per-asset boolean. P15 wires the actual edge-pin
|
|
8
|
+
* operation per provider:
|
|
9
|
+
* - AWS → CloudFront cache invalidation + warmer request.
|
|
10
|
+
* - GCP → set Cache-Control metadata on the GCS object so Cloud CDN
|
|
11
|
+
* respects the long TTL.
|
|
12
|
+
* - Azure → Front Door purge + warm endpoint.
|
|
13
|
+
*
|
|
14
|
+
* The redeploy-orchestrator's `cdnCopyTick()` (currently a no-op for
|
|
15
|
+
* cloud installs) drains the per-asset CDN-pin queue using the adapter
|
|
16
|
+
* registered for the active provider.
|
|
17
|
+
*
|
|
18
|
+
* Each provider's stack publishes a `CdnCopyAdapter` instance via its
|
|
19
|
+
* Pulumi outputs (the actual implementation is dynamically imported by
|
|
20
|
+
* the orchestrator at runtime so `@caelo-cms/provisioning` doesn't pull in
|
|
21
|
+
* `@aws-sdk/...`/`@google-cloud/...`/`@azure/...` as static deps). The
|
|
22
|
+
* interface defined here is the only thing both sides agree on.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface CdnCopyAdapter {
|
|
26
|
+
/**
|
|
27
|
+
* Mark the asset at `assetKey` (object key in primary storage) as
|
|
28
|
+
* CDN-cached. Returns the public CDN URL — admin uses this as the
|
|
29
|
+
* canonical URL for the asset thereafter.
|
|
30
|
+
*/
|
|
31
|
+
pin(assetKey: string): Promise<string>;
|
|
32
|
+
/** Reverse — drop from CDN edge cache. */
|
|
33
|
+
unpin(assetKey: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* No-op adapter for self-hosted installs (Caddy serves directly from
|
|
38
|
+
* disk; no separate CDN tier). Returns the input asset key as a relative
|
|
39
|
+
* URL so callers don't have to special-case the no-CDN case.
|
|
40
|
+
*/
|
|
41
|
+
export const selfHostedCdnCopy: CdnCopyAdapter = {
|
|
42
|
+
async pin(assetKey) {
|
|
43
|
+
return `/media/${assetKey}`;
|
|
44
|
+
},
|
|
45
|
+
async unpin() {
|
|
46
|
+
// no-op
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves the right adapter per `process.env.CAELO_PROVIDER`. Lazy
|
|
52
|
+
* dynamic-imports the per-provider implementation so a self-hosted
|
|
53
|
+
* install never loads the cloud SDKs.
|
|
54
|
+
*
|
|
55
|
+
* Returns `selfHostedCdnCopy` ONLY for `provider==='self-hosted'` or an
|
|
56
|
+
* unset env (treated as self-hosted). For `gcp`/`aws`/`azure`, the
|
|
57
|
+
* matching `cdn-copy-<gcs|aws|azure>.ts` file MUST exist in this
|
|
58
|
+
* directory — those land with the per-provider PRs. Per CLAUDE.md §2
|
|
59
|
+
* "no fallbacks pre-1.0" we deliberately do NOT silently degrade to
|
|
60
|
+
* the self-hosted no-op when the file is missing: a missing module is
|
|
61
|
+
* a deploy bug + the orchestrator tick should crash loudly so the
|
|
62
|
+
* operator notices instead of silently dropping CDN-pin requests.
|
|
63
|
+
*/
|
|
64
|
+
export async function loadCdnCopyAdapter(provider?: string): Promise<CdnCopyAdapter> {
|
|
65
|
+
switch (provider) {
|
|
66
|
+
case "gcp":
|
|
67
|
+
// biome-ignore lint/suspicious/noExplicitAny: opt-in dynamic import
|
|
68
|
+
return ((await import("./cdn-copy-gcs.js" as string)) as any).gcsCloudCdnPin;
|
|
69
|
+
case "gcp-firebase":
|
|
70
|
+
// v0.3.0 — Firebase Hosting manages CDN caching natively (each
|
|
71
|
+
// deploy creates an immutable version + the CDN's freshness is
|
|
72
|
+
// dictated by Firebase Hosting headers). There's no per-asset
|
|
73
|
+
// "pin to edge" API; this adapter is effectively a no-op that
|
|
74
|
+
// returns the same shape as self-hosted.
|
|
75
|
+
return selfHostedCdnCopy;
|
|
76
|
+
case "aws":
|
|
77
|
+
// biome-ignore lint/suspicious/noExplicitAny: opt-in dynamic import
|
|
78
|
+
return ((await import("./cdn-copy-aws.js" as string)) as any).s3CloudfrontPrewarm;
|
|
79
|
+
case "azure":
|
|
80
|
+
// biome-ignore lint/suspicious/noExplicitAny: opt-in dynamic import
|
|
81
|
+
return ((await import("./cdn-copy-azure.js" as string)) as any).azureBlobCdnPrefetch;
|
|
82
|
+
case undefined:
|
|
83
|
+
case "":
|
|
84
|
+
case "self-hosted":
|
|
85
|
+
return selfHostedCdnCopy;
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(
|
|
88
|
+
`loadCdnCopyAdapter: unknown CAELO_PROVIDER='${provider}'. Expected one of: self-hosted | gcp | gcp-firebase | aws | azure.`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import { type CertProbe, domainMappingProbe, lbCertProbe, waitForCert } from "./cert-wait.js";
|
|
5
|
+
import type { GcloudResult } from "./gcloud.js";
|
|
6
|
+
|
|
7
|
+
const ok = (stdout = ""): GcloudResult => ({ ok: true, stdout, stderr: "", exitCode: 0 });
|
|
8
|
+
const fail = (stderr: string): GcloudResult => ({ ok: false, stdout: "", stderr, exitCode: 1 });
|
|
9
|
+
|
|
10
|
+
/** A fake clock the injected sleep advances. */
|
|
11
|
+
function clock() {
|
|
12
|
+
let t = 0;
|
|
13
|
+
return {
|
|
14
|
+
now: () => t,
|
|
15
|
+
sleep: async (ms: number) => {
|
|
16
|
+
t += ms;
|
|
17
|
+
},
|
|
18
|
+
elapsed: () => t,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function probes(...seq: CertProbe[]) {
|
|
23
|
+
let i = 0;
|
|
24
|
+
return async () => seq[Math.min(i++, seq.length - 1)] as CertProbe;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("waitForCert", () => {
|
|
28
|
+
it("returns once the probe reports ready, reporting each new detail once", async () => {
|
|
29
|
+
const c = clock();
|
|
30
|
+
const updates: string[] = [];
|
|
31
|
+
const r = await waitForCert({
|
|
32
|
+
probe: probes(
|
|
33
|
+
{ state: "pending", detail: "waiting for DNS" },
|
|
34
|
+
{ state: "pending", detail: "waiting for DNS" },
|
|
35
|
+
{ state: "pending", detail: "issuing" },
|
|
36
|
+
{ state: "ready" },
|
|
37
|
+
),
|
|
38
|
+
timeoutMs: 60_000,
|
|
39
|
+
intervalMs: 10_000,
|
|
40
|
+
onUpdate: (d) => updates.push(d),
|
|
41
|
+
...c,
|
|
42
|
+
});
|
|
43
|
+
expect(r).toEqual({ ok: true });
|
|
44
|
+
expect(updates).toEqual(["waiting for DNS", "issuing"]);
|
|
45
|
+
expect(c.elapsed()).toBe(30_000);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("times out with the last detail instead of waiting forever", async () => {
|
|
49
|
+
const c = clock();
|
|
50
|
+
const r = await waitForCert({
|
|
51
|
+
probe: probes({ state: "pending", detail: "CertificateProvisioned: DNS not set" }),
|
|
52
|
+
timeoutMs: 25_000,
|
|
53
|
+
intervalMs: 10_000,
|
|
54
|
+
...c,
|
|
55
|
+
});
|
|
56
|
+
expect(r).toEqual({
|
|
57
|
+
ok: false,
|
|
58
|
+
reason: "timeout",
|
|
59
|
+
lastDetail: "CertificateProvisioned: DNS not set",
|
|
60
|
+
});
|
|
61
|
+
// Probed at 0, 10s, 20s, 25s (the deadline) — never slept past it.
|
|
62
|
+
expect(c.elapsed()).toBe(25_000);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("stops at once when gcloud fails, since waiting cannot fix that", async () => {
|
|
66
|
+
const c = clock();
|
|
67
|
+
const r = await waitForCert({
|
|
68
|
+
probe: probes({ state: "error", error: "PERMISSION_DENIED" }),
|
|
69
|
+
timeoutMs: 60_000,
|
|
70
|
+
intervalMs: 10_000,
|
|
71
|
+
...c,
|
|
72
|
+
});
|
|
73
|
+
expect(r).toEqual({ ok: false, reason: "error", error: "PERMISSION_DENIED" });
|
|
74
|
+
expect(c.elapsed()).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const target = { hostname: "admin.example.com", region: "europe-west1", projectId: "p" };
|
|
79
|
+
|
|
80
|
+
describe("domainMappingProbe", () => {
|
|
81
|
+
it("is ready when the mapping's Ready condition is True", async () => {
|
|
82
|
+
const calls: string[][] = [];
|
|
83
|
+
const run = async (args: string[]) => {
|
|
84
|
+
calls.push(args);
|
|
85
|
+
return ok(JSON.stringify({ status: { conditions: [{ type: "Ready", status: "True" }] } }));
|
|
86
|
+
};
|
|
87
|
+
expect(await domainMappingProbe(run, target)).toEqual({ state: "ready" });
|
|
88
|
+
expect(calls[0]).toEqual([
|
|
89
|
+
"beta",
|
|
90
|
+
"run",
|
|
91
|
+
"domain-mappings",
|
|
92
|
+
"describe",
|
|
93
|
+
"--domain=admin.example.com",
|
|
94
|
+
"--region=europe-west1",
|
|
95
|
+
"--project=p",
|
|
96
|
+
"--format=json",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("reports the certificate condition's message while pending", async () => {
|
|
101
|
+
const run = async () =>
|
|
102
|
+
ok(
|
|
103
|
+
JSON.stringify({
|
|
104
|
+
status: {
|
|
105
|
+
conditions: [
|
|
106
|
+
{ type: "Ready", status: "Unknown", message: "not ready" },
|
|
107
|
+
{
|
|
108
|
+
type: "CertificateProvisioned",
|
|
109
|
+
status: "Unknown",
|
|
110
|
+
message: "Certificate issuance pending. Add the DNS record.",
|
|
111
|
+
},
|
|
112
|
+
{ type: "DomainRoutable", status: "True" },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
expect(await domainMappingProbe(run, target)).toEqual({
|
|
118
|
+
state: "pending",
|
|
119
|
+
detail: "CertificateProvisioned: Certificate issuance pending. Add the DNS record.",
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("is pending (not ready) before Google reports any condition", async () => {
|
|
124
|
+
const run = async () => ok(JSON.stringify({ status: {} }));
|
|
125
|
+
expect((await domainMappingProbe(run, target)).state).toBe("pending");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("surfaces a gcloud failure as an error", async () => {
|
|
129
|
+
const run = async () => fail("ERROR: (gcloud) You do not currently have an active account");
|
|
130
|
+
const r = await domainMappingProbe(run, target);
|
|
131
|
+
expect(r.state).toBe("error");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("lbCertProbe", () => {
|
|
136
|
+
const list = (certs: unknown[]) => async () => ok(JSON.stringify(certs));
|
|
137
|
+
const lb = { projectId: "p", hostname: "admin.example.com" };
|
|
138
|
+
|
|
139
|
+
it("is ready when the admin's own certificate is ACTIVE although the main domain's is not", async () => {
|
|
140
|
+
const r = await lbCertProbe(
|
|
141
|
+
list([
|
|
142
|
+
{
|
|
143
|
+
name: "combined",
|
|
144
|
+
managed: {
|
|
145
|
+
status: "PROVISIONING",
|
|
146
|
+
domainStatus: { "example.com": "FAILED_NOT_VISIBLE", "admin.example.com": "ACTIVE" },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "admin-only",
|
|
151
|
+
managed: { status: "ACTIVE", domainStatus: { "admin.example.com": "ACTIVE" } },
|
|
152
|
+
},
|
|
153
|
+
]),
|
|
154
|
+
lb,
|
|
155
|
+
);
|
|
156
|
+
expect(r).toEqual({ state: "ready" });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("does not count a domain that is ACTIVE on a certificate that is not served yet", async () => {
|
|
160
|
+
const r = await lbCertProbe(
|
|
161
|
+
list([
|
|
162
|
+
{
|
|
163
|
+
name: "combined",
|
|
164
|
+
managed: {
|
|
165
|
+
status: "PROVISIONING",
|
|
166
|
+
domainStatus: { "example.com": "FAILED_NOT_VISIBLE", "admin.example.com": "ACTIVE" },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
]),
|
|
170
|
+
lb,
|
|
171
|
+
);
|
|
172
|
+
expect(r).toEqual({ state: "pending", detail: "combined: admin.example.com ACTIVE" });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("is ready for installs that only have the combined certificate, once it is ACTIVE", async () => {
|
|
176
|
+
const r = await lbCertProbe(
|
|
177
|
+
list([
|
|
178
|
+
{
|
|
179
|
+
name: "combined",
|
|
180
|
+
managed: {
|
|
181
|
+
status: "ACTIVE",
|
|
182
|
+
domainStatus: { "example.com": "ACTIVE", "admin.example.com": "ACTIVE" },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
]),
|
|
186
|
+
lb,
|
|
187
|
+
);
|
|
188
|
+
expect(r).toEqual({ state: "ready" });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("is pending when no certificate lists the host", async () => {
|
|
192
|
+
expect((await lbCertProbe(list([]), lb)).state).toBe("pending");
|
|
193
|
+
});
|
|
194
|
+
});
|