Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if (process.env.PACKAGE_LAYOUT_VERSION === '1') {
var sns = require('@aws-cdk/aws-sns');
var sqs = require('@aws-cdk/aws-sqs');
var lambda = require('@aws-cdk/aws-lambda');
var node_lambda = require('@aws-cdk/aws-lambda-nodejs');
var sso = require('@aws-cdk/aws-sso');
var docker = require('@aws-cdk/aws-ecr-assets');
var appsync = require('@aws-cdk/aws-appsync');
Expand All @@ -28,6 +29,7 @@ if (process.env.PACKAGE_LAYOUT_VERSION === '1') {
aws_sns: sns,
aws_sqs: sqs,
aws_lambda: lambda,
aws_lambda_nodejs: node_lambda,
aws_ecr_assets: docker,
aws_appsync: appsync,
Stack
Expand Down Expand Up @@ -264,6 +266,20 @@ class ImportableStack extends cdk.Stack {
});
}

if (process.env.INCLUDE_SINGLE_BUCKET === '1') {
const bucket = new s3.Bucket(this, 'test-bucket', {
removalPolicy: (process.env.RETAIN_SINGLE_BUCKET === '1') ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
});

new cdk.CfnOutput(this, 'BucketLogicalId', {
value: bucket.node.defaultChild.logicalId,
});

new cdk.CfnOutput(this, 'BucketName', {
value: bucket.bucketName,
});
}

if (process.env.LARGE_TEMPLATE === '1') {
for (let i = 1; i <= 70; i++) {
new sqs.Queue(this, `cdk-import-queue-test${i}`, {
Expand All @@ -272,6 +288,24 @@ class ImportableStack extends cdk.Stack {
});
}
}

if (process.env.INCLUDE_NODEJS_FUNCTION_LAMBDA === '1') {
new node_lambda.NodejsFunction(
this,
'cdk-import-nodejs-lambda-test',
{
bundling: {
minify: true,
sourceMap: false,
sourcesContent: false,
target: 'ES2020',
forceDockerBundling: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cli-integ, we don't keep a runtime dependency for esbuild. Uses docker esbuild instead.

},
runtime: lambda.Runtime.NODEJS_18_X,
entry: path.join(__dirname, 'lambda/index.js')
}
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2252,6 +2252,73 @@ integTest(
}),
);

/**
* Create an S3 bucket, orphan that bucket, then import the bucket, with a NodeJSFunction lambda also in the stack.
*
* Validates fix for https://github.com/aws/aws-cdk/issues/31999 (import fails)
*/
integTest(
'test resource import with construct that requires bundling',
withDefaultFixture(async (fixture) => {
// GIVEN
const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json');
await fs.mkdir(path.dirname(outputsFile), { recursive: true });

// First, create a stack that includes a NodeJSFunction lambda and one bucket that will be removed from the stack but NOT deleted from AWS.
await fixture.cdkDeploy('importable-stack', {
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '1', RETAIN_SINGLE_BUCKET: '1' },
options: ['--outputs-file', outputsFile],
});

try {
// Second, now the bucket we will remove is in the stack and has a logicalId. We can now make the resource mapping file.
// This resource mapping file will be used to tell the import operation what bucket to bring into the stack.
const fullStackName = fixture.fullStackName('importable-stack');
const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString());
const bucketLogicalId = outputs[fullStackName].BucketLogicalId;
const bucketName = outputs[fullStackName].BucketName;
const bucketResourceMap = {
[bucketLogicalId]: {
BucketName: bucketName,
},
};
const mappingFile = path.join(fixture.integTestDir, 'outputs', 'mapping.json');
await fs.writeFile(mappingFile, JSON.stringify(bucketResourceMap), { encoding: 'utf-8' });

// Third, remove the bucket from the stack, but don't delete the bucket from AWS.
await fixture.cdkDeploy('importable-stack', {
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '0', RETAIN_SINGLE_BUCKET: '0' },
});
const cfnTemplateBeforeImport = await fixture.aws.cloudFormation.send(
new GetTemplateCommand({ StackName: fullStackName }),
);
expect(cfnTemplateBeforeImport.TemplateBody).not.toContain(bucketLogicalId);

// WHEN
await fixture.cdk(['import', '--resource-mapping', mappingFile, fixture.fullStackName('importable-stack')], {
modEnv: { INCLUDE_NODEJS_FUNCTION_LAMBDA: '1', INCLUDE_SINGLE_BUCKET: '1', RETAIN_SINGLE_BUCKET: '0' },
});

// THEN
const describeStacksResponse = await fixture.aws.cloudFormation.send(
new DescribeStacksCommand({ StackName: fullStackName }),
);
const cfnTemplateAfterImport = await fixture.aws.cloudFormation.send(
new GetTemplateCommand({ StackName: fullStackName }),
);

// If bundling is skipped during import for NodeJSFunction lambda, then the operation should fail and exit
expect(describeStacksResponse.Stacks![0].StackStatus).toEqual('IMPORT_COMPLETE');

// If the import operation is successful, the template should contain the imported bucket
expect(cfnTemplateAfterImport.TemplateBody).toContain(bucketLogicalId);
} finally {
// Clean up the resources we created
await fixture.cdkDestroy('importable-stack');
}
}),
);

/**
* Create a queue, orphan that queue, then import the queue.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as path from 'path';
import * as fs from 'fs';
import { App, Stack, StackProps, ValidationError } from 'aws-cdk-lib';
import { App, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodeJs from 'aws-cdk-lib/aws-lambda-nodejs';
import { ExpectedResult, IntegTest } from '@aws-cdk/integ-tests-alpha';
import { IFunction, Runtime } from 'aws-cdk-lib/aws-lambda';
Expand Down Expand Up @@ -51,39 +49,3 @@ stack.lambdaFunctions.forEach(func=> {
ExecutedVersion: '$LATEST',
}));
});

// Ensure that the code is bundled
const assembly = app.synth();

stack.lambdaFunctions.forEach((func) => {
const template = assembly.getStackArtifact(stack.artifactId).template;
const resourceName = stack.getLogicalId(func.node.defaultChild as lambda.CfnFunction);
const resource = template.Resources[resourceName];

if (!resource || resource.Type !== 'AWS::Lambda::Function') {
throw new ValidationError(`Could not find Lambda function resource for ${func.functionName}`, stack);
}

const s3Bucket = resource.Properties.Code.S3Bucket;
const s3Key = resource.Properties.Code.S3Key;

if (!s3Bucket || !s3Key) {
throw new ValidationError(`Could not find S3 location for function ${func.functionName}`, stack);
}

const assetId = s3Key.split('.')[0]; // S3Key format is <hash>.zip"
const assetDir = path.join(assembly.directory, `asset.${assetId}`);

try {
if (!fs.existsSync(assetDir) || !fs.statSync(assetDir).isDirectory()) {
throw new ValidationError(`Asset directory does not exist for function ${func.functionName}: ${assetDir}`, stack);
}

const indexPath = path.join(assetDir, 'index.js');
if (!fs.existsSync(indexPath)) {
throw new ValidationError(`index.js not found in asset directory for function ${func.functionName}`, stack);
}
} catch (error) {
throw error;
}
});
Loading