@empire-builder-kit/nx 1.0.0-rc.30 → 1.0.0-rc.31
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/executors.json +8 -3
- package/package.json +10 -7
- package/src/aws/child-environment.d.ts +10 -0
- package/src/aws/child-environment.js +77 -0
- package/src/aws/child-environment.js.map +1 -0
- package/src/aws/identity.d.ts +13 -0
- package/src/aws/identity.js +41 -0
- package/src/aws/identity.js.map +1 -0
- package/src/aws-authority/index.d.ts +1 -0
- package/src/aws-authority/index.js +5 -0
- package/src/aws-authority/index.js.map +1 -0
- package/src/aws-authority/stage-authority.d.ts +238 -0
- package/src/aws-authority/stage-authority.js +2836 -0
- package/src/aws-authority/stage-authority.js.map +1 -0
- package/src/developer-agent/generator-planning.js +4 -4
- package/src/developer-agent/generator-planning.js.map +1 -1
- package/src/developer-agent/operation-registry.js +14 -6
- package/src/developer-agent/operation-registry.js.map +1 -1
- package/src/executors/deployment-plan/deployment-plan.js +1 -1
- package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
- package/src/executors/foundation-preflight/foundation-preflight.d.ts +0 -1
- package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
- package/src/executors/foundation-preflight/schema.json +0 -5
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +12 -3
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -32
- package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
- package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +64 -0
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js +221 -0
- package/src/executors/foundation-profile-preflight/production-database-safeguard.js.map +1 -0
- package/src/executors/foundation-profile-preflight/schema.json +1 -6
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +44 -21
- package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -1
- package/src/executors/foundation-remove-preflight/schema.json +6 -1
- package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +25 -369
- package/src/executors/infrastructure-plan/infrastructure-plan.js +222 -5472
- package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
- package/src/executors/infrastructure-plan/schema.json +8 -64
- package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
- package/src/executors/local-deploy/federated-credential-transport.js +252 -0
- package/src/executors/local-deploy/federated-credential-transport.js.map +1 -0
- package/src/executors/local-deploy/local-approval.d.ts +4 -9
- package/src/executors/local-deploy/local-approval.js +3 -7
- package/src/executors/local-deploy/local-approval.js.map +1 -1
- package/src/executors/local-deploy/local-deploy.d.ts +58 -43
- package/src/executors/local-deploy/local-deploy.js +938 -804
- package/src/executors/local-deploy/local-deploy.js.map +1 -1
- package/src/executors/local-deploy/schema.json +3 -20
- package/src/executors/refresh/refresh.js +2 -2
- package/src/executors/refresh/refresh.js.map +1 -1
- package/src/executors/remove/remove.js +2 -2
- package/src/executors/remove/remove.js.map +1 -1
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +25 -1
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +905 -22
- package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
- package/src/executors/router-waf-rehearsal/schema.json +17 -2
- package/src/executors/secret-initialize/schema.json +42 -0
- package/src/executors/secret-initialize/secret-initialize.d.ts +30 -0
- package/src/executors/secret-initialize/secret-initialize.js +229 -0
- package/src/executors/secret-initialize/secret-initialize.js.map +1 -0
- package/src/executors/secret-metadata/secret-metadata.d.ts +2 -1
- package/src/executors/secret-metadata/secret-metadata.js +62 -29
- package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
- package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
- package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
- package/src/executors/sst-lifecycle/generated-secret-retirement.js.map +1 -0
- package/src/executors/sst-lifecycle/governed-native-operation.d.ts +46 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js +153 -0
- package/src/executors/sst-lifecycle/governed-native-operation.js.map +1 -0
- package/src/executors/sst-lifecycle/run-sst-command.d.ts +25 -7
- package/src/executors/sst-lifecycle/run-sst-command.js +459 -89
- package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
- package/src/executors/sst-lifecycle/schema.json +0 -5
- package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
- package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
- package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -1
- package/src/executors/unlock/unlock.js +2 -2
- package/src/executors/unlock/unlock.js.map +1 -1
- package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
- package/src/foundation/router-waf-rate-rehearsal.js +210 -0
- package/src/foundation/router-waf-rate-rehearsal.js.map +1 -0
- package/src/foundation/router-waf.d.ts +59 -9
- package/src/foundation/router-waf.js +427 -32
- package/src/foundation/router-waf.js.map +1 -1
- package/src/foundation/stages.d.ts +1 -0
- package/src/foundation/stages.js +11 -0
- package/src/foundation/stages.js.map +1 -1
- package/src/generators/formatting.d.ts +8 -0
- package/src/generators/formatting.js +118 -0
- package/src/generators/formatting.js.map +1 -0
- package/src/generators/function/function.js +37 -17
- package/src/generators/function/function.js.map +1 -1
- package/src/generators/post-generation-install.js +2 -2
- package/src/generators/post-generation-install.js.map +1 -1
- package/src/generators/preset/generator.js +22 -7
- package/src/generators/preset/generator.js.map +1 -1
- package/src/generators/preset/workspace-files-developer-agent.js +15 -7
- package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
- package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
- package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
- package/src/generators/preset/workspace-files-ecr-inspector.js.map +1 -0
- package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
- package/src/generators/preset/workspace-files-performance-cost.js +8 -8
- package/src/generators/preset/workspace-files-performance-cost.js.map +1 -1
- package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
- package/src/generators/preset/workspace-files-performance-load.js +8 -12
- package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
- package/src/generators/preset/workspace-files-stage-authority.d.ts +4 -0
- package/src/generators/preset/workspace-files-stage-authority.js +275 -0
- package/src/generators/preset/workspace-files-stage-authority.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-deploy.d.ts +35 -0
- package/src/generators/preset/workspace-files-thin-deploy.js +1434 -0
- package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-docs.d.ts +5 -0
- package/src/generators/preset/workspace-files-thin-docs.js +224 -0
- package/src/generators/preset/workspace-files-thin-docs.js.map +1 -0
- package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
- package/src/generators/preset/workspace-files-thin-release.js +26 -0
- package/src/generators/preset/workspace-files-thin-release.js.map +1 -0
- package/src/generators/preset/workspace-files.d.ts +1 -1
- package/src/generators/preset/workspace-files.js +373 -223
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-api-files.js +1 -0
- package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +4 -0
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
- package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-data-files.js +89 -30
- package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-foundation-files.js +3 -5
- package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-frontend-files.js +5 -1
- package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-infrastructure-files.js +199 -188
- package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-metadata-files.js +13 -0
- package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +307 -0
- package/src/generators/slice/built-in-plan-native-runtime-authority-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-operations-files.js +43 -49
- package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-performance-files.js +37 -19
- package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-project-files.js +9 -68
- package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
- package/src/generators/slice/built-in-plan-secret-runtime-files.js +74 -76
- package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +74 -47
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
- package/src/generators/slice/built-in-plan-thin-release-files.js +129 -0
- package/src/generators/slice/built-in-plan-thin-release-files.js.map +1 -0
- package/src/generators/slice/built-in-plan-verification-files.js +6 -2
- package/src/generators/slice/built-in-plan-verification-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-web-files.js +4 -2
- package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-workload-role-files.js +26 -4
- package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
- package/src/generators/slice/built-in-plan.d.ts +1 -0
- package/src/generators/slice/built-in-plan.js +28 -11
- package/src/generators/slice/built-in-plan.js.map +1 -1
- package/src/generators/slice/slice.js +27 -29
- package/src/generators/slice/slice.js.map +1 -1
- package/src/generators/upgrade/upgrade.d.ts +1 -1
- package/src/generators/upgrade/upgrade.js +224 -69
- package/src/generators/upgrade/upgrade.js.map +1 -1
- package/src/generators/workload/performance-registration.d.ts +1 -0
- package/src/generators/workload/performance-registration.js +6 -4
- package/src/generators/workload/performance-registration.js.map +1 -1
- package/src/generators/workload/service-performance-files.d.ts +1 -0
- package/src/generators/workload/service-performance-files.js +27 -7
- package/src/generators/workload/service-performance-files.js.map +1 -1
- package/src/generators/workload/workload.d.ts +1 -1
- package/src/generators/workload/workload.js +55 -28
- package/src/generators/workload/workload.js.map +1 -1
- package/src/git-policy/github-bootstrap.d.ts +19 -6
- package/src/git-policy/github-bootstrap.js +103 -157
- package/src/git-policy/github-bootstrap.js.map +1 -1
- package/src/local-dev/private-state.d.ts +3 -0
- package/src/local-dev/private-state.js +17 -5
- package/src/local-dev/private-state.js.map +1 -1
- package/src/performance/cost-collection.d.ts +1 -1
- package/src/performance/cost-collection.js +4 -2
- package/src/performance/cost-collection.js.map +1 -1
- package/src/performance/cost-controls.d.ts +3 -1
- package/src/performance/cost-controls.js +9 -4
- package/src/performance/cost-controls.js.map +1 -1
- package/src/performance/evidence.d.ts +1 -1
- package/src/performance/evidence.js +5 -3
- package/src/performance/evidence.js.map +1 -1
- package/src/performance/load-safety.js +5 -3
- package/src/performance/load-safety.js.map +1 -1
- package/src/performance/model.d.ts +2 -2
- package/src/performance/model.js +4 -3
- package/src/performance/model.js.map +1 -1
- package/src/release/index.d.ts +0 -2
- package/src/release/index.js +0 -2
- package/src/release/index.js.map +1 -1
- package/src/release/source-authority.d.ts +4 -5
- package/src/release/source-authority.js +13 -123
- package/src/release/source-authority.js.map +1 -1
- package/src/repository-policy/aurora-preflight.d.ts +1 -1
- package/src/repository-policy/aurora-preflight.js +4 -1
- package/src/repository-policy/aurora-preflight.js.map +1 -1
- package/src/repository-policy/policy.d.ts +5 -1
- package/src/repository-policy/policy.js +64 -13
- package/src/repository-policy/policy.js.map +1 -1
- package/src/schemas/performance-policy-v1.json +3 -1
- package/src/secrets/adapters.d.ts +5 -1
- package/src/secrets/adapters.js +22 -1
- package/src/secrets/adapters.js.map +1 -1
- package/src/secrets/aws-control-plane.d.ts +10 -1
- package/src/secrets/aws-control-plane.js +67 -0
- package/src/secrets/aws-control-plane.js.map +1 -1
- package/src/secrets/generated-aws-control-plane.d.ts +23 -0
- package/src/secrets/generated-aws-control-plane.js +259 -0
- package/src/secrets/generated-aws-control-plane.js.map +1 -0
- package/src/secrets/metadata.js +38 -10
- package/src/secrets/metadata.js.map +1 -1
- package/src/secrets/model.d.ts +8 -1
- package/src/secrets/model.js +24 -3
- package/src/secrets/model.js.map +1 -1
- package/src/secrets/operator.d.ts +89 -3
- package/src/secrets/operator.js +422 -9
- package/src/secrets/operator.js.map +1 -1
- package/src/secrets/registry.d.ts +2 -1
- package/src/secrets/registry.js +18 -1
- package/src/secrets/registry.js.map +1 -1
- package/src/secrets/workload-access.js +10 -2
- package/src/secrets/workload-access.js.map +1 -1
- package/src/supply-chain/action-pins.js +61 -7
- package/src/supply-chain/action-pins.js.map +1 -1
- package/src/supply-chain/build-output-bounds.d.ts +2 -0
- package/src/supply-chain/build-output-bounds.js +12 -0
- package/src/supply-chain/build-output-bounds.js.map +1 -0
- package/src/supply-chain/build-output.js +2 -4
- package/src/supply-chain/build-output.js.map +1 -1
- package/src/supply-chain/ecr-scanning.d.ts +5 -3
- package/src/supply-chain/ecr-scanning.js +11 -7
- package/src/supply-chain/ecr-scanning.js.map +1 -1
- package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
- package/src/supply-chain/ecs-deployed-image.js +6 -4
- package/src/supply-chain/ecs-deployed-image.js.map +1 -1
- package/src/supply-chain/generated-container-release-command.js +816 -34
- package/src/supply-chain/generated-container-release-command.js.map +1 -1
- package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
- package/src/supply-chain/generated-ecr-collector-command.js +188 -91
- package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
- package/src/supply-chain/generated-files.js +4 -3
- package/src/supply-chain/generated-files.js.map +1 -1
- package/src/supply-chain/index.d.ts +1 -1
- package/src/supply-chain/index.js +2 -1
- package/src/supply-chain/index.js.map +1 -1
- package/src/workload-roles/index.d.ts +10 -17
- package/src/workload-roles/index.js +5 -18
- package/src/workload-roles/index.js.map +1 -1
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.THIN_DEPLOY_WORKFLOW_PATH = void 0;
|
|
4
|
+
exports.renderThinDeployVerifyCaller = renderThinDeployVerifyCaller;
|
|
5
|
+
exports.stagingPromotionReceiptValidationScript = stagingPromotionReceiptValidationScript;
|
|
6
|
+
exports.restoredCandidateValidationScript = restoredCandidateValidationScript;
|
|
7
|
+
exports.stagingPromotionSealScript = stagingPromotionSealScript;
|
|
8
|
+
exports.postdeploySupplyChainBindingScript = postdeploySupplyChainBindingScript;
|
|
9
|
+
exports.renderThinDeployWorkflowFilePlan = renderThinDeployWorkflowFilePlan;
|
|
10
|
+
exports.THIN_DEPLOY_WORKFLOW_PATH = '.github/workflows/deploy.yml';
|
|
11
|
+
const EXACT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
12
|
+
const WORKSPACE_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
13
|
+
const ACTION_PIN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@[a-f0-9]{40}(?: # [^\r\n]+)?$/;
|
|
14
|
+
function assertOptions(options) {
|
|
15
|
+
if (!WORKSPACE_NAME.test(options.name)) {
|
|
16
|
+
throw new Error('Workspace name must be a bounded lowercase slug.');
|
|
17
|
+
}
|
|
18
|
+
for (const [name, value] of Object.entries(options.actionPins)) {
|
|
19
|
+
if (!ACTION_PIN.test(value)) {
|
|
20
|
+
throw new Error(`${name} must be an exact 40-character action pin.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const [name, value] of Object.entries(options.toolVersions)) {
|
|
24
|
+
if (!EXACT_SEMVER.test(value)) {
|
|
25
|
+
throw new Error(`${name} must be an exact semantic version.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function workflowCallTrigger(profile) {
|
|
30
|
+
return profile === 'starter'
|
|
31
|
+
? ''
|
|
32
|
+
: ` workflow_call:
|
|
33
|
+
inputs:
|
|
34
|
+
source_branch:
|
|
35
|
+
required: true
|
|
36
|
+
type: string
|
|
37
|
+
source_commit:
|
|
38
|
+
required: true
|
|
39
|
+
type: string
|
|
40
|
+
source_ref:
|
|
41
|
+
required: true
|
|
42
|
+
type: string
|
|
43
|
+
verify_run_attempt:
|
|
44
|
+
required: true
|
|
45
|
+
type: string
|
|
46
|
+
verify_run_id:
|
|
47
|
+
required: true
|
|
48
|
+
type: string
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function renderThinDeployVerifyCaller(profile) {
|
|
52
|
+
if (profile === 'starter')
|
|
53
|
+
return '';
|
|
54
|
+
const protectedBranchCondition = profile === 'production-isolated'
|
|
55
|
+
? "github.ref == 'refs/heads/main'"
|
|
56
|
+
: "(github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main')";
|
|
57
|
+
return `
|
|
58
|
+
|
|
59
|
+
deploy:
|
|
60
|
+
name: deploy exact verified source
|
|
61
|
+
needs: [acceptance, release-candidate]
|
|
62
|
+
if: >-
|
|
63
|
+
always() &&
|
|
64
|
+
github.event_name == 'push' &&
|
|
65
|
+
${protectedBranchCondition} &&
|
|
66
|
+
needs.acceptance.result == 'success' &&
|
|
67
|
+
needs.acceptance.outputs.history_only != 'true' &&
|
|
68
|
+
needs.release-candidate.result == 'success'
|
|
69
|
+
permissions:
|
|
70
|
+
actions: read
|
|
71
|
+
contents: read
|
|
72
|
+
id-token: write
|
|
73
|
+
uses: ./.github/workflows/deploy.yml
|
|
74
|
+
with:
|
|
75
|
+
source_branch: \${{ github.ref_name }}
|
|
76
|
+
source_commit: \${{ github.sha }}
|
|
77
|
+
source_ref: \${{ github.ref }}
|
|
78
|
+
verify_run_attempt: \${{ github.run_attempt }}
|
|
79
|
+
verify_run_id: \${{ github.run_id }}`;
|
|
80
|
+
}
|
|
81
|
+
function indentedScript(source, spaces) {
|
|
82
|
+
const prefix = ' '.repeat(spaces);
|
|
83
|
+
return source
|
|
84
|
+
.trim()
|
|
85
|
+
.split('\n')
|
|
86
|
+
.map((line) => `${prefix}${line}`)
|
|
87
|
+
.join('\n');
|
|
88
|
+
}
|
|
89
|
+
/** @internal Shared verbatim with the generated workflow and executable tests. */
|
|
90
|
+
function stagingPromotionReceiptValidationScript() {
|
|
91
|
+
return `import { createHash } from 'node:crypto';
|
|
92
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
93
|
+
import { join } from 'node:path';
|
|
94
|
+
|
|
95
|
+
const commit = /^[a-f0-9]{40,64}$/;
|
|
96
|
+
const integer = /^[1-9][0-9]*$/;
|
|
97
|
+
const digest = /^sha256:[a-f0-9]{64}$/;
|
|
98
|
+
const required = (name, pattern) => {
|
|
99
|
+
const value = process.env[name]?.trim() ?? '';
|
|
100
|
+
if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
|
|
101
|
+
return value;
|
|
102
|
+
};
|
|
103
|
+
const root = process.env.EBK_STAGING_RECEIPT_ROOT?.trim() ?? '';
|
|
104
|
+
if (!root) throw new Error('Staging deployment receipt root is absent.');
|
|
105
|
+
const expectedHasSlices = required('EBK_EXPECTED_HAS_SLICES', /^(?:true|false)$/) === 'true';
|
|
106
|
+
const sourceCommit = required('EBK_STAGING_SOURCE_COMMIT', commit);
|
|
107
|
+
const expectedAccount = required('EBK_EXPECTED_STAGING_ACCOUNT_ID', /^[0-9]{12}$/);
|
|
108
|
+
const expectedProfile = required('EBK_EXPECTED_DEPLOYMENT_PROFILE', /^(?:production-isolated|fully-isolated)$/);
|
|
109
|
+
const expectedRegion = required('EBK_EXPECTED_DEPLOYMENT_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
|
|
110
|
+
const deploymentRunId = required('EBK_STAGING_DEPLOYMENT_RUN_ID', integer);
|
|
111
|
+
const deploymentRunAttempt = required('EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT', integer);
|
|
112
|
+
const receiptArtifactId = required('EBK_STAGING_RECEIPT_ARTIFACT_ID', integer);
|
|
113
|
+
const receiptArtifactDigest = required('EBK_STAGING_RECEIPT_ARTIFACT_DIGEST', digest);
|
|
114
|
+
const receiptBytes = readFileSync(join(root, 'latest.json'));
|
|
115
|
+
const promotionBytes = readFileSync(join(root, 'promotion.json'));
|
|
116
|
+
const receipt = JSON.parse(receiptBytes.toString('utf8'));
|
|
117
|
+
const promotion = JSON.parse(promotionBytes.toString('utf8'));
|
|
118
|
+
const receiptDigest = createHash('sha256').update(receiptBytes).digest('hex');
|
|
119
|
+
if (
|
|
120
|
+
receipt?.kind !== 'ebk-deployment-receipt' ||
|
|
121
|
+
receipt.schemaVersion !== 1 ||
|
|
122
|
+
receipt.outcome !== 'succeeded' ||
|
|
123
|
+
receipt.authority !== 'github' ||
|
|
124
|
+
receipt.stage !== 'staging' ||
|
|
125
|
+
receipt.account !== expectedAccount ||
|
|
126
|
+
receipt.profile !== expectedProfile ||
|
|
127
|
+
receipt.region !== expectedRegion ||
|
|
128
|
+
receipt.source?.clean !== true ||
|
|
129
|
+
receipt.source?.commit !== sourceCommit ||
|
|
130
|
+
promotion?.kind !== 'ebk-staging-deployment-promotion' ||
|
|
131
|
+
promotion.schemaVersion !== 1 ||
|
|
132
|
+
promotion.hasSlices !== expectedHasSlices ||
|
|
133
|
+
promotion.stage !== 'staging' ||
|
|
134
|
+
promotion.account !== expectedAccount ||
|
|
135
|
+
promotion.deploymentProfile !== expectedProfile ||
|
|
136
|
+
promotion.region !== expectedRegion ||
|
|
137
|
+
promotion.sourceCommit !== sourceCommit ||
|
|
138
|
+
promotion.deployment?.runId !== deploymentRunId ||
|
|
139
|
+
promotion.deployment?.runAttempt !== deploymentRunAttempt ||
|
|
140
|
+
promotion.deployment?.receiptDigest !== receiptDigest ||
|
|
141
|
+
(expectedHasSlices
|
|
142
|
+
? !integer.test(promotion.candidate?.artifactId ?? '') ||
|
|
143
|
+
!digest.test(promotion.candidate?.artifactDigest ?? '') ||
|
|
144
|
+
!integer.test(promotion.candidate?.producerRunId ?? '') ||
|
|
145
|
+
!integer.test(promotion.candidate?.producerRunAttempt ?? '') ||
|
|
146
|
+
promotion.candidate?.sourceCommit !== sourceCommit ||
|
|
147
|
+
!/^[a-f0-9]{64}$/.test(promotion.candidate?.materialDigest ?? '') ||
|
|
148
|
+
!integer.test(promotion.container?.artifactId ?? '') ||
|
|
149
|
+
!digest.test(promotion.container?.artifactDigest ?? '')
|
|
150
|
+
: promotion.candidate !== null || promotion.container !== null)
|
|
151
|
+
) {
|
|
152
|
+
throw new Error('Staging deployment receipt and candidate promotion binding disagree.');
|
|
153
|
+
}
|
|
154
|
+
const outputs = {
|
|
155
|
+
deployment_run_attempt: deploymentRunAttempt,
|
|
156
|
+
deployment_run_id: deploymentRunId,
|
|
157
|
+
receipt_artifact_digest: receiptArtifactDigest,
|
|
158
|
+
receipt_artifact_id: receiptArtifactId,
|
|
159
|
+
source_commit: sourceCommit,
|
|
160
|
+
};
|
|
161
|
+
if (expectedHasSlices) Object.assign(outputs, {
|
|
162
|
+
artifact_digest: promotion.candidate.artifactDigest,
|
|
163
|
+
artifact_id: promotion.candidate.artifactId,
|
|
164
|
+
candidate_material_digest: promotion.candidate.materialDigest,
|
|
165
|
+
container_artifact_digest: promotion.container.artifactDigest,
|
|
166
|
+
container_artifact_id: promotion.container.artifactId,
|
|
167
|
+
producer_run_attempt: promotion.candidate.producerRunAttempt,
|
|
168
|
+
producer_run_id: promotion.candidate.producerRunId,
|
|
169
|
+
});
|
|
170
|
+
for (const [name, value] of Object.entries(outputs)) appendFileSync(process.env.GITHUB_OUTPUT, name + '=' + value + '\\n');`;
|
|
171
|
+
}
|
|
172
|
+
/** @internal Shared verbatim with the generated workflow and executable tests. */
|
|
173
|
+
function restoredCandidateValidationScript() {
|
|
174
|
+
return `import { createHash } from 'node:crypto';
|
|
175
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
176
|
+
import { join } from 'node:path';
|
|
177
|
+
|
|
178
|
+
const root = '.ebk/evidence/release-candidate/packages';
|
|
179
|
+
const sourceCommit = process.env.EBK_RELEASE_SOURCE_COMMIT?.trim() ?? '';
|
|
180
|
+
const producerRunId = process.env.EBK_CANDIDATE_PRODUCER_RUN_ID?.trim() ?? '';
|
|
181
|
+
const producerRunAttempt = process.env.EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT?.trim() ?? '';
|
|
182
|
+
const expected = process.env.EBK_CANDIDATE_MATERIAL_DIGEST?.trim() ?? '';
|
|
183
|
+
if (!/^[a-f0-9]{40,64}$/.test(sourceCommit) || !/^[1-9][0-9]*$/.test(producerRunId) || !/^[1-9][0-9]*$/.test(producerRunAttempt) || !/^[a-f0-9]{64}$/.test(expected)) {
|
|
184
|
+
throw new Error('Restored candidate authority is absent or malformed.');
|
|
185
|
+
}
|
|
186
|
+
const projects = readdirSync(root, { withFileTypes: true })
|
|
187
|
+
.filter((entry) => entry.isDirectory())
|
|
188
|
+
.map((entry) => entry.name)
|
|
189
|
+
.sort();
|
|
190
|
+
if (projects.length === 0 || projects.some((project) => !/^[a-z0-9][a-z0-9-]{0,62}$/.test(project))) {
|
|
191
|
+
throw new Error('Restored candidate project inventory is absent or malformed.');
|
|
192
|
+
}
|
|
193
|
+
const entries = projects.map((project) => {
|
|
194
|
+
const bytes = readFileSync(join(root, project, 'candidate.json'));
|
|
195
|
+
const value = JSON.parse(bytes.toString('utf8'));
|
|
196
|
+
if (
|
|
197
|
+
value?.kind !== 'ebk-release-candidate' ||
|
|
198
|
+
value.schemaVersion !== 1 ||
|
|
199
|
+
value.slice !== project ||
|
|
200
|
+
value.sourceCommit !== sourceCommit ||
|
|
201
|
+
value.provenance?.runId !== producerRunId ||
|
|
202
|
+
value.provenance?.runAttempt !== producerRunAttempt
|
|
203
|
+
) throw new Error('Restored candidate provenance is stale or cross-paired.');
|
|
204
|
+
return { manifestDigest: createHash('sha256').update(bytes).digest('hex'), project };
|
|
205
|
+
});
|
|
206
|
+
const observed = createHash('sha256').update(JSON.stringify(entries)).digest('hex');
|
|
207
|
+
if (observed !== expected) throw new Error('Restored candidate material digest differs from staging.');`;
|
|
208
|
+
}
|
|
209
|
+
/** @internal Shared verbatim with the generated workflow and executable tests. */
|
|
210
|
+
function stagingPromotionSealScript() {
|
|
211
|
+
return `import { createHash } from 'node:crypto';
|
|
212
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
213
|
+
import { join } from 'node:path';
|
|
214
|
+
|
|
215
|
+
const required = (name, pattern) => {
|
|
216
|
+
const value = process.env[name]?.trim() ?? '';
|
|
217
|
+
if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
|
|
218
|
+
return value;
|
|
219
|
+
};
|
|
220
|
+
const sourceCommit = required('EBK_RELEASE_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
|
|
221
|
+
const hasSlices = required('EBK_HAS_SLICES', /^(?:true|false)$/) === 'true';
|
|
222
|
+
const account = required('EBK_POLICY_ACCOUNT_ID', /^[0-9]{12}$/);
|
|
223
|
+
const deploymentProfile = required('EBK_DEPLOYMENT_PROFILE', /^(?:production-isolated|fully-isolated)$/);
|
|
224
|
+
const region = required('EBK_POLICY_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
|
|
225
|
+
const deploymentRunId = required('GITHUB_RUN_ID', /^[1-9][0-9]*$/);
|
|
226
|
+
const deploymentRunAttempt = required('GITHUB_RUN_ATTEMPT', /^[1-9][0-9]*$/);
|
|
227
|
+
const receiptPath = '.ebk/evidence/deployment/latest.json';
|
|
228
|
+
const receiptBytes = readFileSync(receiptPath);
|
|
229
|
+
const receipt = JSON.parse(receiptBytes.toString('utf8'));
|
|
230
|
+
if (receipt?.kind !== 'ebk-deployment-receipt' || receipt.schemaVersion !== 1 || receipt.outcome !== 'succeeded' || receipt.authority !== 'github' || receipt.stage !== 'staging' || receipt.account !== account || receipt.profile !== deploymentProfile || receipt.region !== region || receipt.source?.clean !== true || receipt.source?.commit !== sourceCommit) {
|
|
231
|
+
throw new Error('Successful staging deployment receipt is absent or stale.');
|
|
232
|
+
}
|
|
233
|
+
const candidateEnvironment = {
|
|
234
|
+
artifactDigest: process.env.EBK_CANDIDATE_ARTIFACT_DIGEST?.trim() ?? '',
|
|
235
|
+
artifactId: process.env.EBK_CANDIDATE_ARTIFACT_ID?.trim() ?? '',
|
|
236
|
+
producerRunAttempt: process.env.EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT?.trim() ?? '',
|
|
237
|
+
producerRunId: process.env.EBK_CANDIDATE_PRODUCER_RUN_ID?.trim() ?? '',
|
|
238
|
+
};
|
|
239
|
+
const containerEnvironment = {
|
|
240
|
+
artifactDigest: process.env.EBK_CONTAINER_ARTIFACT_DIGEST?.trim() ?? '',
|
|
241
|
+
artifactId: process.env.EBK_CONTAINER_ARTIFACT_ID?.trim() ?? '',
|
|
242
|
+
};
|
|
243
|
+
let candidate = null;
|
|
244
|
+
let container = null;
|
|
245
|
+
if (hasSlices) {
|
|
246
|
+
const producerRunId = required('EBK_CANDIDATE_PRODUCER_RUN_ID', /^[1-9][0-9]*$/);
|
|
247
|
+
const producerRunAttempt = required('EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT', /^[1-9][0-9]*$/);
|
|
248
|
+
const artifactId = required('EBK_CANDIDATE_ARTIFACT_ID', /^[1-9][0-9]*$/);
|
|
249
|
+
const artifactDigest = required('EBK_CANDIDATE_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
|
|
250
|
+
const containerArtifactId = required('EBK_CONTAINER_ARTIFACT_ID', /^[1-9][0-9]*$/);
|
|
251
|
+
const containerArtifactDigest = required('EBK_CONTAINER_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
|
|
252
|
+
const root = '.ebk/evidence/release-candidate/packages';
|
|
253
|
+
const projects = readdirSync(root, { withFileTypes: true })
|
|
254
|
+
.filter((entry) => entry.isDirectory())
|
|
255
|
+
.map((entry) => entry.name)
|
|
256
|
+
.sort();
|
|
257
|
+
if (projects.length === 0 || projects.some((project) => !/^[a-z0-9][a-z0-9-]{0,62}$/.test(project))) throw new Error('Staging candidate inventory is absent or malformed.');
|
|
258
|
+
const entries = projects.map((project) => {
|
|
259
|
+
const bytes = readFileSync(join(root, project, 'candidate.json'));
|
|
260
|
+
const value = JSON.parse(bytes.toString('utf8'));
|
|
261
|
+
if (value?.kind !== 'ebk-release-candidate' || value.schemaVersion !== 1 || value.slice !== project || value.sourceCommit !== sourceCommit || value.provenance?.runId !== producerRunId || value.provenance?.runAttempt !== producerRunAttempt) {
|
|
262
|
+
throw new Error('Staging candidate provenance is stale or cross-paired.');
|
|
263
|
+
}
|
|
264
|
+
return { manifestDigest: createHash('sha256').update(bytes).digest('hex'), project };
|
|
265
|
+
});
|
|
266
|
+
candidate = { artifactDigest, artifactId, materialDigest: createHash('sha256').update(JSON.stringify(entries)).digest('hex'), producerRunAttempt, producerRunId, sourceCommit };
|
|
267
|
+
container = { artifactDigest: containerArtifactDigest, artifactId: containerArtifactId };
|
|
268
|
+
} else if (Object.values(candidateEnvironment).some(Boolean) || Object.values(containerEnvironment).some(Boolean)) {
|
|
269
|
+
throw new Error('A staging promotion without Slices must not carry Slice release material.');
|
|
270
|
+
}
|
|
271
|
+
const promotion = {
|
|
272
|
+
account,
|
|
273
|
+
candidate,
|
|
274
|
+
container,
|
|
275
|
+
deploymentProfile,
|
|
276
|
+
deployment: { receiptDigest: createHash('sha256').update(receiptBytes).digest('hex'), runAttempt: deploymentRunAttempt, runId: deploymentRunId },
|
|
277
|
+
hasSlices,
|
|
278
|
+
kind: 'ebk-staging-deployment-promotion',
|
|
279
|
+
region,
|
|
280
|
+
schemaVersion: 1,
|
|
281
|
+
sourceCommit,
|
|
282
|
+
stage: 'staging',
|
|
283
|
+
};
|
|
284
|
+
mkdirSync('.ebk/evidence/deployment', { recursive: true });
|
|
285
|
+
writeFileSync('.ebk/evidence/deployment/promotion.json', JSON.stringify(promotion, null, 2) + '\\n', { mode: 0o600 });`;
|
|
286
|
+
}
|
|
287
|
+
/** @internal Shared verbatim with the generated workflow and executable tests. */
|
|
288
|
+
function postdeploySupplyChainBindingScript() {
|
|
289
|
+
return `import { createHash } from 'node:crypto';
|
|
290
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
291
|
+
|
|
292
|
+
const required = (name, pattern) => {
|
|
293
|
+
const value = process.env[name]?.trim() ?? '';
|
|
294
|
+
if (!pattern.test(value)) throw new Error(name + ' is absent or malformed.');
|
|
295
|
+
return value;
|
|
296
|
+
};
|
|
297
|
+
const canonical = (value) => {
|
|
298
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
299
|
+
if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']';
|
|
300
|
+
return '{' + Object.entries(value)
|
|
301
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
302
|
+
.map(([key, child]) => JSON.stringify(key) + ':' + canonical(child))
|
|
303
|
+
.join(',') + '}';
|
|
304
|
+
};
|
|
305
|
+
const accountId = required('EBK_POLICY_ACCOUNT_ID', /^[0-9]{12}$/);
|
|
306
|
+
const deploymentCommit = required('EBK_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
|
|
307
|
+
const profile = required('EBK_DEPLOYMENT_PROFILE', /^(?:starter|production-isolated|fully-isolated)$/);
|
|
308
|
+
const region = required('EBK_POLICY_REGION', /^[a-z]{2}(?:-gov)?-[a-z]+(?:-[a-z]+)*-[0-9]$/);
|
|
309
|
+
const releaseCandidateArtifactDigest = required('EBK_CANDIDATE_ARTIFACT_DIGEST', /^sha256:[a-f0-9]{64}$/);
|
|
310
|
+
const releaseCandidateArtifactId = required('EBK_CANDIDATE_ARTIFACT_ID', /^[1-9][0-9]*$/);
|
|
311
|
+
const releaseCandidateProducerRunAttempt = required('EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT', /^[1-9][0-9]*$/);
|
|
312
|
+
const releaseCandidateProducerRunId = required('EBK_CANDIDATE_PRODUCER_RUN_ID', /^[1-9][0-9]*$/);
|
|
313
|
+
const releaseSourceCommit = required('EBK_RELEASE_SOURCE_COMMIT', /^[a-f0-9]{40,64}$/);
|
|
314
|
+
const runAttempt = required('GITHUB_RUN_ATTEMPT', /^[1-9][0-9]*$/);
|
|
315
|
+
const runId = required('GITHUB_RUN_ID', /^[1-9][0-9]*$/);
|
|
316
|
+
const stage = required('EBK_STAGE', /^(?:development|staging|production)$/);
|
|
317
|
+
const receipt = JSON.parse(readFileSync('.ebk/evidence/deployment/latest.json', 'utf8'));
|
|
318
|
+
if (
|
|
319
|
+
receipt?.kind !== 'ebk-deployment-receipt' ||
|
|
320
|
+
receipt.schemaVersion !== 1 ||
|
|
321
|
+
receipt.outcome !== 'succeeded' ||
|
|
322
|
+
receipt.authority !== 'github' ||
|
|
323
|
+
receipt.account !== accountId ||
|
|
324
|
+
receipt.profile !== profile ||
|
|
325
|
+
receipt.region !== region ||
|
|
326
|
+
receipt.stage !== stage ||
|
|
327
|
+
receipt.source?.clean !== true ||
|
|
328
|
+
receipt.source?.commit !== deploymentCommit
|
|
329
|
+
) throw new Error('Successful deployment receipt is absent or cross-paired.');
|
|
330
|
+
const unsigned = {
|
|
331
|
+
accountId,
|
|
332
|
+
deploymentCommit,
|
|
333
|
+
kind: 'ebk-postdeploy-supply-chain-binding',
|
|
334
|
+
profile,
|
|
335
|
+
region,
|
|
336
|
+
releaseCandidateArtifactDigest,
|
|
337
|
+
releaseCandidateArtifactId,
|
|
338
|
+
releaseCandidateProducerRunAttempt,
|
|
339
|
+
releaseCandidateProducerRunId,
|
|
340
|
+
releaseSourceCommit,
|
|
341
|
+
runAttempt,
|
|
342
|
+
runId,
|
|
343
|
+
schemaVersion: 1,
|
|
344
|
+
stage,
|
|
345
|
+
};
|
|
346
|
+
const binding = {
|
|
347
|
+
...unsigned,
|
|
348
|
+
materialDigest: createHash('sha256').update(canonical(unsigned)).digest('hex'),
|
|
349
|
+
};
|
|
350
|
+
mkdirSync('.ebk/evidence/deployment', { recursive: true, mode: 0o700 });
|
|
351
|
+
writeFileSync(
|
|
352
|
+
'.ebk/evidence/deployment/postdeploy-supply-chain.json',
|
|
353
|
+
JSON.stringify(binding, null, 2) + '\\n',
|
|
354
|
+
{ flag: 'wx', mode: 0o600 },
|
|
355
|
+
);`;
|
|
356
|
+
}
|
|
357
|
+
function renderThinDeployWorkflow(options) {
|
|
358
|
+
const { actionPins, deploymentProfile, name, toolVersions } = options;
|
|
359
|
+
return `name: Deploy
|
|
360
|
+
run-name: Deploy \${{ inputs.source_commit || github.sha }}
|
|
361
|
+
|
|
362
|
+
on:
|
|
363
|
+
${workflowCallTrigger(deploymentProfile)} workflow_dispatch:
|
|
364
|
+
inputs:
|
|
365
|
+
action:
|
|
366
|
+
description: Native lifecycle action
|
|
367
|
+
required: true
|
|
368
|
+
default: deploy
|
|
369
|
+
type: choice
|
|
370
|
+
options: [deploy, refresh, unlock, remove]
|
|
371
|
+
project:
|
|
372
|
+
description: Required registered project for unlock or remove recovery
|
|
373
|
+
required: false
|
|
374
|
+
type: string
|
|
375
|
+
|
|
376
|
+
concurrency:
|
|
377
|
+
group: protected-lifecycle-\${{ inputs.source_branch || github.ref_name }}
|
|
378
|
+
cancel-in-progress: false
|
|
379
|
+
|
|
380
|
+
permissions:
|
|
381
|
+
contents: read
|
|
382
|
+
|
|
383
|
+
env:
|
|
384
|
+
EBK_DEPLOYMENT_SOURCE_BRANCH: \${{ inputs.source_branch || github.ref_name }}
|
|
385
|
+
EBK_DEPLOYMENT_SOURCE_COMMIT: \${{ inputs.source_commit || github.sha }}
|
|
386
|
+
EBK_DEPLOYMENT_SOURCE_REF: \${{ inputs.source_ref || github.ref }}
|
|
387
|
+
EBK_EXPECTED_SST_VERSION: ${toolVersions.sst}
|
|
388
|
+
|
|
389
|
+
jobs:
|
|
390
|
+
prepare:
|
|
391
|
+
name: route protected native lifecycle
|
|
392
|
+
runs-on: ubuntu-latest
|
|
393
|
+
outputs:
|
|
394
|
+
action: \${{ steps.route.outputs.action }}
|
|
395
|
+
account_id: \${{ steps.account.outputs.account_id }}
|
|
396
|
+
has_slices: \${{ steps.policy.outputs.has_slices }}
|
|
397
|
+
ordered_projects: \${{ steps.policy.outputs.ordered_projects }}
|
|
398
|
+
profile: \${{ steps.policy.outputs.deployment_profile }}
|
|
399
|
+
project: \${{ steps.route.outputs.project }}
|
|
400
|
+
region: \${{ steps.policy.outputs.deployment_region }}
|
|
401
|
+
slice_projects: \${{ steps.policy.outputs.slice_projects }}
|
|
402
|
+
staging_account_id: \${{ steps.policy.outputs.staging_account_id }}
|
|
403
|
+
stage: \${{ steps.route.outputs.stage }}
|
|
404
|
+
steps:
|
|
405
|
+
- name: Verify exact protected branch tip before setup
|
|
406
|
+
env:
|
|
407
|
+
EBK_CONTEXT_REF: \${{ github.ref }}
|
|
408
|
+
EBK_CONTEXT_REF_NAME: \${{ github.ref_name }}
|
|
409
|
+
EBK_CONTEXT_RUN_ATTEMPT: \${{ github.run_attempt }}
|
|
410
|
+
EBK_CONTEXT_RUN_ID: \${{ github.run_id }}
|
|
411
|
+
EBK_CONTEXT_SHA: \${{ github.sha }}
|
|
412
|
+
EBK_INPUT_RUN_ATTEMPT: \${{ inputs.verify_run_attempt || '' }}
|
|
413
|
+
EBK_INPUT_RUN_ID: \${{ inputs.verify_run_id || '' }}
|
|
414
|
+
EBK_INPUT_SOURCE_BRANCH: \${{ inputs.source_branch || '' }}
|
|
415
|
+
EBK_INPUT_SOURCE_COMMIT: \${{ inputs.source_commit || '' }}
|
|
416
|
+
EBK_INPUT_SOURCE_REF: \${{ inputs.source_ref || '' }}
|
|
417
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
418
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
419
|
+
EBK_SOURCE_REF: \${{ env.EBK_DEPLOYMENT_SOURCE_REF }}
|
|
420
|
+
EBK_TRIGGER: \${{ github.event_name }}
|
|
421
|
+
GH_REPO: \${{ github.repository }}
|
|
422
|
+
GH_TOKEN: \${{ github.token }}
|
|
423
|
+
run: |
|
|
424
|
+
set -euo pipefail
|
|
425
|
+
case "$EBK_SOURCE_COMMIT" in ''|*[!0-9a-f]*) exit 1 ;; esac
|
|
426
|
+
{ [ "\${#EBK_SOURCE_COMMIT}" -eq 40 ] || [ "\${#EBK_SOURCE_COMMIT}" -eq 64 ]; } || exit 1
|
|
427
|
+
case "$EBK_SOURCE_BRANCH:$EBK_SOURCE_REF" in
|
|
428
|
+
develop:refs/heads/develop|main:refs/heads/main) ;;
|
|
429
|
+
*) echo 'Protected lifecycle must bind one exact protected branch ref'; exit 1 ;;
|
|
430
|
+
esac
|
|
431
|
+
if [ -n "$EBK_INPUT_RUN_ID$EBK_INPUT_RUN_ATTEMPT$EBK_INPUT_SOURCE_BRANCH$EBK_INPUT_SOURCE_COMMIT$EBK_INPUT_SOURCE_REF" ]; then
|
|
432
|
+
[ "$EBK_TRIGGER" = push ] || { echo 'Automatic deployment requires one protected push Verify caller'; exit 1; }
|
|
433
|
+
[ -n "$EBK_INPUT_RUN_ID" ] && [ -n "$EBK_INPUT_RUN_ATTEMPT" ] &&
|
|
434
|
+
[ -n "$EBK_INPUT_SOURCE_BRANCH" ] && [ -n "$EBK_INPUT_SOURCE_COMMIT" ] && [ -n "$EBK_INPUT_SOURCE_REF" ] ||
|
|
435
|
+
{ echo 'Automatic deployment caller binding is incomplete'; exit 1; }
|
|
436
|
+
[ "$EBK_INPUT_RUN_ID" = "$EBK_CONTEXT_RUN_ID" ] &&
|
|
437
|
+
[ "$EBK_INPUT_RUN_ATTEMPT" = "$EBK_CONTEXT_RUN_ATTEMPT" ] &&
|
|
438
|
+
[ "$EBK_INPUT_SOURCE_COMMIT" = "$EBK_CONTEXT_SHA" ] &&
|
|
439
|
+
[ "$EBK_INPUT_SOURCE_BRANCH" = "$EBK_CONTEXT_REF_NAME" ] &&
|
|
440
|
+
[ "$EBK_INPUT_SOURCE_REF" = "$EBK_CONTEXT_REF" ] ||
|
|
441
|
+
{ echo 'Automatic deployment differs from its exact Verify caller'; exit 1; }
|
|
442
|
+
else
|
|
443
|
+
[ "$EBK_TRIGGER" = workflow_dispatch ] || { echo 'Manual deployment requires workflow dispatch'; exit 1; }
|
|
444
|
+
fi
|
|
445
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
446
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
447
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
448
|
+
}
|
|
449
|
+
- name: Check out immutable source
|
|
450
|
+
uses: ${actionPins.checkout}
|
|
451
|
+
with:
|
|
452
|
+
fetch-depth: 0
|
|
453
|
+
persist-credentials: false
|
|
454
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
455
|
+
- uses: ${actionPins.pnpmSetup}
|
|
456
|
+
with:
|
|
457
|
+
version: ${toolVersions.pnpm}
|
|
458
|
+
- uses: ${actionPins.setupNode}
|
|
459
|
+
with:
|
|
460
|
+
node-version: ${toolVersions.node}
|
|
461
|
+
cache: pnpm
|
|
462
|
+
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
463
|
+
- id: route
|
|
464
|
+
name: Resolve protected stage and action
|
|
465
|
+
env:
|
|
466
|
+
EBK_DISPATCH_ACTION: \${{ inputs.action || '' }}
|
|
467
|
+
EBK_DISPATCH_PROJECT: \${{ inputs.project || '' }}
|
|
468
|
+
EBK_PROFILE: ${deploymentProfile}
|
|
469
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
470
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
471
|
+
EBK_TRIGGER: \${{ github.event_name }}
|
|
472
|
+
run: |
|
|
473
|
+
set -euo pipefail
|
|
474
|
+
action=deploy
|
|
475
|
+
project=
|
|
476
|
+
if [ "$EBK_TRIGGER" = workflow_dispatch ]; then
|
|
477
|
+
action="$EBK_DISPATCH_ACTION"
|
|
478
|
+
project="$EBK_DISPATCH_PROJECT"
|
|
479
|
+
fi
|
|
480
|
+
case "$EBK_SOURCE_COMMIT" in ''|*[!0-9a-f]*) exit 1 ;; esac
|
|
481
|
+
{ [ "\${#EBK_SOURCE_COMMIT}" -eq 40 ] || [ "\${#EBK_SOURCE_COMMIT}" -eq 64 ]; } || exit 1
|
|
482
|
+
case "$action" in deploy|refresh|unlock|remove) ;; *) exit 1 ;; esac
|
|
483
|
+
case "$project" in ''|*[!A-Za-z0-9_-]*) [ -z "$project" ] || exit 1 ;; esac
|
|
484
|
+
case "$action" in
|
|
485
|
+
unlock|remove) [ -n "$project" ] || { echo 'unlock and remove require an explicit project'; exit 1; } ;;
|
|
486
|
+
deploy|refresh) [ -z "$project" ] || { echo 'deploy and refresh operate on the policy-derived project order'; exit 1; } ;;
|
|
487
|
+
esac
|
|
488
|
+
case "$EBK_SOURCE_BRANCH" in
|
|
489
|
+
main) stage=production ;;
|
|
490
|
+
develop)
|
|
491
|
+
if [ "$EBK_PROFILE" = starter ]; then
|
|
492
|
+
stage=development
|
|
493
|
+
else
|
|
494
|
+
stage=staging
|
|
495
|
+
fi
|
|
496
|
+
;;
|
|
497
|
+
*) echo 'Protected lifecycle must run from develop or main'; exit 1 ;;
|
|
498
|
+
esac
|
|
499
|
+
echo "action=$action" >> "$GITHUB_OUTPUT"
|
|
500
|
+
echo "project=$project" >> "$GITHUB_OUTPUT"
|
|
501
|
+
echo "stage=$stage" >> "$GITHUB_OUTPUT"
|
|
502
|
+
- id: policy
|
|
503
|
+
name: Derive ordered projects from committed policy
|
|
504
|
+
env:
|
|
505
|
+
EBK_ACTION: \${{ steps.route.outputs.action }}
|
|
506
|
+
EBK_PROJECT: \${{ steps.route.outputs.project }}
|
|
507
|
+
EBK_STAGE: \${{ steps.route.outputs.stage }}
|
|
508
|
+
run: |
|
|
509
|
+
set -euo pipefail
|
|
510
|
+
args=("--action=$EBK_ACTION" "--stage=$EBK_STAGE")
|
|
511
|
+
if [ -n "$EBK_PROJECT" ]; then args+=("--project=$EBK_PROJECT"); fi
|
|
512
|
+
pnpm nx run ${name}:deployment-plan -- "\${args[@]}"
|
|
513
|
+
- name: Bind generated profile
|
|
514
|
+
env:
|
|
515
|
+
EBK_RENDERED_PROFILE: ${deploymentProfile}
|
|
516
|
+
EBK_REPOSITORY_PROFILE: \${{ steps.policy.outputs.deployment_profile }}
|
|
517
|
+
run: test "$EBK_RENDERED_PROFILE" = "$EBK_REPOSITORY_PROFILE"
|
|
518
|
+
- id: account
|
|
519
|
+
name: Select committed stage account
|
|
520
|
+
env:
|
|
521
|
+
EBK_DEVELOPMENT_ACCOUNT: \${{ steps.policy.outputs.development_account_id }}
|
|
522
|
+
EBK_PRODUCTION_ACCOUNT: \${{ steps.policy.outputs.production_account_id }}
|
|
523
|
+
EBK_STAGE: \${{ steps.route.outputs.stage }}
|
|
524
|
+
EBK_STAGING_ACCOUNT: \${{ steps.policy.outputs.staging_account_id }}
|
|
525
|
+
run: |
|
|
526
|
+
set -euo pipefail
|
|
527
|
+
case "$EBK_STAGE" in
|
|
528
|
+
development) account_id="$EBK_DEVELOPMENT_ACCOUNT" ;;
|
|
529
|
+
staging) account_id="$EBK_STAGING_ACCOUNT" ;;
|
|
530
|
+
production) account_id="$EBK_PRODUCTION_ACCOUNT" ;;
|
|
531
|
+
*) exit 1 ;;
|
|
532
|
+
esac
|
|
533
|
+
case "$account_id" in *[!0-9]*|'') exit 1 ;; esac
|
|
534
|
+
[ "\${#account_id}" -eq 12 ]
|
|
535
|
+
echo "account_id=$account_id" >> "$GITHUB_OUTPUT"
|
|
536
|
+
|
|
537
|
+
immutable-artifacts:
|
|
538
|
+
name: prepare exact immutable release artifacts
|
|
539
|
+
needs: prepare
|
|
540
|
+
if: >-
|
|
541
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
542
|
+
(
|
|
543
|
+
needs.prepare.outputs.has_slices == 'true' ||
|
|
544
|
+
(needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter')
|
|
545
|
+
)
|
|
546
|
+
runs-on: ubuntu-latest
|
|
547
|
+
permissions:
|
|
548
|
+
actions: read
|
|
549
|
+
contents: read
|
|
550
|
+
outputs:
|
|
551
|
+
artifact_digest: \${{ steps.current.outputs.artifact_digest || steps.staging.outputs.artifact_digest }}
|
|
552
|
+
artifact_id: \${{ steps.current.outputs.artifact_id || steps.staging.outputs.artifact_id }}
|
|
553
|
+
candidate_material_digest: \${{ steps.staging.outputs.candidate_material_digest }}
|
|
554
|
+
container_artifact_digest: \${{ steps.staging.outputs.container_artifact_digest }}
|
|
555
|
+
container_artifact_id: \${{ steps.staging.outputs.container_artifact_id }}
|
|
556
|
+
staging_deployment_run_attempt: \${{ steps.staging.outputs.deployment_run_attempt }}
|
|
557
|
+
staging_deployment_run_id: \${{ steps.staging.outputs.deployment_run_id }}
|
|
558
|
+
producer_run_attempt: \${{ steps.current.outputs.producer_run_attempt || steps.staging.outputs.producer_run_attempt }}
|
|
559
|
+
producer_run_id: \${{ steps.current.outputs.producer_run_id || steps.staging.outputs.producer_run_id }}
|
|
560
|
+
source_commit: \${{ steps.current.outputs.source_commit || steps.staging.outputs.source_commit }}
|
|
561
|
+
steps:
|
|
562
|
+
- name: Check out immutable production ancestry
|
|
563
|
+
if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
|
|
564
|
+
uses: ${actionPins.checkout}
|
|
565
|
+
with:
|
|
566
|
+
fetch-depth: 0
|
|
567
|
+
persist-credentials: false
|
|
568
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
569
|
+
- id: current
|
|
570
|
+
name: Select exact successful current-source CI candidate
|
|
571
|
+
if: >-
|
|
572
|
+
needs.prepare.outputs.has_slices == 'true' &&
|
|
573
|
+
(needs.prepare.outputs.stage != 'production' || needs.prepare.outputs.profile == 'starter')
|
|
574
|
+
env:
|
|
575
|
+
EBK_CALLER_RUN_ATTEMPT: \${{ inputs.verify_run_attempt || '' }}
|
|
576
|
+
EBK_CALLER_RUN_ID: \${{ inputs.verify_run_id || '' }}
|
|
577
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
578
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
579
|
+
GH_REPO: \${{ github.repository }}
|
|
580
|
+
GH_TOKEN: \${{ github.token }}
|
|
581
|
+
run: |
|
|
582
|
+
set -euo pipefail
|
|
583
|
+
if [ -n "$EBK_CALLER_RUN_ID" ]; then
|
|
584
|
+
[ -n "$EBK_CALLER_RUN_ATTEMPT" ] || exit 1
|
|
585
|
+
run_id="$EBK_CALLER_RUN_ID"
|
|
586
|
+
run_attempt="$EBK_CALLER_RUN_ATTEMPT"
|
|
587
|
+
else
|
|
588
|
+
runs="$(gh api "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=push&branch=$EBK_SOURCE_BRANCH&head_sha=$EBK_SOURCE_COMMIT&status=success&per_page=100")"
|
|
589
|
+
producer="$(printf '%s' "$runs" | jq -c --arg branch "$EBK_SOURCE_BRANCH" --arg source "$EBK_SOURCE_COMMIT" \
|
|
590
|
+
'[.workflow_runs[] | select(.head_sha == $source and .head_branch == $branch and .event == "push" and .conclusion == "success" and .path == ".github/workflows/ci.yml")] | if length == 1 then .[0] else empty end')"
|
|
591
|
+
[ -n "$producer" ] || { echo 'Exact successful CI candidate producer is absent or ambiguous'; exit 1; }
|
|
592
|
+
run_id="$(printf '%s' "$producer" | jq -r '.id // empty')"
|
|
593
|
+
run_attempt="$(printf '%s' "$producer" | jq -r '.run_attempt // empty')"
|
|
594
|
+
fi
|
|
595
|
+
artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$run_id/artifacts?per_page=100")"
|
|
596
|
+
artifact="$(printf '%s' "$artifacts" | jq -c --arg source "$EBK_SOURCE_COMMIT" \
|
|
597
|
+
'[.artifacts[] | select(.name == "release-candidate-" + $source and .expired == false)] | if length == 1 then .[0] else empty end')"
|
|
598
|
+
[ -n "$artifact" ] || { echo 'Exact release candidate artifact is absent or ambiguous'; exit 1; }
|
|
599
|
+
artifact_id="$(printf '%s' "$artifact" | jq -r '.id // empty')"
|
|
600
|
+
artifact_digest="$(printf '%s' "$artifact" | jq -r '.digest // empty')"
|
|
601
|
+
case "$run_id:$run_attempt:$artifact_id" in *[!0-9:]*) exit 1 ;; esac
|
|
602
|
+
case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) exit 1 ;; esac
|
|
603
|
+
echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
|
|
604
|
+
echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
|
|
605
|
+
echo "producer_run_attempt=$run_attempt" >> "$GITHUB_OUTPUT"
|
|
606
|
+
echo "producer_run_id=$run_id" >> "$GITHUB_OUTPUT"
|
|
607
|
+
echo "source_commit=$EBK_SOURCE_COMMIT" >> "$GITHUB_OUTPUT"
|
|
608
|
+
- id: staging-receipt
|
|
609
|
+
name: Select exact successful staging deployment receipt
|
|
610
|
+
if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
|
|
611
|
+
env:
|
|
612
|
+
EBK_DEPLOYMENT_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
613
|
+
GH_REPO: \${{ github.repository }}
|
|
614
|
+
GH_TOKEN: \${{ github.token }}
|
|
615
|
+
run: |
|
|
616
|
+
set -euo pipefail
|
|
617
|
+
parents="$(git rev-list --parents -n 1 "$EBK_DEPLOYMENT_SOURCE_COMMIT")"
|
|
618
|
+
set -- $parents
|
|
619
|
+
[ "$#" -eq 3 ] || { echo 'Isolated production requires one exact two-parent promotion merge'; exit 1; }
|
|
620
|
+
[ "$1" = "$EBK_DEPLOYMENT_SOURCE_COMMIT" ] || exit 1
|
|
621
|
+
staging_source="$3"
|
|
622
|
+
git merge-base --is-ancestor "$staging_source" "$EBK_DEPLOYMENT_SOURCE_COMMIT" || {
|
|
623
|
+
echo 'Staging promotion source is not an ancestor of production'; exit 1;
|
|
624
|
+
}
|
|
625
|
+
[ "$(git rev-parse "$EBK_DEPLOYMENT_SOURCE_COMMIT^{tree}")" = "$(git rev-parse "$staging_source^{tree}")" ] || {
|
|
626
|
+
echo 'Production merge tree differs from the promoted staging source'; exit 1;
|
|
627
|
+
}
|
|
628
|
+
git show-ref --verify --quiet refs/remotes/origin/develop || {
|
|
629
|
+
echo 'Protected develop lineage is absent from the immutable checkout'; exit 1;
|
|
630
|
+
}
|
|
631
|
+
git rev-list --first-parent refs/remotes/origin/develop > "$RUNNER_TEMP/develop-first-parent"
|
|
632
|
+
grep -Fxq "$staging_source" "$RUNNER_TEMP/develop-first-parent" || {
|
|
633
|
+
echo 'Staging promotion source is absent from protected develop first-parent history'; exit 1;
|
|
634
|
+
}
|
|
635
|
+
runs="$(gh api "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=push&branch=develop&head_sha=$staging_source&status=success&per_page=100")"
|
|
636
|
+
producer="$(printf '%s' "$runs" | jq -c --arg source "$staging_source" \
|
|
637
|
+
'[.workflow_runs[] | select(.head_sha == $source and .head_branch == "develop" and .event == "push" and .conclusion == "success" and .path == ".github/workflows/ci.yml")] | if length == 1 then .[0] else empty end')"
|
|
638
|
+
[ -n "$producer" ] || { echo 'Exact successful staging Verify deployment is absent or ambiguous'; exit 1; }
|
|
639
|
+
run_id="$(printf '%s' "$producer" | jq -r '.id // empty')"
|
|
640
|
+
run_attempt="$(printf '%s' "$producer" | jq -r '.run_attempt // empty')"
|
|
641
|
+
artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$run_id/artifacts?per_page=100")"
|
|
642
|
+
receipt_name="deployment-receipt-staging-$run_id-$run_attempt"
|
|
643
|
+
receipt="$(printf '%s' "$artifacts" | jq -c --arg name "$receipt_name" \
|
|
644
|
+
'[.artifacts[] | select(.name == $name and .expired == false)] | if length == 1 then .[0] else empty end')"
|
|
645
|
+
[ -n "$receipt" ] || { echo 'Exact successful staging deployment receipt is absent or ambiguous'; exit 1; }
|
|
646
|
+
artifact_id="$(printf '%s' "$receipt" | jq -r '.id // empty')"
|
|
647
|
+
artifact_digest="$(printf '%s' "$receipt" | jq -r '.digest // empty')"
|
|
648
|
+
case "$run_id:$run_attempt:$artifact_id" in *[!0-9:]*) exit 1 ;; esac
|
|
649
|
+
case "$artifact_digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) exit 1 ;; esac
|
|
650
|
+
echo "artifact_digest=$artifact_digest" >> "$GITHUB_OUTPUT"
|
|
651
|
+
echo "artifact_id=$artifact_id" >> "$GITHUB_OUTPUT"
|
|
652
|
+
echo "run_attempt=$run_attempt" >> "$GITHUB_OUTPUT"
|
|
653
|
+
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
|
|
654
|
+
echo "source_commit=$staging_source" >> "$GITHUB_OUTPUT"
|
|
655
|
+
- name: Restore exact staging deployment receipt
|
|
656
|
+
if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
|
|
657
|
+
uses: ${actionPins.downloadArtifact}
|
|
658
|
+
with:
|
|
659
|
+
artifact-ids: \${{ steps.staging-receipt.outputs.artifact_id }}
|
|
660
|
+
github-token: \${{ github.token }}
|
|
661
|
+
path: \${{ runner.temp }}/staging-deployment-receipt
|
|
662
|
+
run-id: \${{ steps.staging-receipt.outputs.run_id }}
|
|
663
|
+
- id: staging
|
|
664
|
+
name: Bind exact staging deployment and release evidence
|
|
665
|
+
if: needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile != 'starter'
|
|
666
|
+
env:
|
|
667
|
+
EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT: \${{ steps.staging-receipt.outputs.run_attempt }}
|
|
668
|
+
EBK_STAGING_DEPLOYMENT_RUN_ID: \${{ steps.staging-receipt.outputs.run_id }}
|
|
669
|
+
EBK_EXPECTED_HAS_SLICES: \${{ needs.prepare.outputs.has_slices }}
|
|
670
|
+
EBK_EXPECTED_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
|
|
671
|
+
EBK_EXPECTED_DEPLOYMENT_REGION: \${{ needs.prepare.outputs.region }}
|
|
672
|
+
EBK_EXPECTED_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
|
|
673
|
+
EBK_STAGING_RECEIPT_ARTIFACT_DIGEST: \${{ steps.staging-receipt.outputs.artifact_digest }}
|
|
674
|
+
EBK_STAGING_RECEIPT_ARTIFACT_ID: \${{ steps.staging-receipt.outputs.artifact_id }}
|
|
675
|
+
EBK_STAGING_RECEIPT_ROOT: \${{ runner.temp }}/staging-deployment-receipt
|
|
676
|
+
EBK_STAGING_SOURCE_COMMIT: \${{ steps.staging-receipt.outputs.source_commit }}
|
|
677
|
+
GH_REPO: \${{ github.repository }}
|
|
678
|
+
GH_TOKEN: \${{ github.token }}
|
|
679
|
+
run: |
|
|
680
|
+
node --input-type=module <<'NODE'
|
|
681
|
+
${indentedScript(stagingPromotionReceiptValidationScript(), 10)}
|
|
682
|
+
NODE
|
|
683
|
+
if [ "$EBK_EXPECTED_HAS_SLICES" = true ]; then
|
|
684
|
+
promotion="$EBK_STAGING_RECEIPT_ROOT/promotion.json"
|
|
685
|
+
candidate_run_id="$(jq -r '.candidate.producerRunId' "$promotion")"
|
|
686
|
+
candidate_run_attempt="$(jq -r '.candidate.producerRunAttempt' "$promotion")"
|
|
687
|
+
candidate_artifact_id="$(jq -r '.candidate.artifactId' "$promotion")"
|
|
688
|
+
candidate_artifact_digest="$(jq -r '.candidate.artifactDigest' "$promotion")"
|
|
689
|
+
container_artifact_id="$(jq -r '.container.artifactId' "$promotion")"
|
|
690
|
+
container_artifact_digest="$(jq -r '.container.artifactDigest' "$promotion")"
|
|
691
|
+
candidate_run="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id")"
|
|
692
|
+
printf '%s' "$candidate_run" | jq -e \
|
|
693
|
+
--arg source "$EBK_STAGING_SOURCE_COMMIT" \
|
|
694
|
+
--arg attempt "$candidate_run_attempt" \
|
|
695
|
+
'.head_sha == $source and .head_branch == "develop" and .event == "push" and .path == ".github/workflows/ci.yml" and .conclusion == "success" and (.run_attempt | tostring) == $attempt' >/dev/null
|
|
696
|
+
candidate_artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$candidate_run_id/artifacts?per_page=100")"
|
|
697
|
+
printf '%s' "$candidate_artifacts" | jq -e \
|
|
698
|
+
--arg id "$candidate_artifact_id" \
|
|
699
|
+
--arg digest "$candidate_artifact_digest" \
|
|
700
|
+
--arg name "release-candidate-$EBK_STAGING_SOURCE_COMMIT" \
|
|
701
|
+
'[.artifacts[] | select((.id | tostring) == $id and .name == $name and .digest == $digest and .expired == false)] | length == 1' >/dev/null
|
|
702
|
+
staging_artifacts="$(gh api "/repos/$GH_REPO/actions/runs/$EBK_STAGING_DEPLOYMENT_RUN_ID/artifacts?per_page=100")"
|
|
703
|
+
printf '%s' "$staging_artifacts" | jq -e \
|
|
704
|
+
--arg id "$container_artifact_id" \
|
|
705
|
+
--arg digest "$container_artifact_digest" \
|
|
706
|
+
--arg name "container-release-staging-$EBK_STAGING_DEPLOYMENT_RUN_ID-$EBK_STAGING_DEPLOYMENT_RUN_ATTEMPT" \
|
|
707
|
+
'[.artifacts[] | select((.id | tostring) == $id and .name == $name and .digest == $digest and .expired == false)] | length == 1' >/dev/null
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
container-platform:
|
|
711
|
+
name: native \${{ needs.prepare.outputs.stage }} OCI \${{ matrix.platform }}
|
|
712
|
+
needs: [prepare, immutable-artifacts]
|
|
713
|
+
if: >-
|
|
714
|
+
always() &&
|
|
715
|
+
needs.prepare.result == 'success' &&
|
|
716
|
+
needs.immutable-artifacts.result == 'success' &&
|
|
717
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
718
|
+
needs.prepare.outputs.has_slices == 'true' &&
|
|
719
|
+
(
|
|
720
|
+
needs.prepare.outputs.stage == 'staging' ||
|
|
721
|
+
(needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
|
|
722
|
+
)
|
|
723
|
+
strategy:
|
|
724
|
+
fail-fast: false
|
|
725
|
+
matrix:
|
|
726
|
+
include:
|
|
727
|
+
- architecture: amd64
|
|
728
|
+
platform: linux/amd64
|
|
729
|
+
runner: ubuntu-latest
|
|
730
|
+
target: container-release-amd64
|
|
731
|
+
- architecture: arm64
|
|
732
|
+
platform: linux/arm64
|
|
733
|
+
runner: ubuntu-24.04-arm
|
|
734
|
+
target: container-release-arm64
|
|
735
|
+
runs-on: \${{ matrix.runner }}
|
|
736
|
+
environment: \${{ needs.prepare.outputs.stage }}
|
|
737
|
+
permissions:
|
|
738
|
+
actions: read
|
|
739
|
+
contents: read
|
|
740
|
+
id-token: write
|
|
741
|
+
env:
|
|
742
|
+
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
743
|
+
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
744
|
+
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
745
|
+
EBK_ACTION: deploy
|
|
746
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
|
|
747
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
|
|
748
|
+
EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
|
|
749
|
+
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
|
|
750
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
751
|
+
EBK_STAGE: \${{ needs.prepare.outputs.stage }}
|
|
752
|
+
EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
|
|
753
|
+
steps:
|
|
754
|
+
- name: Check out immutable container source
|
|
755
|
+
uses: ${actionPins.checkout}
|
|
756
|
+
env:
|
|
757
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
758
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
759
|
+
with:
|
|
760
|
+
fetch-depth: 0
|
|
761
|
+
persist-credentials: false
|
|
762
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
763
|
+
- uses: ${actionPins.pnpmSetup}
|
|
764
|
+
env:
|
|
765
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
766
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
767
|
+
with:
|
|
768
|
+
version: ${toolVersions.pnpm}
|
|
769
|
+
- uses: ${actionPins.setupNode}
|
|
770
|
+
env:
|
|
771
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
772
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
773
|
+
with:
|
|
774
|
+
node-version: ${toolVersions.node}
|
|
775
|
+
cache: pnpm
|
|
776
|
+
- env:
|
|
777
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
778
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
779
|
+
run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
780
|
+
- name: Verify managed workspace integrity before container authority
|
|
781
|
+
env:
|
|
782
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
783
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
784
|
+
run: pnpm nx run ${name}:workspace-health
|
|
785
|
+
- name: Restore exact immutable release candidate for container build
|
|
786
|
+
uses: ${actionPins.downloadArtifact}
|
|
787
|
+
env:
|
|
788
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
789
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
790
|
+
with:
|
|
791
|
+
artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
|
|
792
|
+
github-token: \${{ github.token }}
|
|
793
|
+
path: .ebk/evidence/release-candidate
|
|
794
|
+
run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
|
|
795
|
+
- name: Revalidate exact protected branch tip before OIDC
|
|
796
|
+
env:
|
|
797
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
798
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
799
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
800
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
801
|
+
GH_REPO: \${{ github.repository }}
|
|
802
|
+
GH_TOKEN: \${{ github.token }}
|
|
803
|
+
run: |
|
|
804
|
+
set -euo pipefail
|
|
805
|
+
[ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
|
|
806
|
+
echo 'Deployment checkout differs from the exact protected source'; exit 1;
|
|
807
|
+
}
|
|
808
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
809
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
810
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
811
|
+
}
|
|
812
|
+
- name: Assume the current stage deployment role for container build
|
|
813
|
+
uses: ${actionPins.configureAwsCredentials}
|
|
814
|
+
with:
|
|
815
|
+
aws-region: \${{ vars.AWS_REGION }}
|
|
816
|
+
role-session-name: ebk-container-\${{ matrix.architecture }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
817
|
+
role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
|
|
818
|
+
- name: Verify exact container build authority
|
|
819
|
+
run: |
|
|
820
|
+
set -euo pipefail
|
|
821
|
+
[ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
822
|
+
[ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
|
|
823
|
+
[ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
824
|
+
- name: Build and run exact native OCI artifacts
|
|
825
|
+
run: |
|
|
826
|
+
set -euo pipefail
|
|
827
|
+
IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
|
|
828
|
+
for project in "\${projects[@]}"; do
|
|
829
|
+
test -n "$project" || continue
|
|
830
|
+
pnpm nx run "$project:\${{ matrix.target }}"
|
|
831
|
+
done
|
|
832
|
+
- name: Retain native platform evidence
|
|
833
|
+
uses: ${actionPins.uploadArtifact}
|
|
834
|
+
with:
|
|
835
|
+
name: container-platform-\${{ needs.prepare.outputs.stage }}-\${{ matrix.architecture }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
836
|
+
if-no-files-found: error
|
|
837
|
+
include-hidden-files: true
|
|
838
|
+
retention-days: 30
|
|
839
|
+
path: .ebk/evidence/container-release/partials/\${{ needs.prepare.outputs.stage }}/*/\${{ matrix.architecture }}
|
|
840
|
+
|
|
841
|
+
container-assemble:
|
|
842
|
+
name: assemble \${{ needs.prepare.outputs.stage }} OCI indexes
|
|
843
|
+
needs: [prepare, immutable-artifacts, container-platform]
|
|
844
|
+
if: >-
|
|
845
|
+
always() &&
|
|
846
|
+
needs.prepare.result == 'success' &&
|
|
847
|
+
needs.immutable-artifacts.result == 'success' &&
|
|
848
|
+
needs.container-platform.result == 'success' &&
|
|
849
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
850
|
+
needs.prepare.outputs.has_slices == 'true'
|
|
851
|
+
runs-on: ubuntu-latest
|
|
852
|
+
environment: \${{ needs.prepare.outputs.stage }}
|
|
853
|
+
permissions:
|
|
854
|
+
actions: read
|
|
855
|
+
contents: read
|
|
856
|
+
id-token: write
|
|
857
|
+
outputs:
|
|
858
|
+
artifact_digest: \${{ steps.retain.outputs.artifact-digest }}
|
|
859
|
+
artifact_id: \${{ steps.retain.outputs.artifact-id }}
|
|
860
|
+
env:
|
|
861
|
+
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
862
|
+
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
863
|
+
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
864
|
+
EBK_ACTION: deploy
|
|
865
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
|
|
866
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
|
|
867
|
+
EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
|
|
868
|
+
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
|
|
869
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
870
|
+
EBK_STAGE: \${{ needs.prepare.outputs.stage }}
|
|
871
|
+
EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
|
|
872
|
+
steps:
|
|
873
|
+
- name: Check out immutable container source
|
|
874
|
+
uses: ${actionPins.checkout}
|
|
875
|
+
env:
|
|
876
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
877
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
878
|
+
with:
|
|
879
|
+
fetch-depth: 0
|
|
880
|
+
persist-credentials: false
|
|
881
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
882
|
+
- uses: ${actionPins.pnpmSetup}
|
|
883
|
+
env:
|
|
884
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
885
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
886
|
+
with:
|
|
887
|
+
version: ${toolVersions.pnpm}
|
|
888
|
+
- uses: ${actionPins.setupNode}
|
|
889
|
+
env:
|
|
890
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
891
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
892
|
+
with:
|
|
893
|
+
node-version: ${toolVersions.node}
|
|
894
|
+
cache: pnpm
|
|
895
|
+
- env:
|
|
896
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
897
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
898
|
+
run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
899
|
+
- name: Verify managed workspace integrity before container authority
|
|
900
|
+
env:
|
|
901
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
902
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
903
|
+
run: pnpm nx run ${name}:workspace-health
|
|
904
|
+
- name: Restore exact immutable release candidate for container assembly
|
|
905
|
+
uses: ${actionPins.downloadArtifact}
|
|
906
|
+
env:
|
|
907
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
908
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
909
|
+
with:
|
|
910
|
+
artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
|
|
911
|
+
github-token: \${{ github.token }}
|
|
912
|
+
path: .ebk/evidence/release-candidate
|
|
913
|
+
run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
|
|
914
|
+
- name: Restore both native platform records
|
|
915
|
+
uses: ${actionPins.downloadArtifact}
|
|
916
|
+
env:
|
|
917
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
918
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
919
|
+
with:
|
|
920
|
+
pattern: container-platform-\${{ needs.prepare.outputs.stage }}-*-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
921
|
+
merge-multiple: true
|
|
922
|
+
path: .ebk/evidence/container-release/partials/\${{ needs.prepare.outputs.stage }}
|
|
923
|
+
- name: Revalidate exact protected branch tip before OIDC
|
|
924
|
+
env:
|
|
925
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
926
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
927
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
928
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
929
|
+
GH_REPO: \${{ github.repository }}
|
|
930
|
+
GH_TOKEN: \${{ github.token }}
|
|
931
|
+
run: |
|
|
932
|
+
set -euo pipefail
|
|
933
|
+
[ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
|
|
934
|
+
echo 'Deployment checkout differs from the exact protected source'; exit 1;
|
|
935
|
+
}
|
|
936
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
937
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
938
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
939
|
+
}
|
|
940
|
+
- name: Assume the current stage deployment role for container assembly
|
|
941
|
+
uses: ${actionPins.configureAwsCredentials}
|
|
942
|
+
with:
|
|
943
|
+
aws-region: \${{ vars.AWS_REGION }}
|
|
944
|
+
role-session-name: ebk-container-assemble-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
945
|
+
role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
|
|
946
|
+
- name: Verify exact container assembly authority
|
|
947
|
+
run: |
|
|
948
|
+
set -euo pipefail
|
|
949
|
+
[ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
950
|
+
[ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
|
|
951
|
+
[ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
952
|
+
- name: Assemble exact dual-architecture indexes
|
|
953
|
+
run: |
|
|
954
|
+
set -euo pipefail
|
|
955
|
+
IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
|
|
956
|
+
for project in "\${projects[@]}"; do
|
|
957
|
+
test -n "$project" || continue
|
|
958
|
+
pnpm nx run "$project:container-release-assemble"
|
|
959
|
+
binding=".ebk/evidence/container-release/$EBK_STAGE/packages/$project/binding.json"
|
|
960
|
+
sidecar=".ebk/evidence/container-release/$EBK_STAGE/packages/$project/release-bound-binding.json"
|
|
961
|
+
test -f "$binding" && test ! -e "$sidecar"
|
|
962
|
+
cp "$binding" "$sidecar"
|
|
963
|
+
cmp "$binding" "$sidecar"
|
|
964
|
+
done
|
|
965
|
+
- id: retain
|
|
966
|
+
name: Retain exact predeploy container bundle
|
|
967
|
+
uses: ${actionPins.uploadArtifact}
|
|
968
|
+
with:
|
|
969
|
+
name: container-release-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
970
|
+
if-no-files-found: error
|
|
971
|
+
include-hidden-files: true
|
|
972
|
+
retention-days: 30
|
|
973
|
+
path: .ebk/evidence/container-release
|
|
974
|
+
|
|
975
|
+
container-promote-production:
|
|
976
|
+
name: promote reviewed OCI indexes to production
|
|
977
|
+
needs: [prepare, immutable-artifacts]
|
|
978
|
+
if: >-
|
|
979
|
+
always() &&
|
|
980
|
+
needs.prepare.result == 'success' &&
|
|
981
|
+
needs.immutable-artifacts.result == 'success' &&
|
|
982
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
983
|
+
needs.prepare.outputs.stage == 'production' &&
|
|
984
|
+
needs.prepare.outputs.profile != 'starter' &&
|
|
985
|
+
needs.prepare.outputs.has_slices == 'true'
|
|
986
|
+
runs-on: ubuntu-latest
|
|
987
|
+
environment: production
|
|
988
|
+
permissions:
|
|
989
|
+
actions: read
|
|
990
|
+
contents: read
|
|
991
|
+
id-token: write
|
|
992
|
+
outputs:
|
|
993
|
+
artifact_digest: \${{ steps.retain.outputs.artifact-digest }}
|
|
994
|
+
artifact_id: \${{ steps.retain.outputs.artifact-id }}
|
|
995
|
+
env:
|
|
996
|
+
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
997
|
+
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
998
|
+
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
999
|
+
EBK_ACTION: deploy
|
|
1000
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
|
|
1001
|
+
EBK_POLICY_PRODUCTION_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
|
|
1002
|
+
EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
|
|
1003
|
+
EBK_POLICY_STAGING_ACCOUNT_ID: \${{ needs.prepare.outputs.staging_account_id }}
|
|
1004
|
+
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit }}
|
|
1005
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1006
|
+
EBK_STAGE: production
|
|
1007
|
+
EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
|
|
1008
|
+
steps:
|
|
1009
|
+
- name: Check out immutable production source
|
|
1010
|
+
uses: ${actionPins.checkout}
|
|
1011
|
+
env:
|
|
1012
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1013
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1014
|
+
with:
|
|
1015
|
+
fetch-depth: 0
|
|
1016
|
+
persist-credentials: false
|
|
1017
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1018
|
+
- uses: ${actionPins.pnpmSetup}
|
|
1019
|
+
env:
|
|
1020
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1021
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1022
|
+
with:
|
|
1023
|
+
version: ${toolVersions.pnpm}
|
|
1024
|
+
- uses: ${actionPins.setupNode}
|
|
1025
|
+
env:
|
|
1026
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1027
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1028
|
+
with:
|
|
1029
|
+
node-version: ${toolVersions.node}
|
|
1030
|
+
cache: pnpm
|
|
1031
|
+
- env:
|
|
1032
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1033
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1034
|
+
run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1035
|
+
- name: Verify managed workspace integrity before container authority
|
|
1036
|
+
env:
|
|
1037
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1038
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1039
|
+
run: pnpm nx run ${name}:workspace-health
|
|
1040
|
+
- name: Restore exact immutable release candidate for promotion
|
|
1041
|
+
uses: ${actionPins.downloadArtifact}
|
|
1042
|
+
env:
|
|
1043
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1044
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1045
|
+
with:
|
|
1046
|
+
artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
|
|
1047
|
+
github-token: \${{ github.token }}
|
|
1048
|
+
path: .ebk/evidence/release-candidate
|
|
1049
|
+
run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
|
|
1050
|
+
- name: Restore exact staging container bundle
|
|
1051
|
+
uses: ${actionPins.downloadArtifact}
|
|
1052
|
+
env:
|
|
1053
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1054
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1055
|
+
with:
|
|
1056
|
+
artifact-ids: \${{ needs.immutable-artifacts.outputs.container_artifact_id }}
|
|
1057
|
+
github-token: \${{ github.token }}
|
|
1058
|
+
path: .ebk/evidence/container-release
|
|
1059
|
+
run-id: \${{ needs.immutable-artifacts.outputs.staging_deployment_run_id }}
|
|
1060
|
+
- name: Revalidate exact protected branch tip before OIDC
|
|
1061
|
+
env:
|
|
1062
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1063
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1064
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
1065
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1066
|
+
GH_REPO: \${{ github.repository }}
|
|
1067
|
+
GH_TOKEN: \${{ github.token }}
|
|
1068
|
+
run: |
|
|
1069
|
+
set -euo pipefail
|
|
1070
|
+
[ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1071
|
+
echo 'Deployment checkout differs from the exact protected source'; exit 1;
|
|
1072
|
+
}
|
|
1073
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
1074
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1075
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
1076
|
+
}
|
|
1077
|
+
- name: Assume exact staging registry pull role
|
|
1078
|
+
uses: ${actionPins.configureAwsCredentials}
|
|
1079
|
+
with:
|
|
1080
|
+
aws-region: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1081
|
+
role-session-name: ebk-staging-container-pull-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1082
|
+
role-to-assume: \${{ secrets.AWS_STAGING_PROMOTION_PULL_ROLE_ARN }}
|
|
1083
|
+
- name: Authenticate exact staging source registry
|
|
1084
|
+
env:
|
|
1085
|
+
EBK_STAGING_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
|
|
1086
|
+
EBK_STAGING_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1087
|
+
run: |
|
|
1088
|
+
set -euo pipefail
|
|
1089
|
+
[ "$EBK_STAGING_ACCOUNT_ID" = "$EBK_POLICY_STAGING_ACCOUNT_ID" ]
|
|
1090
|
+
[ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_STAGING_ACCOUNT_ID" ]
|
|
1091
|
+
[ "$EBK_STAGING_ACCOUNT_ID" != "$AWS_EXPECTED_ACCOUNT_ID" ]
|
|
1092
|
+
registry_suffix=.com
|
|
1093
|
+
case "$EBK_STAGING_REGION" in cn-*) registry_suffix=.com.cn ;; esac
|
|
1094
|
+
aws ecr get-login-password --region "$EBK_STAGING_REGION" |
|
|
1095
|
+
docker login --username AWS --password-stdin \
|
|
1096
|
+
"$EBK_STAGING_ACCOUNT_ID.dkr.ecr.$EBK_STAGING_REGION.amazonaws$registry_suffix"
|
|
1097
|
+
- name: Revalidate exact protected branch tip before OIDC
|
|
1098
|
+
env:
|
|
1099
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1100
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1101
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
1102
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1103
|
+
GH_REPO: \${{ github.repository }}
|
|
1104
|
+
GH_TOKEN: \${{ github.token }}
|
|
1105
|
+
run: |
|
|
1106
|
+
set -euo pipefail
|
|
1107
|
+
[ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1108
|
+
echo 'Deployment checkout differs from the exact protected source'; exit 1;
|
|
1109
|
+
}
|
|
1110
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
1111
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1112
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
1113
|
+
}
|
|
1114
|
+
- name: Assume the production deployment role for container promotion
|
|
1115
|
+
uses: ${actionPins.configureAwsCredentials}
|
|
1116
|
+
with:
|
|
1117
|
+
aws-region: \${{ vars.AWS_REGION }}
|
|
1118
|
+
role-session-name: ebk-production-container-push-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1119
|
+
role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
|
|
1120
|
+
- name: Promote exact staging indexes without rebuilding
|
|
1121
|
+
env:
|
|
1122
|
+
EBK_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_STAGING_PROMOTION_EXPECTED_ACCOUNT_ID }}
|
|
1123
|
+
EBK_STAGING_PROMOTION_REGION: \${{ vars.AWS_STAGING_PROMOTION_REGION }}
|
|
1124
|
+
run: |
|
|
1125
|
+
set -euo pipefail
|
|
1126
|
+
[ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_PRODUCTION_ACCOUNT_ID" ]
|
|
1127
|
+
[ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
|
|
1128
|
+
[ "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID" ]
|
|
1129
|
+
IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
|
|
1130
|
+
for project in "\${projects[@]}"; do
|
|
1131
|
+
test -n "$project" || continue
|
|
1132
|
+
export EBK_CONTAINER_PROMOTION_SOURCE_BINDING_PATH="../../.ebk/evidence/container-release/staging/packages/$project/binding.json"
|
|
1133
|
+
export EBK_CONTAINER_PROMOTION_SOURCE_SIDECAR_PATH="../../.ebk/evidence/container-release/staging/packages/$project/release-bound-binding.json"
|
|
1134
|
+
pnpm nx run "$project:container-release-promote"
|
|
1135
|
+
done
|
|
1136
|
+
- id: retain
|
|
1137
|
+
name: Retain production target container binding
|
|
1138
|
+
uses: ${actionPins.uploadArtifact}
|
|
1139
|
+
with:
|
|
1140
|
+
name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1141
|
+
if-no-files-found: error
|
|
1142
|
+
include-hidden-files: true
|
|
1143
|
+
retention-days: 90
|
|
1144
|
+
path: .ebk/evidence/container-release
|
|
1145
|
+
|
|
1146
|
+
execute:
|
|
1147
|
+
name: native \${{ needs.prepare.outputs.action }} \${{ needs.prepare.outputs.stage }}
|
|
1148
|
+
needs: [prepare, immutable-artifacts, container-assemble, container-promote-production]
|
|
1149
|
+
if: >-
|
|
1150
|
+
always() &&
|
|
1151
|
+
needs.prepare.result == 'success' &&
|
|
1152
|
+
(
|
|
1153
|
+
needs.prepare.outputs.action != 'deploy' ||
|
|
1154
|
+
(
|
|
1155
|
+
needs.prepare.outputs.has_slices != 'true' &&
|
|
1156
|
+
(needs.prepare.outputs.stage != 'production' || needs.prepare.outputs.profile == 'starter')
|
|
1157
|
+
) || (
|
|
1158
|
+
needs.immutable-artifacts.result == 'success' &&
|
|
1159
|
+
(
|
|
1160
|
+
needs.prepare.outputs.has_slices != 'true' ||
|
|
1161
|
+
needs.prepare.outputs.stage == 'development' ||
|
|
1162
|
+
(
|
|
1163
|
+
(
|
|
1164
|
+
needs.prepare.outputs.stage == 'staging' ||
|
|
1165
|
+
(needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
|
|
1166
|
+
) &&
|
|
1167
|
+
needs.container-assemble.result == 'success'
|
|
1168
|
+
) ||
|
|
1169
|
+
(
|
|
1170
|
+
needs.prepare.outputs.stage == 'production' &&
|
|
1171
|
+
needs.prepare.outputs.profile != 'starter' &&
|
|
1172
|
+
needs.container-promote-production.result == 'success'
|
|
1173
|
+
)
|
|
1174
|
+
)
|
|
1175
|
+
)
|
|
1176
|
+
)
|
|
1177
|
+
runs-on: ubuntu-latest
|
|
1178
|
+
environment: \${{ needs.prepare.outputs.stage }}
|
|
1179
|
+
permissions:
|
|
1180
|
+
actions: read
|
|
1181
|
+
contents: read
|
|
1182
|
+
id-token: write
|
|
1183
|
+
env:
|
|
1184
|
+
AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
1185
|
+
AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
|
|
1186
|
+
AWS_REGION: \${{ vars.AWS_REGION }}
|
|
1187
|
+
EBK_ACTION: \${{ needs.prepare.outputs.action }}
|
|
1188
|
+
EBK_ALLOW_PRODUCTION_REMOVE: \${{ vars.EBK_ALLOW_PRODUCTION_REMOVE }}
|
|
1189
|
+
EBK_CANDIDATE_ARTIFACT_DIGEST: \${{ needs.immutable-artifacts.outputs.artifact_digest }}
|
|
1190
|
+
EBK_CANDIDATE_ARTIFACT_ID: \${{ needs.immutable-artifacts.outputs.artifact_id }}
|
|
1191
|
+
EBK_CANDIDATE_MATERIAL_DIGEST: \${{ needs.immutable-artifacts.outputs.candidate_material_digest }}
|
|
1192
|
+
EBK_CANDIDATE_PRODUCER_RUN_ATTEMPT: \${{ needs.immutable-artifacts.outputs.producer_run_attempt }}
|
|
1193
|
+
EBK_CANDIDATE_PRODUCER_RUN_ID: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
|
|
1194
|
+
EBK_DEPLOYMENT_PROFILE: \${{ needs.prepare.outputs.profile }}
|
|
1195
|
+
EBK_ORDERED_PROJECTS: \${{ needs.prepare.outputs.ordered_projects }}
|
|
1196
|
+
EBK_POLICY_ACCOUNT_ID: \${{ needs.prepare.outputs.account_id }}
|
|
1197
|
+
EBK_POLICY_REGION: \${{ needs.prepare.outputs.region }}
|
|
1198
|
+
EBK_RELEASE_SOURCE_COMMIT: \${{ needs.immutable-artifacts.outputs.source_commit || env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1199
|
+
EBK_SLICE_PROJECTS: \${{ needs.prepare.outputs.slice_projects }}
|
|
1200
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1201
|
+
EBK_STAGE: \${{ needs.prepare.outputs.stage }}
|
|
1202
|
+
steps:
|
|
1203
|
+
- name: Check out immutable deployment source
|
|
1204
|
+
uses: ${actionPins.checkout}
|
|
1205
|
+
env:
|
|
1206
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1207
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1208
|
+
with:
|
|
1209
|
+
fetch-depth: 0
|
|
1210
|
+
persist-credentials: false
|
|
1211
|
+
ref: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1212
|
+
- uses: ${actionPins.pnpmSetup}
|
|
1213
|
+
env:
|
|
1214
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1215
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1216
|
+
with:
|
|
1217
|
+
version: ${toolVersions.pnpm}
|
|
1218
|
+
- uses: ${actionPins.setupNode}
|
|
1219
|
+
env:
|
|
1220
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1221
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1222
|
+
with:
|
|
1223
|
+
node-version: ${toolVersions.node}
|
|
1224
|
+
cache: pnpm
|
|
1225
|
+
- env:
|
|
1226
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1227
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1228
|
+
run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
|
|
1229
|
+
- name: Verify managed workspace integrity before AWS authority
|
|
1230
|
+
env:
|
|
1231
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1232
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1233
|
+
run: pnpm nx run ${name}:workspace-health
|
|
1234
|
+
- name: Restore exact immutable release candidate
|
|
1235
|
+
if: needs.prepare.outputs.action == 'deploy' && needs.prepare.outputs.has_slices == 'true'
|
|
1236
|
+
uses: ${actionPins.downloadArtifact}
|
|
1237
|
+
env:
|
|
1238
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1239
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1240
|
+
with:
|
|
1241
|
+
artifact-ids: \${{ needs.immutable-artifacts.outputs.artifact_id }}
|
|
1242
|
+
github-token: \${{ github.token }}
|
|
1243
|
+
path: .ebk/evidence/release-candidate
|
|
1244
|
+
run-id: \${{ needs.immutable-artifacts.outputs.producer_run_id }}
|
|
1245
|
+
- name: Restore exact current-stage container binding
|
|
1246
|
+
if: >-
|
|
1247
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
1248
|
+
needs.prepare.outputs.has_slices == 'true' &&
|
|
1249
|
+
(
|
|
1250
|
+
needs.prepare.outputs.stage == 'staging' ||
|
|
1251
|
+
(needs.prepare.outputs.stage == 'production' && needs.prepare.outputs.profile == 'starter')
|
|
1252
|
+
)
|
|
1253
|
+
uses: ${actionPins.downloadArtifact}
|
|
1254
|
+
env:
|
|
1255
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1256
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1257
|
+
with:
|
|
1258
|
+
name: container-release-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1259
|
+
path: .ebk/evidence/container-release
|
|
1260
|
+
- name: Restore exact promoted production container binding
|
|
1261
|
+
if: >-
|
|
1262
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
1263
|
+
needs.prepare.outputs.has_slices == 'true' &&
|
|
1264
|
+
needs.prepare.outputs.stage == 'production' &&
|
|
1265
|
+
needs.prepare.outputs.profile != 'starter'
|
|
1266
|
+
uses: ${actionPins.downloadArtifact}
|
|
1267
|
+
env:
|
|
1268
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1269
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1270
|
+
with:
|
|
1271
|
+
name: container-release-production-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1272
|
+
path: .ebk/evidence/container-release
|
|
1273
|
+
- name: Verify exact predeploy container binding inventory
|
|
1274
|
+
if: >-
|
|
1275
|
+
needs.prepare.outputs.action == 'deploy' &&
|
|
1276
|
+
needs.prepare.outputs.has_slices == 'true' &&
|
|
1277
|
+
needs.prepare.outputs.stage != 'development'
|
|
1278
|
+
env:
|
|
1279
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1280
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1281
|
+
run: |
|
|
1282
|
+
set -euo pipefail
|
|
1283
|
+
IFS=',' read -r -a projects <<< "$EBK_SLICE_PROJECTS"
|
|
1284
|
+
for project in "\${projects[@]}"; do
|
|
1285
|
+
test -n "$project" || continue
|
|
1286
|
+
test -f ".ebk/evidence/container-release/$EBK_STAGE/packages/$project/binding.json" || {
|
|
1287
|
+
echo "Exact predeploy container binding is absent for $project"
|
|
1288
|
+
exit 1
|
|
1289
|
+
}
|
|
1290
|
+
done
|
|
1291
|
+
- name: Verify exact restored staging-tested release candidate
|
|
1292
|
+
if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE == 'production' && needs.prepare.outputs.profile != 'starter' && needs.prepare.outputs.has_slices == 'true'
|
|
1293
|
+
env:
|
|
1294
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1295
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1296
|
+
run: |
|
|
1297
|
+
node --input-type=module <<'NODE'
|
|
1298
|
+
${indentedScript(restoredCandidateValidationScript(), 10)}
|
|
1299
|
+
NODE
|
|
1300
|
+
- name: Authorize deployed Blueprint trust before protected mutation
|
|
1301
|
+
if: env.EBK_ACTION == 'deploy'
|
|
1302
|
+
env:
|
|
1303
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1304
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1305
|
+
EBK_PROTECTED_BLUEPRINT_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1306
|
+
run: |
|
|
1307
|
+
set -euo pipefail
|
|
1308
|
+
case "$EBK_PROTECTED_BLUEPRINT_COMMIT" in
|
|
1309
|
+
''|*[!0-9a-f]*) echo 'Protected Blueprint authority must be one full lowercase Git object ID'; exit 1 ;;
|
|
1310
|
+
esac
|
|
1311
|
+
test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 40 ||
|
|
1312
|
+
test "\${#EBK_PROTECTED_BLUEPRINT_COMMIT}" -eq 64 ||
|
|
1313
|
+
{ echo 'Protected Blueprint authority must be one full Git object ID'; exit 1; }
|
|
1314
|
+
test "$(git rev-parse HEAD)" = "$EBK_PROTECTED_BLUEPRINT_COMMIT" ||
|
|
1315
|
+
{ echo 'Protected Blueprint authority differs from the exact workflow checkout'; exit 1; }
|
|
1316
|
+
case "$EBK_STAGE" in
|
|
1317
|
+
staging) protected_branch=develop ;;
|
|
1318
|
+
production) protected_branch=main ;;
|
|
1319
|
+
*) exit 1 ;;
|
|
1320
|
+
esac
|
|
1321
|
+
node tools/blueprint-trust-check.mjs \
|
|
1322
|
+
--operation=deploy \
|
|
1323
|
+
--protected-branch="$protected_branch" \
|
|
1324
|
+
--protected-ref="$EBK_PROTECTED_BLUEPRINT_COMMIT" \
|
|
1325
|
+
--output=.ebk/evidence/supply-chain/blueprint-trust.json
|
|
1326
|
+
- name: Produce subject-bound protected scanner evidence
|
|
1327
|
+
if: env.EBK_ACTION == 'deploy'
|
|
1328
|
+
env:
|
|
1329
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1330
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1331
|
+
run: pnpm nx run ${name}:supply-chain-scan
|
|
1332
|
+
- name: Enforce protected supply-chain policy before mutation
|
|
1333
|
+
if: env.EBK_ACTION == 'deploy'
|
|
1334
|
+
env:
|
|
1335
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1336
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1337
|
+
run: pnpm nx run ${name}:supply-chain-check-protected
|
|
1338
|
+
- name: Revalidate exact protected branch tip before OIDC
|
|
1339
|
+
env:
|
|
1340
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1341
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1342
|
+
EBK_SOURCE_BRANCH: \${{ env.EBK_DEPLOYMENT_SOURCE_BRANCH }}
|
|
1343
|
+
EBK_SOURCE_COMMIT: \${{ env.EBK_DEPLOYMENT_SOURCE_COMMIT }}
|
|
1344
|
+
GH_REPO: \${{ github.repository }}
|
|
1345
|
+
GH_TOKEN: \${{ github.token }}
|
|
1346
|
+
run: |
|
|
1347
|
+
set -euo pipefail
|
|
1348
|
+
[ "$(git rev-parse HEAD)" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1349
|
+
echo 'Deployment checkout differs from the exact protected source'; exit 1;
|
|
1350
|
+
}
|
|
1351
|
+
branch_tip="$(gh api "/repos/$GH_REPO/git/ref/heads/$EBK_SOURCE_BRANCH" --jq '.object.sha')"
|
|
1352
|
+
[ "$branch_tip" = "$EBK_SOURCE_COMMIT" ] || {
|
|
1353
|
+
echo 'Deployment source is no longer the exact protected branch tip'; exit 1;
|
|
1354
|
+
}
|
|
1355
|
+
- name: Assume the one stage deployment role
|
|
1356
|
+
uses: ${actionPins.configureAwsCredentials}
|
|
1357
|
+
with:
|
|
1358
|
+
aws-region: \${{ vars.AWS_REGION }}
|
|
1359
|
+
role-session-name: ebk-native-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1360
|
+
role-to-assume: \${{ env.AWS_DEPLOY_ROLE_ARN }}
|
|
1361
|
+
- name: Verify exact stage authority
|
|
1362
|
+
run: |
|
|
1363
|
+
set -euo pipefail
|
|
1364
|
+
[ "$AWS_EXPECTED_ACCOUNT_ID" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
1365
|
+
[ "$AWS_REGION" = "$EBK_POLICY_REGION" ]
|
|
1366
|
+
[ "$(aws sts get-caller-identity --query Account --output text)" = "$EBK_POLICY_ACCOUNT_ID" ]
|
|
1367
|
+
- name: Run ordered native lifecycle targets
|
|
1368
|
+
env:
|
|
1369
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1370
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1371
|
+
EBK_GITHUB_JOB_WORKFLOW_REF: \${{ job.workflow_ref }}
|
|
1372
|
+
run: |
|
|
1373
|
+
set -euo pipefail
|
|
1374
|
+
test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'Stage deployment role is absent'; exit 1; }
|
|
1375
|
+
if [ "$EBK_ACTION" = deploy ]; then
|
|
1376
|
+
pnpm nx run ${name}:deploy-local -- \
|
|
1377
|
+
--stage="$EBK_STAGE" \
|
|
1378
|
+
--expectedRoleArn="$AWS_DEPLOY_ROLE_ARN"
|
|
1379
|
+
else
|
|
1380
|
+
[ -n "$EBK_ORDERED_PROJECTS" ] || { echo 'Committed deployment order is empty'; exit 1; }
|
|
1381
|
+
IFS=',' read -r -a projects <<< "$EBK_ORDERED_PROJECTS"
|
|
1382
|
+
seen=,
|
|
1383
|
+
for project in "\${projects[@]}"; do
|
|
1384
|
+
case "$project" in ''|*[!A-Za-z0-9_-]*) exit 1 ;; esac
|
|
1385
|
+
case "$seen" in *,"$project",*) echo "Duplicate lifecycle project: $project"; exit 1 ;; esac
|
|
1386
|
+
seen="$seen$project,"
|
|
1387
|
+
pnpm nx run "$project:$EBK_ACTION" -- --stage="$EBK_STAGE" --region="$AWS_REGION" --expectedRoleArn="$AWS_DEPLOY_ROLE_ARN"
|
|
1388
|
+
done
|
|
1389
|
+
fi
|
|
1390
|
+
- name: Seal exact staging promotion receipt
|
|
1391
|
+
if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE == 'staging' && needs.prepare.outputs.profile != 'starter'
|
|
1392
|
+
env:
|
|
1393
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1394
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1395
|
+
EBK_CONTAINER_ARTIFACT_DIGEST: \${{ needs.container-assemble.outputs.artifact_digest }}
|
|
1396
|
+
EBK_CONTAINER_ARTIFACT_ID: \${{ needs.container-assemble.outputs.artifact_id }}
|
|
1397
|
+
EBK_HAS_SLICES: \${{ needs.prepare.outputs.has_slices }}
|
|
1398
|
+
run: |
|
|
1399
|
+
node --input-type=module <<'NODE'
|
|
1400
|
+
${indentedScript(stagingPromotionSealScript(), 10)}
|
|
1401
|
+
NODE
|
|
1402
|
+
- name: Seal exact postdeploy supply-chain binding
|
|
1403
|
+
if: env.EBK_ACTION == 'deploy' && env.EBK_STAGE != 'development' && needs.prepare.outputs.has_slices == 'true'
|
|
1404
|
+
env:
|
|
1405
|
+
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ''
|
|
1406
|
+
ACTIONS_ID_TOKEN_REQUEST_URL: ''
|
|
1407
|
+
run: |
|
|
1408
|
+
node --input-type=module <<'NODE'
|
|
1409
|
+
${indentedScript(postdeploySupplyChainBindingScript(), 10)}
|
|
1410
|
+
NODE
|
|
1411
|
+
- name: Retain bounded deployment receipt
|
|
1412
|
+
if: always() && needs.prepare.outputs.action == 'deploy'
|
|
1413
|
+
uses: ${actionPins.uploadArtifact}
|
|
1414
|
+
with:
|
|
1415
|
+
if-no-files-found: warn
|
|
1416
|
+
name: deployment-receipt-\${{ needs.prepare.outputs.stage }}-\${{ github.run_id }}-\${{ github.run_attempt }}
|
|
1417
|
+
path: |
|
|
1418
|
+
.ebk/evidence/deployment/latest.json
|
|
1419
|
+
.ebk/evidence/deployment/postdeploy-supply-chain.json
|
|
1420
|
+
.ebk/evidence/deployment/promotion.json
|
|
1421
|
+
.ebk/evidence/container-release/**
|
|
1422
|
+
retention-days: 30
|
|
1423
|
+
`;
|
|
1424
|
+
}
|
|
1425
|
+
function renderThinDeployWorkflowFilePlan(options) {
|
|
1426
|
+
assertOptions(options);
|
|
1427
|
+
return {
|
|
1428
|
+
files: {
|
|
1429
|
+
[exports.THIN_DEPLOY_WORKFLOW_PATH]: renderThinDeployWorkflow(options),
|
|
1430
|
+
},
|
|
1431
|
+
managedPaths: [exports.THIN_DEPLOY_WORKFLOW_PATH],
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
//# sourceMappingURL=workspace-files-thin-deploy.js.map
|