@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
|
@@ -1,911 +1,1045 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
3
|
+
exports.localWorkingTreeDigest = localWorkingTreeDigest;
|
|
4
|
+
exports.localDeploymentProjectOrder = localDeploymentProjectOrder;
|
|
5
5
|
exports.runLocalDeploy = runLocalDeploy;
|
|
6
6
|
const node_crypto_1 = require("node:crypto");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const node_fs_1 = require("node:fs");
|
|
9
|
-
const node_os_1 = require("node:os");
|
|
10
9
|
const node_path_1 = require("node:path");
|
|
11
|
-
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
12
|
-
const client_ssm_1 = require("@aws-sdk/client-ssm");
|
|
13
|
-
const credential_providers_1 = require("@aws-sdk/credential-providers");
|
|
14
10
|
const devkit_1 = require("@nx/devkit");
|
|
15
|
-
const
|
|
16
|
-
const
|
|
11
|
+
const client_sts_1 = require("@aws-sdk/client-sts");
|
|
12
|
+
const index_js_1 = require("../../aws-authority/index.js");
|
|
13
|
+
const child_environment_js_1 = require("../../aws/child-environment.js");
|
|
14
|
+
const identity_js_1 = require("../../aws/identity.js");
|
|
17
15
|
const index_js_2 = require("../../repository-policy/index.js");
|
|
18
|
-
const
|
|
16
|
+
const production_database_safeguard_js_1 = require("../foundation-profile-preflight/production-database-safeguard.js");
|
|
19
17
|
const private_state_js_1 = require("../../local-dev/private-state.js");
|
|
20
|
-
const
|
|
21
|
-
const sst_environment_source_js_1 = require("../sst-lifecycle/sst-environment-source.js");
|
|
18
|
+
const federated_credential_transport_js_1 = require("./federated-credential-transport.js");
|
|
22
19
|
const local_approval_js_1 = require("./local-approval.js");
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const ROLE = /^arn:(aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
'
|
|
39
|
-
];
|
|
40
|
-
const PROTECTED_PLAN_KEYS = [
|
|
41
|
-
'EBK_LOCAL_MUTATION_CAPABILITY_NONCE',
|
|
42
|
-
'EBK_LOCAL_MUTATION_CAPABILITY_PATH',
|
|
43
|
-
'EBK_LOCAL_PLAN_APPROVER_ARN',
|
|
44
|
-
'EBK_LOCAL_PLAN_MATERIAL_DIGEST',
|
|
45
|
-
'EBK_LOCAL_PLAN_SUMMARY_DIGEST',
|
|
46
|
-
'AWS_PLAN_GENERATED_OBJECT_ARNS_JSON',
|
|
47
|
-
'AWS_PLAN_GENERATED_PARAMETER_ARNS_JSON',
|
|
48
|
-
'AWS_PLAN_GENERATED_SECRET_ARNS_JSON',
|
|
49
|
-
'AWS_PLAN_PROVIDER_REFRESH_POLICY_JSON',
|
|
50
|
-
'AWS_PLAN_STATE_KMS_KEY_ARN',
|
|
51
|
-
'EBK_RELEASE_SOURCE_COMMIT',
|
|
52
|
-
];
|
|
53
|
-
function projectEvidencePath(context, project, name) {
|
|
54
|
-
const configured = context.projectsConfigurations?.projects[project]?.root;
|
|
55
|
-
if (!configured) {
|
|
56
|
-
throw new Error(`Local deployment references unknown project ${project}.`);
|
|
57
|
-
}
|
|
58
|
-
return (0, node_path_1.join)((0, node_path_1.isAbsolute)(configured) ? configured : (0, node_path_1.join)(context.root, configured), '.ebk', 'evidence', name);
|
|
59
|
-
}
|
|
60
|
-
function writeLocalMutationCapability(input) {
|
|
61
|
-
const nonce = (0, node_crypto_1.randomBytes)(32).toString('hex');
|
|
62
|
-
const directory = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'ebk-local-mutation-'));
|
|
63
|
-
(0, private_state_js_1.ensurePrivateDirectory)(directory);
|
|
64
|
-
const path = (0, node_path_1.join)(directory, 'capability.json');
|
|
65
|
-
const planContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan.json'), 'utf8');
|
|
66
|
-
const authorizationContents = (0, node_fs_1.readFileSync)(projectEvidencePath(input.context, input.project, 'infrastructure-plan-authorization.json'), 'utf8');
|
|
67
|
-
const capability = (0, local_capability_js_1.createLocalMutationCapability)({
|
|
68
|
-
actorArn: input.actorArn,
|
|
69
|
-
authorizationContents,
|
|
70
|
-
deploymentProfile: input.deploymentProfile,
|
|
71
|
-
deploymentSourceCommit: input.deploymentSourceCommit,
|
|
72
|
-
deploymentSourceTree: input.deploymentSourceTree,
|
|
73
|
-
expectedRoleArn: input.expectedRoleArn,
|
|
74
|
-
issuedAt: input.issuedAt,
|
|
75
|
-
nonce,
|
|
76
|
-
plan: input.plan,
|
|
77
|
-
planContents,
|
|
78
|
-
project: input.project,
|
|
79
|
-
releaseSourceTree: input.releaseSourceTree,
|
|
80
|
-
});
|
|
81
|
-
(0, private_state_js_1.atomicWritePrivateFile)(path, `${JSON.stringify(capability, null, 2)}\n`);
|
|
82
|
-
return { directory, nonce, path };
|
|
83
|
-
}
|
|
84
|
-
function digest(value) {
|
|
85
|
-
return (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
|
|
86
|
-
}
|
|
87
|
-
function ssmParameterAuthority(value, expectedName) {
|
|
88
|
-
const parameter = record(value, expectedName);
|
|
89
|
-
if (parameter['Name'] !== expectedName ||
|
|
90
|
-
parameter['Type'] !== 'SecureString' ||
|
|
91
|
-
typeof parameter['Version'] !== 'number' ||
|
|
92
|
-
!Number.isSafeInteger(parameter['Version']) ||
|
|
93
|
-
parameter['Version'] < 1) {
|
|
94
|
-
throw new Error(`Local SST backend parameter ${expectedName} is missing or malformed.`);
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
name: expectedName,
|
|
98
|
-
type: parameter['Type'],
|
|
99
|
-
version: parameter['Version'],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/** @internal */
|
|
103
|
-
async function prepareLocalSstBackend(input, dependencies = {}) {
|
|
104
|
-
if (input.applications.length === 0 ||
|
|
105
|
-
new Set(input.applications).size !== input.applications.length ||
|
|
106
|
-
input.applications.some((application) => !/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(application))) {
|
|
107
|
-
throw new Error('Local SST backend requires a unique exact application inventory.');
|
|
108
|
-
}
|
|
109
|
-
const client = new client_ssm_1.SSMClient({
|
|
110
|
-
region: input.region,
|
|
111
|
-
...(input.profile
|
|
112
|
-
? { credentials: (0, credential_providers_1.fromIni)({ profile: input.profile }) }
|
|
113
|
-
: {}),
|
|
114
|
-
});
|
|
115
|
-
const send = dependencies.send ??
|
|
116
|
-
(async (command) => client.send(command));
|
|
117
|
-
const bootstrapResponse = await send(new client_ssm_1.GetParameterCommand({ Name: '/sst/bootstrap' }));
|
|
118
|
-
const bootstrapParameter = bootstrapResponse.Parameter;
|
|
119
|
-
let bootstrap;
|
|
120
|
-
try {
|
|
121
|
-
bootstrap = record(JSON.parse(bootstrapParameter?.Value ?? ''), 'Local SST bootstrap value');
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
|
|
125
|
-
}
|
|
126
|
-
const exactBootstrapKeys = [
|
|
127
|
-
'appsyncHttp',
|
|
128
|
-
'appsyncRealtime',
|
|
129
|
-
'asset',
|
|
130
|
-
'assetEcrRegistryId',
|
|
131
|
-
'assetEcrUrl',
|
|
132
|
-
'state',
|
|
133
|
-
'version',
|
|
134
|
-
];
|
|
135
|
-
const state = bootstrap['state'];
|
|
136
|
-
if (bootstrapParameter?.Name !== '/sst/bootstrap' ||
|
|
137
|
-
bootstrapParameter.Type !== 'String' ||
|
|
138
|
-
typeof bootstrapParameter.Version !== 'number' ||
|
|
139
|
-
!Number.isSafeInteger(bootstrapParameter.Version) ||
|
|
140
|
-
bootstrapParameter.Version < 1 ||
|
|
141
|
-
Object.keys(bootstrap).sort().join(',') !== exactBootstrapKeys.join(',') ||
|
|
142
|
-
bootstrap['version'] !== 5 ||
|
|
143
|
-
typeof state !== 'string' ||
|
|
144
|
-
!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(state) ||
|
|
145
|
-
typeof bootstrap['asset'] !== 'string' ||
|
|
146
|
-
!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bootstrap['asset']) ||
|
|
147
|
-
bootstrap['assetEcrRegistryId'] !== input.account ||
|
|
148
|
-
bootstrap['assetEcrUrl'] !==
|
|
149
|
-
`${input.account}.dkr.ecr.${input.region}.amazonaws.com/sst-asset` ||
|
|
150
|
-
typeof bootstrap['appsyncHttp'] !== 'string' ||
|
|
151
|
-
typeof bootstrap['appsyncRealtime'] !== 'string') {
|
|
152
|
-
throw new Error('Local deployment requires an existing exact SST v5 account bootstrap.');
|
|
153
|
-
}
|
|
154
|
-
const parameterAuthority = new Map();
|
|
155
|
-
const missingApplications = [];
|
|
156
|
-
const createdApplications = [];
|
|
157
|
-
for (const application of input.applications) {
|
|
158
|
-
const name = `/sst/passphrase/${application}/${input.stage}`;
|
|
159
|
-
try {
|
|
160
|
-
const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
|
|
161
|
-
parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
if (!(error instanceof Error) || error.name !== 'ParameterNotFound') {
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
missingApplications.push(application);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
for (const application of missingApplications) {
|
|
171
|
-
const name = `/sst/passphrase/${application}/${input.stage}`;
|
|
172
|
-
await send(new client_ssm_1.PutParameterCommand({
|
|
173
|
-
Description: `SST state passphrase for ${application}/${input.stage}`,
|
|
174
|
-
Name: name,
|
|
175
|
-
Overwrite: false,
|
|
176
|
-
Type: 'SecureString',
|
|
177
|
-
Value: dependencies.passphrase?.() ?? (0, node_crypto_1.randomBytes)(32).toString('base64'),
|
|
178
|
-
}));
|
|
179
|
-
createdApplications.push(application);
|
|
180
|
-
const observed = await send(new client_ssm_1.GetParameterCommand({ Name: name }));
|
|
181
|
-
parameterAuthority.set(application, ssmParameterAuthority(observed.Parameter, name));
|
|
182
|
-
}
|
|
183
|
-
const parameters = input.applications.map((application) => {
|
|
184
|
-
const authority = parameterAuthority.get(application);
|
|
185
|
-
if (!authority) {
|
|
186
|
-
throw new Error(`Local SST backend parameter authority is absent for ${application}.`);
|
|
187
|
-
}
|
|
188
|
-
return authority;
|
|
189
|
-
});
|
|
190
|
-
return {
|
|
191
|
-
authorityDigest: digest(JSON.stringify({
|
|
192
|
-
applications: [...input.applications],
|
|
193
|
-
bootstrap: {
|
|
194
|
-
name: bootstrapParameter.Name,
|
|
195
|
-
type: bootstrapParameter.Type,
|
|
196
|
-
version: bootstrapParameter.Version,
|
|
197
|
-
},
|
|
198
|
-
parameters,
|
|
199
|
-
region: input.region,
|
|
200
|
-
stage: input.stage,
|
|
201
|
-
state,
|
|
202
|
-
})),
|
|
203
|
-
createdApplications,
|
|
204
|
-
stateBucketArn: `arn:aws:s3:::${state}`,
|
|
205
|
-
};
|
|
20
|
+
const container_release_js_1 = require("../../supply-chain/container-release.js");
|
|
21
|
+
const run_sst_command_js_1 = require("../sst-lifecycle/run-sst-command.js");
|
|
22
|
+
const SHA = /^[a-f0-9]{40,64}$/u;
|
|
23
|
+
const ROLE = /^arn:(aws|aws-us-gov|aws-cn):iam::\d{12}:role\/[A-Za-z0-9+=,.@_/-]{1,512}$/u;
|
|
24
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES = 64 * 1024;
|
|
25
|
+
const MAX_LOCAL_RELEASE_ARTIFACTS_BYTES = 64 * 1024 * 1024;
|
|
26
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_FILES = 32;
|
|
27
|
+
const MAX_LOCAL_RELEASE_CANDIDATE_BYTES = MAX_LOCAL_RELEASE_ARTIFACTS_BYTES +
|
|
28
|
+
(MAX_LOCAL_RELEASE_CANDIDATE_FILES - 1) *
|
|
29
|
+
MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
|
|
30
|
+
function partitionForRegion(region) {
|
|
31
|
+
if (region.startsWith('cn-'))
|
|
32
|
+
return 'aws-cn';
|
|
33
|
+
if (region.startsWith('us-gov-'))
|
|
34
|
+
return 'aws-us-gov';
|
|
35
|
+
return 'aws';
|
|
206
36
|
}
|
|
207
37
|
function git(root, args, run) {
|
|
208
38
|
const result = run('git', args, {
|
|
209
39
|
cwd: root,
|
|
210
40
|
encoding: 'utf8',
|
|
211
41
|
env: process.env,
|
|
212
|
-
maxBuffer: 1024 * 1024,
|
|
42
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
213
43
|
shell: false,
|
|
214
44
|
});
|
|
215
45
|
if (result.status !== 0) {
|
|
216
46
|
throw new Error(`Local deployment Git preflight failed: ${String(result.stderr).trim()}`);
|
|
217
47
|
}
|
|
218
|
-
return String(result.stdout)
|
|
48
|
+
return String(result.stdout);
|
|
219
49
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
encoding: 'utf8',
|
|
224
|
-
env: process.env,
|
|
225
|
-
maxBuffer: 1024 * 1024,
|
|
226
|
-
shell: false,
|
|
227
|
-
});
|
|
228
|
-
if (result.status !== 0 || String(result.stderr) !== '') {
|
|
229
|
-
throw new Error(`Local deployment Git reconciliation failed: ${String(result.stderr).trim()}`);
|
|
50
|
+
function confinedWorkspacePath(root, candidate) {
|
|
51
|
+
if (!candidate || (0, node_path_1.isAbsolute)(candidate) || candidate.includes('\0')) {
|
|
52
|
+
throw new Error('Working-tree inventory contains an invalid path.');
|
|
230
53
|
}
|
|
231
|
-
|
|
54
|
+
const absolute = (0, node_path_1.join)(root, candidate);
|
|
55
|
+
const confined = (0, node_path_1.relative)(root, absolute);
|
|
56
|
+
if (confined === '..' || confined.startsWith('../') || (0, node_path_1.isAbsolute)(confined)) {
|
|
57
|
+
throw new Error('Working-tree inventory escapes the workspace.');
|
|
58
|
+
}
|
|
59
|
+
return absolute;
|
|
232
60
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
61
|
+
/** Bind a development deployment to the exact tracked diff and untracked
|
|
62
|
+
* source bytes without requiring a commit. Ignored caches and build output are
|
|
63
|
+
* intentionally outside this identity. */
|
|
64
|
+
function localWorkingTreeDigest(root, run = node_child_process_1.spawnSync) {
|
|
65
|
+
const commit = git(root, ['rev-parse', 'HEAD'], run).trim();
|
|
66
|
+
if (!SHA.test(commit)) {
|
|
67
|
+
throw new Error('Local deployment could not resolve one exact Git HEAD.');
|
|
68
|
+
}
|
|
69
|
+
const status = git(root, ['status', '--porcelain=v1', '--untracked-files=all'], run);
|
|
70
|
+
const trackedDiff = git(root, ['diff', '--binary', 'HEAD'], run);
|
|
71
|
+
const untracked = git(root, ['ls-files', '--others', '--exclude-standard', '-z'], run)
|
|
72
|
+
.split('\0')
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.sort();
|
|
75
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
76
|
+
hash.update('ebk-working-tree-v1\0');
|
|
77
|
+
hash.update(commit);
|
|
78
|
+
hash.update('\0');
|
|
79
|
+
hash.update(status);
|
|
80
|
+
hash.update('\0');
|
|
81
|
+
hash.update(trackedDiff);
|
|
82
|
+
hash.update('\0');
|
|
83
|
+
for (const path of untracked) {
|
|
84
|
+
const absolute = confinedWorkspacePath(root, path);
|
|
85
|
+
const stat = (0, node_fs_1.lstatSync)(absolute);
|
|
86
|
+
hash.update(path);
|
|
87
|
+
hash.update('\0');
|
|
88
|
+
if (stat.isSymbolicLink()) {
|
|
89
|
+
hash.update('symlink\0');
|
|
90
|
+
hash.update((0, node_fs_1.readlinkSync)(absolute));
|
|
239
91
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
function nulSeparatedPaths(value) {
|
|
249
|
-
return value.split('\0').filter((entry) => entry !== '');
|
|
250
|
-
}
|
|
251
|
-
function reconcileToolOwnedSource(root, snapshots, run) {
|
|
252
|
-
for (const snapshot of snapshots) {
|
|
253
|
-
(0, sst_environment_source_js_1.reconcileSstEnvironmentSourceTree)(snapshot);
|
|
92
|
+
else if (stat.isFile()) {
|
|
93
|
+
hash.update('file\0');
|
|
94
|
+
hash.update((0, node_fs_1.readFileSync)(absolute));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
throw new Error(`Untracked deployment source ${path} is not a regular file or symbolic link.`);
|
|
98
|
+
}
|
|
99
|
+
hash.update('\0');
|
|
254
100
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
|
|
101
|
+
return { clean: status === '', commit, digest: hash.digest('hex') };
|
|
102
|
+
}
|
|
103
|
+
function assertDeploymentSourceUnchanged(root, expected, run) {
|
|
104
|
+
const current = localWorkingTreeDigest(root, run);
|
|
105
|
+
if (current.clean !== expected.clean ||
|
|
106
|
+
current.commit !== expected.commit ||
|
|
107
|
+
current.digest !== expected.workingTreeDigest) {
|
|
108
|
+
throw new Error('Deployment source changed after lifecycle authority was established; downstream execution is blocked.');
|
|
261
109
|
}
|
|
262
110
|
}
|
|
263
|
-
function
|
|
264
|
-
|
|
111
|
+
function projectDescriptors(context) {
|
|
112
|
+
return Object.entries(context.projectsConfigurations?.projects ?? {}).map(([name, configuration]) => ({
|
|
265
113
|
name,
|
|
266
|
-
|
|
267
|
-
|
|
114
|
+
root: configuration.root,
|
|
115
|
+
tags: configuration.tags ?? [],
|
|
116
|
+
targets: configuration.targets ?? {},
|
|
268
117
|
}));
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
118
|
+
}
|
|
119
|
+
function supportsTarget(project, target) {
|
|
120
|
+
return Object.prototype.hasOwnProperty.call(project.targets, target);
|
|
121
|
+
}
|
|
122
|
+
const LOCAL_CONTAINER_TARGETS = Object.freeze([
|
|
123
|
+
'release-candidate',
|
|
124
|
+
'container-release-amd64',
|
|
125
|
+
'container-release-arm64',
|
|
126
|
+
'container-release-assemble',
|
|
127
|
+
]);
|
|
128
|
+
function requiresLocalContainerPreparation(profile, stage) {
|
|
129
|
+
return ((profile === 'starter' && stage === 'production') ||
|
|
130
|
+
(profile === 'production-isolated' && stage === 'staging'));
|
|
131
|
+
}
|
|
132
|
+
function confinedEvidenceSegments(root, candidate) {
|
|
133
|
+
const confined = (0, node_path_1.relative)(root, candidate);
|
|
134
|
+
if (confined === '' ||
|
|
135
|
+
confined === '..' ||
|
|
136
|
+
confined.startsWith(`..${node_path_1.sep}`) ||
|
|
137
|
+
(0, node_path_1.isAbsolute)(confined)) {
|
|
138
|
+
throw new Error('Local container evidence path escapes the workspace.');
|
|
139
|
+
}
|
|
140
|
+
const segments = confined.split(node_path_1.sep);
|
|
141
|
+
if (segments.some((segment) => segment === '' ||
|
|
142
|
+
segment === '.' ||
|
|
143
|
+
segment === '..' ||
|
|
144
|
+
segment.includes('\0'))) {
|
|
145
|
+
throw new Error('Local container evidence path is invalid.');
|
|
146
|
+
}
|
|
147
|
+
return segments;
|
|
148
|
+
}
|
|
149
|
+
function assertSafeEvidenceTree(path) {
|
|
150
|
+
let entries = 0;
|
|
151
|
+
const visit = (directory) => {
|
|
152
|
+
for (const entry of (0, node_fs_1.readdirSync)(directory, { withFileTypes: true })) {
|
|
153
|
+
entries += 1;
|
|
154
|
+
if (entries > 4096) {
|
|
155
|
+
throw new Error('Local container evidence tree is unexpectedly large.');
|
|
156
|
+
}
|
|
157
|
+
const child = (0, node_path_1.join)(directory, entry.name);
|
|
158
|
+
const identity = (0, node_fs_1.lstatSync)(child);
|
|
159
|
+
if (identity.isSymbolicLink()) {
|
|
160
|
+
throw new Error('Local container evidence cleanup refuses symbolic links.');
|
|
161
|
+
}
|
|
162
|
+
if (identity.isDirectory()) {
|
|
163
|
+
visit(child);
|
|
164
|
+
}
|
|
165
|
+
else if (!identity.isFile()) {
|
|
166
|
+
throw new Error('Local container evidence cleanup accepts only regular files and directories.');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
visit(path);
|
|
171
|
+
}
|
|
172
|
+
function removeExactLocalEvidenceDirectory(root, path) {
|
|
173
|
+
const segments = confinedEvidenceSegments(root, path);
|
|
174
|
+
let current = root;
|
|
175
|
+
const rootIdentity = (0, node_fs_1.lstatSync)(current);
|
|
176
|
+
if (!rootIdentity.isDirectory() || rootIdentity.isSymbolicLink()) {
|
|
177
|
+
throw new Error('Local deployment workspace root is absent or unsafe.');
|
|
275
178
|
}
|
|
276
|
-
|
|
277
|
-
|
|
179
|
+
for (const segment of segments) {
|
|
180
|
+
current = (0, node_path_1.join)(current, segment);
|
|
181
|
+
if (!(0, node_fs_1.existsSync)(current))
|
|
182
|
+
return;
|
|
183
|
+
const identity = (0, node_fs_1.lstatSync)(current);
|
|
184
|
+
if (!identity.isDirectory() || identity.isSymbolicLink()) {
|
|
185
|
+
throw new Error('Local container evidence cleanup encountered an unsafe path.');
|
|
186
|
+
}
|
|
278
187
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
188
|
+
assertSafeEvidenceTree(current);
|
|
189
|
+
(0, node_fs_1.rmSync)(current, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
function ensurePrivateEvidenceDirectory(root, path) {
|
|
192
|
+
const segments = confinedEvidenceSegments(root, path);
|
|
193
|
+
let current = root;
|
|
194
|
+
for (const segment of segments) {
|
|
195
|
+
current = (0, node_path_1.join)(current, segment);
|
|
196
|
+
if ((0, node_fs_1.existsSync)(current)) {
|
|
197
|
+
const identity = (0, node_fs_1.lstatSync)(current);
|
|
198
|
+
if (!identity.isDirectory() || identity.isSymbolicLink()) {
|
|
199
|
+
throw new Error('Local container evidence directory encountered an unsafe path.');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
(0, private_state_js_1.ensurePrivateDirectory)(current);
|
|
285
203
|
}
|
|
286
|
-
const slices = projects
|
|
287
|
-
.filter(({ tags }) => tags?.includes('type:slice'))
|
|
288
|
-
.map(({ name }) => name)
|
|
289
|
-
.sort();
|
|
290
|
-
return [foundations[0], ...slices];
|
|
291
204
|
}
|
|
292
|
-
function
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
'
|
|
296
|
-
|
|
297
|
-
'
|
|
298
|
-
|
|
299
|
-
]
|
|
300
|
-
|
|
301
|
-
encoding: 'utf8',
|
|
302
|
-
env: environment,
|
|
303
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
304
|
-
shell: false,
|
|
305
|
-
stdio: 'inherit',
|
|
306
|
-
});
|
|
307
|
-
if (result.status !== 0) {
|
|
308
|
-
throw new Error(`Local ${target} failed for ${project}.`);
|
|
205
|
+
function resetLocalContainerWorkingEvidence(input) {
|
|
206
|
+
const projectRoot = confinedWorkspacePath(input.root, input.project.root);
|
|
207
|
+
for (const path of [
|
|
208
|
+
(0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate'),
|
|
209
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name),
|
|
210
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', 'partials', input.stage, input.project.name),
|
|
211
|
+
(0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name),
|
|
212
|
+
]) {
|
|
213
|
+
removeExactLocalEvidenceDirectory(input.root, path);
|
|
309
214
|
}
|
|
310
215
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
216
|
+
/** Materialize the same bounded workspace-level candidate layout restored by
|
|
217
|
+
* protected GitHub deployment. The generated container helper and SST image
|
|
218
|
+
* resolver both consume this exact path. */
|
|
219
|
+
function materializeLocalReleaseCandidate(input) {
|
|
220
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(input.project.name)) {
|
|
221
|
+
throw new Error(`Local container release project ${input.project.name} has an unsafe evidence identity.`);
|
|
315
222
|
}
|
|
316
|
-
|
|
317
|
-
|
|
223
|
+
const projectRoot = confinedWorkspacePath(input.root, input.project.root);
|
|
224
|
+
const source = (0, node_path_1.join)(projectRoot, '.ebk', 'evidence', 'release-candidate');
|
|
225
|
+
const sourceIdentity = (0, node_fs_1.lstatSync)(source);
|
|
226
|
+
if (!sourceIdentity.isDirectory() || sourceIdentity.isSymbolicLink()) {
|
|
227
|
+
throw new Error(`Local release candidate for ${input.project.name} is absent or unsafe.`);
|
|
228
|
+
}
|
|
229
|
+
const entries = (0, node_fs_1.readdirSync)(source, { withFileTypes: true }).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
230
|
+
if (entries.length === 0 ||
|
|
231
|
+
entries.length > MAX_LOCAL_RELEASE_CANDIDATE_FILES ||
|
|
232
|
+
entries.some((entry) => !entry.isFile() ||
|
|
233
|
+
entry.isSymbolicLink() ||
|
|
234
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.json$/u.test(entry.name))) {
|
|
235
|
+
throw new Error(`Local release candidate for ${input.project.name} has an invalid file inventory.`);
|
|
236
|
+
}
|
|
237
|
+
let totalBytes = 0;
|
|
238
|
+
const files = entries.map(({ name }) => {
|
|
239
|
+
const path = (0, node_path_1.join)(source, name);
|
|
240
|
+
const identity = (0, node_fs_1.lstatSync)(path);
|
|
241
|
+
const maximumBytes = name === 'release-artifacts.json'
|
|
242
|
+
? MAX_LOCAL_RELEASE_ARTIFACTS_BYTES
|
|
243
|
+
: MAX_LOCAL_RELEASE_CANDIDATE_FILE_BYTES;
|
|
244
|
+
if (!identity.isFile() ||
|
|
245
|
+
identity.isSymbolicLink() ||
|
|
246
|
+
identity.size <= 0 ||
|
|
247
|
+
identity.size > maximumBytes) {
|
|
248
|
+
throw new Error(`Local release candidate file ${input.project.name}/${name} is absent or unsafe.`);
|
|
249
|
+
}
|
|
250
|
+
totalBytes += identity.size;
|
|
251
|
+
return { name, path };
|
|
252
|
+
});
|
|
253
|
+
if (totalBytes > MAX_LOCAL_RELEASE_CANDIDATE_BYTES) {
|
|
254
|
+
throw new Error(`Local release candidate for ${input.project.name} exceeds the bounded inventory size.`);
|
|
318
255
|
}
|
|
319
|
-
|
|
256
|
+
const candidateFiles = files.map(({ name, path }) => ({
|
|
257
|
+
contents: (0, node_fs_1.readFileSync)(path),
|
|
258
|
+
name,
|
|
259
|
+
}));
|
|
260
|
+
const candidateFile = candidateFiles.find(({ name }) => name === 'candidate.json');
|
|
261
|
+
let candidate;
|
|
320
262
|
try {
|
|
321
|
-
|
|
263
|
+
candidate = JSON.parse(candidateFile?.contents.toString('utf8') ?? '');
|
|
322
264
|
}
|
|
323
|
-
catch
|
|
324
|
-
|
|
265
|
+
catch {
|
|
266
|
+
throw new Error(`Local release candidate for ${input.project.name} is invalid JSON.`);
|
|
325
267
|
}
|
|
326
|
-
if (
|
|
327
|
-
|
|
268
|
+
if (candidate.kind !== 'ebk-release-candidate' ||
|
|
269
|
+
candidate.schemaVersion !== 1 ||
|
|
270
|
+
candidate.slice !== input.project.name ||
|
|
271
|
+
candidate.sourceCommit !== input.sourceCommit) {
|
|
272
|
+
throw new Error(`Local release candidate for ${input.project.name} differs from the exact deployment source.`);
|
|
328
273
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
274
|
+
const destination = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'release-candidate', 'packages', input.project.name);
|
|
275
|
+
removeExactLocalEvidenceDirectory(input.root, destination);
|
|
276
|
+
ensurePrivateEvidenceDirectory(input.root, destination);
|
|
277
|
+
for (const { contents, name } of candidateFiles) {
|
|
278
|
+
if (name === 'release-artifacts.json') {
|
|
279
|
+
(0, private_state_js_1.atomicWritePrivateFileWithMaximumBytes)((0, node_path_1.join)(destination, name), contents, MAX_LOCAL_RELEASE_ARTIFACTS_BYTES);
|
|
335
280
|
}
|
|
336
|
-
|
|
337
|
-
|
|
281
|
+
else {
|
|
282
|
+
(0, private_state_js_1.atomicWritePrivateFile)((0, node_path_1.join)(destination, name), contents);
|
|
338
283
|
}
|
|
339
|
-
if (!accepted)
|
|
340
|
-
throw executionError;
|
|
341
284
|
}
|
|
342
285
|
}
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
286
|
+
function attestLocalContainerBinding(input) {
|
|
287
|
+
const path = (0, node_path_1.join)(input.root, '.ebk', 'evidence', 'container-release', input.stage, 'packages', input.project.name, 'binding.json');
|
|
288
|
+
const identity = (0, node_fs_1.lstatSync)(path);
|
|
289
|
+
if (!identity.isFile() ||
|
|
290
|
+
identity.isSymbolicLink() ||
|
|
291
|
+
identity.size <= 0 ||
|
|
292
|
+
identity.size > 64 * 1024 * 1024) {
|
|
293
|
+
throw new Error(`Local protected container binding for ${input.project.name} is absent or unsafe.`);
|
|
294
|
+
}
|
|
295
|
+
const binding = (0, container_release_js_1.validateContainerReleaseBinding)(JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8')));
|
|
296
|
+
if (binding.accountId !== input.account ||
|
|
297
|
+
binding.region !== input.region ||
|
|
298
|
+
binding.slice !== input.project.name ||
|
|
299
|
+
binding.sourceCommit !== input.sourceCommit ||
|
|
300
|
+
binding.stage !== input.stage) {
|
|
301
|
+
throw new Error(`Local protected container binding for ${input.project.name} differs from the exact deployment authority.`);
|
|
346
302
|
}
|
|
347
|
-
return value;
|
|
348
303
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
before.size > BigInt(MAX_PLAN_BYTES)) {
|
|
367
|
-
throw new Error(`Local deployment plan for ${project} is not one bounded regular file.`);
|
|
368
|
-
}
|
|
369
|
-
const bytes = (0, node_fs_1.readFileSync)(path);
|
|
370
|
-
const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
|
|
371
|
-
if (before.dev !== after.dev ||
|
|
372
|
-
before.ino !== after.ino ||
|
|
373
|
-
before.mode !== after.mode ||
|
|
374
|
-
before.nlink !== after.nlink ||
|
|
375
|
-
before.size !== after.size ||
|
|
376
|
-
before.mtimeNs !== after.mtimeNs ||
|
|
377
|
-
before.ctimeNs !== after.ctimeNs ||
|
|
378
|
-
BigInt(bytes.byteLength) !== after.size) {
|
|
379
|
-
throw new Error(`Local deployment plan for ${project} changed while it was read.`);
|
|
380
|
-
}
|
|
381
|
-
let parsed;
|
|
304
|
+
/** Read only the bounded public outputs SST writes after a successful native
|
|
305
|
+
* deploy. This is post-operation smoke input, never preview or mutation
|
|
306
|
+
* authority. Stage and project identity prevent a stale output file from
|
|
307
|
+
* selecting another deployment. */
|
|
308
|
+
function readDeployedSmokeTarget(workspaceRoot, project, stage) {
|
|
309
|
+
const projectRoot = confinedWorkspacePath(workspaceRoot, project.root);
|
|
310
|
+
const outputPath = (0, node_path_1.join)(projectRoot, '.sst', 'outputs.json');
|
|
311
|
+
const identity = (0, node_fs_1.lstatSync)(outputPath);
|
|
312
|
+
if (!identity.isFile() ||
|
|
313
|
+
identity.isSymbolicLink() ||
|
|
314
|
+
identity.size < 2 ||
|
|
315
|
+
identity.size > 64 * 1024) {
|
|
316
|
+
throw new Error(`Native SST outputs for ${project.name} are absent or unsafe.`);
|
|
317
|
+
}
|
|
318
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)(outputPath, 'utf8'));
|
|
319
|
+
let api;
|
|
320
|
+
let web;
|
|
382
321
|
try {
|
|
383
|
-
|
|
322
|
+
api = new URL(String(value.apiUrl ?? ''));
|
|
323
|
+
web = new URL(String(value.webUrl ?? ''));
|
|
384
324
|
}
|
|
385
325
|
catch {
|
|
386
|
-
throw new Error(`
|
|
387
|
-
}
|
|
388
|
-
const plan = parsed;
|
|
389
|
-
if (plan.kind !== index_js_1.INFRASTRUCTURE_PLAN_KIND ||
|
|
390
|
-
plan.schemaVersion !== index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION ||
|
|
391
|
-
plan.action !== action ||
|
|
392
|
-
!SHA.test(plan.sourceCommit) ||
|
|
393
|
-
plan.target?.accountFingerprint !== digest(account) ||
|
|
394
|
-
plan.target?.region !== region ||
|
|
395
|
-
plan.target?.stage !== stage ||
|
|
396
|
-
!Array.isArray(plan.projects) ||
|
|
397
|
-
plan.projects.length !== 1 ||
|
|
398
|
-
plan.projects[0] !== project ||
|
|
399
|
-
!SHA256.test(plan.materialDigest) ||
|
|
400
|
-
(0, index_js_1.computeMaterialPlanDigest)(plan) !== plan.materialDigest) {
|
|
401
|
-
throw new Error(`Local deployment plan for ${project} is not the exact current deployment authority.`);
|
|
402
|
-
}
|
|
403
|
-
if (plan.sourceCommit !== deploymentSourceCommit) {
|
|
404
|
-
try {
|
|
405
|
-
(0, source_authority_js_1.validateReviewedReleaseCheckout)({
|
|
406
|
-
deploymentSourceCommit,
|
|
407
|
-
releaseSourceCommit: plan.sourceCommit,
|
|
408
|
-
repositoryRoot: context.root,
|
|
409
|
-
reviewedSourceCommit: deploymentSourceCommit,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
throw new Error(`Local deployment plan for ${project} is not bound through one exact approval-only source transition.`);
|
|
414
|
-
}
|
|
326
|
+
throw new Error(`Native SST outputs for ${project.name} contain invalid smoke URLs.`);
|
|
415
327
|
}
|
|
416
|
-
|
|
328
|
+
const credentialFreeHttps = (url) => url.protocol === 'https:' &&
|
|
329
|
+
!url.username &&
|
|
330
|
+
!url.password &&
|
|
331
|
+
!url.search &&
|
|
332
|
+
!url.hash;
|
|
333
|
+
if (value.name !== project.name ||
|
|
334
|
+
value.stage !== stage ||
|
|
335
|
+
!credentialFreeHttps(api) ||
|
|
336
|
+
!credentialFreeHttps(web) ||
|
|
337
|
+
api.pathname !== '/' ||
|
|
338
|
+
web.pathname.replace(/\/$/u, '') !== `/${project.name}` ||
|
|
339
|
+
!/^[a-z0-9]{1,128}$/u.test(String(value.authClientId ?? '')) ||
|
|
340
|
+
!/^[A-Za-z0-9_-]{1,128}_[A-Za-z0-9]{1,128}$/u.test(String(value.authUserPoolId ?? ''))) {
|
|
341
|
+
throw new Error(`Native SST outputs for ${project.name} do not match the deployed stage and smoke target.`);
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
apiUrl: api.href,
|
|
345
|
+
clientId: String(value.authClientId),
|
|
346
|
+
userPoolId: String(value.authUserPoolId),
|
|
347
|
+
webUrl: web.href,
|
|
348
|
+
};
|
|
417
349
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
before.nlink !== 1n ||
|
|
424
|
-
before.size > BigInt(MAX_PLAN_BYTES)) {
|
|
425
|
-
return false;
|
|
426
|
-
}
|
|
427
|
-
const bytes = (0, node_fs_1.readFileSync)(path);
|
|
428
|
-
const after = (0, node_fs_1.lstatSync)(path, { bigint: true });
|
|
429
|
-
if (before.dev !== after.dev ||
|
|
430
|
-
before.ino !== after.ino ||
|
|
431
|
-
before.mode !== after.mode ||
|
|
432
|
-
before.nlink !== after.nlink ||
|
|
433
|
-
before.size !== after.size ||
|
|
434
|
-
before.mtimeNs !== after.mtimeNs ||
|
|
435
|
-
before.ctimeNs !== after.ctimeNs ||
|
|
436
|
-
BigInt(bytes.byteLength) !== after.size) {
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
const authorization = record(JSON.parse(bytes.toString('utf8')), `${project} authorization`);
|
|
440
|
-
return (authorization.kind === 'ebk-infrastructure-authorization' &&
|
|
441
|
-
authorization.schemaVersion === index_js_1.INFRASTRUCTURE_CHANGE_SCHEMA_VERSION &&
|
|
442
|
-
authorization.action === 'deploy' &&
|
|
443
|
-
authorization.approval === undefined &&
|
|
444
|
-
authorization.localGovernance === undefined &&
|
|
445
|
-
authorization.governanceEvidenceDigests === undefined &&
|
|
446
|
-
authorization.plannedMaterialDigest === plan.materialDigest &&
|
|
447
|
-
authorization.recomputedMaterialDigest === plan.materialDigest &&
|
|
448
|
-
Array.isArray(authorization.sourceChangesSincePlan) &&
|
|
449
|
-
authorization.sourceChangesSincePlan.length === 0 &&
|
|
450
|
-
authorization.validation.allowed === false &&
|
|
451
|
-
authorization.validation.outcome === 'plan-risk-blocked' &&
|
|
452
|
-
authorization.validation.findings.length === 1 &&
|
|
453
|
-
authorization.validation.findings[0]?.code === 'approval-missing');
|
|
350
|
+
/** Determine the one native deployment order. Foundation is always first and
|
|
351
|
+
* production Slice admission remains a committed policy decision. */
|
|
352
|
+
function localDeploymentProjectOrder(policy, context, stage) {
|
|
353
|
+
if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, stage)) {
|
|
354
|
+
throw new Error(`Deployment stage ${stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
|
|
454
355
|
}
|
|
455
|
-
|
|
456
|
-
|
|
356
|
+
const projects = projectDescriptors(context);
|
|
357
|
+
const foundations = projects.filter((project) => project.tags.includes('scope:foundation') &&
|
|
358
|
+
supportsTarget(project, 'deploy'));
|
|
359
|
+
if (foundations.length !== 1) {
|
|
360
|
+
throw new Error(`Local deployment requires exactly one deployable Foundation; found ${foundations.length}.`);
|
|
457
361
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
Object.keys(input.plans).length !== input.orderedProjects.length) {
|
|
469
|
-
throw new Error('Local deployment plan summary input is invalid.');
|
|
470
|
-
}
|
|
471
|
-
const plans = input.orderedProjects.map((project) => {
|
|
472
|
-
const plan = input.plans[project];
|
|
473
|
-
if (!plan ||
|
|
474
|
-
plan.action !== action ||
|
|
475
|
-
plan.projects.length !== 1 ||
|
|
476
|
-
plan.projects[0] !== project ||
|
|
477
|
-
!SHA256.test(plan.materialDigest)) {
|
|
478
|
-
throw new Error(`Local deployment plan summary omitted exact project ${project}.`);
|
|
362
|
+
const slices = projects
|
|
363
|
+
.filter((project) => project.tags.includes('type:slice') &&
|
|
364
|
+
supportsTarget(project, 'deploy'))
|
|
365
|
+
.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
366
|
+
if (stage === 'production') {
|
|
367
|
+
const missing = slices
|
|
368
|
+
.filter(({ name }) => policy.slices[name] === undefined)
|
|
369
|
+
.map(({ name }) => name);
|
|
370
|
+
if (missing.length > 0) {
|
|
371
|
+
throw new Error(`Registered Product Slices lack an explicit production policy: ${missing.join(', ')}.`);
|
|
479
372
|
}
|
|
480
|
-
|
|
373
|
+
}
|
|
374
|
+
const selectedSlices = stage === 'production'
|
|
375
|
+
? slices.filter(({ name }) => {
|
|
376
|
+
const status = policy.slices[name]?.productionStatus;
|
|
377
|
+
return status === 'pilot' || status === 'ready';
|
|
378
|
+
})
|
|
379
|
+
: slices;
|
|
380
|
+
const selected = [
|
|
381
|
+
{ kind: 'foundation', name: foundations[0]?.name ?? '' },
|
|
382
|
+
...selectedSlices.map(({ name }) => ({ kind: 'slice', name })),
|
|
383
|
+
];
|
|
384
|
+
const decision = (0, index_js_2.evaluateRepositoryPolicyRequest)(policy, {
|
|
385
|
+
action: 'deploy',
|
|
386
|
+
projects: selected,
|
|
387
|
+
stage,
|
|
481
388
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
phase: input.phase,
|
|
487
|
-
plans,
|
|
488
|
-
region: input.region,
|
|
489
|
-
sourceCommit: input.sourceCommit,
|
|
490
|
-
stage: input.stage,
|
|
491
|
-
}));
|
|
492
|
-
}
|
|
493
|
-
function retainEvidence(root, evidence) {
|
|
494
|
-
const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'local-deployment');
|
|
495
|
-
(0, node_fs_1.mkdirSync)(directory, { recursive: true, mode: 0o700 });
|
|
496
|
-
(0, node_fs_1.chmodSync)(directory, 0o700);
|
|
497
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(directory, 'latest.json'), `${JSON.stringify(evidence, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
389
|
+
if (!decision.allowed) {
|
|
390
|
+
throw new Error(`Repository Operating Policy rejected local deployment:\n${decision.reasons.map((reason) => `- ${reason}`).join('\n')}`);
|
|
391
|
+
}
|
|
392
|
+
return selected.map(({ name }) => name);
|
|
498
393
|
}
|
|
499
|
-
|
|
500
|
-
[
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
]
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
property.kind !== 'update' ||
|
|
518
|
-
property.sensitive ||
|
|
519
|
-
observed.has(property.path)) {
|
|
520
|
-
return false;
|
|
394
|
+
function frameworkVersion(root) {
|
|
395
|
+
const candidates = [
|
|
396
|
+
(0, node_path_1.join)(root, 'node_modules', '@empire-builder-kit', 'nx', 'package.json'),
|
|
397
|
+
(0, node_path_1.join)(root, 'package.json'),
|
|
398
|
+
];
|
|
399
|
+
for (const path of candidates) {
|
|
400
|
+
try {
|
|
401
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
|
|
402
|
+
if (value.name === '@empire-builder-kit/nx' && value.version) {
|
|
403
|
+
return value.version;
|
|
404
|
+
}
|
|
405
|
+
const dependency = value.devDependencies?.['@empire-builder-kit/nx'] ??
|
|
406
|
+
value.dependencies?.['@empire-builder-kit/nx'];
|
|
407
|
+
if (dependency)
|
|
408
|
+
return dependency;
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// Try the next deterministic package identity.
|
|
521
412
|
}
|
|
522
|
-
observed.add(property.path);
|
|
523
413
|
}
|
|
524
|
-
return
|
|
414
|
+
return 'unknown';
|
|
525
415
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (plan.diff.resources.length === 0) {
|
|
534
|
-
return plan.driftStatus === 'no-drift';
|
|
535
|
-
}
|
|
536
|
-
if (plan.driftStatus !== 'drift-detected' ||
|
|
537
|
-
!plan.diff.resources.every(isFrameworkTagProviderObservation) ||
|
|
538
|
-
plan.requiredApprovals.some((approval) => ![
|
|
539
|
-
'deployment-review',
|
|
540
|
-
'drift-decision-and-state-recovery',
|
|
541
|
-
'point-of-no-return',
|
|
542
|
-
].includes(approval))) {
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
const observedResourceIds = new Set(plan.diff.resources.map(({ logicalId }) => logicalId));
|
|
546
|
-
return plan.resourceFindings.every((finding) => (finding.resourceId === undefined &&
|
|
547
|
-
finding.class === 'stateful-or-destructive' &&
|
|
548
|
-
finding.code === 'protected-state-refresh' &&
|
|
549
|
-
finding.disposition === 'block') ||
|
|
550
|
-
(finding.resourceId !== undefined &&
|
|
551
|
-
observedResourceIds.has(finding.resourceId) &&
|
|
552
|
-
finding.class === 'in-place-reviewed' &&
|
|
553
|
-
finding.code === 'in-place-update' &&
|
|
554
|
-
finding.disposition === 'review'));
|
|
416
|
+
function prepareReceiptDestination(root) {
|
|
417
|
+
const directory = (0, node_path_1.join)(root, '.ebk', 'evidence', 'deployment');
|
|
418
|
+
ensurePrivateEvidenceDirectory(root, directory);
|
|
419
|
+
const path = (0, node_path_1.join)(directory, 'latest.json');
|
|
420
|
+
if ((0, node_fs_1.existsSync)(path))
|
|
421
|
+
(0, private_state_js_1.assertPrivatePath)(path, 'file');
|
|
422
|
+
return path;
|
|
555
423
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
424
|
+
function writeReceipt(root, receipt) {
|
|
425
|
+
(0, private_state_js_1.atomicWritePrivateFile)(prepareReceiptDestination(root), `${JSON.stringify(receipt, null, 2)}\n`);
|
|
426
|
+
}
|
|
427
|
+
function invalidatePriorReceipt(root) {
|
|
428
|
+
const path = prepareReceiptDestination(root);
|
|
429
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
430
|
+
return;
|
|
431
|
+
(0, private_state_js_1.assertPrivatePath)(path, 'file');
|
|
432
|
+
(0, node_fs_1.rmSync)(path, { force: true });
|
|
433
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
434
|
+
throw new Error('Prior deployment receipt invalidation failed.');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function runNxTarget(input) {
|
|
438
|
+
const forwarded = [
|
|
439
|
+
`--stage=${input.stage}`,
|
|
440
|
+
`--expectedRoleArn=${input.expectedRoleArn}`,
|
|
441
|
+
`--region=${input.region}`,
|
|
442
|
+
...(input.profile ? [`--profile=${input.profile}`] : []),
|
|
443
|
+
];
|
|
444
|
+
const args = [
|
|
445
|
+
(0, node_path_1.join)(input.context.root, 'node_modules', 'nx', 'dist', 'bin', 'nx.js'),
|
|
446
|
+
'run',
|
|
447
|
+
`${input.project}:${input.target}`,
|
|
448
|
+
'--',
|
|
449
|
+
...forwarded,
|
|
450
|
+
];
|
|
451
|
+
const result = input.runSync
|
|
452
|
+
? input.runSync(process.execPath, args, {
|
|
453
|
+
cwd: input.context.root,
|
|
454
|
+
env: input.environment,
|
|
455
|
+
shell: false,
|
|
456
|
+
stdio: 'inherit',
|
|
457
|
+
})
|
|
458
|
+
: await (0, run_sst_command_js_1.runGovernedChildProcess)({
|
|
459
|
+
args,
|
|
460
|
+
cwd: input.context.root,
|
|
461
|
+
environment: input.environment,
|
|
462
|
+
executable: process.execPath,
|
|
463
|
+
...(input.spawnChild ? { spawnChild: input.spawnChild } : {}),
|
|
464
|
+
});
|
|
465
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
466
|
+
throw new Error(result.error
|
|
467
|
+
? `${input.target} failed for ${input.project}: ${result.error.message}`
|
|
468
|
+
: result.signal
|
|
469
|
+
? `${input.target} failed for ${input.project} with signal ${result.signal}.`
|
|
470
|
+
: `${input.target} failed for ${input.project} with status ${result.status ?? 'unknown'}.`);
|
|
603
471
|
}
|
|
604
|
-
return providerReadFindings.size === providerReads.size;
|
|
605
472
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
473
|
+
function productionConfirmation(account, region, commit) {
|
|
474
|
+
return `deploy:production:${account}:${region}:${commit}`;
|
|
475
|
+
}
|
|
476
|
+
async function defaultIdentifyGithubCaller(region) {
|
|
477
|
+
return new client_sts_1.STSClient({
|
|
478
|
+
endpoint: (0, identity_js_1.canonicalStsEndpoint)(region),
|
|
479
|
+
region,
|
|
480
|
+
}).send(new client_sts_1.GetCallerIdentityCommand({}));
|
|
481
|
+
}
|
|
482
|
+
function assertGithubDeploymentAuthority(input) {
|
|
483
|
+
const repository = process.env.GITHUB_REPOSITORY?.trim() ?? '';
|
|
484
|
+
const ref = process.env.GITHUB_REF?.trim() ?? '';
|
|
485
|
+
const eventName = process.env.GITHUB_EVENT_NAME?.trim() ?? '';
|
|
486
|
+
const callerWorkflow = process.env.GITHUB_WORKFLOW_REF?.trim() ?? '';
|
|
487
|
+
const calledWorkflow = process.env.EBK_GITHUB_JOB_WORKFLOW_REF?.trim() ?? '';
|
|
488
|
+
const expectedBranch = input.stage === 'production' ? 'main' : 'develop';
|
|
489
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
490
|
+
const expectedCalledWorkflow = `${repository}/.github/workflows/deploy.yml@${expectedRef}`;
|
|
491
|
+
const expectedCallerWorkflow = eventName === 'push'
|
|
492
|
+
? `${repository}/.github/workflows/ci.yml@${expectedRef}`
|
|
493
|
+
: eventName === 'workflow_dispatch'
|
|
494
|
+
? expectedCalledWorkflow
|
|
495
|
+
: undefined;
|
|
496
|
+
const allowedEvent = eventName === 'workflow_dispatch' ||
|
|
497
|
+
(eventName === 'push' && input.policy.deploymentProfile !== 'starter');
|
|
498
|
+
if (process.env.GITHUB_ACTIONS !== 'true' ||
|
|
499
|
+
!repository ||
|
|
500
|
+
!allowedEvent ||
|
|
501
|
+
process.env.GITHUB_SHA !== input.commit ||
|
|
502
|
+
ref !== expectedRef ||
|
|
503
|
+
callerWorkflow !== expectedCallerWorkflow ||
|
|
504
|
+
calledWorkflow !== expectedCalledWorkflow) {
|
|
505
|
+
throw new Error('Protected deployment requires the exact generated GitHub caller and reusable deploy workflows, branch, and immutable source commit.');
|
|
506
|
+
}
|
|
507
|
+
if (input.policy.deploymentProfile === 'starter' &&
|
|
508
|
+
input.stage === 'staging') {
|
|
509
|
+
throw new Error('The starter deployment profile has no staging stage.');
|
|
510
|
+
}
|
|
511
|
+
const clean = git(input.root, ['status', '--porcelain=v1', '--untracked-files=all'], input.run);
|
|
512
|
+
if (clean !== '') {
|
|
513
|
+
throw new Error('Protected GitHub deployment requires a clean checkout.');
|
|
514
|
+
}
|
|
618
515
|
}
|
|
619
516
|
async function runLocalDeploy(options, context, dependencies = {}) {
|
|
620
517
|
const run = dependencies.run ?? node_child_process_1.spawnSync;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const expectedConfirmation = (0, infrastructure_plan_js_1.localProductionConfirmation)({
|
|
652
|
-
action,
|
|
653
|
-
accountId: expectedAccount,
|
|
654
|
-
region,
|
|
655
|
-
sourceCommit,
|
|
656
|
-
});
|
|
657
|
-
if (options.stage === 'production' &&
|
|
658
|
-
options.confirmation !== expectedConfirmation) {
|
|
659
|
-
throw new Error(`Local production ${action} requires --confirmation=${expectedConfirmation}.`);
|
|
660
|
-
}
|
|
661
|
-
const caller = await (dependencies.caller ??
|
|
662
|
-
(async (targetRegion) => new client_sts_1.STSClient({
|
|
663
|
-
region: targetRegion,
|
|
664
|
-
...(options.profile
|
|
665
|
-
? { credentials: (0, credential_providers_1.fromIni)({ profile: options.profile }) }
|
|
666
|
-
: {}),
|
|
667
|
-
}).send(new client_sts_1.GetCallerIdentityCommand({}))))(region);
|
|
668
|
-
const account = caller.Account?.trim() ?? '';
|
|
669
|
-
const arn = caller.Arn?.trim() ?? '';
|
|
670
|
-
if (account !== expectedAccount ||
|
|
671
|
-
!(0, infrastructure_plan_js_1.matchesExpectedAssumedRoleArn)(expectedAccount, arn, options.expectedRoleArn)) {
|
|
672
|
-
throw new Error('Local AWS caller does not match the committed account and exact temporary assumed role.');
|
|
673
|
-
}
|
|
674
|
-
const orderedProjects = projectInventory(context, options.stage, phase, action);
|
|
675
|
-
const sourceSnapshots = toolOwnedSourceSnapshots(context, orderedProjects);
|
|
676
|
-
const startedAt = (dependencies.now ?? (() => new Date()))().toISOString();
|
|
677
|
-
let backend;
|
|
678
|
-
let result = 'failed';
|
|
679
|
-
let planDigests = {};
|
|
680
|
-
let planSummaryDigest = null;
|
|
681
|
-
let deployNoOpProjects = [];
|
|
682
|
-
const mutatedProjects = [];
|
|
683
|
-
let operatorAcknowledgementDigest = null;
|
|
684
|
-
let replanRequiredProject = null;
|
|
685
|
-
let refreshNoOpProjects = [];
|
|
686
|
-
try {
|
|
687
|
-
backend = await (dependencies.backend ?? prepareLocalSstBackend)({
|
|
688
|
-
account: expectedAccount,
|
|
689
|
-
applications: orderedProjects,
|
|
690
|
-
...(options.profile ? { profile: options.profile } : {}),
|
|
518
|
+
const now = dependencies.now ?? (() => new Date());
|
|
519
|
+
const startedAt = now().toISOString();
|
|
520
|
+
const children = [];
|
|
521
|
+
const migrations = {};
|
|
522
|
+
const smoke = {};
|
|
523
|
+
let account = '';
|
|
524
|
+
let actor = '';
|
|
525
|
+
let policy;
|
|
526
|
+
let region = '';
|
|
527
|
+
let source = { clean: false, commit: '', workingTreeDigest: '' };
|
|
528
|
+
let transport;
|
|
529
|
+
let productionDatabaseBefore;
|
|
530
|
+
let productionDatabaseAfterFoundation;
|
|
531
|
+
let outcome = 'failed';
|
|
532
|
+
let receiptDestinationPrepared = false;
|
|
533
|
+
const githubAuthority = process.env.GITHUB_ACTIONS === 'true';
|
|
534
|
+
const retain = () => {
|
|
535
|
+
if (!policy || !receiptDestinationPrepared)
|
|
536
|
+
return;
|
|
537
|
+
writeReceipt(context.root, {
|
|
538
|
+
account,
|
|
539
|
+
actor,
|
|
540
|
+
authority: githubAuthority ? 'github' : 'local',
|
|
541
|
+
children,
|
|
542
|
+
completedAt: now().toISOString(),
|
|
543
|
+
frameworkVersion: frameworkVersion(context.root),
|
|
544
|
+
kind: 'ebk-deployment-receipt',
|
|
545
|
+
migrations,
|
|
546
|
+
outcome,
|
|
547
|
+
profile: policy.deploymentProfile,
|
|
691
548
|
region,
|
|
549
|
+
schemaVersion: 1,
|
|
550
|
+
smoke,
|
|
551
|
+
source,
|
|
692
552
|
stage: options.stage,
|
|
553
|
+
startedAt,
|
|
693
554
|
});
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
// Local deployment's reviewed clean Git commit is the one stage-wide
|
|
703
|
-
// workload-role candidate shared by Foundation and every Slice. Never
|
|
704
|
-
// accept an ambient candidate identity from the caller.
|
|
705
|
-
EBK_ACTION: action,
|
|
706
|
-
EBK_CANDIDATE_IDENTITY: sourceCommit,
|
|
707
|
-
EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
|
|
708
|
-
EBK_LOCAL_SOURCE_TREE: sourceTree,
|
|
709
|
-
EBK_SOURCE_COMMIT: sourceCommit,
|
|
710
|
-
EBK_STAGE: options.stage,
|
|
711
|
-
ORDERED_PROJECTS: orderedProjects.join(','),
|
|
712
|
-
};
|
|
713
|
-
for (const key of PROTECTED_PLAN_KEYS)
|
|
714
|
-
delete environment[key];
|
|
715
|
-
if (options.profile) {
|
|
716
|
-
for (const key of AMBIENT_CREDENTIAL_KEYS)
|
|
717
|
-
delete environment[key];
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
invalidatePriorReceipt(context.root);
|
|
558
|
+
receiptDestinationPrepared = true;
|
|
559
|
+
(0, run_sst_command_js_1.assertGovernedAwsLifecyclePlatform)(dependencies.platform);
|
|
560
|
+
policy = (0, index_js_2.readRepositoryOperatingPolicy)(context.root);
|
|
561
|
+
if (!(0, index_js_2.isActiveDeploymentStage)(policy.deploymentProfile, options.stage)) {
|
|
562
|
+
throw new Error(`Deployment stage ${options.stage} is inactive for the ${policy.deploymentProfile} deployment profile.`);
|
|
718
563
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
for (const project of orderedProjects) {
|
|
726
|
-
runNxWithToolOwnedSourceReconciliation(context, project, 'infrastructure-plan', [`--action=${action}`, ...common], environment, sourceSnapshots, run, action === 'deploy'
|
|
727
|
-
? () => {
|
|
728
|
-
const plan = retainedPlan(context, project, sourceCommit, expectedAccount, region, options.stage, action);
|
|
729
|
-
const accepted = isFreshInvocationPlan(plan, startedAt, (dependencies.now ?? (() => new Date()))()) &&
|
|
730
|
-
isSemanticallyConvergedDeploy(plan) &&
|
|
731
|
-
hasExactPendingLocalAuthorizationRefusal(context, project, plan);
|
|
732
|
-
if (accepted) {
|
|
733
|
-
devkit_1.logger.info(`Local infrastructure-plan for ${project} returned a conservative non-success after emitting one fresh provider-read-only semantic convergence record.`);
|
|
734
|
-
}
|
|
735
|
-
return accepted;
|
|
736
|
-
}
|
|
737
|
-
: undefined);
|
|
564
|
+
if (!githubAuthority &&
|
|
565
|
+
(0, index_js_2.requiresGithubDeployment)(policy.deploymentProfile, options.stage)) {
|
|
566
|
+
throw new Error(`The ${policy.deploymentProfile} profile requires GitHub deployment authority for ${options.stage}.`);
|
|
567
|
+
}
|
|
568
|
+
if (!ROLE.test(options.expectedRoleArn)) {
|
|
569
|
+
throw new Error('Local deployment requires one exact IAM role ARN.');
|
|
738
570
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
project,
|
|
745
|
-
plans[project].materialDigest,
|
|
746
|
-
]));
|
|
747
|
-
planSummaryDigest = localPlanSummaryDigest({
|
|
748
|
-
account: expectedAccount,
|
|
749
|
-
action,
|
|
750
|
-
orderedProjects,
|
|
751
|
-
phase,
|
|
752
|
-
plans,
|
|
571
|
+
region = (0, index_js_2.configuredDeploymentRegion)(policy.deploymentRegion);
|
|
572
|
+
account = (0, index_js_2.configuredDeploymentAccountForStage)(policy.deploymentProfile, policy.deploymentAccounts, options.stage);
|
|
573
|
+
const expectedRoleArn = (0, index_js_1.createStageAuthorityIdentity)({
|
|
574
|
+
accountId: account,
|
|
575
|
+
partition: partitionForRegion(region),
|
|
753
576
|
region,
|
|
754
|
-
sourceCommit,
|
|
755
577
|
stage: options.stage,
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
578
|
+
}).deploymentRoleArn;
|
|
579
|
+
if (options.expectedRoleArn !== expectedRoleArn) {
|
|
580
|
+
throw new Error(`Deployment role must equal the committed ${options.stage} stage authority role ${expectedRoleArn}.`);
|
|
581
|
+
}
|
|
582
|
+
const identity = localWorkingTreeDigest(context.root, run);
|
|
583
|
+
source = {
|
|
584
|
+
clean: identity.clean,
|
|
585
|
+
commit: identity.commit,
|
|
586
|
+
workingTreeDigest: identity.digest,
|
|
761
587
|
};
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
588
|
+
if ((githubAuthority || options.stage !== 'development') && !source.clean) {
|
|
589
|
+
throw new Error(`Local ${options.stage} deployment requires a completely clean source tree.`);
|
|
590
|
+
}
|
|
591
|
+
if (githubAuthority) {
|
|
592
|
+
if (options.profile) {
|
|
593
|
+
throw new Error('Protected GitHub deployment must use ambient OIDC credentials, not a local AWS profile.');
|
|
766
594
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
refreshNoOpProjects =
|
|
773
|
-
action === 'refresh'
|
|
774
|
-
? orderedProjects.filter((project) => isSemanticallyConvergedRefresh(plans[project]))
|
|
775
|
-
: [];
|
|
776
|
-
const mutationNoOpProjectSet = new Set([
|
|
777
|
-
...deployNoOpProjects,
|
|
778
|
-
...refreshNoOpProjects,
|
|
779
|
-
]);
|
|
780
|
-
const materialProjectIndex = orderedProjects.findIndex((project) => !mutationNoOpProjectSet.has(project));
|
|
781
|
-
if (materialProjectIndex >= 0) {
|
|
782
|
-
const project = orderedProjects[materialProjectIndex];
|
|
783
|
-
const plan = plans[project];
|
|
784
|
-
const challenge = `approve:${action}:${options.stage}:${project}:${plan.materialDigest}`;
|
|
785
|
-
devkit_1.logger.info(`[ebk-local-deployment-review]\n${JSON.stringify({
|
|
786
|
-
action,
|
|
787
|
-
challenge,
|
|
788
|
-
firstMaterialProject: project,
|
|
789
|
-
orderedProjects,
|
|
790
|
-
plan: {
|
|
791
|
-
aggregateRisk: plan.aggregateRisk,
|
|
792
|
-
diff: plan.diff,
|
|
793
|
-
materialDigest: plan.materialDigest,
|
|
794
|
-
requiredApprovals: plan.requiredApprovals,
|
|
795
|
-
resourceFindings: plan.resourceFindings,
|
|
796
|
-
stateDigest: plan.state.digest,
|
|
797
|
-
},
|
|
798
|
-
planSummaryDigest,
|
|
799
|
-
sourceCommit,
|
|
595
|
+
assertGithubDeploymentAuthority({
|
|
596
|
+
commit: source.commit,
|
|
597
|
+
policy,
|
|
598
|
+
root: context.root,
|
|
599
|
+
run,
|
|
800
600
|
stage: options.stage,
|
|
801
|
-
}
|
|
601
|
+
});
|
|
602
|
+
const caller = await (dependencies.identifyGithubCaller ?? defaultIdentifyGithubCaller)(region);
|
|
603
|
+
if (caller.Account?.trim() !== account ||
|
|
604
|
+
!(0, identity_js_1.matchesExpectedAssumedRoleArn)(account, caller.Arn?.trim() ?? '', expectedRoleArn)) {
|
|
605
|
+
throw new Error('Protected GitHub deployment caller does not match the committed account and exact assumed role.');
|
|
606
|
+
}
|
|
607
|
+
actor = process.env.GITHUB_ACTOR?.trim() ?? '';
|
|
608
|
+
if (!actor) {
|
|
609
|
+
throw new Error('Protected GitHub deployment actor is absent.');
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
else if (options.stage === 'staging') {
|
|
613
|
+
const challenge = `approve:deploy:staging:${source.commit}`;
|
|
802
614
|
let acknowledged = false;
|
|
803
615
|
try {
|
|
804
616
|
acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
|
|
805
|
-
action,
|
|
617
|
+
action: 'deploy',
|
|
806
618
|
challenge,
|
|
807
|
-
|
|
808
|
-
project,
|
|
619
|
+
sourceCommit: source.commit,
|
|
809
620
|
stage: options.stage,
|
|
810
621
|
});
|
|
811
622
|
}
|
|
812
623
|
catch (error) {
|
|
813
624
|
if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
|
|
814
|
-
|
|
625
|
+
outcome = 'review-required';
|
|
815
626
|
}
|
|
816
627
|
throw error;
|
|
817
628
|
}
|
|
818
629
|
if (!acknowledged) {
|
|
819
|
-
|
|
820
|
-
throw new Error(
|
|
630
|
+
outcome = 'review-required';
|
|
631
|
+
throw new Error('Local staging deployment acknowledgement was declined.');
|
|
821
632
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
633
|
+
}
|
|
634
|
+
if (!githubAuthority && options.stage === 'production') {
|
|
635
|
+
const expected = productionConfirmation(account, region, source.commit);
|
|
636
|
+
let acknowledged = false;
|
|
637
|
+
try {
|
|
638
|
+
acknowledged = await (dependencies.approve ?? (0, local_approval_js_1.createTtyLocalMutationApprover)())({
|
|
639
|
+
action: 'deploy',
|
|
640
|
+
challenge: expected,
|
|
641
|
+
sourceCommit: source.commit,
|
|
642
|
+
stage: options.stage,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
catch (error) {
|
|
646
|
+
if (error instanceof local_approval_js_1.LocalMutationApprovalUnavailableError) {
|
|
647
|
+
outcome = 'review-required';
|
|
648
|
+
}
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
if (!acknowledged) {
|
|
652
|
+
outcome = 'review-required';
|
|
653
|
+
throw new Error('Local production deployment acknowledgement was declined.');
|
|
832
654
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
655
|
+
}
|
|
656
|
+
const orderedProjects = localDeploymentProjectOrder(policy, context, options.stage);
|
|
657
|
+
const descriptors = projectDescriptors(context);
|
|
658
|
+
const localProtectedContainerStage = !githubAuthority &&
|
|
659
|
+
requiresLocalContainerPreparation(policy.deploymentProfile, options.stage)
|
|
660
|
+
? options.stage
|
|
661
|
+
: undefined;
|
|
662
|
+
const localProtectedContainerProjects = localProtectedContainerStage
|
|
663
|
+
? orderedProjects
|
|
664
|
+
.map((project) => descriptors.find((candidate) => candidate.name === project))
|
|
665
|
+
.filter((project) => project?.tags.includes('type:slice') === true)
|
|
666
|
+
: [];
|
|
667
|
+
for (const project of localProtectedContainerProjects) {
|
|
668
|
+
const missingTargets = LOCAL_CONTAINER_TARGETS.filter((target) => !supportsTarget(project, target));
|
|
669
|
+
if (missingTargets.length > 0) {
|
|
670
|
+
throw new Error(`Local ${policy.deploymentProfile} ${options.stage} requires the complete immutable OCI target set for ${project.name}: ${missingTargets.join(', ')}.`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!githubAuthority) {
|
|
674
|
+
transport = await (dependencies.createTransport ?? federated_credential_transport_js_1.createLocalFederatedCredentialTransport)({
|
|
675
|
+
account,
|
|
676
|
+
expectedRoleArn,
|
|
677
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
678
|
+
region,
|
|
679
|
+
});
|
|
680
|
+
const firstIdentity = await transport.refresh();
|
|
681
|
+
actor = firstIdentity.actorArn;
|
|
682
|
+
}
|
|
683
|
+
if (options.stage === 'production') {
|
|
684
|
+
if (transport)
|
|
685
|
+
await transport.refresh();
|
|
686
|
+
const boundaryEnvironment = transport
|
|
687
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
688
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
689
|
+
preserveVerifiedGithubCredentials: true,
|
|
690
|
+
});
|
|
691
|
+
productionDatabaseBefore = await (dependencies.attestProductionDatabase ??
|
|
692
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
693
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
694
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
695
|
+
: {}),
|
|
696
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
697
|
+
? {
|
|
698
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
699
|
+
}
|
|
700
|
+
: {}),
|
|
701
|
+
expectedAccountId: account,
|
|
702
|
+
expectedRegion: region,
|
|
703
|
+
expectedRoleArn,
|
|
704
|
+
now: now(),
|
|
705
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
706
|
+
requireExisting: false,
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const localContainerRunId = localProtectedContainerProjects.length === 0
|
|
710
|
+
? ''
|
|
711
|
+
: ((0, node_crypto_1.randomBytes)(8).readBigUInt64BE() + 1n).toString();
|
|
712
|
+
for (const project of localProtectedContainerProjects) {
|
|
713
|
+
resetLocalContainerWorkingEvidence({
|
|
842
714
|
project,
|
|
843
|
-
|
|
715
|
+
root: context.root,
|
|
716
|
+
stage: localProtectedContainerStage,
|
|
844
717
|
});
|
|
718
|
+
for (const target of LOCAL_CONTAINER_TARGETS) {
|
|
719
|
+
if (transport)
|
|
720
|
+
await transport.refresh();
|
|
721
|
+
const environment = {
|
|
722
|
+
...(transport
|
|
723
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
724
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env)),
|
|
725
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
726
|
+
AWS_REGION: region,
|
|
727
|
+
EBK_LOCAL_CONTAINER_RUN_ID: localContainerRunId,
|
|
728
|
+
EBK_LOCAL_DEPLOYMENT_MODE: 'federated',
|
|
729
|
+
EBK_DEPLOYMENT_PROFILE: policy.deploymentProfile,
|
|
730
|
+
EBK_NX_STAGE_LIFECYCLE: 'local',
|
|
731
|
+
EBK_RELEASE_SOURCE_COMMIT: source.commit,
|
|
732
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
733
|
+
EBK_STAGE: options.stage,
|
|
734
|
+
EBK_WORKING_TREE_DIRTY: 'false',
|
|
735
|
+
};
|
|
736
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
737
|
+
const childStartedAt = now().toISOString();
|
|
738
|
+
try {
|
|
739
|
+
await runNxTarget({
|
|
740
|
+
context,
|
|
741
|
+
environment,
|
|
742
|
+
expectedRoleArn,
|
|
743
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
744
|
+
project: project.name,
|
|
745
|
+
region,
|
|
746
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
747
|
+
...(dependencies.spawnChild
|
|
748
|
+
? { spawnChild: dependencies.spawnChild }
|
|
749
|
+
: {}),
|
|
750
|
+
stage: options.stage,
|
|
751
|
+
target,
|
|
752
|
+
});
|
|
753
|
+
if (target === 'release-candidate') {
|
|
754
|
+
materializeLocalReleaseCandidate({
|
|
755
|
+
project,
|
|
756
|
+
root: context.root,
|
|
757
|
+
sourceCommit: source.commit,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
if (target === 'container-release-assemble') {
|
|
761
|
+
(dependencies.attestContainerBinding ?? attestLocalContainerBinding)({
|
|
762
|
+
account,
|
|
763
|
+
project,
|
|
764
|
+
region,
|
|
765
|
+
root: context.root,
|
|
766
|
+
sourceCommit: source.commit,
|
|
767
|
+
stage: options.stage,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
children.push({
|
|
771
|
+
completedAt: now().toISOString(),
|
|
772
|
+
outcome: 'succeeded',
|
|
773
|
+
project: project.name,
|
|
774
|
+
startedAt: childStartedAt,
|
|
775
|
+
target,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
children.push({
|
|
780
|
+
completedAt: now().toISOString(),
|
|
781
|
+
outcome: 'failed',
|
|
782
|
+
project: project.name,
|
|
783
|
+
startedAt: childStartedAt,
|
|
784
|
+
target,
|
|
785
|
+
});
|
|
786
|
+
throw error;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
for (const project of orderedProjects) {
|
|
791
|
+
const descriptor = descriptors.find((candidate) => candidate.name === project);
|
|
792
|
+
if (!descriptor) {
|
|
793
|
+
throw new Error(`Local deployment project ${project} disappeared.`);
|
|
794
|
+
}
|
|
795
|
+
if (transport)
|
|
796
|
+
await transport.refresh();
|
|
797
|
+
const environment = {
|
|
798
|
+
...(transport
|
|
799
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
800
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
801
|
+
preserveVerifiedGithubCredentials: true,
|
|
802
|
+
})),
|
|
803
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
804
|
+
AWS_REGION: region,
|
|
805
|
+
EBK_LOCAL_SOURCE_TREE: source.workingTreeDigest,
|
|
806
|
+
EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
|
|
807
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
808
|
+
EBK_WORKING_TREE_DIRTY: source.clean ? 'false' : 'true',
|
|
809
|
+
};
|
|
810
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
811
|
+
const childStartedAt = now().toISOString();
|
|
812
|
+
try {
|
|
813
|
+
await runNxTarget({
|
|
814
|
+
context,
|
|
815
|
+
environment,
|
|
816
|
+
expectedRoleArn,
|
|
817
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
818
|
+
project,
|
|
819
|
+
region,
|
|
820
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
821
|
+
...(dependencies.spawnChild
|
|
822
|
+
? { spawnChild: dependencies.spawnChild }
|
|
823
|
+
: {}),
|
|
824
|
+
stage: options.stage,
|
|
825
|
+
target: 'deploy',
|
|
826
|
+
});
|
|
827
|
+
children.push({
|
|
828
|
+
completedAt: now().toISOString(),
|
|
829
|
+
outcome: 'succeeded',
|
|
830
|
+
project,
|
|
831
|
+
startedAt: childStartedAt,
|
|
832
|
+
target: 'deploy',
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
children.push({
|
|
837
|
+
completedAt: now().toISOString(),
|
|
838
|
+
outcome: 'failed',
|
|
839
|
+
project,
|
|
840
|
+
startedAt: childStartedAt,
|
|
841
|
+
target: 'deploy',
|
|
842
|
+
});
|
|
843
|
+
throw error;
|
|
844
|
+
}
|
|
845
|
+
if (options.stage === 'production' &&
|
|
846
|
+
descriptor.tags.includes('scope:foundation')) {
|
|
847
|
+
if (transport)
|
|
848
|
+
await transport.refresh();
|
|
849
|
+
const boundaryEnvironment = transport
|
|
850
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
851
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
852
|
+
preserveVerifiedGithubCredentials: true,
|
|
853
|
+
});
|
|
854
|
+
const postflightStartedAt = now().toISOString();
|
|
855
|
+
const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
|
|
856
|
+
try {
|
|
857
|
+
productionDatabaseAfterFoundation = await (dependencies.attestProductionDatabase ??
|
|
858
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
859
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
860
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
861
|
+
: {}),
|
|
862
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
863
|
+
? {
|
|
864
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
865
|
+
}
|
|
866
|
+
: {}),
|
|
867
|
+
expectedAccountId: account,
|
|
868
|
+
expectedCapacity: {
|
|
869
|
+
max: Number.parseFloat(profile.aurora.max),
|
|
870
|
+
min: Number.parseFloat(profile.aurora.min),
|
|
871
|
+
},
|
|
872
|
+
expectedRegion: region,
|
|
873
|
+
expectedRoleArn,
|
|
874
|
+
now: now(),
|
|
875
|
+
priorDiscovery: productionDatabaseBefore,
|
|
876
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
877
|
+
requireExisting: true,
|
|
878
|
+
});
|
|
879
|
+
children.push({
|
|
880
|
+
completedAt: now().toISOString(),
|
|
881
|
+
outcome: 'succeeded',
|
|
882
|
+
project,
|
|
883
|
+
startedAt: postflightStartedAt,
|
|
884
|
+
target: 'production-database-postflight',
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
children.push({
|
|
889
|
+
completedAt: now().toISOString(),
|
|
890
|
+
outcome: 'failed',
|
|
891
|
+
project,
|
|
892
|
+
startedAt: postflightStartedAt,
|
|
893
|
+
target: 'production-database-postflight',
|
|
894
|
+
});
|
|
895
|
+
throw error;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
migrations[project] = supportsTarget(descriptor, 'db-migrate')
|
|
899
|
+
? 'completed-by-deploy-target'
|
|
900
|
+
: 'not-applicable';
|
|
901
|
+
if (supportsTarget(descriptor, 'smoke-deployed')) {
|
|
902
|
+
if (transport)
|
|
903
|
+
await transport.refresh();
|
|
904
|
+
const smokeTarget = readDeployedSmokeTarget(context.root, descriptor, options.stage);
|
|
905
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
906
|
+
const smokeStartedAt = now().toISOString();
|
|
907
|
+
try {
|
|
908
|
+
await runNxTarget({
|
|
909
|
+
context,
|
|
910
|
+
environment: {
|
|
911
|
+
...(transport
|
|
912
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
913
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
914
|
+
preserveVerifiedGithubCredentials: true,
|
|
915
|
+
})),
|
|
916
|
+
AWS_EXPECTED_ACCOUNT_ID: account,
|
|
917
|
+
AWS_REGION: region,
|
|
918
|
+
EBK_NX_STAGE_LIFECYCLE: githubAuthority ? 'github' : 'local',
|
|
919
|
+
EBK_SOURCE_COMMIT: source.commit,
|
|
920
|
+
EBK_SMOKE_API_URL: smokeTarget.apiUrl,
|
|
921
|
+
EBK_SMOKE_AUTHORITY_VERSION: 'cognito-admin-cloudwatch-observe-v1',
|
|
922
|
+
EBK_SMOKE_CLIENT_ID: smokeTarget.clientId,
|
|
923
|
+
EBK_SMOKE_URL: smokeTarget.webUrl,
|
|
924
|
+
EBK_SMOKE_USER_POOL_ID: smokeTarget.userPoolId,
|
|
925
|
+
EBK_STAGE: options.stage,
|
|
926
|
+
},
|
|
927
|
+
expectedRoleArn,
|
|
928
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
929
|
+
project,
|
|
930
|
+
region,
|
|
931
|
+
...(dependencies.run ? { runSync: dependencies.run } : {}),
|
|
932
|
+
...(dependencies.spawnChild
|
|
933
|
+
? { spawnChild: dependencies.spawnChild }
|
|
934
|
+
: {}),
|
|
935
|
+
stage: options.stage,
|
|
936
|
+
target: 'smoke-deployed',
|
|
937
|
+
});
|
|
938
|
+
children.push({
|
|
939
|
+
completedAt: now().toISOString(),
|
|
940
|
+
outcome: 'succeeded',
|
|
941
|
+
project,
|
|
942
|
+
startedAt: smokeStartedAt,
|
|
943
|
+
target: 'smoke-deployed',
|
|
944
|
+
});
|
|
945
|
+
smoke[project] = 'passed';
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
children.push({
|
|
949
|
+
completedAt: now().toISOString(),
|
|
950
|
+
outcome: 'failed',
|
|
951
|
+
project,
|
|
952
|
+
startedAt: smokeStartedAt,
|
|
953
|
+
target: 'smoke-deployed',
|
|
954
|
+
});
|
|
955
|
+
throw error;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
smoke[project] = 'not-applicable';
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (options.stage === 'production') {
|
|
963
|
+
if (!productionDatabaseAfterFoundation) {
|
|
964
|
+
throw new Error('Production Foundation database postflight was not completed before final deployment acceptance.');
|
|
965
|
+
}
|
|
966
|
+
if (transport)
|
|
967
|
+
await transport.refresh();
|
|
968
|
+
const boundaryEnvironment = transport
|
|
969
|
+
? transport.childEnvironment((0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env))
|
|
970
|
+
: (0, child_environment_js_1.sanitizeGovernedAwsChildEnvironment)(process.env, {
|
|
971
|
+
preserveVerifiedGithubCredentials: true,
|
|
972
|
+
});
|
|
973
|
+
const finalPostflightStartedAt = now().toISOString();
|
|
974
|
+
const foundationProject = orderedProjects[0] ?? 'foundation';
|
|
975
|
+
const profile = (0, index_js_2.productionFoundationProfileContract)(policy.productionFoundation.profile).contract;
|
|
845
976
|
try {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
...(
|
|
849
|
-
?
|
|
850
|
-
:
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
977
|
+
await (dependencies.attestProductionDatabase ??
|
|
978
|
+
production_database_safeguard_js_1.attestProductionDatabaseLifecycle)({
|
|
979
|
+
...(boundaryEnvironment.AWS_CONFIG_FILE
|
|
980
|
+
? { configFilepath: boundaryEnvironment.AWS_CONFIG_FILE }
|
|
981
|
+
: {}),
|
|
982
|
+
...(boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE
|
|
983
|
+
? {
|
|
984
|
+
credentialsFilepath: boundaryEnvironment.AWS_SHARED_CREDENTIALS_FILE,
|
|
985
|
+
}
|
|
986
|
+
: {}),
|
|
987
|
+
expectedAccountId: account,
|
|
988
|
+
expectedCapacity: {
|
|
989
|
+
max: Number.parseFloat(profile.aurora.max),
|
|
990
|
+
min: Number.parseFloat(profile.aurora.min),
|
|
991
|
+
},
|
|
992
|
+
expectedRegion: region,
|
|
993
|
+
expectedRoleArn,
|
|
994
|
+
now: now(),
|
|
995
|
+
priorDiscovery: productionDatabaseAfterFoundation,
|
|
996
|
+
...(transport ? { profile: transport.profile } : {}),
|
|
997
|
+
requireExisting: true,
|
|
998
|
+
});
|
|
999
|
+
children.push({
|
|
1000
|
+
completedAt: now().toISOString(),
|
|
1001
|
+
outcome: 'succeeded',
|
|
1002
|
+
project: foundationProject,
|
|
1003
|
+
startedAt: finalPostflightStartedAt,
|
|
1004
|
+
target: 'production-database-final-postflight',
|
|
1005
|
+
});
|
|
856
1006
|
}
|
|
857
|
-
|
|
858
|
-
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
children.push({
|
|
1009
|
+
completedAt: now().toISOString(),
|
|
1010
|
+
outcome: 'failed',
|
|
1011
|
+
project: foundationProject,
|
|
1012
|
+
startedAt: finalPostflightStartedAt,
|
|
1013
|
+
target: 'production-database-final-postflight',
|
|
1014
|
+
});
|
|
1015
|
+
throw error;
|
|
859
1016
|
}
|
|
860
|
-
mutatedProjects.push(project);
|
|
861
|
-
result = 'replan-required';
|
|
862
|
-
replanRequiredProject = orderedProjects[materialProjectIndex + 1] ?? null;
|
|
863
|
-
throw new Error(replanRequiredProject
|
|
864
|
-
? `Local deployment applied ${project} and requires a new complete-stage plan before ${replanRequiredProject}. Rerun and acknowledge the new current plan before any further mutation.`
|
|
865
|
-
: `Local deployment applied ${project} and requires a new complete-stage plan before accepting stage convergence. Rerun and require every current project to be a semantic no-op.`);
|
|
866
1017
|
}
|
|
867
|
-
|
|
1018
|
+
assertDeploymentSourceUnchanged(context.root, source, run);
|
|
1019
|
+
if (transport) {
|
|
1020
|
+
transport.cleanup();
|
|
1021
|
+
transport = undefined;
|
|
1022
|
+
}
|
|
1023
|
+
outcome = 'succeeded';
|
|
1024
|
+
retain();
|
|
1025
|
+
devkit_1.logger.info(`${githubAuthority ? 'Protected GitHub' : 'Local'} ${options.stage} deployment completed through native SST for ${children.filter(({ target }) => target === 'deploy').length} project(s).`);
|
|
868
1026
|
return { success: true };
|
|
869
1027
|
}
|
|
870
|
-
finally {
|
|
871
|
-
retainEvidence(context.root, {
|
|
872
|
-
account: expectedAccount,
|
|
873
|
-
action,
|
|
874
|
-
actorArnDigest: digest(arn),
|
|
875
|
-
backendAuthorityDigest: backend?.authorityDigest ?? null,
|
|
876
|
-
backendCreatedApplications: backend
|
|
877
|
-
? [...backend.createdApplications]
|
|
878
|
-
: [],
|
|
879
|
-
completedAt: (dependencies.now ?? (() => new Date()))().toISOString(),
|
|
880
|
-
deployNoOpProjects,
|
|
881
|
-
kind: 'ebk-local-deployment-evidence',
|
|
882
|
-
mutatedProjects,
|
|
883
|
-
orderedProjects,
|
|
884
|
-
operatorAcknowledgementDigest,
|
|
885
|
-
planDigests,
|
|
886
|
-
planSummaryDigest,
|
|
887
|
-
phase,
|
|
888
|
-
profile: policy.deploymentProfile,
|
|
889
|
-
replanRequiredProject,
|
|
890
|
-
refreshNoOpProjects,
|
|
891
|
-
region,
|
|
892
|
-
result,
|
|
893
|
-
schemaVersion: 1,
|
|
894
|
-
sourceCommit,
|
|
895
|
-
sourceDigest: digest(`${sourceCommit}:${sourceTree}`),
|
|
896
|
-
stage: options.stage,
|
|
897
|
-
startedAt,
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const executor = async (options, context) => {
|
|
902
|
-
try {
|
|
903
|
-
return await runLocalDeploy(options, context);
|
|
904
|
-
}
|
|
905
1028
|
catch (error) {
|
|
906
1029
|
devkit_1.logger.error(error instanceof Error ? error.message : String(error));
|
|
1030
|
+
if (transport) {
|
|
1031
|
+
try {
|
|
1032
|
+
transport.cleanup();
|
|
1033
|
+
transport = undefined;
|
|
1034
|
+
}
|
|
1035
|
+
catch {
|
|
1036
|
+
devkit_1.logger.error('Local federated credential transport cleanup failed; deployment acceptance is blocked.');
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
retain();
|
|
907
1040
|
return { success: false };
|
|
908
1041
|
}
|
|
909
|
-
}
|
|
1042
|
+
}
|
|
1043
|
+
const executor = (options, context) => runLocalDeploy(options, context);
|
|
910
1044
|
exports.default = executor;
|
|
911
1045
|
//# sourceMappingURL=local-deploy.js.map
|