@caelo-cms/provisioning 0.10.43 → 0.10.46
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 +1 -1
- package/dist/adc-impersonation.d.ts +28 -0
- package/dist/adc-impersonation.d.ts.map +1 -0
- package/dist/adc-impersonation.js +126 -0
- package/dist/adc-impersonation.js.map +1 -0
- package/dist/caddy.js +1 -1
- package/dist/checkpoint-secrets.d.ts.map +1 -1
- package/dist/checkpoint-secrets.js +2 -1
- package/dist/checkpoint-secrets.js.map +1 -1
- package/dist/cli.js +208 -43
- package/dist/cli.js.map +1 -1
- package/dist/compose.d.ts +45 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +112 -29
- package/dist/compose.js.map +1 -1
- package/dist/gcloud-retry.d.ts +5 -1
- package/dist/gcloud-retry.d.ts.map +1 -1
- package/dist/gcloud-retry.js.map +1 -1
- package/dist/gcloud.d.ts +2 -1
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +11 -13
- package/dist/gcloud.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +24 -0
- package/dist/infra-contract.d.ts.map +1 -0
- package/dist/infra-contract.js +25 -0
- package/dist/infra-contract.js.map +1 -0
- package/dist/install-state.d.ts +68 -2
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +166 -22
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts +6 -0
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +185 -819
- package/dist/lifecycle.js.map +1 -1
- package/dist/mcp-iap.js +1 -1
- package/dist/migration-runner.d.ts +3 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +7 -5
- package/dist/migration-runner.js.map +1 -1
- package/dist/ops-backend.d.ts +46 -0
- package/dist/ops-backend.d.ts.map +1 -0
- package/dist/ops-backend.js +370 -0
- package/dist/ops-backend.js.map +1 -0
- package/dist/provisioner-identity.d.ts +103 -0
- package/dist/provisioner-identity.d.ts.map +1 -0
- package/dist/provisioner-identity.js +323 -0
- package/dist/provisioner-identity.js.map +1 -0
- package/dist/regions.d.ts.map +1 -1
- package/dist/regions.js +2 -1
- package/dist/regions.js.map +1 -1
- package/dist/release-contract.d.ts +37 -0
- package/dist/release-contract.d.ts.map +1 -0
- package/dist/release-contract.js +55 -0
- package/dist/release-contract.js.map +1 -0
- package/dist/release-manifest.d.ts +140 -0
- package/dist/release-manifest.d.ts.map +1 -0
- package/dist/release-manifest.js +319 -0
- package/dist/release-manifest.js.map +1 -0
- package/dist/self-command.d.ts +16 -0
- package/dist/self-command.d.ts.map +1 -0
- package/dist/self-command.js +20 -0
- package/dist/self-command.js.map +1 -0
- package/dist/self-hosted-release.d.ts +81 -0
- package/dist/self-hosted-release.d.ts.map +1 -0
- package/dist/self-hosted-release.js +148 -0
- package/dist/self-hosted-release.js.map +1 -0
- package/dist/self-hosted-upgrade.d.ts +91 -0
- package/dist/self-hosted-upgrade.d.ts.map +1 -0
- package/dist/self-hosted-upgrade.js +355 -0
- package/dist/self-hosted-upgrade.js.map +1 -0
- package/dist/stack-contract.js +2 -2
- package/dist/stack-converge.d.ts.map +1 -1
- package/dist/stack-converge.js +2 -1
- package/dist/stack-converge.js.map +1 -1
- package/dist/staging-admin.js +3 -3
- package/dist/state-backend.d.ts +103 -0
- package/dist/state-backend.d.ts.map +1 -0
- package/dist/state-backend.js +180 -0
- package/dist/state-backend.js.map +1 -0
- package/dist/state-store.d.ts +55 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +140 -0
- package/dist/state-store.js.map +1 -0
- package/dist/upgrade/app-roll.d.ts +164 -0
- package/dist/upgrade/app-roll.d.ts.map +1 -0
- package/dist/upgrade/app-roll.js +270 -0
- package/dist/upgrade/app-roll.js.map +1 -0
- package/dist/upgrade/backup.d.ts +48 -0
- package/dist/upgrade/backup.d.ts.map +1 -0
- package/dist/upgrade/backup.js +71 -0
- package/dist/upgrade/backup.js.map +1 -0
- package/dist/upgrade/converge.d.ts +45 -0
- package/dist/upgrade/converge.d.ts.map +1 -0
- package/dist/upgrade/converge.js +287 -0
- package/dist/upgrade/converge.js.map +1 -0
- package/dist/upgrade/gcp-lookup.d.ts +23 -0
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -0
- package/dist/upgrade/gcp-lookup.js +95 -0
- package/dist/upgrade/gcp-lookup.js.map +1 -0
- package/dist/upgrade/health.d.ts +99 -0
- package/dist/upgrade/health.d.ts.map +1 -0
- package/dist/upgrade/health.js +214 -0
- package/dist/upgrade/health.js.map +1 -0
- package/dist/upgrade/release-images.d.ts +125 -0
- package/dist/upgrade/release-images.d.ts.map +1 -0
- package/dist/upgrade/release-images.js +315 -0
- package/dist/upgrade/release-images.js.map +1 -0
- package/dist/upgrade/reporter.d.ts +17 -0
- package/dist/upgrade/reporter.d.ts.map +1 -0
- package/dist/upgrade/reporter.js +28 -0
- package/dist/upgrade/reporter.js.map +1 -0
- package/dist/upgrade/upgrade.d.ts +79 -0
- package/dist/upgrade/upgrade.d.ts.map +1 -0
- package/dist/upgrade/upgrade.js +105 -0
- package/dist/upgrade/upgrade.js.map +1 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +7 -3
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-cost.d.ts.map +1 -1
- package/dist/wizards/gcp-cost.js +7 -1
- package/dist/wizards/gcp-cost.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +99 -8
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +157 -50
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +185 -61
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +4 -3
- package/stacks/aws/README.md +1 -1
- package/stacks/gcp/Pulumi.yaml +1 -1
- package/stacks/gcp/README.md +1 -1
- package/stacks/gcp/index.ts +1 -1
- package/stacks/gcp-firebase/Pulumi.yaml +1 -1
- package/stacks/gcp-firebase/index.ts +2 -2
- package/stacks/self-hosted/README.md +4 -0
- package/stacks/self-hosted/index.ts +10 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep health check after an app-roll (epic #696).
|
|
3
|
+
*
|
|
4
|
+
* Cloud Run's readiness only proves the new container listens on its port. A
|
|
5
|
+
* revision can do that while it cannot reach the database, runs against a
|
|
6
|
+
* schema its migrations never reached, or is not the release the roll asked
|
|
7
|
+
* for. After both services moved, the app-roll asks them:
|
|
8
|
+
*
|
|
9
|
+
* - admin `GET /_caelo/health?deep=1` → `{ ok, version, migrations }`:
|
|
10
|
+
* both databases reachable as the expected roles, and every
|
|
11
|
+
* migration the image ships recorded as applied.
|
|
12
|
+
* - gateway `GET /api/_caelo/health?deep=1` → `{ ok, version }`: its
|
|
13
|
+
* database logins work. Under `/api/` because that is the only
|
|
14
|
+
* path the gcp load balancer routes to the gateway.
|
|
15
|
+
*
|
|
16
|
+
* Both answer with no secrets and no raw database errors. The admin sits
|
|
17
|
+
* behind IAP, so the probe signs an IAP credential as the install's MCP
|
|
18
|
+
* service account (`caelo-mcp@`, mcp-iap.ts) exactly like the MCP shim does
|
|
19
|
+
* (packages/mcp-server/src/ingress-auth.ts): a JWT with `aud` = the admin
|
|
20
|
+
* origin + `/*`, signed through the IAM Credentials API by whoever runs the
|
|
21
|
+
* upgrade (the converge step lets every IAP-allowlisted operator sign as
|
|
22
|
+
* that account). The gateway is public by design and gets no credential.
|
|
23
|
+
*
|
|
24
|
+
* Before anything changes, {@link HealthProbe.baseline} proves the probe can
|
|
25
|
+
* reach both services at all. Otherwise a DNS, certificate or IAP problem
|
|
26
|
+
* would only show after migrations, as a "failed" health check that rolls a
|
|
27
|
+
* healthy release back.
|
|
28
|
+
*/
|
|
29
|
+
import type { GcloudRunner, Sleep } from "../gcloud-retry.js";
|
|
30
|
+
import type { HttpFetch } from "../runtime-secrets.js";
|
|
31
|
+
export type HealthOutcome = {
|
|
32
|
+
readonly ok: true;
|
|
33
|
+
readonly detail: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly ok: false;
|
|
36
|
+
readonly error: string;
|
|
37
|
+
};
|
|
38
|
+
export interface HealthProbe {
|
|
39
|
+
/** Reach both services as they serve now (before any change). */
|
|
40
|
+
baseline(): Promise<HealthOutcome>;
|
|
41
|
+
/**
|
|
42
|
+
* Deep-check both services after the roll, retrying while the new
|
|
43
|
+
* revisions warm up. `version` is the release the roll targets (null when
|
|
44
|
+
* a channel tag like `latest` was rolled: then both must report the same
|
|
45
|
+
* version).
|
|
46
|
+
*/
|
|
47
|
+
deep(expect: {
|
|
48
|
+
readonly version: string | null;
|
|
49
|
+
}): Promise<HealthOutcome>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Judge the admin's deep health answer.
|
|
53
|
+
*
|
|
54
|
+
* @returns the version the admin runs on success
|
|
55
|
+
*/
|
|
56
|
+
export declare function evaluateAdminDeepHealth(status: number, text: string, expectedVersion: string | null): {
|
|
57
|
+
ok: true;
|
|
58
|
+
version: string;
|
|
59
|
+
detail: string;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
error: string;
|
|
63
|
+
};
|
|
64
|
+
/** Judge the gateway's deep health answer against the version the admin runs. */
|
|
65
|
+
export declare function evaluateGatewayDeepHealth(status: number, text: string, expectedVersion: string): HealthOutcome;
|
|
66
|
+
/** Where the probe reaches the install. */
|
|
67
|
+
export interface HealthTarget {
|
|
68
|
+
readonly provider: "gcp" | "gcp-firebase";
|
|
69
|
+
readonly projectId: string;
|
|
70
|
+
readonly domain: string;
|
|
71
|
+
/** The admin service's `run.app` URL (IAP-gated on gcp-firebase). */
|
|
72
|
+
readonly adminServiceUrl: string;
|
|
73
|
+
/** The gateway service's `run.app` URL (public on gcp-firebase). */
|
|
74
|
+
readonly gatewayServiceUrl: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The origins the probe calls. On gcp the admin's IAP lives on the load
|
|
78
|
+
* balancer (`admin.<domain>`) and the gateway's ingress is load-balancer-only
|
|
79
|
+
* (`<domain>/api/*`); on gcp-firebase both `run.app` URLs are reachable — the
|
|
80
|
+
* admin's through Cloud Run's own IAP, the gateway's publicly.
|
|
81
|
+
*/
|
|
82
|
+
export declare function healthOrigins(target: HealthTarget): {
|
|
83
|
+
admin: string;
|
|
84
|
+
gateway: string;
|
|
85
|
+
};
|
|
86
|
+
export interface HealthProbeDeps {
|
|
87
|
+
readonly run: GcloudRunner;
|
|
88
|
+
readonly http: HttpFetch;
|
|
89
|
+
readonly sleep: Sleep;
|
|
90
|
+
/** Deep-check attempts while the new revisions warm up (default 12). */
|
|
91
|
+
readonly attempts?: number;
|
|
92
|
+
/** Pause between attempts in ms (default 5000). */
|
|
93
|
+
readonly intervalMs?: number;
|
|
94
|
+
/** Seconds since epoch. */
|
|
95
|
+
readonly now?: () => number;
|
|
96
|
+
}
|
|
97
|
+
/** The IAP-backed probe of a gcp / gcp-firebase install. */
|
|
98
|
+
export declare function createIapHealthProbe(target: HealthTarget, deps: HealthProbeDeps): HealthProbe;
|
|
99
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/upgrade/health.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IACnC;;;;;OAKG;IACH,IAAI,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC3E;AAuBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,GAAG,IAAI,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAuC9E;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,GACtB,aAAa,CAgBf;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,KAAK,GAAG,cAAc,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAOtF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,2BAA2B;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAKD,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,GAAG,WAAW,CAqH7F"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
import { mcpIapServiceAccountEmail } from "../gcp-names.js";
|
|
3
|
+
function parseJson(text) {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(text);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Judge the admin's deep health answer.
|
|
13
|
+
*
|
|
14
|
+
* @returns the version the admin runs on success
|
|
15
|
+
*/
|
|
16
|
+
export function evaluateAdminDeepHealth(status, text, expectedVersion) {
|
|
17
|
+
const body = parseJson(text);
|
|
18
|
+
if (!body)
|
|
19
|
+
return { ok: false, error: `admin answered HTTP ${status} without JSON` };
|
|
20
|
+
const m = body.migrations;
|
|
21
|
+
// Pending migrations come with a 503; name them rather than "unhealthy".
|
|
22
|
+
for (const [db, state] of [
|
|
23
|
+
["cms_admin", m?.admin],
|
|
24
|
+
["cms_public", m?.public],
|
|
25
|
+
]) {
|
|
26
|
+
if (state && state.pending !== 0) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: `${db} is missing ${state.pending} migration(s) the new admin image ships (applied head: ${state.head ?? "none"})`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (status !== 200 || body.ok !== true) {
|
|
34
|
+
const why = body.error ? `${body.error.kind ?? "?"}: ${body.error.message ?? ""}` : "no reason";
|
|
35
|
+
return { ok: false, error: `admin reports unhealthy (HTTP ${status}, ${why})` };
|
|
36
|
+
}
|
|
37
|
+
if (!body.version)
|
|
38
|
+
return { ok: false, error: "admin reported no version" };
|
|
39
|
+
if (expectedVersion !== null && body.version !== expectedVersion) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: `admin runs ${body.version}, the roll targeted ${expectedVersion}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!m?.admin || !m.public) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
error: "admin did not report its migration state (its image predates the deep health check; pass --skip-health-check to roll to such a release)",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
version: body.version,
|
|
54
|
+
detail: `admin ${body.version}, migrations at ${m.admin.head ?? "?"} / ${m.public.head ?? "?"}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Judge the gateway's deep health answer against the version the admin runs. */
|
|
58
|
+
export function evaluateGatewayDeepHealth(status, text, expectedVersion) {
|
|
59
|
+
const body = parseJson(text);
|
|
60
|
+
if (!body)
|
|
61
|
+
return { ok: false, error: `gateway answered HTTP ${status} without JSON` };
|
|
62
|
+
if (status !== 200 || body.ok !== true) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `gateway reports unhealthy (HTTP ${status}, ${body.error?.kind ?? "no reason"})`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (body.version !== expectedVersion) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: `gateway runs ${body.version ?? "an unknown version"}, the admin runs ${expectedVersion}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { ok: true, detail: `gateway ${body.version}` };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The origins the probe calls. On gcp the admin's IAP lives on the load
|
|
78
|
+
* balancer (`admin.<domain>`) and the gateway's ingress is load-balancer-only
|
|
79
|
+
* (`<domain>/api/*`); on gcp-firebase both `run.app` URLs are reachable — the
|
|
80
|
+
* admin's through Cloud Run's own IAP, the gateway's publicly.
|
|
81
|
+
*/
|
|
82
|
+
export function healthOrigins(target) {
|
|
83
|
+
return target.provider === "gcp"
|
|
84
|
+
? { admin: `https://admin.${target.domain}`, gateway: `https://${target.domain}` }
|
|
85
|
+
: {
|
|
86
|
+
admin: new URL(target.adminServiceUrl).origin,
|
|
87
|
+
gateway: new URL(target.gatewayServiceUrl).origin,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const JWT_LIFETIME_S = 600;
|
|
91
|
+
const IAM_CREDENTIALS = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts";
|
|
92
|
+
/** The IAP-backed probe of a gcp / gcp-firebase install. */
|
|
93
|
+
export function createIapHealthProbe(target, deps) {
|
|
94
|
+
const origins = healthOrigins(target);
|
|
95
|
+
const signer = mcpIapServiceAccountEmail(target.projectId);
|
|
96
|
+
const attempts = deps.attempts ?? 12;
|
|
97
|
+
const intervalMs = deps.intervalMs ?? 5_000;
|
|
98
|
+
const now = deps.now ?? (() => Math.floor(Date.now() / 1000));
|
|
99
|
+
/**
|
|
100
|
+
* A JWT IAP accepts for the admin origin, signed as the MCP service
|
|
101
|
+
* account. Never throws: a transport failure is a failed outcome, so the
|
|
102
|
+
* retries and the app-roll's rollback still run.
|
|
103
|
+
*/
|
|
104
|
+
async function iapToken() {
|
|
105
|
+
try {
|
|
106
|
+
return await signIapToken();
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: `could not sign an IAP credential as ${signer}: ${e instanceof Error ? e.message : String(e)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function signIapToken() {
|
|
116
|
+
const token = await deps.run(["auth", "print-access-token"]);
|
|
117
|
+
const bearer = token.ok ? token.stdout.trim() : "";
|
|
118
|
+
if (!bearer) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: `gcloud auth print-access-token: ${token.stderr.trim() || "empty"}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const iat = now();
|
|
125
|
+
const res = await deps.http(`${IAM_CREDENTIALS}/${encodeURIComponent(signer)}:signJwt`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { Authorization: `Bearer ${bearer}`, "Content-Type": "application/json" },
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
payload: JSON.stringify({
|
|
130
|
+
iss: signer,
|
|
131
|
+
sub: signer,
|
|
132
|
+
aud: `${origins.admin}/*`,
|
|
133
|
+
iat,
|
|
134
|
+
exp: iat + JWT_LIFETIME_S,
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
const text = await res.text();
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
error: `could not sign an IAP credential as ${signer} (HTTP ${res.status}). Your account needs roles/iam.serviceAccountTokenCreator on it — upgrade's converge step grants it to every IAP-allowlisted user. ${text.slice(0, 300)}`,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const jwt = parseJson(text)?.signedJwt;
|
|
146
|
+
return jwt ? { ok: true, jwt } : { ok: false, error: "signJwt returned no signedJwt" };
|
|
147
|
+
}
|
|
148
|
+
async function get(url, headers) {
|
|
149
|
+
try {
|
|
150
|
+
const res = await deps.http(url, { method: "GET", headers });
|
|
151
|
+
return { reached: true, status: res.status, text: await res.text() };
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
return { reached: false, error: e instanceof Error ? e.message : String(e) };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
async baseline() {
|
|
159
|
+
const token = await iapToken();
|
|
160
|
+
if (!token.ok)
|
|
161
|
+
return token;
|
|
162
|
+
const auth = { Authorization: `Bearer ${token.jwt}` };
|
|
163
|
+
const admin = await get(`${origins.admin}/_caelo/health`, auth);
|
|
164
|
+
if (!admin.reached) {
|
|
165
|
+
return { ok: false, error: `admin unreachable at ${origins.admin}: ${admin.error}` };
|
|
166
|
+
}
|
|
167
|
+
// The serving (old) admin answers its shallow health JSON. Anything
|
|
168
|
+
// else — IAP's login redirect, a 401/403, an HTML error page — means
|
|
169
|
+
// the probe would not see the new revision either.
|
|
170
|
+
const body = parseJson(admin.text);
|
|
171
|
+
if (!body?.version) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
error: `admin at ${origins.admin} did not answer its health check (HTTP ${admin.status}); IAP may not admit ${signer}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const gateway = await get(`${origins.gateway}/api/_caelo/health`, {});
|
|
178
|
+
if (!gateway.reached) {
|
|
179
|
+
return { ok: false, error: `gateway unreachable at ${origins.gateway}: ${gateway.error}` };
|
|
180
|
+
}
|
|
181
|
+
return { ok: true, detail: `admin ${body.version} and gateway reachable` };
|
|
182
|
+
},
|
|
183
|
+
async deep(expect) {
|
|
184
|
+
let last = { ok: false, error: "no attempt made" };
|
|
185
|
+
for (let i = 0; i < attempts; i++) {
|
|
186
|
+
if (i > 0)
|
|
187
|
+
await deps.sleep(intervalMs);
|
|
188
|
+
last = await deepOnce(expect.version);
|
|
189
|
+
if (last.ok)
|
|
190
|
+
return last;
|
|
191
|
+
}
|
|
192
|
+
return last;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
async function deepOnce(expectedVersion) {
|
|
196
|
+
const token = await iapToken();
|
|
197
|
+
if (!token.ok)
|
|
198
|
+
return token;
|
|
199
|
+
const admin = await get(`${origins.admin}/_caelo/health?deep=1`, {
|
|
200
|
+
Authorization: `Bearer ${token.jwt}`,
|
|
201
|
+
});
|
|
202
|
+
if (!admin.reached)
|
|
203
|
+
return { ok: false, error: `admin unreachable: ${admin.error}` };
|
|
204
|
+
const a = evaluateAdminDeepHealth(admin.status, admin.text, expectedVersion);
|
|
205
|
+
if (!a.ok)
|
|
206
|
+
return a;
|
|
207
|
+
const gateway = await get(`${origins.gateway}/api/_caelo/health?deep=1`, {});
|
|
208
|
+
if (!gateway.reached)
|
|
209
|
+
return { ok: false, error: `gateway unreachable: ${gateway.error}` };
|
|
210
|
+
const g = evaluateGatewayDeepHealth(gateway.status, gateway.text, a.version);
|
|
211
|
+
return g.ok ? { ok: true, detail: `${a.detail}; ${g.detail}` } : g;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/upgrade/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/** The two Caelo services shipped as release images. */
|
|
2
|
+
export type ReleaseService = "admin" | "gateway";
|
|
3
|
+
/** The services every install runs, in roll order (admin first, then gateway). */
|
|
4
|
+
export declare const RELEASE_SERVICES: readonly ReleaseService[];
|
|
5
|
+
/** GHCR — the canonical registry (release-images.yml), used by self-hosted Compose. */
|
|
6
|
+
export declare const GHCR_REGISTRY = "ghcr.io";
|
|
7
|
+
export declare const GHCR_NAMESPACE = "caelo-cms";
|
|
8
|
+
/** `ghcr.io/caelo-cms/<service>` — the repository of a service's release image. */
|
|
9
|
+
export declare function ghcrRepository(service: ReleaseService): string;
|
|
10
|
+
/** Where Caelo's release workflows live; every signer identity starts here. */
|
|
11
|
+
export declare const RELEASE_WORKFLOWS_URL = "https://github.com/caelo-cms/caelo-cms/.github/workflows";
|
|
12
|
+
/**
|
|
13
|
+
* The keyless signer cosign must see on every release image: one of the two
|
|
14
|
+
* release workflows of the caelo-cms repo, via GitHub's OIDC issuer. A
|
|
15
|
+
* different identity means a registry compromise, a typosquat, or a fork's
|
|
16
|
+
* image — every caller refuses to run it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const RELEASE_SIGNER_IDENTITY_REGEXP = "https://github.com/caelo-cms/caelo-cms/.github/workflows/(release-images|release).yml@.*";
|
|
19
|
+
/** OIDC issuer of every Caelo release signature — images and release manifest (GitHub Actions). */
|
|
20
|
+
export declare const RELEASE_OIDC_ISSUER = "https://token.actions.githubusercontent.com";
|
|
21
|
+
/**
|
|
22
|
+
* The `cosign verify` argv for one image reference pinned by digest.
|
|
23
|
+
*
|
|
24
|
+
* @example cosignVerifyArgv("ghcr.io/caelo-cms/admin@sha256:…")
|
|
25
|
+
*/
|
|
26
|
+
export declare function cosignVerifyArgv(imageRef: string): string[];
|
|
27
|
+
/** A `sha256:<64 hex>` image / blob digest. */
|
|
28
|
+
export declare const IMAGE_DIGEST: RegExp;
|
|
29
|
+
/** Whether `s` is a well-formed `sha256:<64 hex>` digest. */
|
|
30
|
+
export declare function isImageDigest(s: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* `s` rebuilt character by character from a constant alphabet when it is a
|
|
33
|
+
* well-formed digest, else null. The digest comes from a registry's HTTP
|
|
34
|
+
* header and ends up in files (release.json, the Compose file); rebuilding
|
|
35
|
+
* it — rather than passing the checked string on — guarantees that nothing
|
|
36
|
+
* but `sha256:` and 64 hex digits from our own constants is ever written.
|
|
37
|
+
*/
|
|
38
|
+
export declare function canonicalDigest(s: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* A semver release tag (`v0.10.43`, `0.11.0-rc.1`) rebuilt from a constant
|
|
41
|
+
* alphabet, else null. The tag comes from the GitHub API and is recorded in
|
|
42
|
+
* release.json, so — like {@link canonicalDigest} — only our own characters
|
|
43
|
+
* reach the file.
|
|
44
|
+
*/
|
|
45
|
+
export declare function canonicalReleaseTag(s: string): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* The registry tag for an upgrade request: an explicit version (a leading
|
|
48
|
+
* `v` dropped, as release tags are `vX.Y.Z` but image tags `X.Y.Z`), else
|
|
49
|
+
* the channel's moving tag, else `latest`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function releaseTag(opts: {
|
|
52
|
+
readonly version?: string;
|
|
53
|
+
readonly channel?: "stable" | "rc" | "beta";
|
|
54
|
+
}): string;
|
|
55
|
+
export type DigestResult = {
|
|
56
|
+
ok: true;
|
|
57
|
+
digest: string;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
reason: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Resolve `<registryHost>/<repository>:<tag>` to the digest a `docker pull`
|
|
64
|
+
* of that tag would fetch, through the Docker Registry V2 API anonymously.
|
|
65
|
+
*
|
|
66
|
+
* The images are public, but registries differ in how they say so: Artifact
|
|
67
|
+
* Registry answers the manifest HEAD directly, GHCR answers 401 with a
|
|
68
|
+
* `WWW-Authenticate: Bearer realm=…,service=…,scope=…` challenge whose realm
|
|
69
|
+
* hands out an anonymous pull token. Both paths are handled here, so callers
|
|
70
|
+
* never need registry credentials (end users have none for Caelo's).
|
|
71
|
+
*
|
|
72
|
+
* @param repository - path below the host, e.g. `caelo-cms/admin`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveRegistryDigest(registryHost: string, repository: string, tag: string, fetchImpl?: typeof fetch): Promise<DigestResult>;
|
|
75
|
+
/**
|
|
76
|
+
* The key/value pairs of a `Bearer k="v",k2="v2"` challenge, or null when
|
|
77
|
+
* it is not a Bearer challenge.
|
|
78
|
+
*
|
|
79
|
+
* @example parseBearerChallenge('Bearer realm="https://ghcr.io/token",service="ghcr.io"')
|
|
80
|
+
* // → { realm: "https://ghcr.io/token", service: "ghcr.io" }
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseBearerChallenge(header: string | null): Record<string, string> | null;
|
|
83
|
+
/**
|
|
84
|
+
* The tag of the newest stable Caelo release on GitHub (e.g. `v0.10.43`),
|
|
85
|
+
* or an error reason. Used to turn "upgrade to latest" into an exact
|
|
86
|
+
* version before anything is resolved, so the version an install records is
|
|
87
|
+
* a real release, never the moving `latest` tag.
|
|
88
|
+
*/
|
|
89
|
+
export declare function fetchLatestReleaseTag(fetchImpl?: typeof fetch): Promise<{
|
|
90
|
+
ok: true;
|
|
91
|
+
tag: string;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
reason: string;
|
|
95
|
+
}>;
|
|
96
|
+
/** Where Caelo's release images live (public Artifact Registry repo). */
|
|
97
|
+
export declare const RELEASE_REGISTRY: {
|
|
98
|
+
readonly region: "europe-west1";
|
|
99
|
+
readonly project: "caelo-website";
|
|
100
|
+
readonly repo: "caelo-cms-images";
|
|
101
|
+
};
|
|
102
|
+
/** The digest-pinned reference of release image `slug@digest` in the Artifact Registry mirror. */
|
|
103
|
+
export declare function releaseImageRef(slug: string, digest: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a tag (e.g. `0.2.6` or `latest`) to its digest in the public
|
|
106
|
+
* Artifact Registry mirror, anonymously through the Registry V2 API
|
|
107
|
+
* ({@link resolveRegistryDigest}) — the caller's gcloud session needs no IAM
|
|
108
|
+
* access to the registry's project.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveTagDigest(image: string, tag: string): Promise<DigestResult>;
|
|
111
|
+
/**
|
|
112
|
+
* P21 ship 4 — verify cosign keyless signatures on every planned
|
|
113
|
+
* image digest. Sigstore Fulcio + Rekor; the certificate identity
|
|
114
|
+
* must match the Caelo release-images workflow. A mismatch means
|
|
115
|
+
* either a registry compromise, a typosquat, or the operator pointed
|
|
116
|
+
* at a fork's image. Either way: refuse to roll.
|
|
117
|
+
*
|
|
118
|
+
* Returns true on success (or skips with a clear error and returns
|
|
119
|
+
* false if cosign isn't installed / verification fails).
|
|
120
|
+
*/
|
|
121
|
+
export declare function verifyCosignAll(plans: readonly {
|
|
122
|
+
readonly slug: string;
|
|
123
|
+
readonly digest: string;
|
|
124
|
+
}[]): Promise<boolean>;
|
|
125
|
+
//# sourceMappingURL=release-images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-images.d.ts","sourceRoot":"","sources":["../../src/upgrade/release-images.ts"],"names":[],"mappings":"AAkBA,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,CAAC;AAEjD,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAAyB,CAAC;AAEhF,uFAAuF;AACvF,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,cAAc,cAAc,CAAC;AAE1C,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAE9D;AAED,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,6DAA6D,CAAC;AAEhG;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,6FAA6D,CAAC;AAEzG,mGAAmG;AACnG,eAAO,MAAM,mBAAmB,gDAAgD,CAAC;AAEjF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAU3D;AAED,+CAA+C;AAC/C,eAAO,MAAM,YAAY,QAA0B,CAAC;AAEpD,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEhD;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIxD;AAKD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC;CAC7C,GAAG,MAAM,CAKT;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAUxF;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,YAAY,CAAC,CAsBvB;AAwBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAQzF;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAepE;AAED,yEAAyE;AACzE,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AAEX,kGAAkG;AAClG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAGlF;AA2DD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,GACnE,OAAO,CAAC,OAAO,CAAC,CA+BlB"}
|