@fjall/deploy-core 0.89.2
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 +21 -0
- package/dist/src/aws/AwsProvider.d.ts +39 -0
- package/dist/src/aws/AwsProvider.js +1 -0
- package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
- package/dist/src/aws/SimpleAwsProvider.js +73 -0
- package/dist/src/aws/index.d.ts +4 -0
- package/dist/src/aws/index.js +3 -0
- package/dist/src/aws/organisations/accounts.d.ts +21 -0
- package/dist/src/aws/organisations/accounts.js +99 -0
- package/dist/src/aws/organisations/backup.d.ts +12 -0
- package/dist/src/aws/organisations/backup.js +28 -0
- package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
- package/dist/src/aws/organisations/costAllocation.js +26 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
- package/dist/src/aws/organisations/identityCentre.js +19 -0
- package/dist/src/aws/organisations/index.d.ts +16 -0
- package/dist/src/aws/organisations/index.js +12 -0
- package/dist/src/aws/organisations/ipam.d.ts +7 -0
- package/dist/src/aws/organisations/ipam.js +18 -0
- package/dist/src/aws/organisations/organisation.d.ts +12 -0
- package/dist/src/aws/organisations/organisation.js +94 -0
- package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
- package/dist/src/aws/organisations/organisationalUnits.js +125 -0
- package/dist/src/aws/organisations/policies.d.ts +7 -0
- package/dist/src/aws/organisations/policies.js +36 -0
- package/dist/src/aws/organisations/ram.d.ts +7 -0
- package/dist/src/aws/organisations/ram.js +15 -0
- package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
- package/dist/src/aws/organisations/serviceAccess.js +38 -0
- package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
- package/dist/src/aws/organisations/trustedAccess.js +15 -0
- package/dist/src/aws/organisations/types.d.ts +29 -0
- package/dist/src/aws/organisations/types.js +16 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
- package/dist/src/aws/utils/cloudformationEvents.js +596 -0
- package/dist/src/aws/utils/errors.d.ts +26 -0
- package/dist/src/aws/utils/errors.js +59 -0
- package/dist/src/aws/utils/regions.d.ts +1 -0
- package/dist/src/aws/utils/regions.js +1 -0
- package/dist/src/aws/utils/stackStatus.d.ts +23 -0
- package/dist/src/aws/utils/stackStatus.js +90 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +45 -0
- package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
- package/dist/src/orchestration/applicationDeploy.js +327 -0
- package/dist/src/orchestration/contextHelpers.d.ts +9 -0
- package/dist/src/orchestration/contextHelpers.js +14 -0
- package/dist/src/orchestration/deploy.d.ts +10 -0
- package/dist/src/orchestration/deploy.js +42 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
- package/dist/src/orchestration/detectionPipeline.js +65 -0
- package/dist/src/orchestration/dockerInterface.d.ts +56 -0
- package/dist/src/orchestration/dockerInterface.js +1 -0
- package/dist/src/orchestration/domainInterface.d.ts +37 -0
- package/dist/src/orchestration/domainInterface.js +1 -0
- package/dist/src/orchestration/index.d.ts +8 -0
- package/dist/src/orchestration/index.js +3 -0
- package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
- package/dist/src/orchestration/organisationDeploy.js +382 -0
- package/dist/src/orchestration/organisationSetup.d.ts +42 -0
- package/dist/src/orchestration/organisationSetup.js +227 -0
- package/dist/src/orchestration/resolveOperation.d.ts +10 -0
- package/dist/src/orchestration/resolveOperation.js +53 -0
- package/dist/src/orchestration/serviceFactory.d.ts +15 -0
- package/dist/src/orchestration/serviceFactory.js +16 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
- package/dist/src/services/application/ApplicationStackService.js +436 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
- package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
- package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
- package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
- package/dist/src/services/infrastructure/CdkService.js +254 -0
- package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
- package/dist/src/services/infrastructure/index.d.ts +8 -0
- package/dist/src/services/infrastructure/index.js +7 -0
- package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
- package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
- package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
- package/dist/src/services/supporting/TemplateHashService.js +152 -0
- package/dist/src/services/supporting/helpers.d.ts +46 -0
- package/dist/src/services/supporting/helpers.js +81 -0
- package/dist/src/services/supporting/index.d.ts +3 -0
- package/dist/src/services/supporting/index.js +3 -0
- package/dist/src/types/FjallState.d.ts +50 -0
- package/dist/src/types/FjallState.js +118 -0
- package/dist/src/types/ProgressEvent.d.ts +35 -0
- package/dist/src/types/ProgressEvent.js +48 -0
- package/dist/src/types/apiClient.d.ts +34 -0
- package/dist/src/types/apiClient.js +1 -0
- package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
- package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
- package/dist/src/types/application/index.d.ts +1 -0
- package/dist/src/types/application/index.js +1 -0
- package/dist/src/types/callbacks.d.ts +36 -0
- package/dist/src/types/callbacks.js +1 -0
- package/dist/src/types/constants.d.ts +6 -0
- package/dist/src/types/constants.js +6 -0
- package/dist/src/types/credentials.d.ts +30 -0
- package/dist/src/types/credentials.js +1 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
- package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
- package/dist/src/types/deployment/DeploymentTypes.js +1 -0
- package/dist/src/types/deployment/cloudformation.d.ts +14 -0
- package/dist/src/types/deployment/cloudformation.js +1 -0
- package/dist/src/types/deployment/index.d.ts +5 -0
- package/dist/src/types/deployment/index.js +1 -0
- package/dist/src/types/deployment/parallel.d.ts +46 -0
- package/dist/src/types/deployment/parallel.js +10 -0
- package/dist/src/types/errors/CdkError.d.ts +14 -0
- package/dist/src/types/errors/CdkError.js +20 -0
- package/dist/src/types/errors/ServiceError.d.ts +86 -0
- package/dist/src/types/errors/ServiceError.js +119 -0
- package/dist/src/types/events.d.ts +40 -0
- package/dist/src/types/events.js +5 -0
- package/dist/src/types/index.d.ts +20 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/operations.d.ts +193 -0
- package/dist/src/types/operations.js +285 -0
- package/dist/src/types/orgConfig.d.ts +28 -0
- package/dist/src/types/orgConfig.js +11 -0
- package/dist/src/types/params.d.ts +74 -0
- package/dist/src/types/params.js +1 -0
- package/dist/src/types/patternDetection.d.ts +43 -0
- package/dist/src/types/patternDetection.js +92 -0
- package/dist/src/types/validation.d.ts +12 -0
- package/dist/src/types/validation.js +1 -0
- package/dist/src/util/fsHelpers.d.ts +4 -0
- package/dist/src/util/fsHelpers.js +16 -0
- package/dist/src/util/index.d.ts +3 -0
- package/dist/src/util/index.js +3 -0
- package/dist/src/util/securityHelpers.d.ts +31 -0
- package/dist/src/util/securityHelpers.js +124 -0
- package/dist/src/util/singleton.d.ts +2 -0
- package/dist/src/util/singleton.js +9 -0
- package/dist/src/util/sleep.d.ts +4 -0
- package/dist/src/util/sleep.js +4 -0
- package/package.json +42 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { STACK_NOT_FOUND_PATTERN, CDK_NO_STACKS_MATCH, INFRASTRUCTURE_FILENAME } from "../../types/constants.js";
|
|
2
|
+
import { APPLICATION_STACKS, getApplicationStackName, getApplicationDestroyOrder, getParallelDestroyGroups } from "../../types/operations.js";
|
|
3
|
+
import { detectPattern, isOpenNextPattern } from "../../types/patternDetection.js";
|
|
4
|
+
import { success, failure, isFailure } from "@fjall/generator";
|
|
5
|
+
import { ApplicationError } from "../../types/application/ApplicationServiceTypes.js";
|
|
6
|
+
import { logger } from "@fjall/util";
|
|
7
|
+
import { convertCloudFormationOutputsToRecord } from "../supporting/helpers.js";
|
|
8
|
+
/**
|
|
9
|
+
* Service for deploying and destroying individual CloudFormation stacks.
|
|
10
|
+
*
|
|
11
|
+
* Handles single-stack deploy/destroy, parallel operations, and the full
|
|
12
|
+
* destroy-all-stacks orchestration.
|
|
13
|
+
*
|
|
14
|
+
* Unlike the CLI version, services are constructor-injected rather than
|
|
15
|
+
* fetched via singleton factories.
|
|
16
|
+
*/
|
|
17
|
+
export class ApplicationStackService {
|
|
18
|
+
cdkService;
|
|
19
|
+
cloudFormationService;
|
|
20
|
+
aws;
|
|
21
|
+
constructor(cdkService, cloudFormationService) {
|
|
22
|
+
this.cdkService = cdkService;
|
|
23
|
+
this.cloudFormationService = cloudFormationService;
|
|
24
|
+
}
|
|
25
|
+
setAwsContext(aws) {
|
|
26
|
+
this.aws = aws;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Deploy a specific stack type for an application
|
|
30
|
+
*/
|
|
31
|
+
async deployStack(stackType, context, callbacks) {
|
|
32
|
+
const appName = context.target;
|
|
33
|
+
const stackName = getApplicationStackName(appName, stackType);
|
|
34
|
+
const result = await this.cdkService.runCdkDeploy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws);
|
|
35
|
+
if (result.success) {
|
|
36
|
+
const stepData = result.data;
|
|
37
|
+
logger.debug("ApplicationStackService", "CDK deploy result", {
|
|
38
|
+
stackName,
|
|
39
|
+
stackType,
|
|
40
|
+
success: true,
|
|
41
|
+
message: stepData.message,
|
|
42
|
+
status: stepData.status,
|
|
43
|
+
skipped: stepData.details?.skipped,
|
|
44
|
+
hasOutput: !!stepData.details?.output
|
|
45
|
+
});
|
|
46
|
+
const outputsResult = await this.cloudFormationService.getStackOutputs(stackName);
|
|
47
|
+
logger.debug("ApplicationStackService", "Stack outputs after deploy", {
|
|
48
|
+
stackName,
|
|
49
|
+
hasOutputs: outputsResult.success && outputsResult.data.length > 0,
|
|
50
|
+
outputCount: outputsResult.success ? outputsResult.data.length : 0
|
|
51
|
+
});
|
|
52
|
+
const outputs = outputsResult.success && outputsResult.data.length > 0
|
|
53
|
+
? convertCloudFormationOutputsToRecord(outputsResult.data)
|
|
54
|
+
: undefined;
|
|
55
|
+
return success({
|
|
56
|
+
stackType,
|
|
57
|
+
stackName,
|
|
58
|
+
skipped: stepData.details?.skipped === true,
|
|
59
|
+
outputs
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const errorMsg = result.error || `Failed to deploy ${stackType} infrastructure`;
|
|
64
|
+
logger.error("ApplicationStackService", "Stack deployment failed", {
|
|
65
|
+
stackType,
|
|
66
|
+
target: context.target,
|
|
67
|
+
error: errorMsg
|
|
68
|
+
});
|
|
69
|
+
return failure(new ApplicationError(errorMsg, {
|
|
70
|
+
errorType: "deployment_failed",
|
|
71
|
+
appName: context.target,
|
|
72
|
+
operation: "deployStack",
|
|
73
|
+
stackType
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Deploy multiple stacks in parallel within a deployment phase.
|
|
79
|
+
*
|
|
80
|
+
* Uses Promise.allSettled to ensure all stacks are attempted even if some fail.
|
|
81
|
+
*/
|
|
82
|
+
async deployStacksInParallel(stacks, context, callbacks) {
|
|
83
|
+
const onOutputCallback = callbacks?.onOutput;
|
|
84
|
+
const onResourceProgressCallback = callbacks?.onResourceProgress;
|
|
85
|
+
const onStackCompleteCallback = callbacks?.onStackComplete;
|
|
86
|
+
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const result = await this.deployStack(stack, context, {
|
|
89
|
+
onOutput: onOutputCallback
|
|
90
|
+
? (chunk) => onOutputCallback(chunk, stack)
|
|
91
|
+
: undefined,
|
|
92
|
+
onResourceProgress: onResourceProgressCallback
|
|
93
|
+
? (event) => onResourceProgressCallback(event, stack)
|
|
94
|
+
: undefined
|
|
95
|
+
});
|
|
96
|
+
const duration = Date.now() - startTime;
|
|
97
|
+
const deploymentResult = {
|
|
98
|
+
stack,
|
|
99
|
+
success: result.success,
|
|
100
|
+
error: result.success ? undefined : result.error,
|
|
101
|
+
duration,
|
|
102
|
+
...(result.success && result.data.outputs !== undefined
|
|
103
|
+
? { outputs: result.data.outputs }
|
|
104
|
+
: {})
|
|
105
|
+
};
|
|
106
|
+
if (!result.success && result.error) {
|
|
107
|
+
logger.debug("deployStacksInParallel", `Stack ${stack} failed`, {
|
|
108
|
+
error: result.error.message
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
onStackCompleteCallback?.(stack, result.success, duration, result.success ? undefined : result.error);
|
|
112
|
+
return deploymentResult;
|
|
113
|
+
}));
|
|
114
|
+
const deployResults = results.map((settledResult, index) => {
|
|
115
|
+
if (settledResult.status === "fulfilled") {
|
|
116
|
+
return settledResult.value;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
stack: stacks[index],
|
|
120
|
+
success: false,
|
|
121
|
+
error: settledResult.reason instanceof Error
|
|
122
|
+
? settledResult.reason
|
|
123
|
+
: new Error(String(settledResult.reason)),
|
|
124
|
+
duration: 0
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
return success(deployResults);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Destroy a specific stack type
|
|
131
|
+
*/
|
|
132
|
+
async destroyStack(stackType, context, callbacks, useCdkOut) {
|
|
133
|
+
const appName = context.target;
|
|
134
|
+
const stackName = getApplicationStackName(appName, stackType);
|
|
135
|
+
const result = await this.cdkService.runCdkDestroy(context, stackName, callbacks?.onOutput, callbacks?.onResourceProgress, this.aws, useCdkOut);
|
|
136
|
+
if (result.success) {
|
|
137
|
+
return success({
|
|
138
|
+
stackType,
|
|
139
|
+
stackName,
|
|
140
|
+
skipped: false,
|
|
141
|
+
outputs: undefined
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
return failure(new ApplicationError(result.error || `Failed to destroy ${stackType} stack`, {
|
|
146
|
+
errorType: "destroy_failed",
|
|
147
|
+
appName: context.target,
|
|
148
|
+
operation: "destroyStack",
|
|
149
|
+
stackType
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Destroy multiple stacks in parallel within a destroy phase.
|
|
155
|
+
*
|
|
156
|
+
* IMPORTANT: For parallel operations, useCdkOut must be true and CDK synth must
|
|
157
|
+
* be run beforehand. Multiple CDK commands cannot run in parallel because they
|
|
158
|
+
* all try to synthesise to the same cdk.out directory.
|
|
159
|
+
*/
|
|
160
|
+
async destroyStacksInParallel(stacks, context, callbacks, useCdkOut) {
|
|
161
|
+
const onOutputCallback = callbacks?.onOutput;
|
|
162
|
+
const onResourceProgressCallback = callbacks?.onResourceProgress;
|
|
163
|
+
const onStackCompleteCallback = callbacks?.onStackComplete;
|
|
164
|
+
const results = await Promise.allSettled(stacks.map(async (stack) => {
|
|
165
|
+
const startTime = Date.now();
|
|
166
|
+
const result = await this.destroyStack(stack, context, {
|
|
167
|
+
onOutput: onOutputCallback
|
|
168
|
+
? (chunk) => onOutputCallback(chunk, stack)
|
|
169
|
+
: undefined,
|
|
170
|
+
onResourceProgress: onResourceProgressCallback
|
|
171
|
+
? (event) => onResourceProgressCallback(event, stack)
|
|
172
|
+
: undefined
|
|
173
|
+
}, useCdkOut);
|
|
174
|
+
const duration = Date.now() - startTime;
|
|
175
|
+
const errorMessage = result.success
|
|
176
|
+
? ""
|
|
177
|
+
: (result.error?.message ?? "");
|
|
178
|
+
const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
179
|
+
errorMessage.includes(CDK_NO_STACKS_MATCH);
|
|
180
|
+
const destroyResult = {
|
|
181
|
+
stack,
|
|
182
|
+
success: result.success || isAlreadyDeleted,
|
|
183
|
+
error: result.success || isAlreadyDeleted ? undefined : result.error,
|
|
184
|
+
duration
|
|
185
|
+
};
|
|
186
|
+
if (!destroyResult.success && destroyResult.error) {
|
|
187
|
+
logger.debug("destroyStacksInParallel", `Stack ${stack} failed`, {
|
|
188
|
+
error: destroyResult.error.message
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
onStackCompleteCallback?.(stack, destroyResult.success, duration, destroyResult.success ? undefined : destroyResult.error);
|
|
192
|
+
return destroyResult;
|
|
193
|
+
}));
|
|
194
|
+
const destroyResults = results.map((settledResult, index) => {
|
|
195
|
+
if (settledResult.status === "fulfilled") {
|
|
196
|
+
return settledResult.value;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
stack: stacks[index],
|
|
200
|
+
success: false,
|
|
201
|
+
error: settledResult.reason instanceof Error
|
|
202
|
+
? settledResult.reason
|
|
203
|
+
: new Error(String(settledResult.reason)),
|
|
204
|
+
duration: 0
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
return success(destroyResults);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get stack outputs for a deployed stack
|
|
211
|
+
*/
|
|
212
|
+
async getStackOutputs(appName, stackType) {
|
|
213
|
+
const stackName = getApplicationStackName(appName, stackType);
|
|
214
|
+
const result = await this.cloudFormationService.getStackOutputs(stackName);
|
|
215
|
+
if (!result.success) {
|
|
216
|
+
return failure(new ApplicationError(`Failed to get outputs for ${stackName}`, {
|
|
217
|
+
errorType: "stack_error",
|
|
218
|
+
appName,
|
|
219
|
+
operation: "getStackOutputs",
|
|
220
|
+
stackType,
|
|
221
|
+
details: result.error
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
return success(convertCloudFormationOutputsToRecord(result.data));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Resolve the website URL for an application from CloudFormation stack outputs.
|
|
228
|
+
* Checks Compute stack for LoadBalancer URL, then CDN stack for CloudFront distribution.
|
|
229
|
+
*/
|
|
230
|
+
async resolveWebsiteUrl(appName) {
|
|
231
|
+
try {
|
|
232
|
+
const computeStackName = getApplicationStackName(appName, APPLICATION_STACKS.COMPUTE);
|
|
233
|
+
const computeOutputs = await this.cloudFormationService.getStackOutputs(computeStackName);
|
|
234
|
+
if (computeOutputs.success && computeOutputs.data.length > 0) {
|
|
235
|
+
const lbUrl = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerUrl"));
|
|
236
|
+
if (lbUrl?.OutputValue) {
|
|
237
|
+
return lbUrl.OutputValue.toLowerCase();
|
|
238
|
+
}
|
|
239
|
+
const lbDns = computeOutputs.data.find((o) => o.OutputKey?.includes("LoadBalancerDnsName"));
|
|
240
|
+
if (lbDns?.OutputValue) {
|
|
241
|
+
return `http://${lbDns.OutputValue.toLowerCase()}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const cdnStackName = getApplicationStackName(appName, APPLICATION_STACKS.CDN);
|
|
245
|
+
const cdnOutputs = await this.cloudFormationService.getStackOutputs(cdnStackName);
|
|
246
|
+
if (cdnOutputs.success && cdnOutputs.data.length > 0) {
|
|
247
|
+
const dist = cdnOutputs.data.find((o) => o.OutputKey?.includes("DistributionDomainName"));
|
|
248
|
+
if (dist?.OutputValue) {
|
|
249
|
+
return `https://${dist.OutputValue.toLowerCase()}`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
logger.debug("ApplicationStackService", "URL resolution failed", {
|
|
256
|
+
error: error instanceof Error ? error.message : String(error)
|
|
257
|
+
});
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Destroy all stacks for an application in reverse order.
|
|
263
|
+
* Supports parallel destruction for OpenNext patterns (nextjs/payload).
|
|
264
|
+
*/
|
|
265
|
+
async destroyAllStacks(context, callbacks, resources) {
|
|
266
|
+
const patternResult = detectPattern(context.path);
|
|
267
|
+
const pattern = patternResult.pattern;
|
|
268
|
+
const appName = context.target;
|
|
269
|
+
if (isOpenNextPattern(pattern)) {
|
|
270
|
+
const destroyGroups = getParallelDestroyGroups();
|
|
271
|
+
for (const group of destroyGroups) {
|
|
272
|
+
const stacks = group.stacks;
|
|
273
|
+
if (stacks.length === 1) {
|
|
274
|
+
const stackType = stacks[0];
|
|
275
|
+
const stackName = getApplicationStackName(appName, stackType);
|
|
276
|
+
callbacks?.onStackStart?.(stackType, stackName);
|
|
277
|
+
const result = await this.destroyStack(stackType, context, {
|
|
278
|
+
onOutput: callbacks?.onOutput
|
|
279
|
+
? (chunk) => callbacks.onOutput?.(chunk, stackType)
|
|
280
|
+
: undefined,
|
|
281
|
+
onResourceProgress: callbacks?.onResourceProgress
|
|
282
|
+
? (event) => callbacks.onResourceProgress?.(event, stackType)
|
|
283
|
+
: undefined
|
|
284
|
+
});
|
|
285
|
+
const finalResult = this.convertDestroyResultIfNotExists(result, stackType, appName);
|
|
286
|
+
if (callbacks?.onStackComplete) {
|
|
287
|
+
await callbacks.onStackComplete(stackType, finalResult);
|
|
288
|
+
}
|
|
289
|
+
const errorResult = this.handleDestroyError(finalResult, stackType);
|
|
290
|
+
if (errorResult) {
|
|
291
|
+
if (errorResult.continue)
|
|
292
|
+
continue;
|
|
293
|
+
return errorResult.result;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const synthResult = await this.cdkService.runCdkSynth(context);
|
|
298
|
+
if (!synthResult.success) {
|
|
299
|
+
return failure(new ApplicationError(synthResult.error, {
|
|
300
|
+
errorType: "synth_failed",
|
|
301
|
+
appName,
|
|
302
|
+
operation: "destroy-parallel-synth"
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
callbacks?.onParallelPhaseStart?.(stacks, group.description);
|
|
306
|
+
for (const stackType of stacks) {
|
|
307
|
+
callbacks?.onStackStart?.(stackType, getApplicationStackName(appName, stackType));
|
|
308
|
+
}
|
|
309
|
+
const parallelResult = await this.destroyStacksInParallel(stacks, context, {
|
|
310
|
+
onOutput: callbacks?.onOutput,
|
|
311
|
+
onResourceProgress: callbacks?.onResourceProgress,
|
|
312
|
+
onStackComplete: async (stack, stackSuccess, _duration, error) => {
|
|
313
|
+
const result = stackSuccess
|
|
314
|
+
? success({
|
|
315
|
+
stackName: `${appName}${stack}`,
|
|
316
|
+
stackType: stack,
|
|
317
|
+
outputs: {}
|
|
318
|
+
})
|
|
319
|
+
: failure(error instanceof ApplicationError
|
|
320
|
+
? error
|
|
321
|
+
: new ApplicationError(error?.message ?? "Stack destruction failed", {
|
|
322
|
+
errorType: "destroy_failed",
|
|
323
|
+
appName,
|
|
324
|
+
operation: `destroy-${stack}`
|
|
325
|
+
}));
|
|
326
|
+
await callbacks?.onStackComplete?.(stack, result);
|
|
327
|
+
}
|
|
328
|
+
}, true);
|
|
329
|
+
if (parallelResult.success) {
|
|
330
|
+
callbacks?.onParallelPhaseComplete?.(parallelResult.data);
|
|
331
|
+
const failures = parallelResult.data.filter((r) => !r.success);
|
|
332
|
+
if (failures.length > 0) {
|
|
333
|
+
const realFailures = failures.filter((f) => f.error &&
|
|
334
|
+
!f.error.message.includes(STACK_NOT_FOUND_PATTERN) &&
|
|
335
|
+
!f.error.message.includes(CDK_NO_STACKS_MATCH));
|
|
336
|
+
if (realFailures.length > 0) {
|
|
337
|
+
const failedStacks = realFailures
|
|
338
|
+
.map((f) => f.stack)
|
|
339
|
+
.join(", ");
|
|
340
|
+
const errorDetails = realFailures
|
|
341
|
+
.map((f) => `${f.stack}: ${f.error?.message || "Unknown error"}`)
|
|
342
|
+
.join("\n");
|
|
343
|
+
return failure(new ApplicationError(`Failed to destroy stacks: ${failedStacks}\n\n${errorDetails}`, {
|
|
344
|
+
errorType: "destroy_failed",
|
|
345
|
+
appName,
|
|
346
|
+
operation: "destroy-parallel"
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
const stackOrder = getApplicationDestroyOrder({ pattern, resources });
|
|
356
|
+
for (const stackType of stackOrder) {
|
|
357
|
+
const stackName = getApplicationStackName(appName, stackType);
|
|
358
|
+
callbacks?.onStackStart?.(stackType, stackName);
|
|
359
|
+
const result = await this.destroyStack(stackType, context, {
|
|
360
|
+
onOutput: callbacks?.onOutput
|
|
361
|
+
? (chunk) => callbacks.onOutput?.(chunk, stackType)
|
|
362
|
+
: undefined,
|
|
363
|
+
onResourceProgress: callbacks?.onResourceProgress
|
|
364
|
+
? (event) => callbacks.onResourceProgress?.(event, stackType)
|
|
365
|
+
: undefined
|
|
366
|
+
});
|
|
367
|
+
const finalResult = this.convertDestroyResultIfNotExists(result, stackType, appName);
|
|
368
|
+
if (callbacks?.onStackComplete) {
|
|
369
|
+
await callbacks.onStackComplete(stackType, finalResult);
|
|
370
|
+
}
|
|
371
|
+
const errorResult = this.handleDestroyError(finalResult, stackType);
|
|
372
|
+
if (errorResult) {
|
|
373
|
+
if (errorResult.continue)
|
|
374
|
+
continue;
|
|
375
|
+
return errorResult.result;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return success({ message: "All stacks destroyed" });
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Convert "doesn't exist" errors to success for destroy operations.
|
|
383
|
+
*/
|
|
384
|
+
convertDestroyResultIfNotExists(result, stackType, appName) {
|
|
385
|
+
if (result.success)
|
|
386
|
+
return result;
|
|
387
|
+
const errorMessage = isFailure(result) ? result.error.message : "";
|
|
388
|
+
const isAlreadyDeleted = errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
389
|
+
errorMessage.includes(CDK_NO_STACKS_MATCH);
|
|
390
|
+
if (isAlreadyDeleted) {
|
|
391
|
+
return success({
|
|
392
|
+
stackName: getApplicationStackName(appName, stackType),
|
|
393
|
+
stackType,
|
|
394
|
+
outputs: {}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Handle destroy errors and determine if we should continue or fail
|
|
401
|
+
*/
|
|
402
|
+
handleDestroyError(result, stackType) {
|
|
403
|
+
if (result.success)
|
|
404
|
+
return null;
|
|
405
|
+
const errorMessage = isFailure(result) ? result.error.message : "";
|
|
406
|
+
if (errorMessage.includes("TSError") ||
|
|
407
|
+
errorMessage.includes("Unable to compile TypeScript") ||
|
|
408
|
+
errorMessage.includes("Subprocess exited with error")) {
|
|
409
|
+
const tsErrorMatch = errorMessage.match(/infrastructure\.ts\(\d+,\d+\): error TS\d+: .+/);
|
|
410
|
+
const errorDetail = tsErrorMatch
|
|
411
|
+
? tsErrorMatch[0]
|
|
412
|
+
: `Check ${INFRASTRUCTURE_FILENAME} for syntax errors`;
|
|
413
|
+
return {
|
|
414
|
+
continue: false,
|
|
415
|
+
result: failure(new ApplicationError(`CDK synthesis failed: ${errorDetail}`, {
|
|
416
|
+
errorType: "synth_failed",
|
|
417
|
+
operation: `destroy-${stackType}`
|
|
418
|
+
}))
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (errorMessage.includes(STACK_NOT_FOUND_PATTERN) ||
|
|
422
|
+
errorMessage.includes(CDK_NO_STACKS_MATCH)) {
|
|
423
|
+
return {
|
|
424
|
+
continue: true,
|
|
425
|
+
result: success({ message: "Stack already deleted" })
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
continue: false,
|
|
430
|
+
result: failure(new ApplicationError(`Failed to destroy ${stackType} stack: ${errorMessage}`, {
|
|
431
|
+
errorType: "destroy_failed",
|
|
432
|
+
operation: `destroy-${stackType}`
|
|
433
|
+
}))
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ApplicationStackService } from "./ApplicationStackService.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ApplicationStackService } from "./ApplicationStackService.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CdkContext, CdkOptions } from "./CdkService.js";
|
|
2
|
+
export declare class CdkArgumentBuilder {
|
|
3
|
+
buildContextArgs(context?: CdkContext): string[];
|
|
4
|
+
injectCascadeCredentials(env: NodeJS.ProcessEnv, credentials?: CdkOptions["credentials"]): void;
|
|
5
|
+
buildCdkEnv(options?: {
|
|
6
|
+
context?: {
|
|
7
|
+
region?: string;
|
|
8
|
+
accountId?: string;
|
|
9
|
+
};
|
|
10
|
+
credentials?: CdkOptions["credentials"];
|
|
11
|
+
}): NodeJS.ProcessEnv;
|
|
12
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { filterDangerousEnvVars } from "../../util/securityHelpers.js";
|
|
2
|
+
export class CdkArgumentBuilder {
|
|
3
|
+
buildContextArgs(context) {
|
|
4
|
+
const args = [];
|
|
5
|
+
if (context?.accountId) {
|
|
6
|
+
args.push("-c", `accountId=${context.accountId}`);
|
|
7
|
+
}
|
|
8
|
+
if (context?.environment) {
|
|
9
|
+
args.push("-c", `environment=${context.environment}`);
|
|
10
|
+
}
|
|
11
|
+
if (context?.managedAccount) {
|
|
12
|
+
args.push("-c", "managedAccount=true");
|
|
13
|
+
}
|
|
14
|
+
if (context?.accountName) {
|
|
15
|
+
args.push("-c", `accountName=${context.accountName}`);
|
|
16
|
+
}
|
|
17
|
+
if (context?.imageVersion) {
|
|
18
|
+
args.push("-c", `imageVersion=${context.imageVersion}`);
|
|
19
|
+
}
|
|
20
|
+
if (context?.orgId) {
|
|
21
|
+
args.push("-c", `orgId=${context.orgId}`);
|
|
22
|
+
}
|
|
23
|
+
if (context?.rootId) {
|
|
24
|
+
args.push("-c", `rootId=${context.rootId}`);
|
|
25
|
+
}
|
|
26
|
+
if (context?.managementAccountId) {
|
|
27
|
+
args.push("-c", `managementAccountId=${context.managementAccountId}`);
|
|
28
|
+
}
|
|
29
|
+
if (context?.ipamPoolId) {
|
|
30
|
+
args.push("-c", `ipamPoolId=${context.ipamPoolId}`);
|
|
31
|
+
}
|
|
32
|
+
if (context?.fjallOrgId) {
|
|
33
|
+
args.push("-c", `fjallOrgId=${context.fjallOrgId}`);
|
|
34
|
+
}
|
|
35
|
+
if (context?.orgConfig) {
|
|
36
|
+
args.push("-c", `orgConfig=${context.orgConfig}`);
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
injectCascadeCredentials(env, credentials) {
|
|
41
|
+
if (credentials) {
|
|
42
|
+
env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
|
|
43
|
+
env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
|
|
44
|
+
if (credentials.sessionToken) {
|
|
45
|
+
env.AWS_SESSION_TOKEN = credentials.sessionToken;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
buildCdkEnv(options) {
|
|
50
|
+
const env = {
|
|
51
|
+
...filterDangerousEnvVars(process.env),
|
|
52
|
+
CI: "true",
|
|
53
|
+
FORCE_COLOR: "0",
|
|
54
|
+
CDK_DISABLE_VERSION_CHECK: "1"
|
|
55
|
+
};
|
|
56
|
+
if (options?.context?.region) {
|
|
57
|
+
env.AWS_REGION = options.context.region;
|
|
58
|
+
env.AWS_DEFAULT_REGION = options.context.region;
|
|
59
|
+
env.CDK_DEFAULT_REGION = options.context.region;
|
|
60
|
+
}
|
|
61
|
+
if (options?.context?.accountId) {
|
|
62
|
+
env.CDK_DEFAULT_ACCOUNT = options.context.accountId;
|
|
63
|
+
}
|
|
64
|
+
this.injectCascadeCredentials(env, options?.credentials);
|
|
65
|
+
return env;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Result } from "@fjall/generator";
|
|
2
|
+
import { CdkError } from "../../types/errors/CdkError.js";
|
|
3
|
+
import { type DiffDetails } from "./CdkOutputParser.js";
|
|
4
|
+
import type { CdkOptions, CdkOutput } from "./CdkService.js";
|
|
5
|
+
export interface CheckDifferencesResult {
|
|
6
|
+
hasDifferences: boolean;
|
|
7
|
+
output?: string;
|
|
8
|
+
details?: DiffDetails;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs low-level CDK CLI commands (synth, deploy, destroy, diff, bootstrap, import).
|
|
12
|
+
* Delegates process management to CdkProcessManager.
|
|
13
|
+
*/
|
|
14
|
+
export declare class CdkCommandRunner {
|
|
15
|
+
private processManager;
|
|
16
|
+
constructor();
|
|
17
|
+
checkDifferences(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CheckDifferencesResult, CdkError>>;
|
|
18
|
+
deploy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
|
|
19
|
+
destroy(path: string, stackName?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
|
|
20
|
+
/**
|
|
21
|
+
* Import existing AWS resources into CDK stack
|
|
22
|
+
*
|
|
23
|
+
* NOTE: This method is fully functional but currently unreachable as import
|
|
24
|
+
* functionality is temporarily disabled at the AST layer (astCodeModification.ts).
|
|
25
|
+
* When import is re-enabled, this method will work without changes.
|
|
26
|
+
*/
|
|
27
|
+
runImport(path: string, resourceMappingFile?: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
|
|
28
|
+
synth(path: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
|
|
29
|
+
bootstrap(accountId: string, region: string, options?: CdkOptions): Promise<Result<CdkOutput, string>>;
|
|
30
|
+
}
|