Reference and import existing assets into AWS CDK
Archive post - originally published 30 September 2021 on devsintheshed.com. I’ve left the article exactly as it ran back then: CDK v1,
aws_cdk.core, the per-serviceaws-cdk.aws-*packages, the lot. It’s a snapshot of how this worked at the time. CDK has since moved to v2 and a fair bit of the code below no longer runs as written - skip to the 2026 appendix at the end for what’s different now.
Hey everyone,
I’ve spent a lot of time recently using AWS CDK to build cloud infrastructure in code, and what I like about it is that I can write AWS infrastructure in my preferred language (usually Python or Javascript) in less time than I used to spend on Cloudformation, which is a lot more verbose.
This post covers two things I find myself doing often:
- Importing existing assets into AWS CDK that I created in the past with Cloudformation, so I don’t have to rewrite all of it in CDK.
- Referencing existing shared infrastructure in my AWS account (VPCs, Subnets, Load Balancers) so I can use it within CDK. This is handy when I don’t have CDK constructs creating those resources and set them up in the console at some point, though I aim to have everything in CDK eventually so I can move infrastructure between environments and accounts.
If you’re new to AWS CDK and want to follow along, work through AWS’s CDK Getting Started Guide first to set up the CDK CLI.
To Start #
We’ll be working with Python, so start by creating a new cdk project in your terminal:
mkdir cdk-fun && cd cdk-fun && cdk init app --language=python
Before we get started, within app.py edit the file to look like this:
#!/usr/bin/env python3
from aws_cdk import core as cdk
# For consistency with TypeScript code, `cdk` is the preferred import name for
# the CDK's core module. The following line also imports it as `core` for use
# with examples from the CDK Developer's Guide, which are in the process of
# being updated to use `cdk`. You may delete this import if you don't need it.
from aws_cdk import core
from cdk_fun.cdk_fun_stack import CdkFunStack
existing_environment = core.Environment(
# can use env imports if using Dev / UAT / Prod
account = "", # Your AWS Account
region = "ap-southeast-2" # Your AWS Region
)
app = core.App()
CdkFunStack(
app,
"CdkFunStack",
env = existing_environment
)
app.synth()
This is close to what was already in app.py, but we’ve added our AWS account and region details since we’ll need them when we look up existing resources in the account later.
Make sure to update lines 14 and 15 in app.py to reflect your account details:
account = "", # Your AWS Account
region = "ap-southeast-2" # Your AWS Region
From your root project directory, bootstrap the CDK project to your AWS account:
cdk bootstrap
Next we’ll edit the cdk_fun_stack.py file in the cdk_fun folder, starting with the imports at the top of the file for the CDK libraries we’ll use:
from aws_cdk import (
core as cdk,
cloudformation_include as cfn_inc,
aws_s3 as s3,
aws_apigateway as apigateway,
aws_lambda as lambda_,
aws_ec2 as ec2,
aws_iam as iam
)
Lastly, install the required libraries:
pip install aws-cdk.core aws-cdk.cloudformation_include aws-cdk.aws-s3 aws-cdk.aws-lambda aws-cdk.aws-ec2 aws-cdk.aws-apigateway aws-cdk.aws-iam
Long term you may want to run pip freeze > requirements.txt so you can install everything together later with pip install -r requirements.txt.
Add existing Cloudformation #
For this example we’ll add some existing Cloudformation for an S3 bucket that we want to create whenever we build our CDK stack in AWS.
Create a new file called cformation_s3.json in the cdk_fun folder, with the following Cloudformation that creates a bucket called MyBucket-S3B4E9YC:
{
"Resources": {
"MyBucket-S3B4E9YC": {
"Type": "AWS::S3::Bucket",
"Properties": {
}
}
}
}
Back in cdk_fun_stack.py, underneath the line that says # The code that defines your stack goes here, add the following:
# Import Existing Cloudformation Stack
template = cfn_inc.CfnInclude(
self, "Template",
template_file="cdk_fun/cformation_s3.json",
preserve_logical_ids=True
)
# Use within CDK and convert to a L2 construct
existing_bucket_l1 = template.get_resource("MyBucket-S3B4E9YC")
existing_bucket_l2 = s3.Bucket.from_bucket_name(
self, "Bucket", existing_bucket_l1.ref
)
This reads the Cloudformation file and makes it available as a high-level construct we can use with the rest of our infrastructure in CDK.
We can test this by creating a Lambda underneath your existing code:
# Show it being used with a lambda!
handler = lambda_.Function(self, "WidgetHandler",
runtime=lambda_.Runtime.NODEJS_10_X,
code=lambda_.Code.from_asset(
"./cdk_fun/lambda"
),
handler="widgets.main",
environment=dict(
BUCKET=existing_bucket_l2.bucket_name
)
)
existing_bucket_l2.grant_read_write(handler)
api = apigateway.RestApi(self, "widgets-api",
rest_api_name="Widget Service",
description="This service serves widgets.")
get_widgets_integration = apigateway.LambdaIntegration(handler,
request_templates={"application/json": '{ "statusCode": "200" }'})
api.root.add_method("GET", get_widgets_integration) # GET /
We then need the code for the lambda itself, so create a new folder inside cdk_fun called lambda, and in it a file called widgets.js with the following code:
/*
This code uses callbacks to handle asynchronous function responses.
It currently demonstrates using an async-await pattern.
AWS supports both the async-await and promises patterns.
*/
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
const bucketName = process.env.BUCKET;
exports.main = async function(event, context) {
try {
if (event.httpMethod === "GET") {
if (event.path === "/") {
const data = await S3.listObjectsV2({ Bucket: bucketName }).promise();
let body = {
widgets: data.Contents.map(function(e) { return e.Key })
};
return {
statusCode: 200,
headers: {},
body: JSON.stringify(body)
};
}
}
// We only accept GET for now
return {
statusCode: 400,
headers: {},
body: "We only accept GET /"
};
} catch(error) {
let body = error.stack || JSON.stringify(error, null, 2);
return {
statusCode: 400,
headers: {},
body: JSON.stringify(body)
}
}
}
Now we can test the app and check that the Cloudformation CDK generates is valid, by running this from your root project directory:
cdk synth
You should see a folder called cdk.out at your root project level, containing the Cloudformation files based on what you’ve created with CDK.
If that worked, deploy the project to your AWS environment:
cdk deploy
Now if you check the AWS console you should see an S3 bucket appear in a few minutes.
Reference existing assets within your AWS account #
Next we’ll use CDK’s from_lookup feature to reference an existing VPC I have set up in my account, which I called TestVPC. If you want to create a VPC and follow along, do Step 1 in AWS’s official guide.
We’ll look the VPC up by adding the following to the end of our cdk_fun_stack.py file:
# You can use lookups for importing VPC's within environments like
existing_vpc = ec2.Vpc.from_lookup(self, "TestVPC",
is_default=False,
vpc_name="TestVPC"
)
This finds the VPC I created called TestVPC. You can search by id and a few other values, see the VpcLookupOptions docs for more.
We can then use the VPC when we create other resources. Adding the following to the bottom of your file creates an EC2 instance and security group, and registers the instance with Systems Manager:
# Deploy an SSM managed EC2 instance within the VPC with a basic security group
security_group = ec2.SecurityGroup(
self,
"ssh-security-group",
vpc=existing_vpc,
allow_all_outbound=True,
)
security_group.add_ingress_rule(
peer=ec2.Peer.ipv4('10.2.0.0/16'),
description="allow ssh",
connection=ec2.Port.tcp(22)
)
role = iam.Role(self, "InstanceSSM",
assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"))
role.add_managed_policy(iam.ManagedPolicy.from_aws_managed_policy_name(
"AmazonSSMManagedInstanceCore"))
ec2_instance = ec2.Instance(
self,
"ec2-instance",
instance_name="ec2-instance01",
instance_type=ec2.InstanceType("t3.micro"),
machine_image=ec2.MachineImage.latest_amazon_linux(
generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
edition=ec2.AmazonLinuxEdition.STANDARD,
virtualization=ec2.AmazonLinuxVirt.HVM,
storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
),
vpc=existing_vpc,
security_group=security_group,
)
As we did with the existing Cloudformation earlier, we can test that CDK can reach the VPC and create these resources by running this from your root project folder:
cdk synth
To then deploy, run this from your root project folder:
cdk deploy
Now if you check the AWS console you should see an EC2 instance created in a few minutes!
Wrap-up #
If you followed along you should now be able to import existing assets into CDK that you created earlier with Cloudformation, and reference existing infrastructure in your account like VPCs so you can use it within CDK.
If you ran a deploy and created resources in your account, you can clean them up with this from your root project folder:
cdk destroy
Hope this was helpful for your own CDK projects. The full examples are in this repository.
Till next time!
- Matt Coles
Originally published at https://www.devsintheshed.com
Appendix (2026): what’s changed in CDK v2 #
I wrote the above in 2021 against CDK v1. CDK v1 reached end-of-support in June 2023, so if you start a new project today you’re on v2, and most of the code above won’t run unchanged. The techniques still hold (CfnInclude to pull in CloudFormation, from_lookup to reference existing resources) but the imports, the packages, and a couple of the APIs have all moved. Here’s the short list of what to change.
Everything moved into one library. The biggest change. In v1 you pip installed a separate package per service (aws-cdk.core, aws-cdk.aws-s3, aws-cdk.aws-ec2…) and they all had to be on the same version or things broke in confusing ways. v2 ships everything in a single aws-cdk-lib, plus the separate constructs library. So the whole install line collapses to:
pip install aws-cdk-lib constructs
core is gone. There’s no aws_cdk.core any more. The top-level constructs (App, Stack, Environment, Duration, RemovalPolicy) live directly under aws_cdk, and Construct comes from the constructs package. So the imports become:
from aws_cdk import (
App,
Stack,
Environment,
cloudformation_include as cfn_inc,
aws_s3 as s3,
aws_apigateway as apigateway,
aws_lambda as lambda_,
aws_ec2 as ec2,
aws_iam as iam,
)
from constructs import Construct
core.App() becomes App(), core.Environment(...) becomes Environment(...), and your stack’s constructor signature is now def __init__(self, scope: Construct, construct_id: str, **kwargs). The CfnInclude and from_lookup calls themselves are unchanged; only where you import them from moved.
That Lambda runtime won’t deploy. lambda_.Runtime.NODEJS_10_X is long dead: Node 10 hit end-of-life years ago and the runtime is removed from Lambda. Use a current one, e.g. lambda_.Runtime.NODEJS_24_X. While you’re there, MachineImage.latest_amazon_linux(...) is deprecated in favour of MachineImage.latest_amazon_linux2() (or latest_amazon_linux2023()), which drops all that generation/edition/virtualization boilerplate.
There are first-class commands for this now. In 2021 the CfnInclude dance was the main way to get existing infrastructure under CDK. v2 added two commands that often do the job better:
cdk import- bring already-deployed resources into a stack without recreating them. You add the construct to your code, thencdk importadopts the live resource into the stack instead of trying to make a new one.cdk migrate- generate a brand-new CDK app from an existing CloudFormation template, a deployed stack, or even resources discovered in your account. This is the fastest path if you’re starting from CloudFormation you already have, and it didn’t exist when I wrote the original.
There’s also the newer cdk refactor, which lets you move and rename constructs without CloudFormation deleting and recreating the underlying resource. That delete-and-recreate behaviour is what used to make people nervous about touching a working stack.
Everything else works the same: cdk bootstrap, cdk synth, cdk deploy, cdk destroy, and the whole idea of looking resources up rather than redefining them. Swap the imports, bump the Lambda runtime, and the walkthrough above runs on v2 as written, right down to the EC2 instance appearing in the console a few minutes after cdk deploy.
$ comments --load