@fjall/deploy-core 0.89.5 → 0.89.6
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/LICENSE +50 -21
- package/README.md +25 -0
- package/dist/.minified +1 -0
- package/dist/src/__test-utils__/awsMockHelpers.d.ts +20 -0
- package/dist/src/__test-utils__/awsMockHelpers.js +1 -0
- package/dist/src/__test-utils__/index.d.ts +1 -0
- package/dist/src/__test-utils__/index.js +1 -0
- package/dist/src/aws/AwsProvider.js +0 -1
- package/dist/src/aws/SimpleAwsProvider.js +1 -70
- package/dist/src/aws/index.d.ts +4 -2
- package/dist/src/aws/index.js +1 -3
- package/dist/src/aws/organisations/accounts.js +10 -10
- package/dist/src/aws/organisations/backup.js +4 -2
- package/dist/src/aws/organisations/costAllocation.js +4 -2
- package/dist/src/aws/organisations/delegatedAdmin.d.ts +9 -0
- package/dist/src/aws/organisations/delegatedAdmin.js +43 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
- package/dist/src/aws/organisations/identityCentre.js +6 -2
- package/dist/src/aws/organisations/index.d.ts +4 -3
- package/dist/src/aws/organisations/index.js +1 -12
- package/dist/src/aws/organisations/ipam.js +4 -2
- package/dist/src/aws/organisations/organisation.js +27 -18
- package/dist/src/aws/organisations/organisationalUnits.d.ts +26 -6
- package/dist/src/aws/organisations/organisationalUnits.js +149 -35
- package/dist/src/aws/organisations/policies.js +4 -3
- package/dist/src/aws/organisations/ram.js +6 -2
- package/dist/src/aws/organisations/serviceAccess.js +12 -6
- package/dist/src/aws/organisations/trustedAccess.js +6 -2
- package/dist/src/aws/organisations/types.d.ts +23 -1
- package/dist/src/aws/organisations/types.js +1 -16
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.d.ts +6 -0
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +48 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventTypes.d.ts +45 -0
- package/dist/src/aws/utils/cloudformationEventTypes.js +1 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +8 -54
- package/dist/src/aws/utils/cloudformationEvents.js +1 -596
- package/dist/src/aws/utils/index.d.ts +5 -0
- package/dist/src/aws/utils/index.js +1 -0
- package/dist/src/aws/utils/stackStatus.js +1 -90
- package/dist/src/events/index.d.ts +13 -0
- package/dist/src/events/index.js +1 -0
- package/dist/src/index.d.ts +34 -17
- package/dist/src/index.js +41 -21
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.d.ts +12 -0
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +78 -0
- package/dist/src/orchestration/activeDeploymentGuard.d.ts +10 -0
- package/dist/src/orchestration/activeDeploymentGuard.js +39 -0
- package/dist/src/orchestration/applicationDeploy.js +46 -229
- package/dist/src/orchestration/applicationDeployHelpers.d.ts +39 -0
- package/dist/src/orchestration/applicationDeployHelpers.js +223 -0
- package/dist/src/orchestration/applicationDestroy.d.ts +14 -0
- package/dist/src/orchestration/applicationDestroy.js +131 -0
- package/dist/src/orchestration/builders/dockerBuilder.d.ts +17 -0
- package/dist/src/orchestration/builders/dockerBuilder.js +98 -0
- package/dist/src/orchestration/builders/frameworkRegistry.d.ts +23 -0
- package/dist/src/orchestration/builders/frameworkRegistry.js +1 -0
- package/dist/src/orchestration/builders/index.d.ts +4 -0
- package/dist/src/orchestration/builders/index.js +1 -0
- package/dist/src/orchestration/builders/openNextBuilder.d.ts +21 -0
- package/dist/src/orchestration/builders/openNextBuilder.js +144 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +30 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -0
- package/dist/src/orchestration/cascadeHelpers.d.ts +46 -0
- package/dist/src/orchestration/cascadeHelpers.js +160 -0
- package/dist/src/orchestration/contextHelpers.d.ts +46 -2
- package/dist/src/orchestration/contextHelpers.js +93 -1
- package/dist/src/orchestration/destroy.d.ts +13 -0
- package/dist/src/orchestration/destroy.js +67 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +2 -11
- package/dist/src/orchestration/detectionPipeline.js +29 -10
- package/dist/src/orchestration/dockerBuildHelper.d.ts +10 -0
- package/dist/src/orchestration/dockerBuildHelper.js +49 -0
- package/dist/src/orchestration/dockerInterface.d.ts +4 -2
- package/dist/src/orchestration/index.d.ts +8 -1
- package/dist/src/orchestration/index.js +1 -3
- package/dist/src/orchestration/manifestSecretParser.d.ts +11 -0
- package/dist/src/orchestration/manifestSecretParser.js +1 -0
- package/dist/src/orchestration/openNextBuild.d.ts +28 -0
- package/dist/src/orchestration/openNextBuild.js +243 -0
- package/dist/src/orchestration/organisationDeploy.js +110 -233
- package/dist/src/orchestration/organisationDestroy.d.ts +24 -0
- package/dist/src/orchestration/organisationDestroy.js +189 -0
- package/dist/src/orchestration/organisationSetup.d.ts +6 -4
- package/dist/src/orchestration/organisationSetup.js +28 -8
- package/dist/src/orchestration/resolveOperation.js +68 -6
- package/dist/src/orchestration/serviceFactory.d.ts +4 -0
- package/dist/src/orchestration/serviceFactory.js +1 -16
- package/dist/src/orchestration/spawnHelpers.d.ts +47 -0
- package/dist/src/orchestration/spawnHelpers.js +1 -0
- package/dist/src/orchestration/stackCleanup.d.ts +39 -0
- package/dist/src/orchestration/stackCleanup.js +1 -0
- package/dist/src/orchestration/welcomeImageHelper.d.ts +15 -0
- package/dist/src/orchestration/welcomeImageHelper.js +64 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +21 -30
- package/dist/src/services/application/ApplicationStackService.js +16 -234
- package/dist/src/services/application/applicationStackHelpers.d.ts +46 -0
- package/dist/src/services/application/applicationStackHelpers.js +248 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -1
- package/dist/src/services/index.d.ts +6 -0
- package/dist/src/services/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -67
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +10 -2
- package/dist/src/services/infrastructure/CdkCommandRunner.js +18 -15
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +16 -194
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -41
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -1
- package/dist/src/services/infrastructure/CdkOutputParser.js +2 -33
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +5 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +81 -47
- package/dist/src/services/infrastructure/CdkService.d.ts +7 -53
- package/dist/src/services/infrastructure/CdkService.js +41 -83
- package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +50 -0
- package/dist/src/services/infrastructure/CdkServiceTypes.js +0 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +9 -10
- package/dist/src/services/infrastructure/ICdkProcessManager.d.ts +27 -0
- package/dist/src/services/infrastructure/ICdkProcessManager.js +1 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.js +1 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.d.ts +7 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -0
- package/dist/src/services/infrastructure/index.d.ts +3 -1
- package/dist/src/services/infrastructure/index.js +1 -7
- package/dist/src/services/supporting/TemplateHashService.js +1 -1
- package/dist/src/services/supporting/helpers.js +1 -81
- package/dist/src/services/supporting/index.js +1 -3
- package/dist/src/steps/index.d.ts +1 -0
- package/dist/src/steps/index.js +1 -0
- package/dist/src/steps/stepRegistry.d.ts +71 -0
- package/dist/src/steps/stepRegistry.js +505 -0
- package/dist/src/types/FjallState.js +1 -118
- package/dist/src/types/ProgressEvent.js +1 -48
- package/dist/src/types/application/ApplicationServiceTypes.js +1 -30
- package/dist/src/types/application/index.js +1 -1
- package/dist/src/types/callbacks.d.ts +76 -4
- package/dist/src/types/callbacks.js +0 -1
- package/dist/src/types/constants.d.ts +2 -0
- package/dist/src/types/constants.js +1 -6
- package/dist/src/types/credentials.js +0 -1
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +5 -2
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
- package/dist/src/types/deployment/DeploymentTypes.js +0 -1
- package/dist/src/types/deployment/cloudformation.js +0 -1
- package/dist/src/types/deployment/index.d.ts +3 -1
- package/dist/src/types/deployment/index.js +1 -1
- package/dist/src/types/deployment/parallel.js +1 -10
- package/dist/src/types/deploymentEventSchema.d.ts +158 -0
- package/dist/src/types/deploymentEventSchema.js +1 -0
- package/dist/src/types/detection.d.ts +22 -0
- package/dist/src/types/detection.js +1 -0
- package/dist/src/types/entitlements.d.ts +31 -0
- package/dist/src/types/entitlements.js +0 -0
- package/dist/src/types/errors/CdkError.js +1 -20
- package/dist/src/types/errors/ServiceError.d.ts +2 -1
- package/dist/src/types/errors/ServiceError.js +1 -119
- package/dist/src/types/errors/index.d.ts +2 -0
- package/dist/src/types/errors/index.js +1 -0
- package/dist/src/types/events.d.ts +3 -9
- package/dist/src/types/events.js +0 -5
- package/dist/src/types/frameworkBuilder.d.ts +96 -0
- package/dist/src/types/frameworkBuilder.js +8 -0
- package/dist/src/types/index.d.ts +19 -4
- package/dist/src/types/index.js +1 -9
- package/dist/src/types/operations.d.ts +3 -2
- package/dist/src/types/operations.js +1 -285
- package/dist/src/types/orgConfig.d.ts +2 -10
- package/dist/src/types/orgConfig.js +0 -11
- package/dist/src/types/params.d.ts +60 -1
- package/dist/src/types/patternDetection.d.ts +14 -16
- package/dist/src/types/patternDetection.js +14 -18
- package/dist/src/types/patternTypes.d.ts +19 -0
- package/dist/src/types/patternTypes.js +1 -0
- package/dist/src/types/stepDefinitions.d.ts +163 -0
- package/dist/src/types/stepDefinitions.js +98 -0
- package/dist/src/types/validation.js +0 -1
- package/dist/src/util/dockerfileDetection.d.ts +5 -0
- package/dist/src/util/dockerfileDetection.js +1 -0
- package/dist/src/util/index.d.ts +4 -3
- package/dist/src/util/index.js +1 -3
- package/dist/src/util/sequencedCallbacks.d.ts +44 -0
- package/dist/src/util/sequencedCallbacks.js +1 -0
- package/package.json +49 -8
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +0 -32
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +0 -228
- package/dist/src/aws/utils/errors.d.ts +0 -26
- package/dist/src/aws/utils/errors.js +0 -59
- package/dist/src/util/fsHelpers.d.ts +0 -4
- package/dist/src/util/fsHelpers.js +0 -16
- package/dist/src/util/securityHelpers.d.ts +0 -31
- package/dist/src/util/securityHelpers.js +0 -124
- package/dist/src/util/singleton.d.ts +0 -2
- package/dist/src/util/singleton.js +0 -9
- package/dist/src/util/sleep.d.ts +0 -4
- package/dist/src/util/sleep.js +0 -4
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { success, failure } from "@fjall/generator";
|
|
2
|
-
import { logger } from "@fjall/util";
|
|
3
2
|
import { ORGANISATION_TYPES, getOrganisationStackName } from "../types/operations.js";
|
|
4
3
|
import { CdkContextBuilder } from "../services/supporting/CdkContextBuilder.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { stubCallerIdentity } from "../types/deployment/index.js";
|
|
5
|
+
import { buildParamsContext, collectStackOutputs, synthOrFail, bootstrapOrFail, forwardOutput, forwardResourceProgress } from "./contextHelpers.js";
|
|
6
|
+
import { partitionAccounts, deployCascadeAccount, readPlatformIpamPoolIds, deployDomains } from "./cascadeHelpers.js";
|
|
7
|
+
import { maskSensitiveOutput } from "@fjall/util";
|
|
8
|
+
import { INFRA_STEP_NAME, STEP_IDS } from "../types/stepDefinitions.js";
|
|
8
9
|
/**
|
|
9
10
|
* Organisation deployment orchestration.
|
|
10
11
|
*
|
|
@@ -41,34 +42,33 @@ function buildOrgContext(params, services, operation, deployType, accountName) {
|
|
|
41
42
|
path: operation.path,
|
|
42
43
|
region: services.awsProvider.getRegion(),
|
|
43
44
|
accountName,
|
|
44
|
-
callerIdentity:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
callerIdentity: stubCallerIdentity(services.awsProvider.getAccountId()),
|
|
46
|
+
...buildParamsContext({
|
|
47
|
+
orgConfig: params.orgConfig,
|
|
48
|
+
identity: params.identity,
|
|
49
|
+
skipOidc: params.options?.skipOidc
|
|
50
|
+
})
|
|
50
51
|
}, {
|
|
51
52
|
verbose: params.options?.verbose,
|
|
52
53
|
infraOnly: params.options?.infraOnly
|
|
53
54
|
}, params.orgConfig);
|
|
54
55
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Infrastructure deployment step names — match the webapp's
|
|
57
|
-
* EXPECTED_INFRASTRUCTURE_STEPS exactly so the UI timeline lights up.
|
|
58
|
-
*/
|
|
59
56
|
const INFRA_STEPS = {
|
|
60
|
-
CONNECT: { id:
|
|
61
|
-
PREPARE: { id:
|
|
62
|
-
DEPLOY: { id:
|
|
63
|
-
MONITORING: { id:
|
|
57
|
+
CONNECT: { id: STEP_IDS.CONNECT, name: INFRA_STEP_NAME.CONNECT },
|
|
58
|
+
PREPARE: { id: STEP_IDS.PREPARE_ENVIRONMENT, name: INFRA_STEP_NAME.PREPARE },
|
|
59
|
+
DEPLOY: { id: STEP_IDS.DEPLOY, name: INFRA_STEP_NAME.DEPLOY },
|
|
60
|
+
MONITORING: { id: STEP_IDS.MONITORING, name: INFRA_STEP_NAME.MONITORING },
|
|
61
|
+
ORG_DEPLOY: {
|
|
62
|
+
id: STEP_IDS.ORG_DEPLOY,
|
|
63
|
+
name: "Deploying organisation infrastructure"
|
|
64
|
+
}
|
|
64
65
|
};
|
|
65
66
|
const INFRA_STEP_TOTAL = 4;
|
|
66
67
|
/**
|
|
67
68
|
* Deploy a single organisation component (platform or account).
|
|
68
69
|
*
|
|
69
|
-
* Emits 4 named steps
|
|
70
|
-
*
|
|
71
|
-
* infrastructure → Enable monitoring.
|
|
70
|
+
* Emits 4 named steps from INFRASTRUCTURE_STEP_NAMES: Connect securely →
|
|
71
|
+
* Prepare environment → Deploy infrastructure → Enable monitoring.
|
|
72
72
|
*/
|
|
73
73
|
async function deploySingleComponent(params, services, operation, deployType, startTime) {
|
|
74
74
|
const { callbacks } = params;
|
|
@@ -80,36 +80,35 @@ async function deploySingleComponent(params, services, operation, deployType, st
|
|
|
80
80
|
const context = buildOrgContext(params, services, operation, deployType, deployType === "account" ? operation.target : undefined);
|
|
81
81
|
// Synth
|
|
82
82
|
callbacks.onLog?.(`Synthesising ${deployType} infrastructure…`, "info");
|
|
83
|
-
const synthResult = await services
|
|
83
|
+
const synthResult = await synthOrFail(services, context, callbacks, "CDK synthesis failed");
|
|
84
84
|
if (!synthResult.success) {
|
|
85
85
|
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "error", 1, INFRA_STEP_TOTAL);
|
|
86
|
-
|
|
87
|
-
callbacks.onError?.(error);
|
|
88
|
-
return failure(error);
|
|
86
|
+
return synthResult;
|
|
89
87
|
}
|
|
90
88
|
// Bootstrap
|
|
91
|
-
callbacks
|
|
92
|
-
|
|
93
|
-
if (!bootstrapResult.success) {
|
|
94
|
-
callbacks.onCDKBootstrap?.("failed");
|
|
89
|
+
const bsResult = await bootstrapOrFail(services, context, callbacks);
|
|
90
|
+
if (!bsResult.success) {
|
|
95
91
|
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "error", 1, INFRA_STEP_TOTAL);
|
|
96
|
-
|
|
97
|
-
callbacks.onError?.(error);
|
|
98
|
-
return failure(error);
|
|
92
|
+
return bsResult;
|
|
99
93
|
}
|
|
100
|
-
callbacks.onCDKBootstrap?.("complete");
|
|
101
94
|
callbacks.onStepComplete?.(INFRA_STEPS.PREPARE.id, INFRA_STEPS.PREPARE.name, "completed", 1, INFRA_STEP_TOTAL);
|
|
102
95
|
// Step 3: Deploy infrastructure
|
|
103
96
|
const stackName = getOrganisationStackName(operation.type);
|
|
104
97
|
callbacks.onStepStart?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, 2, INFRA_STEP_TOTAL);
|
|
105
|
-
const deployResult = await services.cdkService.runCdkDeploy(context, stackName, (
|
|
98
|
+
const deployResult = await services.cdkService.runCdkDeploy(context, stackName, forwardOutput(callbacks), forwardResourceProgress(callbacks), services.awsProvider);
|
|
106
99
|
if (!deployResult.success) {
|
|
107
100
|
callbacks.onStepComplete?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, "error", 2, INFRA_STEP_TOTAL);
|
|
108
|
-
const error = new Error(deployResult.error);
|
|
101
|
+
const error = new Error(maskSensitiveOutput(deployResult.error));
|
|
109
102
|
callbacks.onError?.(error);
|
|
110
103
|
return failure(error);
|
|
111
104
|
}
|
|
112
105
|
callbacks.onStepComplete?.(INFRA_STEPS.DEPLOY.id, INFRA_STEPS.DEPLOY.name, "completed", 2, INFRA_STEP_TOTAL);
|
|
106
|
+
// Capture CloudFormation outputs (OIDC role ARN, etc.)
|
|
107
|
+
const outputsResult = await services.cfnService.getStackOutputs(stackName);
|
|
108
|
+
if (!outputsResult.success) {
|
|
109
|
+
callbacks.onLog?.("Failed to read stack outputs (non-critical)", "debug");
|
|
110
|
+
}
|
|
111
|
+
const outputs = collectStackOutputs(outputsResult);
|
|
113
112
|
// Step 4: Enable monitoring — CloudTrail + alarms are part of the
|
|
114
113
|
// Account stack. Signal post-deploy readiness.
|
|
115
114
|
callbacks.onStepStart?.(INFRA_STEPS.MONITORING.id, INFRA_STEPS.MONITORING.name, 3, INFRA_STEP_TOTAL);
|
|
@@ -117,6 +116,7 @@ async function deploySingleComponent(params, services, operation, deployType, st
|
|
|
117
116
|
return success({
|
|
118
117
|
target: operation.target,
|
|
119
118
|
deploymentType: "organisation",
|
|
119
|
+
outputs,
|
|
120
120
|
durationMs: Date.now() - startTime
|
|
121
121
|
});
|
|
122
122
|
}
|
|
@@ -127,54 +127,67 @@ async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
|
127
127
|
const { callbacks, options } = params;
|
|
128
128
|
const providerAccounts = params.orgConfig?.providerAccounts ?? [];
|
|
129
129
|
const context = buildOrgContext(params, services, operation, "organisation");
|
|
130
|
+
// Compute step counts upfront so pre-deploy failures can reference them
|
|
131
|
+
const cascadeEnabled = options?.cascade !== false;
|
|
132
|
+
const cascadeAccountCount = cascadeEnabled ? providerAccounts.length : 0;
|
|
133
|
+
// Steps: prepare (synth+bootstrap) + org-deploy + cascade accounts
|
|
134
|
+
const totalSteps = 2 + cascadeAccountCount;
|
|
135
|
+
const { id: prepareStepId, name: prepareStepName } = INFRA_STEPS.PREPARE;
|
|
136
|
+
// Step 0: Prepare (synth + bootstrap)
|
|
137
|
+
callbacks.onStepStart?.(prepareStepId, prepareStepName, 0, totalSteps);
|
|
130
138
|
// Synth
|
|
131
139
|
callbacks.onLog?.("Synthesising organisation infrastructure…", "info");
|
|
132
|
-
const synthResult = await services
|
|
140
|
+
const synthResult = await synthOrFail(services, context, callbacks, "CDK synthesis failed");
|
|
133
141
|
if (!synthResult.success) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return failure(error);
|
|
142
|
+
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "error", 0, totalSteps);
|
|
143
|
+
return synthResult;
|
|
137
144
|
}
|
|
138
145
|
// Bootstrap org account
|
|
139
|
-
callbacks
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const error = new Error(`Bootstrap failed: ${bootstrapResult.error}`);
|
|
144
|
-
callbacks.onError?.(error);
|
|
145
|
-
return failure(error);
|
|
146
|
+
const bsResult = await bootstrapOrFail(services, context, callbacks);
|
|
147
|
+
if (!bsResult.success) {
|
|
148
|
+
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "error", 0, totalSteps);
|
|
149
|
+
return bsResult;
|
|
146
150
|
}
|
|
147
|
-
callbacks.
|
|
148
|
-
// Deploy org infrastructure
|
|
149
|
-
const orgStepId =
|
|
150
|
-
|
|
151
|
-
const cascadeEnabled = options?.cascade !== false;
|
|
152
|
-
const cascadeAccountCount = cascadeEnabled ? providerAccounts.length : 0;
|
|
153
|
-
const totalSteps = 1 + cascadeAccountCount;
|
|
154
|
-
callbacks.onStepStart?.(orgStepId, orgStepName, 0, totalSteps);
|
|
151
|
+
callbacks.onStepComplete?.(prepareStepId, prepareStepName, "completed", 0, totalSteps);
|
|
152
|
+
// Step 1: Deploy org infrastructure
|
|
153
|
+
const { id: orgStepId, name: orgStepName } = INFRA_STEPS.ORG_DEPLOY;
|
|
154
|
+
callbacks.onStepStart?.(orgStepId, orgStepName, 1, totalSteps);
|
|
155
155
|
const orgStackName = getOrganisationStackName(ORGANISATION_TYPES.ORGANISATION);
|
|
156
|
-
const orgResult = await services.cdkService.runCdkDeploy(context, orgStackName, (
|
|
156
|
+
const orgResult = await services.cdkService.runCdkDeploy(context, orgStackName, forwardOutput(callbacks), forwardResourceProgress(callbacks), services.awsProvider);
|
|
157
157
|
if (!orgResult.success) {
|
|
158
|
-
callbacks.onStepComplete?.(orgStepId, orgStepName, "error",
|
|
159
|
-
const error = new Error(orgResult.error);
|
|
158
|
+
callbacks.onStepComplete?.(orgStepId, orgStepName, "error", 1, totalSteps);
|
|
159
|
+
const error = new Error(maskSensitiveOutput(orgResult.error));
|
|
160
160
|
callbacks.onError?.(error);
|
|
161
161
|
return failure(error);
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
// Capture org root stack outputs (OIDC role ARN, etc.)
|
|
164
|
+
const orgOutputsResult = await services.cfnService.getStackOutputs(orgStackName);
|
|
165
|
+
if (!orgOutputsResult.success) {
|
|
166
|
+
callbacks.onLog?.("Failed to read org stack outputs (non-critical)", "debug");
|
|
167
|
+
}
|
|
168
|
+
const orgOutputs = collectStackOutputs(orgOutputsResult);
|
|
169
|
+
callbacks.onStepComplete?.(orgStepId, orgStepName, "completed", 1, totalSteps);
|
|
164
170
|
// Cascade to platform + domains + member accounts
|
|
171
|
+
const cascadeErrors = [];
|
|
172
|
+
const allCascadeOutputs = [];
|
|
165
173
|
if (cascadeEnabled && providerAccounts.length > 0) {
|
|
166
174
|
callbacks.onCascadeStart?.();
|
|
167
|
-
const cascadeErrors = [];
|
|
168
175
|
let accountsDeployed = 0;
|
|
169
176
|
let platformDeployed = false;
|
|
170
177
|
let domainsDeployed = false;
|
|
171
178
|
// Phase 1: Deploy platform account
|
|
172
|
-
const platformAccount = providerAccounts
|
|
179
|
+
const { platformAccount, memberAccounts } = partitionAccounts(providerAccounts);
|
|
173
180
|
if (platformAccount) {
|
|
174
181
|
callbacks.onCascadePhaseStart?.("platform");
|
|
175
182
|
const platformResult = await deployCascadeAccount(params, services, operation, platformAccount, "platform", callbacks);
|
|
176
183
|
if (platformResult.success) {
|
|
177
184
|
platformDeployed = true;
|
|
185
|
+
if (platformResult.data.outputs) {
|
|
186
|
+
allCascadeOutputs.push({
|
|
187
|
+
accountId: platformAccount.id,
|
|
188
|
+
outputs: platformResult.data.outputs
|
|
189
|
+
});
|
|
190
|
+
}
|
|
178
191
|
}
|
|
179
192
|
else {
|
|
180
193
|
cascadeErrors.push({
|
|
@@ -182,6 +195,7 @@ async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
|
182
195
|
error: platformResult.error.message
|
|
183
196
|
});
|
|
184
197
|
}
|
|
198
|
+
callbacks.onCascadePhaseComplete?.("platform");
|
|
185
199
|
}
|
|
186
200
|
// Phase 1.5: Read Platform stack outputs for IPAM pool IDs
|
|
187
201
|
let ipamPoolIds = new Map();
|
|
@@ -193,24 +207,43 @@ async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
|
193
207
|
const domainResult = await deployDomains(params.domainProvider, callbacks);
|
|
194
208
|
domainsDeployed = domainResult.domainsDeployed > 0;
|
|
195
209
|
for (const err of domainResult.errors) {
|
|
196
|
-
cascadeErrors.push({
|
|
210
|
+
cascadeErrors.push({
|
|
211
|
+
accountId: "domains",
|
|
212
|
+
error: maskSensitiveOutput(err)
|
|
213
|
+
});
|
|
197
214
|
}
|
|
198
215
|
}
|
|
199
216
|
// Phase 3: Deploy member accounts in parallel
|
|
200
|
-
const memberAccounts = providerAccounts.filter((acc) => acc.environment !== "root" && acc.environment !== "platform");
|
|
201
217
|
if (memberAccounts.length > 0) {
|
|
202
218
|
callbacks.onCascadePhaseStart?.("accounts");
|
|
203
219
|
const region = services.awsProvider.getRegion();
|
|
204
|
-
const
|
|
220
|
+
const memberSettled = await Promise.allSettled(memberAccounts.map((account) => {
|
|
205
221
|
const regionSuffix = region.replace(/-/g, "");
|
|
206
222
|
const ipamPoolId = ipamPoolIds.get(`${account.id}-${regionSuffix}`);
|
|
207
223
|
return deployCascadeAccount(params, services, operation, account, "account", callbacks, ipamPoolId);
|
|
208
224
|
}));
|
|
209
|
-
|
|
210
|
-
const result = memberResults[j];
|
|
225
|
+
memberSettled.forEach((settled, j) => {
|
|
211
226
|
const account = memberAccounts[j];
|
|
227
|
+
if (!account)
|
|
228
|
+
return;
|
|
229
|
+
if (settled.status === "rejected") {
|
|
230
|
+
cascadeErrors.push({
|
|
231
|
+
accountId: account.id,
|
|
232
|
+
error: maskSensitiveOutput(settled.reason instanceof Error
|
|
233
|
+
? settled.reason.message
|
|
234
|
+
: String(settled.reason))
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const result = settled.value;
|
|
212
239
|
if (result.success) {
|
|
213
240
|
accountsDeployed++;
|
|
241
|
+
if (result.data.outputs) {
|
|
242
|
+
allCascadeOutputs.push({
|
|
243
|
+
accountId: account.id,
|
|
244
|
+
outputs: result.data.outputs
|
|
245
|
+
});
|
|
246
|
+
}
|
|
214
247
|
}
|
|
215
248
|
else {
|
|
216
249
|
cascadeErrors.push({
|
|
@@ -218,7 +251,8 @@ async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
|
218
251
|
error: result.error.message
|
|
219
252
|
});
|
|
220
253
|
}
|
|
221
|
-
}
|
|
254
|
+
});
|
|
255
|
+
callbacks.onCascadePhaseComplete?.("accounts");
|
|
222
256
|
}
|
|
223
257
|
callbacks.onCascadeComplete?.({
|
|
224
258
|
platformDeployed,
|
|
@@ -231,177 +265,20 @@ async function deployOrgWithCascade(params, services, operation, startTime) {
|
|
|
231
265
|
const errorSummary = cascadeErrors
|
|
232
266
|
.map((e) => ` ${e.accountId}: ${e.error}`)
|
|
233
267
|
.join("\n");
|
|
234
|
-
callbacks.onLog?.(`Cascade completed with ${cascadeErrors.length} failure(s):\n${errorSummary}
|
|
268
|
+
callbacks.onLog?.(maskSensitiveOutput(`Cascade completed with ${cascadeErrors.length} failure(s):\n${errorSummary}`), "warn");
|
|
235
269
|
}
|
|
236
270
|
}
|
|
271
|
+
const warnings = cascadeErrors.length > 0
|
|
272
|
+
? cascadeErrors.map((e) => maskSensitiveOutput(`${e.accountId}: ${e.error}`))
|
|
273
|
+
: undefined;
|
|
237
274
|
return success({
|
|
238
275
|
target: operation.target,
|
|
239
276
|
deploymentType: "organisation",
|
|
240
|
-
|
|
277
|
+
outputs: orgOutputs,
|
|
278
|
+
...(allCascadeOutputs.length > 0
|
|
279
|
+
? { cascadeOutputs: allCascadeOutputs }
|
|
280
|
+
: {}),
|
|
281
|
+
durationMs: Date.now() - startTime,
|
|
282
|
+
warnings
|
|
241
283
|
});
|
|
242
284
|
}
|
|
243
|
-
/**
|
|
244
|
-
* Deploy a single cascade account (platform or member).
|
|
245
|
-
* Assumes the target account's role, sets env credentials, and deploys.
|
|
246
|
-
*/
|
|
247
|
-
async function deployCascadeAccount(params, services, operation, account, deployType, callbacks, ipamPoolId) {
|
|
248
|
-
const operationKey = `${account.name}-${account.id}`;
|
|
249
|
-
const region = services.awsProvider.getRegion();
|
|
250
|
-
callbacks.onCascadeAccountStart?.(operationKey, account.id, region);
|
|
251
|
-
// Assume role in target account
|
|
252
|
-
if (!services.awsProvider.assumeRole) {
|
|
253
|
-
const error = new Error(`Cannot cascade to account ${account.name}: AwsProvider does not support assumeRole`);
|
|
254
|
-
callbacks.onCascadeAccountComplete?.(operationKey, false, error.message, region);
|
|
255
|
-
return failure(error);
|
|
256
|
-
}
|
|
257
|
-
const roleArn = `arn:aws:iam::${account.id}:role/fjall-deployment-role`;
|
|
258
|
-
let assumedCredentials;
|
|
259
|
-
try {
|
|
260
|
-
assumedCredentials = await services.awsProvider.assumeRole(roleArn, `fjall-cascade-${account.name}`);
|
|
261
|
-
}
|
|
262
|
-
catch (err) {
|
|
263
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
264
|
-
callbacks.onCascadeAccountComplete?.(operationKey, false, message, region);
|
|
265
|
-
return failure(new Error(`Failed to assume role for ${account.name}: ${message}`));
|
|
266
|
-
}
|
|
267
|
-
// Build context for this account (includes IPAM pool ID if available)
|
|
268
|
-
const accountContext = CdkContextBuilder.buildDeploymentContext({
|
|
269
|
-
deployType,
|
|
270
|
-
target: operation.target,
|
|
271
|
-
path: operation.path,
|
|
272
|
-
region,
|
|
273
|
-
accountName: account.name,
|
|
274
|
-
callerIdentity: { Account: account.id, Arn: "", UserId: "" },
|
|
275
|
-
ipamPoolId,
|
|
276
|
-
...buildParamsContext(params)
|
|
277
|
-
}, { verbose: params.options?.verbose }, params.orgConfig);
|
|
278
|
-
// Create account-scoped provider for CDK monitoring
|
|
279
|
-
const accountProvider = new SimpleAwsProvider({
|
|
280
|
-
accessKeyId: assumedCredentials.accessKeyId,
|
|
281
|
-
secretAccessKey: assumedCredentials.secretAccessKey,
|
|
282
|
-
sessionToken: assumedCredentials.sessionToken,
|
|
283
|
-
region,
|
|
284
|
-
accountId: account.id
|
|
285
|
-
});
|
|
286
|
-
// Export account credentials for CDK subprocess
|
|
287
|
-
accountProvider.exportToEnv();
|
|
288
|
-
// Bootstrap the target account
|
|
289
|
-
callbacks.onCascadeAccountPhaseChange?.(operationKey, "synth", region);
|
|
290
|
-
const bootstrapResult = await services.cdkService.runCdkBootstrap(accountContext, (chunk) => callbacks.onOutput?.(chunk));
|
|
291
|
-
if (!bootstrapResult.success) {
|
|
292
|
-
services.awsProvider.exportToEnv();
|
|
293
|
-
callbacks.onCascadeAccountComplete?.(operationKey, false, `Bootstrap failed: ${bootstrapResult.error}`, region);
|
|
294
|
-
return failure(new Error(`Bootstrap failed for ${account.name}: ${bootstrapResult.error}`));
|
|
295
|
-
}
|
|
296
|
-
// Deploy the account stack
|
|
297
|
-
callbacks.onCascadeAccountPhaseChange?.(operationKey, "deploy", region);
|
|
298
|
-
const stackName = getOrganisationStackName(deployType === "platform"
|
|
299
|
-
? ORGANISATION_TYPES.PLATFORM
|
|
300
|
-
: ORGANISATION_TYPES.ACCOUNT);
|
|
301
|
-
const deployResult = await services.cdkService.runCdkDeploy(accountContext, stackName, (chunk) => callbacks.onOutput?.(chunk), (event) => callbacks.onCascadeAccountResourceProgress?.(operationKey, event, region), accountProvider);
|
|
302
|
-
// Restore parent credentials
|
|
303
|
-
services.awsProvider.exportToEnv();
|
|
304
|
-
if (!deployResult.success) {
|
|
305
|
-
callbacks.onCascadeAccountComplete?.(operationKey, false, deployResult.error, region);
|
|
306
|
-
return failure(new Error(deployResult.error));
|
|
307
|
-
}
|
|
308
|
-
callbacks.onCascadeAccountComplete?.(operationKey, true, undefined, region);
|
|
309
|
-
return success(undefined);
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Read Platform stack outputs to extract IPAM pool IDs for member accounts.
|
|
313
|
-
* Output keys follow the pattern `IpamPoolId{12-digit-accountId}{regionSuffix}`.
|
|
314
|
-
* Returns a map keyed by `{accountId}-{regionSuffix}` → pool ID.
|
|
315
|
-
* Non-fatal: returns empty map on any error.
|
|
316
|
-
*/
|
|
317
|
-
async function readPlatformIpamPoolIds(services, platformAccount, callbacks) {
|
|
318
|
-
const poolIds = new Map();
|
|
319
|
-
// Assume role in platform account to read its stack outputs
|
|
320
|
-
if (!services.awsProvider.assumeRole) {
|
|
321
|
-
logger.debug("organisationDeploy", "Cannot read Platform outputs: assumeRole not available");
|
|
322
|
-
return poolIds;
|
|
323
|
-
}
|
|
324
|
-
const region = services.awsProvider.getRegion();
|
|
325
|
-
const roleArn = `arn:aws:iam::${platformAccount.id}:role/fjall-deployment-role`;
|
|
326
|
-
let assumedCredentials;
|
|
327
|
-
try {
|
|
328
|
-
assumedCredentials = await services.awsProvider.assumeRole(roleArn, `fjall-ipam-read-${platformAccount.name}`);
|
|
329
|
-
}
|
|
330
|
-
catch (err) {
|
|
331
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
332
|
-
logger.debug("organisationDeploy", `Cannot read Platform outputs: AssumeRole failed: ${message}`);
|
|
333
|
-
return poolIds;
|
|
334
|
-
}
|
|
335
|
-
// Create a temporary provider scoped to the platform account
|
|
336
|
-
const platformProvider = new SimpleAwsProvider({
|
|
337
|
-
accessKeyId: assumedCredentials.accessKeyId,
|
|
338
|
-
secretAccessKey: assumedCredentials.secretAccessKey,
|
|
339
|
-
sessionToken: assumedCredentials.sessionToken,
|
|
340
|
-
region,
|
|
341
|
-
accountId: platformAccount.id
|
|
342
|
-
});
|
|
343
|
-
const platformCfn = new CloudFormationService(platformProvider);
|
|
344
|
-
const outputsResult = await platformCfn.getStackOutputs("Platform");
|
|
345
|
-
if (!outputsResult.success) {
|
|
346
|
-
logger.debug("organisationDeploy", `Failed to read Platform stack outputs: ${outputsResult.error.message}`);
|
|
347
|
-
return poolIds;
|
|
348
|
-
}
|
|
349
|
-
const ipamPattern = /^IpamPoolId(\d{12})(\w+)$/;
|
|
350
|
-
for (const output of outputsResult.data) {
|
|
351
|
-
const match = output.OutputKey?.match(ipamPattern);
|
|
352
|
-
if (match && output.OutputValue) {
|
|
353
|
-
const key = `${match[1]}-${match[2]}`;
|
|
354
|
-
poolIds.set(key, output.OutputValue);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (poolIds.size > 0) {
|
|
358
|
-
callbacks.onLog?.(`Read ${poolIds.size} IPAM pool ID(s) from Platform stack`, "info");
|
|
359
|
-
}
|
|
360
|
-
return poolIds;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Deploy configured domains: apex domains sequentially, then delegated
|
|
364
|
-
* domains in parallel. Delegates to the caller-provided DomainDeployProvider.
|
|
365
|
-
*/
|
|
366
|
-
async function deployDomains(provider, callbacks) {
|
|
367
|
-
const domains = provider.getDomains();
|
|
368
|
-
if (domains.length === 0) {
|
|
369
|
-
return { domainsDeployed: 0, errors: [] };
|
|
370
|
-
}
|
|
371
|
-
callbacks.onCascadePhaseStart?.("domains");
|
|
372
|
-
const apexDomains = domains.filter((d) => d.type === "apex");
|
|
373
|
-
const delegatedDomains = domains.filter((d) => d.type === "delegated");
|
|
374
|
-
let domainsDeployed = 0;
|
|
375
|
-
const errors = [];
|
|
376
|
-
// Phase A: Apex domains (sequential — delegation roles must exist first)
|
|
377
|
-
for (const domain of apexDomains) {
|
|
378
|
-
const result = await provider.deployDomain(domain.name, callbacks);
|
|
379
|
-
if (result.success) {
|
|
380
|
-
domainsDeployed++;
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
errors.push(`${domain.name}: ${result.error.message}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
// Phase B: Delegated subdomains (parallel — independent of each other)
|
|
387
|
-
if (delegatedDomains.length > 0) {
|
|
388
|
-
const subdomainResults = await Promise.allSettled(delegatedDomains.map(async (domain) => {
|
|
389
|
-
const result = await provider.deployDomain(domain.name, callbacks);
|
|
390
|
-
if (result.success) {
|
|
391
|
-
domainsDeployed++;
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
errors.push(`${domain.name}: ${result.error.message}`);
|
|
395
|
-
}
|
|
396
|
-
}));
|
|
397
|
-
for (const result of subdomainResults) {
|
|
398
|
-
if (result.status === "rejected") {
|
|
399
|
-
const message = result.reason instanceof Error
|
|
400
|
-
? result.reason.message
|
|
401
|
-
: String(result.reason);
|
|
402
|
-
errors.push(`Subdomain deploy error: ${message}`);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return { domainsDeployed, errors };
|
|
407
|
-
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Organisation destroy orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Receives pre-authenticated credentials and destroys all organisation
|
|
5
|
+
* infrastructure in cascade order:
|
|
6
|
+
* 1. Member accounts across ALL regions in parallel
|
|
7
|
+
* 2. Platform account in primary region
|
|
8
|
+
* 3. Organisation root stack
|
|
9
|
+
*
|
|
10
|
+
* Auth, verification, and interactive prompts are the caller's job
|
|
11
|
+
* (per engine/consumer boundary).
|
|
12
|
+
*/
|
|
13
|
+
import { type Result } from "@fjall/generator";
|
|
14
|
+
import type { DestroyParams, DestroyResult } from "../types/params.js";
|
|
15
|
+
import type { OrganisationOperation } from "../types/operations.js";
|
|
16
|
+
import type { DeployServices } from "./serviceFactory.js";
|
|
17
|
+
/**
|
|
18
|
+
* Destroy organisation infrastructure with cascade.
|
|
19
|
+
*
|
|
20
|
+
* The cascade ordering is: members (parallel, multi-region) -> platform -> org stack.
|
|
21
|
+
* If any member or platform destruction fails, the org stack is NOT destroyed
|
|
22
|
+
* (to avoid orphaning resources).
|
|
23
|
+
*/
|
|
24
|
+
export declare function destroyOrganisation(params: DestroyParams, services: DeployServices, operation: OrganisationOperation): Promise<Result<DestroyResult>>;
|