If you’ve ever wrote AWS CloudFormation template, you probably know that it can be a daunting task. Luckily, it can be much easier, if you use Python’s library “Troposphere”.

Troposphere let’s you create Python objects in place of CloudFormation elements, does some basic validation of your input and generates the JSON template for CloudFormation for you. It is much easier and cleaner to use that writing JSON templates manually.

To install troposphere, just run:

pip install troposphere

A very simple stack that creates a new VPC with private and public subnets in AZs A-C, can look like this:

from troposphere import Ref, Template, Tags, Join
from troposphere.ec2 import VPC, Subnet, NetworkAcl, NetworkAclEntry, InternetGateway, \
VPCGatewayAttachment, RouteTable, Route, SubnetRouteTableAssociation, SubnetNetworkAclAssociation

VPC_NETWORK = "172.21.0.0/16"
VPC_PRIVATE_A = "172.21.1.0/24"
VPC_PRIVATE_B = "172.21.2.0/24"
VPC_PRIVATE_C = "172.21.3.0/24"
VPC_PUBLIC_A = "172.21.128.0/24"
VPC_PUBLIC_B = "172.21.129.0/24"
VPC_PUBLIC_C = "172.21.130.0/24"

t = Template()

t.add_description("Stack creating a VPC")

vpc = t.add_resource(VPC(
  "VPC",
  CidrBlock=VPC_NETWORK,
  InstanceTenancy="default",
  EnableDnsSupport=True,
  EnableDnsHostnames=False,
  Tags=Tags(
    Name=Ref("AWS::StackName")
  )
))

# internet gateway
internetGateway = t.add_resource(InternetGateway(
  "InternetGateway",
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), "-gateway"]),
  ),
))

gatewayAttachment = t.add_resource(VPCGatewayAttachment(
  "InternetGatewayAttachment",
  InternetGatewayId=Ref(internetGateway),
  VpcId=Ref(vpc)
))

# public routing table
publicRouteTable = t.add_resource(RouteTable(
  "PublicRouteTable",
  VpcId=Ref(vpc),
  Tags=Tags(
   Name=Join("-", [Ref("AWS::StackName"), "public-rt"]),
  ),
))

privateRouteTable = t.add_resource(RouteTable(
  "PrivateRouteTable",
  VpcId=Ref(vpc),
  Tags=Tags(
    Name=Join("-", [Ref("AWS::StackName"), "private-rt"]),
  ),
))

internetRoute = t.add_resource(Route(
  "RouteToInternet",
  DestinationCidrBlock="0.0.0.0/0",
  GatewayId=Ref(internetGateway),
  RouteTableId=Ref(publicRouteTable),
  DependsOn=gatewayAttachment.title
))

# private subnetworks
subnetPrivateA = t.add_resource(Subnet(
  "StackPrivateSubnetA",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
  CidrBlock=VPC_PRIVATE_A,
  MapPublicIpOnLaunch=False,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " private subnet A"]),
  ),
  VpcId=Ref(vpc)
))

t.add_resource(SubnetRouteTableAssociation(
  "PrivateSubnetARouteTable",
  RouteTableId=Ref(privateRouteTable),
  SubnetId=Ref(subnetPrivateA)
))

subnetPrivateB = t.add_resource(Subnet(
  "StackPrivateSubnetB",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
  CidrBlock=VPC_PRIVATE_B,
  MapPublicIpOnLaunch=False,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " private subnet B"]),
  ),
  VpcId=Ref(vpc)
))

t.add_resource(SubnetRouteTableAssociation(
  "PrivateSubnetBRouteTable",
  RouteTableId=Ref(privateRouteTable),
  SubnetId=Ref(subnetPrivateB)
))

subnetPrivateC = t.add_resource(Subnet(
  "StackPrivateSubnetC",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
  CidrBlock=VPC_PRIVATE_C,
  MapPublicIpOnLaunch=False,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " private subnet C"]),
  ),
  VpcId=Ref(vpc)
))

t.add_resource(SubnetRouteTableAssociation(
  "PrivateSubnetCRouteTable",
  RouteTableId=Ref(privateRouteTable),
  SubnetId=Ref(subnetPrivateC)
))

