Skip to content

Commit 20a59a8

Browse files
committed
feat: Support nested stacks in aws-cdk
1 parent e4cbd2d commit 20a59a8

30 files changed

+877
-5
lines changed

package-lock.json

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"test": "npm run build && RUN_TEST_FROM_CLI=true vitest run && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run",
5353
"test-cdk-basic": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/cdk-basic.test.ts",
5454
"test-cdk-basic-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/cdk-basic.test.ts",
55+
"test-cdk-nested": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/cdk-nested.test.ts",
56+
"test-cdk-nested-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/cdk-nested.test.ts",
5557
"test-cdk-esm": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/cdk-esm.test.ts",
5658
"test-cdk-esm-observable": "npm run build && RUN_TEST_FROM_CLI=true OBSERVABLE_MODE=true vitest run test/cdk-esm.test.ts",
5759
"test-sls-basic": "npm run build && RUN_TEST_FROM_CLI=true vitest run test/sls-basic.test.ts",
@@ -148,6 +150,7 @@
148150
"src/extension/*",
149151
"test",
150152
"test/cdk-basic",
153+
"test/cdk-nested",
151154
"test/cdk-esm",
152155
"test/cdk-config",
153156
"test/sls-basic",

src/cloudFormation.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,54 @@ async function getLambdasInStack(
159159
);
160160
}
161161

162+
/**
163+
* Get CloudFormation stack template
164+
* @param stackName
165+
* @param awsConfiguration
166+
* @returns
167+
*/
168+
async function getStackResourcePhysicalResourceId(
169+
stackName: string,
170+
logicalResourceId: string,
171+
awsConfiguration: AwsConfiguration,
172+
) {
173+
const { DescribeStackResourceCommand } = await import(
174+
'@aws-sdk/client-cloudformation'
175+
);
176+
const command = new DescribeStackResourceCommand({
177+
StackName: stackName,
178+
LogicalResourceId: logicalResourceId,
179+
});
180+
const cloudFormationClient = await getCloudFormationClient(awsConfiguration);
181+
182+
try {
183+
const response = await cloudFormationClient.send(command);
184+
185+
if (!response.StackResourceDetail) {
186+
throw new Error(
187+
`No resource details found in stack ${stackName} for ${logicalResourceId}`,
188+
);
189+
}
190+
191+
const physicalResourceId = response.StackResourceDetail.PhysicalResourceId;
192+
if (!physicalResourceId)
193+
throw new Error(
194+
`No physicalResourceId found in stack ${stackName} for ${logicalResourceId}`,
195+
);
196+
return physicalResourceId;
197+
} catch (error: any) {
198+
if (error.name === 'ValidationError') {
199+
Logger.error(
200+
`Stack ${stackName} and/or ${logicalResourceId} not found. Try specifying a region. Error: ${error.message}`,
201+
error,
202+
);
203+
}
204+
throw error;
205+
}
206+
}
207+
162208
export const CloudFormation = {
163209
getCloudFormationStackTemplate,
164210
getLambdasInStack,
211+
getStackResourcePhysicalResourceId: getStackResourcePhysicalResourceId,
165212
};

src/frameworks/cdkFramework.ts

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,48 @@ export class CdkFramework implements IFramework {
7070
JSON.stringify(lambdasInCdk, null, 2),
7171
);
7272

