Importing AWS Resources to CDK Apps with Python

Shikisoft Blog - Importing existing AWS resources to AWS CDK stacks

In my AWS CDK with Python Step by Step course, I teach you to define your constructs from the ground up. You learn to specify your AWS resources through CDK constructs using Python’s object-oriented methods.

However, what if you need to use or reference an existing resource from your AWS account, such as a VPC? Can you import a resource to your AWS CDK app?

So, in this blog post, I will discuss how to import an existing resource as a CDK construct. But you cannot achieve this in environment-agnostic stacks. Hence, we will start with specifying targetted CDK environments for your CDK stacks.

How does resource importing work on AWS CDK?

Before implementing, let’s first understand how CDK imports an existing resource from your AWS account.

In a CDK app, you specify all resources as L1, L2, or L3 constructs in your CDK stacks. Then, when you use the cdk synth or cdk deploy commands, the CDK Toolkit synthesizes a CloudFormation template from your code for each CDK stack. So, the templates are usable for all the target CDK environments you deploy.

However, the process is somewhat different if a resource needs to be imported to your CDK stack.

1) You again declare the existing AWS resource you want to import as a CDK construct using a special from method provided by its L2-level CDK construct. You need to give the resource ID, ARN, or another field to search for the resource. After that, you can reference it in other constructs, like a regular L2-level construct of its type.

2) When you execute the cdk synth or cdk deploy commands, CDK uses the AWS SDK for your programming language to search for the resource in your target CDK environment and synthesizes the template with that resource’s referenced values if the search is successful.

3) CDK deploys the stack to your target CDK environment.

You cannot modify an AWS resource imported to your CDK environment as a CDK construct. You can only use it in read-only mode. You reference it as a CDK construct while configuring other constructs to benefit from CDK’s object-oriented methods.

This also won’t work in an environment-agnostic CDK stack because the CDK Toolkit must know the target CDK environment before looking for the resource. It gets them from your CDK app. Therefore, let’s continue with discussing CDK environments and how you specify the target CDK environments for your stacks.

What is a CDK environment?

A CDK environment is where you deploy your CDK stack. CDK stacks correspond to CloudFormation stacks during deployment. So, they are regional resources. Hence, a CDK environment is your deployment target consisting of an AWS account and a region.

When you initialize a CDK app, it sets up an environment-agnostic stack by default. In other words, it initalizes a CDK stack with no CDK environment setting. For environment-agnostic stacks, the CDK Toolkit deploys the synthesized CloudFormation templates to the CDK environment of your current AWS CLI profile.

By the way, a CDK environment doesn’t exist only in theory. You must physically bootstrap your CDK environment before executing any CDK deployments to prepare the environment beforehand. You do this using the cdk bootstrap command, which deploys a CloudFormation stack in the target AWS account and region for the necessary resources used by the CDK Toolkit. The bootstrap command uses a template provided by the CDK team. So, you don’t need to do anything other than execute the command to bootstrap your CDK environment.

However, bootstrapping an environment is not sufficient to import an existing resource to your CDK app. You must also set the CDK environment of your stack in your app code to inform the CDK Toolkit about the target environment for the search. Importing doesn’t work in environment-agnostic stacks.

There are a few differences between environment-agnostic stacks and stacks with specified environments, including this resource importing feature. We can discuss them in more detail later in another blog post. But for now, let’s continue with setting your stack’s CDK environment.

How do you set your CDK stack’s target environment?

When you initialize a CDK app, you will see a comment in the stack definition in your app.py file, which explains the initialization of an environment-agnostic stack by default. As also described in this comment, there are two ways to set the target environment of your CDK stack.

Method 1: Specifying AWS account ID and region code

The first option is hardcoding your AWS account ID and region, as shown below.

app.py file

...

app = cdk.App()
SampleStack(app, "SampleStack",
           env=cdk.Environment(account='444433332222',
                                region='eu-central-1')
)

Here, 444433332222 is the hypothetical number for my AWS account ID, and eu-central-1 is the code of the AWS Frankfurt region. Please replace them with your own AWS account number and region.

Of course, a CDK environment must be bootstrapped in this AWS account for this region. You must also have permission to create CloudFormation stacks and resources specified in the target environment.

This hardcoded setting always tries to deploy your CDK stack to the same AWS account and region, regardless of your AWS CLI configuration. In this way, you lose the reusability of your CDK code for other AWS accounts and regions. However, by hardcoding its environment as a target, you ensure that the imported resource exists.

However, there is another method that enables you to deploy your app in other environments, too, without making your stack environment-agnostic. I never liked hardcoding values in my code, so let’s also discuss the second method as an alternative.

Method 2: Using the current AWS CLI profile settings

You can use the environment variables set for your AWS CLI profile while configuring the environment of your CDK stack. So, for the same stack, the example becomes like below.

app.py file

...

