Skip to content

Commit 47a3f90

Browse files
authored
test(cli): cdk import works when used on a stack containing a nodejs lambda function (#33372)
### Improves testing for #33322. ### Reason for this change The original test (removed below) does not adequately test the CDK import fix from the above-mentioned PR. It calls CDK synth, but does not use the CLI and does not test `cdk import`. ### Description of changes The old test is removed. The new test ensures that `cdk import` works when an already-deployed stack contains a NodeJSFunction lambda. In our test, we deploy a stack containing a NodeJSFunction Lambda. We also create, orphan, and import an S3 bucket, which replicates the customer scenario in issue #31999. The test: 1. Installs `esbuild` so that we can use it for bundling. 2. Deploys a NodeJSFunction and an S3 bucket. 3. Orphans the S3 bucket but allows the bucket to persist (not destroyed). 4. Imports the S3 bucket into the template using its `BucketName` property. The test will fail if the import operation fails for any reason. The test does not assert any specific values or patterns for asset metadata keys. When investigating the issue, the metadata keys were different when using import-synth (skipped bundling) and deploy/diff/synth (did not skip bundling). The difference in the keys was another symptom of the same issue. ### Describe any new or updated permissions being added No permissions changes. ### Description of how you validated changes This PR tests the feature. I ensured that the test failed on code prior to the change in PR #33322, and passes on code following the change. ### Checklist - [X] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent cce7d3e commit 47a3f90

File tree

3 files changed

+102
-39
lines changed

3 files changed

+102
-39
lines changed

packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js

+34
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ if (process.env.PACKAGE_LAYOUT_VERSION === '1') {
1111
var sns = require('@aws-cdk/aws-sns');
1212
var sqs = require('@aws-cdk/aws-sqs');
1313
var lambda = require('@aws-cdk/aws-lambda');
14+
var node_lambda = require('@aws-cdk/aws-lambda-nodejs');
1415
var sso = require('@aws-cdk/aws-sso');
1516
var docker = require('@aws-cdk/aws-ecr-assets');
1617
var appsync = require('@aws-cdk/aws-appsync');
@@ -28,6 +29,7 @@ if (process.env.PACKAGE_LAYOUT_VERSION === '1') {
2829
aws_sns: sns,
2930
aws_sqs: sqs,
3031
aws_lambda: lambda,
32+
aws_lambda_nodejs: node_lambda,
3133
aws_ecr_assets: docker,
3234
aws_appsync: appsync,
3335
Stack
@@ -264,6 +266,20 @@ class ImportableStack extends cdk.Stack {
264266
});
265267
}
266268

269+
if (process.env.INCLUDE_SINGLE_BUCKET === '1') {
270+
const bucket = new s3.Bucket(this, 'test-bucket', {
271+
removalPolicy: (process.env.RETAIN_SINGLE_BUCKET === '1') ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
272+
});
273+
274+
new cdk.CfnOutput(this, 'BucketLogicalId', {
275+
value: bucket.node.defaultChild.logicalId,
276+
});
277+
278+
new cdk.CfnOutput(this, 'BucketName', {
279+
value: bucket.bucketName,
280+
});
281+
}
282+
267283
if (process.env.LARGE_TEMPLATE === '1') {
268284
for (let i = 1; i <= 70; i++) {
269285
new sqs.Queue(this, `cdk-import-queue-test${i}`, {
@@ -272,6 +288,24 @@ class ImportableStack extends cdk.Stack {
272288
});
273289
}
274290
}
291+
292+
if (process.env.INCLUDE_NODEJS_FUNCTION_LAMBDA === '1') {
293+
new node_lambda.NodejsFunction(
294+
this,
295+
'cdk-import-nodejs-lambda-test',
296+
{
297+
bundling: {
298+
minify: true,
299+
sourceMap: false,
300+
sourcesContent: false,
301+
target: 'ES2020',
302+
forceDockerBundling: true,
303+
},
304+
runtime: lambda.Runtime.NODEJS_18_X,
305+
entry: path.join(__dirname, 'lambda/index.js')
306+
}
307+
)
308+
}
275309
}
276310
}
277311

packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts

+67
Original file line numberDiff line numberDiff line change
@@ -2252,6 +2252,73 @@ integTest(
22522252
}),
22532253
);
22542254

