@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,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Release-contract constants that every signed release manifest carries
|
|
5
|
+
* (epic #696). The infrastructure contract number itself lives in
|
|
6
|
+
* infra-contract.ts (shared with the upgrade split's `needsConverge`);
|
|
7
|
+
* this module says which files can change it and the min-from version. They tell an upgrader, without running any code from the
|
|
8
|
+
* new release, whether the release can be installed as an app-roll or
|
|
9
|
+
* needs the operator's own identity.
|
|
10
|
+
*
|
|
11
|
+
* The admin's upgrader job (Phase 2) only rolls images: it holds no IAM
|
|
12
|
+
* write, no secret create, no resource create. Anything a release needs
|
|
13
|
+
* beyond "new image digests + expand-only migrations" must therefore be
|
|
14
|
+
* declared here, so the upgrader refuses it and sends the Owner to the
|
|
15
|
+
* "Run in Cloud Shell" path instead of half-applying it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Oldest installed version a release can be applied to directly. An
|
|
20
|
+
* install below it has to step through an intermediate release first.
|
|
21
|
+
*
|
|
22
|
+
* Raise it when a release depends on a migration or runtime change that
|
|
23
|
+
* an older install has not been through (the expand/contract rule only
|
|
24
|
+
* guarantees N-1 → N).
|
|
25
|
+
*/
|
|
26
|
+
export const RELEASE_MIN_FROM_VERSION = "0.10.0";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Repo-relative paths (a trailing `/` means "everything below") whose
|
|
30
|
+
* changes can alter the infrastructure contract. Tests and READMEs under
|
|
31
|
+
* them are ignored — see {@link isInfraContractPath}.
|
|
32
|
+
*/
|
|
33
|
+
export const INFRA_CONTRACT_PATHS: readonly string[] = [
|
|
34
|
+
"packages/provisioning/stacks/",
|
|
35
|
+
"packages/provisioning/src/stack-contract.ts",
|
|
36
|
+
"packages/provisioning/src/stack-converge.ts",
|
|
37
|
+
"packages/provisioning/src/runtime-secrets.ts",
|
|
38
|
+
"packages/provisioning/src/operator-access.ts",
|
|
39
|
+
"packages/provisioning/src/gcp-names.ts",
|
|
40
|
+
// Upgrade-time mutators outside stack-converge: the converge half of the
|
|
41
|
+
// upgrade (it decides which ensure* steps run), the MCP IAP service
|
|
42
|
+
// account + bindings, the default firewall policy.
|
|
43
|
+
"packages/provisioning/src/upgrade/converge.ts",
|
|
44
|
+
"packages/provisioning/src/mcp-iap.ts",
|
|
45
|
+
"packages/provisioning/src/default-firewall.ts",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether a changed repo-relative path can alter the infrastructure
|
|
50
|
+
* contract.
|
|
51
|
+
*
|
|
52
|
+
* @param path - a path as `git diff --name-only` prints it.
|
|
53
|
+
*/
|
|
54
|
+
export function isInfraContractPath(path: string): boolean {
|
|
55
|
+
if (/\.test\.ts$/.test(path) || /(^|\/)README\.md$/.test(path)) return false;
|
|
56
|
+
return INFRA_CONTRACT_PATHS.some((p) => (p.endsWith("/") ? path.startsWith(p) : path === p));
|
|
57
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { INFRA_CONTRACT_VERSION } from "./infra-contract.js";
|
|
7
|
+
import { isInfraContractPath } from "./release-contract.js";
|
|
8
|
+
import {
|
|
9
|
+
assessAutoInstall,
|
|
10
|
+
channelForVersion,
|
|
11
|
+
compareImageDigests,
|
|
12
|
+
compareSemver,
|
|
13
|
+
fetchVerifiedReleaseManifest,
|
|
14
|
+
RELEASE_MANIFEST_ARTIFACT_TYPE,
|
|
15
|
+
RELEASE_MANIFEST_MEDIA_TYPE,
|
|
16
|
+
RELEASE_OIDC_ISSUER,
|
|
17
|
+
type ReleaseManifest,
|
|
18
|
+
type ReleaseManifestDeps,
|
|
19
|
+
ReleaseManifestSchema,
|
|
20
|
+
releaseManifestSignerIdentity,
|
|
21
|
+
SIGSTORE_BUNDLE_MEDIA_TYPE,
|
|
22
|
+
} from "./release-manifest.js";
|
|
23
|
+
|
|
24
|
+
const d = (c: string) => `sha256:${c.repeat(64)}`;
|
|
25
|
+
|
|
26
|
+
const MANIFEST: ReleaseManifest = {
|
|
27
|
+
manifestVersion: 1,
|
|
28
|
+
version: "0.11.0",
|
|
29
|
+
channel: "stable",
|
|
30
|
+
digests: { admin: d("a"), gateway: d("b"), ops: d("c") },
|
|
31
|
+
minFromVersion: "0.10.0",
|
|
32
|
+
infraContractVersion: 1,
|
|
33
|
+
migrations: {
|
|
34
|
+
contract: false,
|
|
35
|
+
head: "0262_tool_approval_branch",
|
|
36
|
+
lastContractVersion: "0.10.42",
|
|
37
|
+
},
|
|
38
|
+
security: false,
|
|
39
|
+
advisories: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const sha = (b: Uint8Array) => `sha256:${createHash("sha256").update(b).digest("hex")}`;
|
|
43
|
+
const enc = (s: string) => new TextEncoder().encode(s);
|
|
44
|
+
|
|
45
|
+
interface Fixture {
|
|
46
|
+
manifestBody?: string;
|
|
47
|
+
bundleBody?: string;
|
|
48
|
+
artifactType?: string;
|
|
49
|
+
/** Serve this instead of the real manifest blob (digest stays the original). */
|
|
50
|
+
tamperManifestBlob?: string;
|
|
51
|
+
manifestStatus?: number;
|
|
52
|
+
cosignExit?: number;
|
|
53
|
+
dropBundleLayer?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** An in-memory OCI registry + cosign stub. Records every cosign call. */
|
|
57
|
+
function fixture(f: Fixture = {}) {
|
|
58
|
+
const manifestBytes = enc(f.manifestBody ?? JSON.stringify(MANIFEST));
|
|
59
|
+
const bundleBytes = enc(
|
|
60
|
+
f.bundleBody ?? '{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}',
|
|
61
|
+
);
|
|
62
|
+
const layers = [
|
|
63
|
+
{ mediaType: RELEASE_MANIFEST_MEDIA_TYPE, digest: sha(manifestBytes) },
|
|
64
|
+
...(f.dropBundleLayer
|
|
65
|
+
? []
|
|
66
|
+
: [{ mediaType: SIGSTORE_BUNDLE_MEDIA_TYPE, digest: sha(bundleBytes) }]),
|
|
67
|
+
];
|
|
68
|
+
const blobs = new Map<string, Uint8Array>([
|
|
69
|
+
[sha(manifestBytes), f.tamperManifestBlob ? enc(f.tamperManifestBlob) : manifestBytes],
|
|
70
|
+
[sha(bundleBytes), bundleBytes],
|
|
71
|
+
]);
|
|
72
|
+
const urls: string[] = [];
|
|
73
|
+
const cosignCalls: (readonly string[])[] = [];
|
|
74
|
+
const deps: ReleaseManifestDeps = {
|
|
75
|
+
registry: "registry.test",
|
|
76
|
+
repository: "caelo/release-manifest",
|
|
77
|
+
fetch: async (url) => {
|
|
78
|
+
urls.push(url);
|
|
79
|
+
const m = /\/v2\/caelo\/release-manifest\/(manifests|blobs)\/(.+)$/.exec(url);
|
|
80
|
+
if (!m) return new Response("", { status: 404 });
|
|
81
|
+
if (m[1] === "manifests") {
|
|
82
|
+
if (f.manifestStatus) return new Response("", { status: f.manifestStatus });
|
|
83
|
+
return Response.json({
|
|
84
|
+
schemaVersion: 2,
|
|
85
|
+
mediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
86
|
+
artifactType: f.artifactType ?? RELEASE_MANIFEST_ARTIFACT_TYPE,
|
|
87
|
+
layers,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const blob = blobs.get(m[2] ?? "");
|
|
91
|
+
return blob ? new Response(blob) : new Response("", { status: 404 });
|
|
92
|
+
},
|
|
93
|
+
cosign: async (args) => {
|
|
94
|
+
cosignCalls.push(args);
|
|
95
|
+
return { exitCode: f.cosignExit ?? 0, stderr: f.cosignExit ? "no matching signatures" : "" };
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
return { deps, urls, cosignCalls };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe("fetchVerifiedReleaseManifest", () => {
|
|
102
|
+
it("returns the manifest when signature, schema and version check out", async () => {
|
|
103
|
+
const { deps, urls } = fixture();
|
|
104
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
105
|
+
expect(r).toEqual({ ok: true, manifest: MANIFEST });
|
|
106
|
+
expect(urls[0]).toBe("https://registry.test/v2/caelo/release-manifest/manifests/0.11.0");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("pins cosign to release.yml on the exact tag", async () => {
|
|
110
|
+
const { deps, cosignCalls } = fixture();
|
|
111
|
+
await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
112
|
+
expect(cosignCalls).toHaveLength(1);
|
|
113
|
+
const args = cosignCalls[0] ?? [];
|
|
114
|
+
expect(args[0]).toBe("verify-blob");
|
|
115
|
+
expect(args).toContain("--bundle");
|
|
116
|
+
const id = args[args.indexOf("--certificate-identity") + 1];
|
|
117
|
+
expect(id).toBe(
|
|
118
|
+
"https://github.com/caelo-cms/caelo-cms/.github/workflows/release.yml@refs/tags/v0.11.0",
|
|
119
|
+
);
|
|
120
|
+
expect(args[args.indexOf("--certificate-oidc-issuer") + 1]).toBe(RELEASE_OIDC_ISSUER);
|
|
121
|
+
// An identity regexp would accept other refs; only the exact flag is used.
|
|
122
|
+
expect(args).not.toContain("--certificate-identity-regexp");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("refuses when cosign rejects the signature", async () => {
|
|
126
|
+
const { deps } = fixture({ cosignExit: 1 });
|
|
127
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
128
|
+
expect(r.ok).toBe(false);
|
|
129
|
+
if (!r.ok) {
|
|
130
|
+
expect(r.error.code).toBe("signature");
|
|
131
|
+
expect(r.error.message).toContain(releaseManifestSignerIdentity("0.11.0"));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("refuses a blob whose bytes do not match its digest, before calling cosign", async () => {
|
|
136
|
+
const tampered = JSON.stringify({ ...MANIFEST, security: true, advisories: [] });
|
|
137
|
+
const { deps, cosignCalls } = fixture({ tamperManifestBlob: tampered });
|
|
138
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
139
|
+
expect(r.ok ? null : r.error.code).toBe("blob-digest");
|
|
140
|
+
expect(cosignCalls).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("refuses a signed manifest that describes another version (replay)", async () => {
|
|
144
|
+
const { deps } = fixture({ manifestBody: JSON.stringify({ ...MANIFEST, version: "0.9.0" }) });
|
|
145
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
146
|
+
expect(r.ok ? null : r.error.code).toBe("version-mismatch");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("refuses a manifest that fails the schema", async () => {
|
|
150
|
+
const bad = { ...MANIFEST, digests: { admin: "sha256:nope", gateway: d("b"), ops: d("c") } };
|
|
151
|
+
const { deps } = fixture({ manifestBody: JSON.stringify(bad) });
|
|
152
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", deps);
|
|
153
|
+
expect(r.ok ? null : r.error.code).toBe("schema");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("refuses an artifact of the wrong type or without the bundle layer", async () => {
|
|
157
|
+
const wrongType = await fetchVerifiedReleaseManifest(
|
|
158
|
+
"0.11.0",
|
|
159
|
+
fixture({ artifactType: "application/vnd.oci.image.config.v1+json" }).deps,
|
|
160
|
+
);
|
|
161
|
+
expect(wrongType.ok ? null : wrongType.error.code).toBe("artifact-shape");
|
|
162
|
+
const noBundle = await fetchVerifiedReleaseManifest(
|
|
163
|
+
"0.11.0",
|
|
164
|
+
fixture({ dropBundleLayer: true }).deps,
|
|
165
|
+
);
|
|
166
|
+
expect(noBundle.ok ? null : noBundle.error.code).toBe("artifact-shape");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("reports a missing manifest as a registry error with a next step", async () => {
|
|
170
|
+
const r = await fetchVerifiedReleaseManifest("0.11.0", fixture({ manifestStatus: 404 }).deps);
|
|
171
|
+
expect(r.ok ? null : r.error.code).toBe("registry");
|
|
172
|
+
expect(r.ok ? "" : r.error.message).toContain("HTTP 404");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("rejects a non-SemVer version without touching the network", async () => {
|
|
176
|
+
const { deps, urls } = fixture();
|
|
177
|
+
const r = await fetchVerifiedReleaseManifest("v0.11.0", deps);
|
|
178
|
+
expect(r.ok ? null : r.error.code).toBe("schema");
|
|
179
|
+
expect(urls).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("ReleaseManifestSchema", () => {
|
|
184
|
+
it("accepts the canonical fixture and rejects a non-GHSA advisory", () => {
|
|
185
|
+
expect(ReleaseManifestSchema.safeParse(MANIFEST).success).toBe(true);
|
|
186
|
+
const cve = { ...MANIFEST, security: true, advisories: ["CVE-2026-1234"] };
|
|
187
|
+
expect(ReleaseManifestSchema.safeParse(cve).success).toBe(false);
|
|
188
|
+
const ghsa = { ...MANIFEST, security: true, advisories: ["GHSA-2c2m-4x8q-wx9f"] };
|
|
189
|
+
expect(ReleaseManifestSchema.safeParse(ghsa).success).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("compareImageDigests", () => {
|
|
194
|
+
it("is empty when every resolved digest matches", () => {
|
|
195
|
+
expect(compareImageDigests(MANIFEST, { admin: d("a"), gateway: d("b"), ops: d("c") })).toEqual(
|
|
196
|
+
[],
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("names each mismatching or unresolved image, ignores absent ones", () => {
|
|
201
|
+
expect(compareImageDigests(MANIFEST, { admin: d("f"), gateway: null })).toEqual([
|
|
202
|
+
{ image: "admin", expected: d("a"), actual: d("f") },
|
|
203
|
+
{ image: "gateway", expected: d("b"), actual: null },
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("assessAutoInstall", () => {
|
|
209
|
+
const installed = { version: "0.10.43", infraContractVersion: 1 };
|
|
210
|
+
|
|
211
|
+
it("auto-installs an expand-only release with the same infra contract", () => {
|
|
212
|
+
expect(assessAutoInstall(MANIFEST, installed)).toEqual({
|
|
213
|
+
mode: "auto",
|
|
214
|
+
kind: "normal",
|
|
215
|
+
reasons: [],
|
|
216
|
+
});
|
|
217
|
+
expect(assessAutoInstall({ ...MANIFEST, security: true }, installed).kind).toBe("security");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("downgrades a security release with contract migrations to manual", () => {
|
|
221
|
+
const m = {
|
|
222
|
+
...MANIFEST,
|
|
223
|
+
security: true,
|
|
224
|
+
migrations: { contract: true, head: "x", lastContractVersion: "0.11.0" },
|
|
225
|
+
};
|
|
226
|
+
const a = assessAutoInstall(m, installed);
|
|
227
|
+
expect(a.mode).toBe("manual");
|
|
228
|
+
expect(a.kind).toBe("normal");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("makes skipping a contract release manual, but not upgrading past it from after it", () => {
|
|
232
|
+
const m = {
|
|
233
|
+
...MANIFEST,
|
|
234
|
+
migrations: { ...MANIFEST.migrations, lastContractVersion: "0.10.50" },
|
|
235
|
+
};
|
|
236
|
+
const skipped = assessAutoInstall(m, installed);
|
|
237
|
+
expect(skipped.mode).toBe("manual");
|
|
238
|
+
expect(skipped.reasons[0]).toContain("skips v0.10.50");
|
|
239
|
+
expect(assessAutoInstall(m, { ...installed, version: "0.10.50" }).mode).toBe("auto");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("downgrades an infra contract bump to manual, also when skipped releases carried it", () => {
|
|
243
|
+
const a = assessAutoInstall(
|
|
244
|
+
{ ...MANIFEST, security: true, infraContractVersion: 3 },
|
|
245
|
+
installed,
|
|
246
|
+
);
|
|
247
|
+
expect(a.mode).toBe("manual");
|
|
248
|
+
expect(a.reasons[0]).toContain("1 → 3");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("refuses downgrades, re-installs and versions below minFromVersion", () => {
|
|
252
|
+
expect(assessAutoInstall(MANIFEST, { ...installed, version: "0.11.0" }).mode).toBe("refused");
|
|
253
|
+
expect(assessAutoInstall(MANIFEST, { ...installed, version: "0.12.0" }).mode).toBe("refused");
|
|
254
|
+
expect(assessAutoInstall(MANIFEST, { ...installed, version: "0.9.9" }).mode).toBe("refused");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("compareSemver / channelForVersion", () => {
|
|
259
|
+
it("orders releases and pre-releases per SemVer", () => {
|
|
260
|
+
expect(compareSemver("0.10.43", "0.10.9")).toBeGreaterThan(0);
|
|
261
|
+
expect(compareSemver("0.11.0-rc.1", "0.11.0")).toBeLessThan(0);
|
|
262
|
+
expect(compareSemver("0.11.0-rc.2", "0.11.0-rc.10")).toBeLessThan(0);
|
|
263
|
+
expect(compareSemver("0.11.0-beta.1", "0.11.0-rc.1")).toBeLessThan(0);
|
|
264
|
+
expect(compareSemver("1.0.0", "1.0.0")).toBe(0);
|
|
265
|
+
expect(() => compareSemver("v1.0.0", "1.0.0")).toThrow();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("maps versions to channels", () => {
|
|
269
|
+
expect(channelForVersion("0.11.0")).toBe("stable");
|
|
270
|
+
expect(channelForVersion("0.11.0-rc.1")).toBe("rc");
|
|
271
|
+
expect(channelForVersion("0.11.0-nightly.1")).toBeNull();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("release-contract", () => {
|
|
276
|
+
it("watches the stacks and converge sources, not their tests", () => {
|
|
277
|
+
expect(isInfraContractPath("packages/provisioning/stacks/gcp/index.ts")).toBe(true);
|
|
278
|
+
expect(isInfraContractPath("packages/provisioning/src/stack-converge.ts")).toBe(true);
|
|
279
|
+
expect(isInfraContractPath("packages/provisioning/src/upgrade/converge.ts")).toBe(true);
|
|
280
|
+
expect(isInfraContractPath("packages/provisioning/src/stack-converge.test.ts")).toBe(false);
|
|
281
|
+
expect(isInfraContractPath("packages/provisioning/stacks/gcp/README.md")).toBe(false);
|
|
282
|
+
expect(isInfraContractPath("packages/provisioning/src/cli.ts")).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("keeps INFRA_CONTRACT_VERSION a literal the release scripts can read from a past tag", () => {
|
|
286
|
+
// scripts/release-manifest.ts reads the previous tag's value with a
|
|
287
|
+
// regex over `git show <tag>:<file>`, so it must stay a plain literal.
|
|
288
|
+
const src = readFileSync(new URL("./infra-contract.ts", import.meta.url), "utf8");
|
|
289
|
+
expect(src).toMatch(
|
|
290
|
+
new RegExp(`export const INFRA_CONTRACT_VERSION = ${INFRA_CONTRACT_VERSION};`),
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
});
|