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 lets 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 troposphereA very simple stack that creates a new VPC with private and public subnets in AZs A-C, can look like this:
1from troposphere import Ref, Template, Tags, Join
2from troposphere.ec2 import VPC, Subnet, NetworkAcl, NetworkAclEntry, InternetGateway, \
3VPCGatewayAttachment, RouteTable, Route, SubnetRouteTableAssociation, SubnetNetworkAclAssociation
4
5VPC_NETWORK = "172.21.0.0/16"
6VPC_PRIVATE_A = "172.21.1.0/24"
7VPC_PRIVATE_B = "172.21.2.0/24"
8VPC_PRIVATE_C = "172.21.3.0/24"
9VPC_PUBLIC_A = "172.21.128.0/24"
10VPC_PUBLIC_B = "172.21.129.0/24"
11VPC_PUBLIC_C = "172.21.130.0/24"
12
13t = Template()
14
15t.add_description("Stack creating a VPC")
16
17vpc = t.add_resource(VPC(
18 "VPC",
19 CidrBlock=VPC_NETWORK,
20 InstanceTenancy="default",
21 EnableDnsSupport=True,
22 EnableDnsHostnames=False,
23 Tags=Tags(
24 Name=Ref("AWS::StackName")
25 )
26))
27
28# internet gateway
29internetGateway = t.add_resource(InternetGateway(
30 "InternetGateway",
31 Tags=Tags(
32 Name=Join("", [Ref("AWS::StackName"), "-gateway"]),
33 ),
34))
35
36gatewayAttachment = t.add_resource(VPCGatewayAttachment(
37 "InternetGatewayAttachment",
38 InternetGatewayId=Ref(internetGateway),
39 VpcId=Ref(vpc)
40))
41
42# public routing table
43publicRouteTable = t.add_resource(RouteTable(
44 "PublicRouteTable",
45 VpcId=Ref(vpc),
46 Tags=Tags(
47 Name=Join("-", [Ref("AWS::StackName"), "public-rt"]),
48 ),
49))
50
51privateRouteTable = t.add_resource(RouteTable(
52 "PrivateRouteTable",
53 VpcId=Ref(vpc),
54 Tags=Tags(
55 Name=Join("-", [Ref("AWS::StackName"), "private-rt"]),
56 ),
57))
58
59internetRoute = t.add_resource(Route(
60 "RouteToInternet",
61 DestinationCidrBlock="0.0.0.0/0",
62 GatewayId=Ref(internetGateway),
63 RouteTableId=Ref(publicRouteTable),
64 DependsOn=gatewayAttachment.title
65))
66
67# private subnetworks
68subnetPrivateA = t.add_resource(Subnet(
69 "StackPrivateSubnetA",
70 AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
71 CidrBlock=VPC_PRIVATE_A,
72 MapPublicIpOnLaunch=False,
73 Tags=Tags(
74 Name=Join("", [Ref("AWS::StackName"), " private subnet A"]),
75 ),
76 VpcId=Ref(vpc)
77))
78
79t.add_resource(SubnetRouteTableAssociation(
80 "PrivateSubnetARouteTable",
81 RouteTableId=Ref(privateRouteTable),
82 SubnetId=Ref(subnetPrivateA)
83))
84
85subnetPrivateB = t.add_resource(Subnet(
86 "StackPrivateSubnetB",
87 AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
88 CidrBlock=VPC_PRIVATE_B,
89 MapPublicIpOnLaunch=False,
90 Tags=Tags(
91 Name=Join("", [Ref("AWS::StackName"), " private subnet B"]),
92 ),
93 VpcId=Ref(vpc)
94))
95
96t.add_resource(SubnetRouteTableAssociation(
97 "PrivateSubnetBRouteTable",
98 RouteTableId=Ref(privateRouteTable),
99 SubnetId=Ref(subnetPrivateB)
100))
101
102subnetPrivateC = t.add_resource(Subnet(
103 "StackPrivateSubnetC",
104 AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
105 CidrBlock=VPC_PRIVATE_C,
106 MapPublicIpOnLaunch=False,
107 Tags=Tags(
108 Name=Join("", [Ref("AWS::StackName"), " private subnet C"]),
109 ),
110 VpcId=Ref(vpc)
111))
112
113t.add_resource(SubnetRouteTableAssociation(
114 "PrivateSubnetCRouteTable",
115 RouteTableId=Ref(privateRouteTable),
116 SubnetId=Ref(subnetPrivateC)
117))
118
119# public subnetworks
120subnetPublicA = t.add_resource(Subnet(
121 "StackPublicSubnetA",
122 AvailabilityZone=Join("", [Ref("AWS::Region"), "a"]),
123 CidrBlock=VPC_PUBLIC_A,
124 MapPublicIpOnLaunch=True,
125 Tags=Tags(
126 Name=Join("", [Ref("AWS::StackName"), " public subnet A"]),
127 ),
128 VpcId=Ref(vpc),
129))
130
131t.add_resource(SubnetRouteTableAssociation(
132 "PublicSubnetARouteTable",
133 RouteTableId=Ref(publicRouteTable),
134 SubnetId=Ref(subnetPublicA)
135))
136
137subnetPublicB = t.add_resource(Subnet(
138 "StackPublicSubnetB",
139 AvailabilityZone=Join("", [Ref("AWS::Region"), "b"]),
140 CidrBlock=VPC_PUBLIC_B,
141 MapPublicIpOnLaunch=True,
142 Tags=Tags(
143 Name=Join("", [Ref("AWS::StackName"), " public subnet B"]),
144 ),
145 VpcId=Ref(vpc),
146))
147
148t.add_resource(SubnetRouteTableAssociation(
149 "PublicSubnetBRouteTable",
150 RouteTableId=Ref(publicRouteTable),
151 SubnetId=Ref(subnetPublicB)
152))
153
154subnetPublicC = t.add_resource(Subnet(
155 "StackPublicSubnetC",
156 AvailabilityZone=Join("", [Ref("AWS::Region"), "c"]),
157 CidrBlock=VPC_PUBLIC_C,
158 MapPublicIpOnLaunch=True,
159 Tags=Tags(
160 Name=Join("", [Ref("AWS::StackName"), " public subnet C"]),
161 ),
162 VpcId=Ref(vpc)
163))
164
165t.add_resource(SubnetRouteTableAssociation(
166 "PublicSubnetCRouteTable",
167 RouteTableId=Ref(publicRouteTable),
168 SubnetId=Ref(subnetPublicC)
169))
170
171# network ACL for private subnets
172privateNetworkAcl = t.add_resource(NetworkAcl(
173 "PrivateNetworkAcl",
174 VpcId=Ref(vpc),
175 Tags=Tags(
176 Name=Join("", [Ref("AWS::StackName"), "-private-nacl"]),
177 ),
178))
179
180t.add_resource(SubnetNetworkAclAssociation(
181 "PrivateNetworkAAclAss",
182 SubnetId=Ref(subnetPrivateA),
183 NetworkAclId=Ref(privateNetworkAcl)
184))
185
186t.add_resource(SubnetNetworkAclAssociation(
187 "PrivateNetworkBAclAss",
188 SubnetId=Ref(subnetPrivateB),
189 NetworkAclId=Ref(privateNetworkAcl)
190))
191
192t.add_resource(SubnetNetworkAclAssociation(
193 "PrivateNetworkCAclAss",
194 SubnetId=Ref(subnetPrivateC),
195 NetworkAclId=Ref(privateNetworkAcl)
196))
197
198t.add_resource(NetworkAclEntry(
199 "PrivateNetworkAclEntryIngress",
200 CidrBlock=VPC_NETWORK,
201 Egress=False,
202 NetworkAclId=Ref(privateNetworkAcl),
203 Protocol=-1,
204 RuleAction="allow",
205 RuleNumber=200
206))
207
208t.add_resource(NetworkAclEntry(
209 "PrivateNetworkAclEntryEgress",
210 CidrBlock=VPC_NETWORK,
211 Egress=True,
212 NetworkAclId=Ref(privateNetworkAcl),
213 Protocol=-1,
214 RuleAction="allow",
215 RuleNumber=200
216))
217
218print(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.