Simplifying CloudFormation stack management with the new refactoring feature
1. Problem statement
If you’ve worked with CloudFormation stacks, regardless of the infrastructure as code method, you’ve likely encountered the need to change a stack.
Either the logical name didn’t accurately reflect the resource, or you wanted to move one or more resources to a different stack.
1.1. Changing the logical name
The logical name (or ID) is unique within the stack, and other resources reference it to use the given resource. Changing the logical name requires updating every reference in the template file.
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyBucket: # <-- This is the logical name (ID).
Type: AWS::S3::Bucket
Properties:
BucketName: mybucket-1234 # This is the physical name of the bucket,
# which is usually different from the logical ID. It's what we see
# in the S3 console.
When you update a stack after changing a resource’s ID, CloudFormation creates a new resource with the new logical ID and, by default, attempts to delete the old one. This behaviour can be problematic, especially if the resource is part of a heavily used application. For example, changing the logical name of an S3 bucket requires migrating all objects from the old bucket to the new one. This can be inconvenient.
1.2. Refactoring a stack
Another scenario is stack refactoring. It’s common to work with multiple stacks, even for smaller applications. These stacks often depend on each other, using resources from one stack in another by exporting and importing.
As the application grows, the stacks become heavily interdependent. In such cases, you might want to place some resources in a centralized, shared stack, aiming for a hub-and-spoke style architecture.
Migrating resources from one stack to another almost always requires taking down some stacks, leading to service disruptions.
2. Stack refactoring
AWS announced the CloudFormation Stack Refactoring feature a few days ago. This addition can solve most of the problems described above.
As a new feature, it currently has some limitations, and as of writing this, it only works in the CLI and the SDK.
In this post, I’ll explore some scenarios where this feature can be a good solution. I’ll use native CloudFormation yaml
templates and CLI commands throughout.
This post assumes that you already have one or more CloudFormation stacks running.
3. How stack refactoring works
During stack refactoring, CloudFormation moves resources by exporting them from the current stack and importing them into the new stack (or the same one in the case of a rename) in the background.
![Exporting resources](https://res.cloudinary.com/dqvr2v7s4/image/upload/v1739384690/cloudformation/export-resource-cfn.png)
Using the new feature involves several steps.
3.1. Create the new templates
First, we must create the infrastructure templates that reflect the new stack states. If we move a resource from StackA
to StackB
, the new template for StackB
should contain the resources already existing in StackB
plus the ones we move. Also, StackA
’s new template should not include the resource we want to remove.
Let’s say that StackA
contains an S3 bucket and an SQS queue. This is StackA
’s current template:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyBucket:
Type: AWS::S3::Bucket
MyQueue:
Type: AWS::SQS::Queue
We want to move the queue to StackB
that already has an SNS topic. StackB
’s current template can look like this:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyTopic:
Type: AWS::SNS::Topic
We will create two new templates that will look as follows.
StackA
’s new template will contain the S3 bucket but won’t have the SQS queue. Let’s call the template file S3Only.yaml
:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyBucket:
Type: AWS::S3::Bucket
Then, we have to add the SQS queue to StackB
s new template called SnsSqs.yaml
that already contains the SNS topic:
AWSTemplateFormatVersion: '2010-09-09'
Resources:
MyTopic:
Type: AWS::SNS::Topic
MyQueue: # This is the same queue as in the original template
Type: AWS::SQS::Queue
3.2. Create a refactor mappings file (optional)
If we want to rename some resources, we must create a JSON
file that looks like this:
[
{
"Source": {
"StackName": "S3Stack",
"LogicalResourceId": "MyBucket"
},
"Destination": {
"StackName": "S3Stack",
"LogicalResourceId": "MyNewBucket"
}
}
]
Inside the array, we can have multiple Source
-Destination
pairs. This example leaves the resource in the same stack (the StackName
values in the Source
and Destination
objects are the same) but we change the logical ID of the bucket.
3.3. Create stack refactor
Next, we execute the create-stack-refactor
CLI command:
aws cloudformation create-stack-refactor --stack-definitions
StackName=StackA,TemplateBody@=file://S3Only.yaml StackName=StackB,
TemplateBody@=file://SnsSqs.yaml
This command specifies that StackA
should be built from the S3Only.yaml
template file, and StackB
from the SnsSqs.yaml
template file.
The response will be similar to this:
{
"StackRefactorId": "REFACTOR_ID"
}
We will need the refactor ID in later steps.
At this stage, CloudFormation has not yet made any changes to the stacks.
Moving to a new stack
If we want to move some resources to a new stack, we should add the --enable-stack-creation
flag.
Renaming resources
If we want to rename some resources, we need to add the --resource-mappings file://refactor-mappings.json
option, where refactor-mappings.json
is the name of the mappings file.
3.4. Check if the refactor is successful
It’s worth checking that the request is in CREATE_COMPLETE
status since this indicates that we can execute the refactor. We can use the following command to check if the refactor has been successful:
aws cloudformation describe-stack-refactor --stack-refactor-id REFACTOR_ID
If everything goes well, we will see a response similar to this:
{
"StackRefactorId": "REFACTOR_ID",
"StackIds": [
"arn:aws:cloudformation:eu-central-1:ACCOUNT_ID:stack/STACK_NAME/STACK_ID"
],
"ExecutionStatus": "AVAILABLE",
"Status": "CREATE_COMPLETE"
}
If we see this, we can move on to the next step.
Create failed
It sometimes happens that the refactor request goes into the CREATE_FAILED
status. This usually indicates a syntax error in the command or the refactor mappings file.
3.5. Execute the refactor
We can now execute the refactor:
aws cloudformation execute-stack-refactor --stack-refactor-id REFACTOR_ID
This is when CloudFormation will update existing stacks and create new stacks if applicable.
4. Some refactoring scenarios
Let’s investigate some stack refactoring scenarios.
4.1. Change the logical ID of the bucket
Stack Refactoring will change the logical ID of the bucket without us needing to touch the objects in the bucket! This can save us from a lot of stress.
Since we rename resources in the template, we must create a mappings file. In this example, we change the logical ID of the bucket but leave it in the same stack:
[
{
"Source": {
"StackName": "S3Stack",
"LogicalResourceId": "MyBucket"
},
"Destination": {
"StackName": "S3Stack",
"LogicalResourceId": "MyNewBucket"
}
}
]
We must also add the --resource-mappings
option to the create-stack-refactor
command.
4.2. Rename and move at the same time
This example is more complex because we change the logical ID of a resource and also move it to a new stack at the same time.
We need the mappings file called refactor-mappings.json
here too:
[
{
"Source": {
"StackName": "S3VpcStack",
"LogicalResourceId": "MyBucket"
},
"Destination": {
"StackName": "NewS3Stack",
"LogicalResourceId": "MyNewBucket"
}
},
{
"Source": {
"StackName": "S3VpcStack",
"LogicalResourceId": "MyVPC"
},
"Destination": {
"StackName": "S3VpcStack",
"LogicalResourceId": "RenamedVPC"
}
}
]
In this case, the S3VpcStack
contains an S3 bucket, a VPC, an internet gateway, and an attachment. We:
- Move the S3 bucket to a brand new stack called
NewS3Stack
. - Change the logical ID of the bucket while moving it to the new stack.
- Change the logical ID of the VPC, which remains in the original
S3VpcStack
.
We run the following command:
aws cloudformation create-stack-refactor --stack-definitions
StackName=NewS3Stack,TemplateBody@=file://newS3.yaml StackName=S3VpcStack,
TemplateBody@=file://vpcLeft.yaml --enable-stack-creation
--resource-mappings file://refactor-mappings.json
The template file for the new stack (NewS3Stack
) is called newS3.yaml
, and it contains the S3 bucket we want to move. The new content of the original stack (S3VpcStack
) has been written to the vpcLeft.yaml
file, which contains the VPC, the internet gateway, and the internet gateway attachment.
After executing the create-stack-refactor
command, CloudFormation will create the new stack (NewS3Stack
). The stack will be in REVIEW_IN_PROGRESS
status until we confirm and execute the refactor. Since a new stack is created, we must add the enable-stack-creation
flag to the command.
After we run execute-stack-refactor
in the CLI, the new stack with the S3 bucket will be created, and the original stack will be updated. CloudFormation will move the S3 bucket with the objects in the bucket to the new stack. The bucket’s physical name will remain the same.
4.3. Moving resources between two existing stacks
It’s also possible to move resources between two existing stacks. Please see the before/after template files under 3.1. above.
5. Considerations
The feature is very new and, as such, it has some limitations.
One of them is that not every resource can be refactored. You can find the current list of such resources in the documentation.
Also, we can’t add resources that haven’t existed yet, delete existing ones permanently, or change their configurations. That is, stack refactoring is a lift-and-shift operation.
Lastly, we can’t leave a stack empty. There has to be at least one resource type left in the stack.
6. Summary
CloudFormation Stack Refactoring allows us to move resources between stacks and rename resource logical IDs. The new feature addresses some pain points and makes stack refactoring easier, saving developers time and making the migration less complex.
As of writing this, the feature only works in the CLI and the SDK.
7. Further reading
Intrinsic Function Reference - CloudFormation built-in functions.
Create a Stack from the CloudFormation Console - How to create a stack