Formation

Warning: Formation is under active development and its API is unstable.

Formation defines a terse Python syntax which compiles to CloudFormation. Formation aims to bring two new features to CloudFormation:

Features

Automation

Formation aggressively automates the CloudFormation template writing process. To show this, we first examine a template written in stock CloudFormation. To create a template containing a VPC, we can write the following:

Resources:
  VPC:
    Type: AWS::EC2::VPC

VPCs require a CIDR block property. We can specify the CIDR block of our VPC as 10.0.0.0/16:

Resources:
  VPC:
    Type: AWS::EC2::VPC
  Properties:
    CidrBlock: 10.0.0.0/16

However, it is considered bad practice to hardcode values in templates. We can improve reusability by moving the value of the CIDR block to a Parameter. The person who uses the template can set the CIDR block value.

Parameters:
  CidrBlockParam:
    Type: String

Resources:
  VPC:
    Type: AWS::EC2::VPC
  Properties:
    CidrBlock: !Ref CidrBlockParam

We should add relevant Outputs to our VPC stack. Outputs allow us to find out information about the resources contained in a stack. VPCs have the following outputs: VpcId, CidrBlock, DefaultNetworkAcl, DefaultSecurityGroup and Ipv6CidrBlocks. We should add them all, so we won’t have to later if we happen to require one:

Outputs:
  VpcId:
    Value: !Ref VPC
  CidrBlock:
    Value: !GetAtt VPC.CidrBlock
  DefaultNetworkAcl:
    Value: !GetAtt VPC.DefaultNetworkAcl
  DefaultSecurityGroup:
    Value: !GetAtt VPC.DefaultSecurityGroup
  Ipv6CidrBlocks:
    Value: !GetAtt VPC.Ipv6CidrBlocks

Parameters:
  CidrBlockParam:
    Type: String

Resources:
  VPC:
    Type: AWS::EC2::VPC
  Properties:
    CidrBlock: !Ref CidrBlockParam

We see that creating a simple CloudFormation template containing a single VPC requires writing a lot of boilerplate code. Formation aims to automate this. Formation looks up a resource’s required properties and automatically parameterises them. Formation looks up a resource’s outputs and automatically adds them.

>>> from formation import AtomicTemplate
>>> vpc = AtomicTemplate("VPC", "EC2::VPC")
>>> print vpc.to_yaml()
Outputs:
VPCCidrBlock:
  Value:
    Fn::GetAtt:
    - VPC
    - CidrBlock
VPCDefaultNetworkAcl:
  Value:
    Fn::GetAtt:
    - VPC
    - DefaultNetworkAcl
VPCDefaultSecurityGroup:
  Value:
    Fn::GetAtt:
    - VPC
    - DefaultSecurityGroup
VPCIpv6CidrBlocks:
  Value:
    Fn::GetAtt:
    - VPC
    - Ipv6CidrBlocks
VPCRef:
  Value:
    Ref: VPC
Parameters:
  VPCCidrBlock:
    Type: String
Resources:
  VPC:
    Properties:
      CidrBlock:
        Ref: VPCCidrBlock
    Type: AWS::EC2::VPC

A few lines of Python produce functionally identical CloudFormation.

Composability

Stock CloudFormation templates suffer from two problems:

  • Complexity. As infrastructure is added, CloudFormation templates grow in size and complexity. Individual templates often become hundreds or thousands of lines long. This leads to templates that are difficult to understand. Systems which are difficult to understand are more error prone.
  • Reusability. Sections of code are often repeated across CloudFormation templates. This is wasteful, and any changes need to be made in multiple places.

We can solve both these problems by modularising CloudFormation templates. Large templates can be broken down into smaller chunks, and reusable pieces of code can be refactored out. Modularity isn’t natively supported in CloudFormation. Small, reusable templates can be written, but the only way to combine them is by copying and pasting their contents. Formation’s composability gives you the power to write modular templates and combine them programatically.

>>> from formation import AtomicTemplate, Template

>>> vpc = AtomicTemplate("VPC", "EC2::VPC")
>>> subnet = AtomicTemplate("Subnet", "EC2::Subnet")
>>> network = Template()
>>> network.merge(vpc)
>>> network.merge(subnet)
>>> print network.to_yaml()
Outputs:
  SubnetAvailabilityZone:
    Value:
      Fn::GetAtt:
      - Subnet
      - AvailabilityZone
  ...  # Output truncated
Parameters:
  SubnetCidrBlock:
    Type: String
  SubnetVpcId:
    Type: String
  VPCCidrBlock:
    Type: String
Resources:
  Subnet:
    Properties:
      CidrBlock:
        Ref: SubnetCidrBlock
      VpcId:
        Ref: SubnetVpcId
    Type: AWS::EC2::Subnet
  VPC:
    Properties:
      CidrBlock:
        Ref: VPCCidrBlock
    Type: AWS::EC2::VPC

In this example, two modularised templates, vpc and subnet are composed into a single network template.