In my previous post I mentioned that I was starting to migrate some existing CloudFormation templates to be generated using TypeScript and Amazon’s Cloud Development Kit. I started with some simple static websites that are hosted from S3 buckets, and for these simple sites found it easiest to completely tear down the old infrastructure and recreate it with the CDK. I’m now starting to work on infrastructure that I don’t want to destroy – systems like the EC2 instance that host this blog.
That post also mentioned the possibility to import existing resources into a CloudFormation stack. The CDK doesn’t provide support for this, but it’s still possible. Let’s start with a simple resource that I can recreate if something goes wrong: an IAM user.
Removing the user from the old stack
Let’s start with a simple stack (OldStack
) that contains a user (Leigh
), and investigate how we can move this to a new stack (NewStack
) managed using the CDK. Here’s the old stack:
Resources:
Leigh:
Type: AWS::IAM::User
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
- arn:aws:iam::aws:policy/job-function/Billing
UserName: leigh
We want to drop the user from this stack so that we can use it in the new one. We can do this by adding a DeletionPolicy
and then removing it (in two separate steps). First add the policy:
Resources:
Leigh:
Type: AWS::IAM::User
+ DeletionPolicy: Retain
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
- arn:aws:iam::aws:policy/job-function/Billing
UserName: leigh
Deploy the new template so the policy is applied within CloudFormation. We can then remove the resource from the stack and deploy again. For this example we only have one resource, so let’s just delete the whole stack. Here’s the stack events as reported by the AWS console:
The user wasn’t deleted because we added the DeletionPolicy
. We now have an orphaned IAM user that’s not attached to any stacks.
Building the new stack
Let’s build a new stack within the CDK that will (eventually) host it. Here’s some code for an empty stack:
import { Stack } from '@aws-cdk/core';
import type { Construct, StackProps } from '@aws-cdk/core';
class NewStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
}
}
export default NewStack;
We can deploy this as usual with the CDK.
Preparing the template for import
Once the new stack has been created we’re ready to import the user. First we need to add code that describes it:
+import { User } from '@aws-cdk/aws-iam';
import { Stack } from '@aws-cdk/core';
import type { Construct, StackProps } from '@aws-cdk/core';
class NewStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
+
+ new User(this, 'User', {
+ userName: 'leigh',
+ managedPolicies: [
+ { managedPolicyArn: 'arn:aws:iam::aws:policy/AdministratorAccess' },
+ { managedPolicyArn: 'arn:aws:iam::aws:policy/job-function/Billing' },
+ ],
+ });
}
}
export default NewStack;
We can now generate the stack template using cdk synth
:
$ cdk synth NewStack
Resources:
User00B015A1:
Type: AWS::IAM::User
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
- arn:aws:iam::aws:policy/job-function/Billing
UserName: leigh
Metadata:
aws:cdk:path: NewStack/User/Resource
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAAAyWMQQqAIBAA39LdtoQIugX9oOgBsm1gkcKu1kH8e0mnOcwwGoYOdDWaR2rcziahZ4K0BIOnmryTwBGDmnY3k/jISFmV1poL0irERRXmrJzfCA5pbv0te2irQ6ytObpgL4L55wuPOD5FcQAAAA==
Metadata:
aws:cdk:path: NewStack/CDKMetadata/Default
Unfortunately this template can’t be used to import the user:
- “Each resource to import must have a DeletionPolicy attribute in your template” (source) but this is missing.
- The
AWS::CDK::Metadata
resource changed when generating the updated template. CloudFormation doesn’t allow you to change existing resources at the same time as an import.
Luckily these problems can easily be resolved manually. We can add a DeletionPolicy
and revert the metadata to match the value that’s currently deployed (you can get this from the Template tab within CloudFormation). Save the YAML output from cdk synth
or edit the template directly (by default this is written to a cdk.out
directory).
Resources:
User00B015A1:
Type: AWS::IAM::User
+ DeletionPolicy: Retain
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
- arn:aws:iam::aws:policy/job-function/Billing
UserName: leigh
Metadata:
aws:cdk:path: NewStack/User/Resource
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
- Analytics: v2:deflate64:H4sIAAAAAAAAAyWMQQqAIBAA39LdtoQIugX9oOgBsm1gkcKu1kH8e0mnOcwwGoYOdDWaR2rcziahZ4K0BIOnmryTwBGDmnY3k/jISFmV1poL0irERRXmrJzfCA5pbv0te2irQ6ytObpgL4L55wuPOD5FcQAAAA==
+ Analytics: v2:deflate64:H4sIAAAAAAAAAzPUszTRM1R0SCwv1k1OydZPzi9K1asOLklMztZxzs8rLikqTS7RcU7LC0otzi8tSk6t1cnLT0nVyyrWLzME6jTTM1DMKs7M1C0qzSvJzE3VC4LQAMvlLV1YAAAA
Metadata:
aws:cdk:path: NewStack/CDKMetadata/Default
Is it safe to deploy with out-of-date metadata? I think so. If we decode the metadata values (these are base64-encoded gzip) then we get the following:
$ echo -n 'H4sIAAAAAAAAAzPUszTRM1R0SCwv1k1OydZPzi9K1asOLklMztZxzs8rLikqTS7RcU7LC0otzi8tSk6t1cnLT0nVyyrWLzME6jTTM1DMKs7M1C0qzSvJzE3VC4LQAMvlLV1YAAAA' | base64 -d | gunzip
1.94.1!@aws-cdk/core.{Stack,Construct,CfnResource},node.js/v14.16.0!jsii-runtime.Runtime
$ echo -n 'H4sIAAAAAAAAAyWMQQqAIBAA39LdtoQIugX9oOgBsm1gkcKu1kH8e0mnOcwwGoYOdDWaR2rcziahZ4K0BIOnmryTwBGDmnY3k/jISFmV1poL0irERRXmrJzfCA5pbv0te2irQ6ytObpgL4L55wuPOD5FcQAAAA==' | base64 -d | gunzip
1.94.1!@aws-cdk/{core.{Stack,Construct,CfnResource},aws-iam.{User,CfnUser}},node.js/v14.16.0!jsii-runtime.Runtime
This doesn’t look very important, especially when the data is stored under a key called “Analytics”. I can’t find any documentation for the AWS::CDK::Metadata
resource, so think it’s reasonable to conclude this doesn’t have any meaningful impact on the environment. In any case we’ll fix it later.
Importing the user into the new stack
Select the new stack and choose Import resources into stack to get the process started:
Upload the template that we’ve edited:
Tell CloudFormation how to find the user we want to import:
Preview the changes and import:
Cleaning up
We’ve now successfully imported the user into the new stack, but the template doesn’t match that defined using the CDK because of the manual changes we made:
$ cdk diff NewStack
Stack Root
Resources
[~] AWS::IAM::User User User00B015A1
└─ [-] DeletionPolicy
└─ Retain
Deploy the stack in order to revert these changes and bring CloudFormation into alignment with the CDK code.