2255+
/**
2256+
* Create an S3 bucket, orphan that bucket, then import the bucket, with a NodeJSFunction lambda also in the stack.
2257+
*
2258+
* Validates fix for https://github.com/aws/aws-cdk/issues/31999 (import fails)
2259+
*/
2260+
integTest(
2261+
'test resource import with construct that requires bundling',
2262+
withDefaultFixture(async (fixture) => {
2263+
// GIVEN
2264+
const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json');
2265+
await fs.mkdir(path.dirname(outputsFile), { recursive: true });
2266+
2267+
// First, create a stack that includes a NodeJSFunction lambda and one bucket that will be removed from the stack but NOT deleted from AWS.
2268+
await fixture.cdkDeploy('importable-stack', {
2269+
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '1', RETAIN_SINGLE_BUCKET: '1' },
2270+
options: ['--outputs-file', outputsFile],
2271+
});
2272+
2273+
try {
2274+
// Second, now the bucket we will remove is in the stack and has a logicalId. We can now make the resource mapping file.
2275+
// This resource mapping file will be used to tell the import operation what bucket to bring into the stack.
2276+
const fullStackName = fixture.fullStackName('importable-stack');
2277+
const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString());
2278+
const bucketLogicalId = outputs[fullStackName].BucketLogicalId;
2279+
const bucketName = outputs[fullStackName].BucketName;
2280+
const bucketResourceMap = {
2281+
[bucketLogicalId]: {
2282+
BucketName: bucketName,
2283+
},
2284+
};
2285+
const mappingFile = path.join(fixture.integTestDir, 'outputs', 'mapping.json');
2286+
await fs.writeFile(mappingFile, JSON.stringify(bucketResourceMap), { encoding: 'utf-8' });
2287+
2288+
// Third, remove the bucket from the stack, but don't delete the bucket from AWS.
2289+
await fixture.cdkDeploy('importable-stack', {
2290+
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '0', RETAIN_SINGLE_BUCKET: '0' },
2291+
});
2292+
const cfnTemplateBeforeImport = await fixture.aws.cloudFormation.send(
2293+
new GetTemplateCommand({ StackName: fullStackName }),
2294+
);
2295+
expect(cfnTemplateBeforeImport.TemplateBody).not.toContain(bucketLogicalId);
2296+
2297+
// WHEN
2298+
await fixture.cdk(['import', '--resource-mapping', mappingFile, fixture.fullStackName('importable-stack')], {
2299+
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '1', RETAIN_SINGLE_BUCKET: '0' },
2300+
});
2301+
2302+
// THEN
2303+
const describeStacksResponse = await fixture.aws.cloudFormation.send(
2304+
new DescribeStacksCommand({ StackName: fullStackName }),
2305+
);
2306+
const cfnTemplateAfterImport = await fixture.aws.cloudFormation.send(
2307+
new GetTemplateCommand({ StackName: fullStackName }),
2308+
);
2309+
2310+
// If bundling is skipped during import for NodeJSFunction lambda, then the operation should fail and exit
2311+
expect(describeStacksResponse.Stacks![0].StackStatus).toEqual('IMPORT_COMPLETE');
2312+
2313+
// If the import operation is successful, the template should contain the imported bucket
2314+
expect(cfnTemplateAfterImport.TemplateBody).toContain(bucketLogicalId);
2315+
} finally {
2316+
// Clean up the resources we created
2317+
await fixture.cdkDestroy('importable-stack');
2318+
}
2319+
}),
2320+
);
2321+
22552322
/**
22562323
* Create a queue, orphan that queue, then import the queue.
22572324
*

packages/@aws-cdk-testing/framework-integ/test/aws-lambda-nodejs/test/integ.nodejs.build.images.ts

+1-39
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import * as path from 'path';
2-
import * as fs from 'fs';
3-
import { App, Stack, StackProps, ValidationError } from 'aws-cdk-lib';
2+
import { App, Stack, StackProps } from 'aws-cdk-lib';
43
import { Construct } from 'constructs';
5-
import * as lambda from 'aws-cdk-lib/aws-lambda';
64
import * as lambdaNodeJs from 'aws-cdk-lib/aws-lambda-nodejs';
75
import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha';
86
import { IFunction, Runtime } from 'aws-cdk-lib/aws-lambda';
@@ -51,39 +49,3 @@ stack.lambdaFunctions.forEach(func=> {
5149
ExecutedVersion: '$LATEST',
5250
}));
5351
});
54-
55-
// Ensure that the code is bundled
56-
const assembly = app.synth();
57-
58-
stack.lambdaFunctions.forEach((func) => {
59-
const template = assembly.getStackArtifact(stack.artifactId).template;
60-
const resourceName = stack.getLogicalId(func.node.defaultChild as lambda.CfnFunction);
61-
const resource = template.Resources[resourceName];
62-
63-
if (!resource || resource.Type !== 'AWS::Lambda::Function') {
64-
throw new ValidationError(`Could not find Lambda function resource for ${func.functionName}`, stack);
65-
}
66-
67-
const s3Bucket = resource.Properties.Code.S3Bucket;
68-
const s3Key = resource.Properties.Code.S3Key;
69-
70-
if (!s3Bucket || !s3Key) {
71-
throw new ValidationError(`Could not find S3 location for function ${func.functionName}`, stack);
72-
}
73-
74-
const assetId = s3Key.split('.')[0]; // S3Key format is <hash>.zip"
75-
const assetDir = path.join(assembly.directory, `asset.${assetId}`);
76-
77-
try {
78-
if (!fs.existsSync(assetDir) || !fs.statSync(assetDir).isDirectory()) {
79-
throw new ValidationError(`Asset directory does not exist for function ${func.functionName}: ${assetDir}`, stack);
80-
}
81-
82-
const indexPath = path.join(assetDir, 'index.js');
83-
if (!fs.existsSync(indexPath)) {
84-
throw new ValidationError(`index.js not found in asset directory for function ${func.functionName}`, stack);
85-
}
86-
} catch (error) {
87-
throw error;
88-
}
89-
});

0 commit comments

Comments
 (0)