app = cdk.App()
SampleStack(app, "SampleStack",
           env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'),
                                region=os.getenv('CDK_DEFAULT_REGION')),
)

Here, you reference the CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION environment variables using Python’s os library. These are implied environment variables that the CDK Toolkit sets in reference to the values of your current AWS CLI profile. Before performing the lookup method to search for the AWS resource, the CDK Toolkit will replace them with the values from your AWS CLI profile configuration. So, the search will be performed on the target environment.

OK, you also set the environment of your CDK stack. Now is the time to discuss importing an existing resource in your CDK app.

The from_xxx() methods provided by CDK constructs

L2-level CDK constructs are the first-class citizen CDK constructs that provide many object-oriented features, making up most of the CDK’s advantages over JSON or YAML CloudFormation templates. The from_xxx() methods are among those (xxx changes according to the resource type), which enable you to import existing AWS resources as L2-level CDK constructs from your target environment by searching for their specific features.

You can’t use an imported CDK construct to modify the underlying resource. However, you can use its construct object like regular objects of that resource type to configure other resources.

Now, let’s provide an example by importing an existing VPC to our CDK app to launch an EC2 instance in it.

Example: Importing a VPC to your CDK stack

Suppose you have a preconfigured VPC in your AWS account. Instead of creating a new one, you want to create your CDK app’s resources in this VPC. You may have other resources managed by other CDK apps and may not want to configure a separate VPC for each app. Whatever your reason, you can use the ec2.Vpc construct’s from_lookup() method to import your VPC as below.

CDK constructs correspond to one or more AWS resources. You define them in your CDK stacks. Below is an example of importing an existing VPC in the init method of a sample CDK stack.

sample_app.sample_stack.py

...

class SampleStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        my_vpc = ec2.Vpc.from_lookup(self, 'MyVpc', vpc_id='vpc-0f1a1122223333456')

...

Here, we provided the ID of the VPC we want to import to the vpc_id attribute. So, the CDK Toolkit will perform the lookup by searching for a VPC with this VPC ID in the target CDK environment of the stack and proceed with the synthesis afterward.

Instead of the VPC ID, you can filter the VPCs according to a tag value via the tags attribute or use the vpc_name to filter according to its name.

VPC construct also provides the from_vpc_attributes method if you need to search for a VPC by providing more attributes, such as isolated subnet IDs, availability zones, etc. But if you use an attribute in this method, the values you provide, such as isolated subnet IDs, must match the existing values in length and order. Using the from_lookup method is recommended instead.

Anyway. The returned my_vpc construct is an object of the aws_ec2.Vpc construct class. So, you can use it while configuring an EC2 instance.

sample_app.sample_stack.py

...

class SampleStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        my_vpc = ec2.Vpc.from_lookup(self, 'MyVpc', vpc_id='vpc-0f8a1264829501695')

        my_server = ec2.Instance(self, 'WebServer',
                                    vpc=my_vpc,
                                    instance_type=ec2.InstanceType.of(instance_class=ec2.InstanceClass.T3, instance_size=ec2.InstanceSize.MICRO),
                                    machine_image=ec2.MachineImage.latest_amazon_linux2023()
                                )

        ...

Please note how we assign the my_vpc variable representing the imported VPC to the vpc_id attribute. Its usage is the same as that of a VPC construct you created from scratch in the same app.

Many L2-level CDK constructs provide the from_xxx class methods similar to importing an existing resource, where xxx changes according to the resource type. For example, you can import an existing AWS CodeCommit repository using the from_repository_arn or from_repository_name class methods of the aws_codecommit.Repository construct by providing the repository ARN or repository name, respectively. But unlike aws_ec2.Vpc, the aws_codecommit.Repository construct doesn’t provide the from_lookup method. So, it would be best if you referred to the CDK Construct Library reference for the import method of your L2-level construct class. But they often begin with from_.

Learn AWS CDK with Python!

Would you like to learn how to initialize new CDK apps and define your L1, L2 constructs, or CDK patterns (L3)? By joining my online AWS CDK course on Udemy, you can learn AWS CDK with Python as the programming language, from the most basic features to advanced concepts like CDK aspects and testing constructs.

With this course, I helped many people like you to understand AWS CDK concepts with practical hands-on examples. Besides, as a reader of this blog, you can join my AWS CDK with Python Step by Step course with a special discount using the links provided in this post.

So, enroll today and start learning AWS CDK step by step!

Conclusion

Sometimes, you need to reference an existing AWS resource in your CDK apps. In this post, I provided some insights into importing your resources as CDK constructs using the class methods provided by L2-level CDK constructs. I hope you find it helpful while using AWS CDK.

Thanks for reading!

References

Shikisoft Blog - Importing existing AWS resources to AWS CDK stacks
Emre Yilmaz

AWS Consultant • Instructor • Founder @ Shikisoft

Follow