How to migrate AWS CloudFormation templated resources to CDK

How to migrate AWS CloudFormation templated resources to CDK

I was on a project using CloudFormation templates to manage resources and it became more desirable to migrate the resources to CDK since the project was growing.

I haven't seen a lot of docs on this out there so I'm sharing this. From a high level, it involves detaching the resources from the existing stack, creating the new stack with CDK, translating the code, and then importing your resources into the new stack. In detail:

1. Set a DeletionPolicy on all of your existing stack's resources

We first need to set a DeletionPolicy of "Retain" on each resource in your CF template. This is important since it lets us delete the old stack without removing its resources.

On each resource in your stack, add the following:

AWSTemplateFormatVersion: 2010-09-09
Resources:
   NotificationTopic:
     Type: AWS::SNS::Topic
+    DeletionPolicy: Retain
     Properties:
       TopicName: "MySNSTopic"

Apply the changes to the stack:

aws cloudformation update-stack \
    --stack-name MyOldStack \
    --template-body file://myoldstack.yml

2. Delete the stack to detach the resources

Make sure you've completed the previous step and validate each resource to make sure they aren't deleted.

aws cloudformation delete-stack --stack-name MyOldStack

You can confirm that this is complete via the AWS console. You ought to see the status DELETE_SKIPPED for all the resources in the stack. The resources should continue to exist and function

3. Create the new CDK target stack if doesn't already exist

If it doesn't already exist, add your new target stack in your CDK code and run cdk deploy to create it:

import aws_cdk as cdk

class MyCDKStack(cdk.Stack):
    def __init__(self, scope: cdk.App, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
cdk deploy -e MyCDKStack
# ...
# ✨  Deployment time: 11.83s

# Stack ARN:
# arn:aws:cloudformation:us-east-1:123456789:stack/MyCDKStack/xxxx

# ✨  Total time: 20.41s

4. Define the resources you're importing in the CDK stack code

You'll need to translate all of the CloudFormation template code into whichever language your CDK code is in and add it to your stack. LLMs are incredibly helpful with this and I found the best results with ChatGPT. This is the prompt I used:

transform the following cloudformation template resource into Python CDK code
```
Resources:
   NotificationTopic:
     Type: AWS::SNS::Topic
     DeletionPolicy: Retain
     Properties:
       TopicName: "MySNSTopic"
```

I also had to instruct it severally to use the correct imports in Python by adding a specific instruction "Please rewrite the resource code to use the L2 construct imported from aws_cdk.xyz".

Here is a sample of what it output, added to our stack code from earlier

...
from aws_cdk import aws_sns as sns

class MyCDKStack(cdk.Stack):
    def __init__(self, scope: cdk.App, construct_id: str, **kwargs) -> None:
        ...
        notification_topic = sns.Topic(
            self, 
            "MySNSTopic",
            display_name="MySNSTopic",
            # This is an API detail it got wrong
            # removal_policy=core.RemovalPolicy.RETAIN 
        )

You'll have to do a fair amount of editing and refer to the CDK docs but ChatGPT provided an incredibly convenient starting point. Make sure to include every parameter you have on the live resource or else you may get more diffs after importing.

5. Import the resources into the CDK stack

Once all your resources have been translated into CDK code, run cdk import. This command is still in preview at the time of writing this and may be buggy. It will prompt you to provide resource identifiers of your existing resources:

cdk import MyCDKStack
# The 'cdk import' feature is currently in preview.
# MyCDKStack
# MyCDKStack/MySNSTopic/Resource (AWS::SNS::Topic): enter TopicArn [undefined]: arn:aws:sns:us-east-1:123456789:MySNSTopic
# MyCDKStack: importing resources into stack...
# MyCDKStack: creating CloudFormation changeset...

# ✅  MyCDKStack

6. Diff and update any missing details

Run a diff to see whether there is a difference between the resources you've imported and the code you have defined

cdk diff -e MyCDKStack

If there is a diff, once you resolve them in your code, you can run cdk deploy -e MyCDKStack to apply the changes.

Other notes

  • Some resources like AWS SNS topic subscriptions cannot be imported and need to be recreated in your CDK stack. You can find the full list of importable resources here.

  • CloudFormation Parameters don't mix well with CDK. If you were relying on this you may want to use environment variables wherein your CDK code is running instead.

  • When you use an IAM role on a CDK resource, CDK's default behaviour is to add a new policy for it behind the scenes. This will cause you to see a diff even if you already had a relevant policy defined in the role. To resolve this you may want to use the WithoutPolicyUpdates role object instead i.e.

      role = iam.Role(...)
      some_resource = cdk.SomeResource(role=role.without_policy_updates())