CloudFormation Templates in Non-JSON Format

Update 2016-09-27: The AWS CloudFormation team has just released a new feature that allows you to write your templates in YAML. They even improved on the problems I outlined in this article by allowing shorthand versions of the intrinsic functions. I now suggest everyone to move to YAML as it is the clear winner.

Working with CloudFormation is great once it finally clicks in your brain that you can create many AWS resources and then delete all of them to make cleaning up super easy. Other than the steep learning curve when a person first learn CloudFormation, the next annoyance people have is that they have to write in JSON.

JSON is both verbose and lacks nice features, like comments. I prefer JSON as a data transfer mechanism, rather than as a configuration file. In other words, I like it when robots write JSON rather than humans.

However, AWS currently only supports JSON as their CloudFormation template format, so we need to work with that. But, if AWS started to support other formats, like YAML or TOML, what would it look like? Let's take a look.

JSON

Below is the JSON template for a basic CloudFormation stack that creates a DNS CNAME record in Route53. It is one of the simplest things you can setup in CloudFormation that doesn't cause the learning curve to be too steep.

{
  "AWSTemplateFormatVersion": "2010-09-09",

  "Description": "Sample CF template",

  "Parameters": {
    "HostedZoneName": {
      "Type": "String",
      "Description": "(Domain) Name of the hosted zone"
    },
    "HostedZoneId": {
      "Type": "AWS::Route53::HostedZone::Id",
      "Description": "ID of the hosted zone"
    },
    "DnsTtl": {
      "Type": "Number",
      "Description": "Time, in seconds, for the record to live in cache"
    }
  },

  "Resources": {
    "CNAMERecord": {
      "Type": "AWS::Route53::RecordSet",
      "Properties": {
        "HostedZoneId": { "Ref": "HostedZoneId" }
        "Type": "CNAME",
        "TTL": { "Ref": "DnsTtl" },
        "Name": {
          "Fn::Join": [
            ".",
            [
              "www",
              { "Ref": "HostedZoneName" }
            ]
          ]
        },
        "ResourceRecords": [
          "www.example.com"
        ]
      }
    }
  }
}

Obviously, there's a lot of curly braces involved so you can say goodbye to your pinky fingers. Also, writing out the functions that CloudFormation provides ends up being a nasty mix of dictionaries and arrays.

YAML

So what about YAML? I like how comments are allowed, and I (sometimes) like how YAML is whitespace-sensitive. This is the same template rendered in YAML.

---
AWSTemplateFormatVersion: "2010-09-09"

Description: "Sample CF template"

Parameters:
  HostedZoneName:
    Type: "String"
    Description: "(Domain) Name of the hosted zone"
  HostedZoneId:
    Type: "AWS::Route53::HostedZone::Id"
    Description: "ID of the hosted zone"
  DnsTtl:
    Type: "Number"
    Description: "Time, in seconds, for the record to live in cache"

Resources:
  CNAMERecord:
    Type: "AWS::Route53::RecordSet"
    Properties:
      HostedZoneId:
        Ref: "HostedZoneId"
      Type: "CNAME"
      TTL:
        Ref: "DnsTtl"
      Name: {
        "Fn::Join": [
          - "."
          -
            - "www"
            - Ref:
                HostedZoneName
      ResourceRecords:
        - "www.example.com"

Overall, YAML looks simple and clean. Because CloudFormation templates are basically large, nested dictionaries, it is still easy to write and read the template in YAML format. However, when you need to write the CloudFormation functions that use arrays, you are left with some needlessly obtuse syntax. I can imagine a developer making the wrong change on one of the empty array elements and breaking the entire template.

TOML

TOML is a newer format that I am quite enamored with. It reminds me of the configuration file formats on Python and Unix. It makes any configuration easy to read and write, it accepts comments and sane data types, and isn't whitespace sensitive. The following is the same template translated to TOML.

AWSTemplateFormatVersion = "2010-09-09"

Description = "Sample CF template"

[Parameters.HostedZoneName]

Type = "String"
Description = "(Domain) Name of the hosted zone"

[Parameters.HostedZoneId]

Type = "AWS::Route53::HostedZone::Id"
Description = "ID of the hosted zone"

[Parameters.DnsTtl]

Type = "Number"
Description = "Time, in seconds, for the record to live in cache"

[Resources.CNAMERecord]

Type = "AWS::Route53::RecordSet"

[Resources.CNAMERecord.Properties]

HostedZoneId = { "Ref" = "HostedZoneId" }
Type = "CNAME"
TTL = { "Ref" = "DnsTtl" }
Name = { "Fn::Join": [ ".", [ "www", { "Ref" = "HostedZoneName" } ] ] }
ResourceRecords = [ "www.example.com" ]

TOML starts off pretty nice. The parameter and resource sections are just nested tables. Creating the keys underneath are fairly easy to create also. But TOML starts to break down quickly when the nested resources have keys and more nested tables. Also, writing the CloudFormation functions prove to be exceedingly difficult, making it hard to write and even worse to read. I had to create inline tables and arrays just to make some attributes legible.

So for now, it seems that JSON is the lesser evil winner of the formats.

What About Troposphere and its ilk?

I like writing CloudFormation scripts in pure JSON as it means I don't need to drag along a bunch of libraries and programming languages with me. At Unbounce we started with Troposphere and found a few problems with it that made us move to pure JSON, and never looked back. These problems were:

  1. A Python interpreter was required to reside on the machine.
  2. A specific (read: recent) version of Python was required.
  3. The troposphere library had to be installed (usually via pip) and kept up to date. There were breaking changes between versions that meant the scripts worked on some machines and not on others.
  4. The deliverable was a CloudFormation template rendered from the Troposphere script. We version-controlled the Troposphere script but never the rendered Cloudformation JSON template, so we could not easily reproduce the same JSON template from the same commit hash.
  5. Creating CloudFormation templates were now a two-step operation. Generate the JSON template, then upload the template.
  6. Adding a full programming language to the mix allowed people to do wacky things, things we could not easily understand in the rendered JSON template.
« Previous: Removing Comments
Next: Nanoservices »