73-
//get all stack names
74-
const stackNames = [
75-
...new Set( // unique
73+
const cdkTokenRegex = /^\${Token\[TOKEN\.\d+\]}$/;
74+
const stackTokensCdkPathMappings = Object.fromEntries(
75+
lambdasInCdk
76+
.filter((lambda) => cdkTokenRegex.test(lambda.stackName))
77+
.map((lambda) => [lambda.stackName, lambda.stackCdkPath]),
78+
);
79+
const realStackNames = [
80+
...new Set(
7681
lambdasInCdk.map((lambda) => {
77-
return lambda.stackName;
82+
return lambda.rootStackName;
7883
}),
7984
),
8085
];
86+
87+
const unresolvedTokens = new Set(Object.keys(stackTokensCdkPathMappings));
88+
const resolvedTokenizedStackNames = new Set<string>();
89+
90+
if (Object.keys(stackTokensCdkPathMappings).length) {
91+
Logger.verbose(
92+
`[CDK] Found tokenized stackNames: ${[...unresolvedTokens].join(', ')}`,
93+
);
94+
Logger.verbose(
95+
`[CDK] Will look for tokenized stackNames in ${realStackNames.join(
96+
', ',
97+
)}`,
98+
);
99+
for (const realStackName of realStackNames) {
100+
if (!unresolvedTokens.size) break;
101+
await this.resolveTokenizedStackNames(
102+
unresolvedTokens,
103+
resolvedTokenizedStackNames,
104+
stackTokensCdkPathMappings,
105+
awsConfiguration,
106+
realStackName,
107+
);
108+
}
109+
}
110+
111+
//get all stack names
112+
const stackNames = [
113+
...new Set(realStackNames.concat([...resolvedTokenizedStackNames])), // unique
114+
];
81115
Logger.verbose(
82116
`[CDK] Found the following stacks in CDK: ${stackNames.join(', ')}`,
83117
);
@@ -170,6 +204,61 @@ export class CdkFramework implements IFramework {
170204
return lambdasDiscovered;
171205
}
172206

207+
protected async resolveTokenizedStackNames(
208+
unresolvedTokens: Set<string>,
209+
resolvedTokenizedStackNames: Set<string>,
210+
stackTokensCdkPathMappings: Record<string, string>,
211+
awsConfiguration: AwsConfiguration,
212+
stackName: string,
213+
): Promise<void> {
214+
if (!unresolvedTokens.size) {
215+
return;
216+
}
217+
218+
const cfTemplate = await CloudFormation.getCloudFormationStackTemplate(
219+
stackName,
220+
awsConfiguration,
221+
);
222+
223+
if (cfTemplate) {
224+
const nestedStacks = Object.entries(cfTemplate.Resources)
225+
.filter(
226+
([, resource]: [string, any]) =>
227+
resource.Type === 'AWS::CloudFormation::Stack',
228+
)
229+
.map(([key, resource]: [string, any]) => {
230+
return {
231+
logicalId: key,
232+
cdkPath: resource.Metadata['aws:cdk:path'],
233+
};
234+
});
235+
236+
for (const nestedStack of nestedStacks) {
237+
const mapping = Object.entries(stackTokensCdkPathMappings).find(
238+
(f) => f[1] === nestedStack.cdkPath,
239+
);
240+
241+
if (mapping) {
242+
unresolvedTokens.delete(mapping[0]);
243+
const physicalResourceId =
244+
await CloudFormation.getStackResourcePhysicalResourceId(
245+
stackName,
246+
nestedStack.logicalId,
247+
awsConfiguration,
248+
);
249+
resolvedTokenizedStackNames.add(physicalResourceId);
250+
await this.resolveTokenizedStackNames(
251+
unresolvedTokens,
252+
resolvedTokenizedStackNames,
253+
stackTokensCdkPathMappings,
254+
awsConfiguration,
255+
physicalResourceId,
256+
);
257+
}
258+
}
259+
}
260+
}
261+
173262
/**
174263
* Getz Lambda functions from the CloudFormation template metadata
175264
* @param stackName
@@ -308,9 +397,15 @@ export class CdkFramework implements IFramework {
308397
`;
309398
global.lambdas = global.lambdas ?? [];
310399
400+
let rootStack = this.stack;
401+
while (rootStack.nestedStackParent) {
402+
rootStack = rootStack.nestedStackParent;
403+
}
311404
const lambdaInfo = {
312405
//cdkPath: this.node.defaultChild?.node.path ?? this.node.path,
406+
stackCdkPath: this.stack.node.defaultChild?.node.path ?? this.stack.node.path,
313407
stackName: this.stack.stackName,
408+
rootStackName: rootStack.stackName,
314409
codePath: props.entry,
315410
code: props.code,
316411
node: this.node,
@@ -320,6 +415,8 @@ export class CdkFramework implements IFramework {
320415
321416
// console.log("CDK INFRA: ", {
322417
// stackName: lambdaInfo.stackName,
418+
// stackCdkPath: lambdaInfo.stackCdkPath,
419+
// rootStackName: lambdaInfo.rootStackName,
323420
// codePath: lambdaInfo.codePath,
324421
// code: lambdaInfo.code,
325422
// handler: lambdaInfo.handler,
@@ -490,6 +587,8 @@ export class CdkFramework implements IFramework {
490587
return {
491588
cdkPath: lambda.cdkPath,
492589
stackName: lambda.stackName,
590+
stackCdkPath: lambda.stackCdkPath,
591+
rootStackName: lambda.rootStackName,
493592
packageJsonPath,
494593
codePath,
495594
handler,
@@ -572,6 +671,8 @@ export class CdkFramework implements IFramework {
572671
return lambdas as {
573672
cdkPath: string;
574673
stackName: string;
674+
stackCdkPath: string;
675+
rootStackName: string;
575676
codePath?: string;
576677
code: {
577678
path?: string;

src/frameworks/cdkFrameworkWorker.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ parentPort.on('message', async (data) => {
2525
handler: lambda.handler,
2626
stackName: lambda.stackName,
2727
codePath: lambda.codePath,
28+
stackCdkPath: lambda.stackCdkPath,
29+
rootStackName: lambda.rootStackName,
2830
code: {
2931
path: lambda.code?.path,
3032
},

0 commit comments

Comments
 (0)