Consider converting CloudFormation sources from JSON to YAML? When using cfn-flip or cfnflip.com, your functions are most likely converted to the shorthand notation. This blog post is there to warn you about something I encountered today. After converting JSON to YAML with shorthand, the Change Set evaluation told me that some resources will be replaced! Of course this is not what I wanted, and even not what I would have expected!
This is an example of the original template, written in JSON, already deployed and heavily used by other stacks.
{
"Resources": {
"MyVPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/20"
}
},
"MySubnet": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "MyVPC"
},
"CidrBlock": {
"Fn::Select": [
0,
{
"Fn::Cidr": [
{
"Fn::GetAtt": [
"MyVPC",
"CidrBlock"
]
},
16,
8
]
}
]
}
}
}
}
}
When I convert this template to YAML the following template gets produced. Less rows, the option for inline comments and above all: easier to read, mainly because of the shorthand notation of functions. Agree?
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/20
MySubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref 'MyVPC'
CidrBlock: !Select
- 0
- !Cidr
- !GetAtt 'MyVPC.CidrBlock'
- 16
- 8
Then I create a Change Set to evaluate my changes. I expect it will fail, because there should be no changes at all. So the next screenshot surprised me, a lot. The Subnet is going to be replaced!
Of course my conversion was a bit more complex, so it took me a while to discover the cause was the use of shorthand functions. When I change the YAML template to a long syntax for the Functions (cfn-flip –long), I get the following template. A few more lines, and harder to read to be honest.
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/20
MySubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: MyVPC
CidrBlock:
Fn::Select:
- 0
- Fn::Cidr:
- Fn::GetAtt:
- MyVPC
- CidrBlock
- 16
- 8
When creating a new ChangeSet, it now says: “FAILED – The submitted information didn’t contain changes. Submit different information to create a change set.”. This is what I expected!
And when adding a new property to the template, like tags, it will be modified and not replaced. I really recommend to change at least something. Just add a tag, and remove if it hurts.
Or (mind the False there):
And the details of the change:
[
{
"resourceChange": {
"logicalResourceId": "MySubnet",
"action": "Modify",
"physicalResourceId": "subnet-054e",
"resourceType": "AWS::EC2::Subnet",
"replacement": "False",
"details": [
{
"target": {
"name": null,
"requiresRecreation": "Never",
"attribute": "Tags"
},
"causingEntity": null,
"evaluation": "Static",
"changeSource": "DirectModification"
}
],
"scope": [
"Tags"
]
},
"type": "Resource"
}
]
Conclusion
Keep in mind when transforming JSON to YAML you cannot use the shorthand, unless you want to replace the resources or deploy the whole stack again. Also, start all your new projects in either JSON or YAML and stick to this decision. Whether you like it or not. I strongly recommend you to change something together with moving from JSON to YAML, to make sure you did the migration successful and there are no surprises later.
I’m not sure if it applies only to this specific Function / Resource / Property, but will find out and update accordingly.