# public subnetworks
subnetPublicA = t.add_resource(Subnet(
  "StackPublicSubnetA",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
  CidrBlock=VPC_PUBLIC_A,
  MapPublicIpOnLaunch=True,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " public subnet A"]),
  ),
  VpcId=Ref(vpc),
))

t.add_resource(SubnetRouteTableAssociation(
  "PublicSubnetARouteTable",
  RouteTableId=Ref(publicRouteTable),
  SubnetId=Ref(subnetPublicA)
))

subnetPublicB = t.add_resource(Subnet(
  "StackPublicSubnetB",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
  CidrBlock=VPC_PUBLIC_B,
  MapPublicIpOnLaunch=True,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " public subnet B"]),
  ),
  VpcId=Ref(vpc),
))

t.add_resource(SubnetRouteTableAssociation(
  "PublicSubnetBRouteTable",
  RouteTableId=Ref(publicRouteTable),
  SubnetId=Ref(subnetPublicB)
))

subnetPublicC = t.add_resource(Subnet(
  "StackPublicSubnetC",
  AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
  CidrBlock=VPC_PUBLIC_C,
  MapPublicIpOnLaunch=True,
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), " public subnet C"]),
  ),
  VpcId=Ref(vpc)
))

t.add_resource(SubnetRouteTableAssociation(
  "PublicSubnetCRouteTable",
  RouteTableId=Ref(publicRouteTable),
  SubnetId=Ref(subnetPublicC)
))

# network ACL for private subnets
privateNetworkAcl = t.add_resource(NetworkAcl(
  "PrivateNetworkAcl",
  VpcId=Ref(vpc),
  Tags=Tags(
    Name=Join("", [Ref("AWS::StackName"), "-private-nacl"]),
  ),
))

t.add_resource(SubnetNetworkAclAssociation(
  "PrivateNetworkAAclAss",
  SubnetId=Ref(subnetPrivateA),
  NetworkAclId=Ref(privateNetworkAcl)
))

t.add_resource(SubnetNetworkAclAssociation(
  "PrivateNetworkBAclAss",
  SubnetId=Ref(subnetPrivateB),
  NetworkAclId=Ref(privateNetworkAcl)
))

t.add_resource(SubnetNetworkAclAssociation(
  "PrivateNetworkCAclAss",
  SubnetId=Ref(subnetPrivateC),
  NetworkAclId=Ref(privateNetworkAcl)
))

t.add_resource(NetworkAclEntry(
  "PrivateNetworkAclEntryIngress",
  CidrBlock=VPC_NETWORK,
  Egress=False,
  NetworkAclId=Ref(privateNetworkAcl),
  Protocol=-1,
  RuleAction="allow",
  RuleNumber=200
))

t.add_resource(NetworkAclEntry(
  "PrivateNetworkAclEntryEgress",
  CidrBlock=VPC_NETWORK,
  Egress=True,
  NetworkAclId=Ref(privateNetworkAcl),
  Protocol=-1,
  RuleAction="allow",
  RuleNumber=200
))

print(t.to_json())

Save this as vpc_stack.py and run python vpc_stack.py. Output will be a JSON template that can be used for CloudFormation!

You can easily direct the output to a file, by running python vpc_stack.py > vpc_stack.json. The JSON file can be uploaded directly to CloudFormation via CLI or AWS Management Console.

To find out more and for full documentation, see troposphere on github: https://github.com/cloudtools/troposphere.

For more examples of CloudFormation stacks with troposphere, check out our github repo: https://github.com/MysteriousCode/cloudformation-examples.



4 Comments

  1. Vijay

    Nice article, can you please provide a sample for nested cloud formation template

  2. Maciej Walkowiak

    Nice article!

    Tiny little typo: python vpc_stack.py > vpc_stack.json rather than python vpc_stack.py > vpc_stack.py 😉

    • Paulina Budzon

      Good eye! 🙂 Fixed.

  3. Atul Kaulgud

    Hi Paulina
    I would like to prepare for AWS solution architect – professional exam. Could you please share any documents or reference material

    Regards


Leave a comment