Infrastructure as data code.
CloudFormation provides a nice way to provision AWS resources in a declarative way. But, programming in JSON can be painful.
CloudShaped provides a simple, extensible DSL for generating CloudFormation templates.
Add this line to your application's Gemfile:
gem 'cloud_shaped'
require 'cloud_shaped'
require 'json'
template = CloudShaped.template do |t|
t.def_parameter "appName"
t.def_resource "app", "AWS::Appity:AppApp", "Name" => t.ref("appName")
t.def_output "appAddress", t.ref("app", "address")
end
puts JSON.pretty_generate(template)
outputs
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters": {
"appName": {
"Type": "String"
}
},
"Resources": {
"app": {
"Type": "AWS::Appity:AppApp",
"Properties": {
"Name": {
"Ref": "appName"
}
}
}
},
"Outputs": {
"appAddress": {
"Value": {
"Fn::GetAtt": ["app", "address"]
}
}
}
}
Declare CloudFormation resources using the def_resource
method. It takes a resource name, type, and properties:
t.def_resource "adminEmail", "AWS::SNS::Topic",
"Subscription" => [{"Protocol" => "email", "Endpoint" => "mdub@example.com"}]
If you prefer, you can set properties using a block:
t.def_resource "adminEmail", "AWS::SNS::Topic" do |topic|
topic["Subscription"] = [
{ "Protocol" => "email", "Endpoint" => "mdub@example.com" }
]
end
Typically, the "type" argument will be a string specifying a CloudFormation resource type. However, it's also possible to provide a Ruby symbol, in which case def_resource
will call the named method, passing remaining arguments. This allows common resource patterns to be abstracted as methods:
def t.email_topic(address)
resource "AWS::SNS::Topic",
"Subscription" => [{ "Protocol" => "email", "Endpoint" => address }]
end
t.def_resource "adminEmail", :email_topic, "mdub@example.com"
Sometimes it's necessary to embed files or even scripts in a CloudFormation template, but include references to stack parameters or resources; the resulting mess of "Fn::Join" and "Fn::GetAtt" can be hard to follow.
CloudShaped's interpolate
function makes it easier; it detects references of the form {{RESOURCE}}
or {{RESOURCE.ATTRIBUTE}}
in a string, and generates "Ref" and "Fn::GetAtt" as appropriate, e.g.
script = <<-'BASH'
#!/bin/sh
... stuff ...
/usr/local/bin/cfn-signal -e 0 \
--stack {{AWS::StackName}} --region {{AWS::Region}} \
--resource thisHereInstance
BASH
t.interpolate(script) #=> ...
{
"Fn::Join" =>
[
"\n",
[
"#!/bin/sh",
"... stuff ...",
"/usr/local/bin/cfn-signal -e 0 \\",
{
"Fn::Join" => [
"",
[
" --stack ",
{ "Ref" => "AWS::StackName" },
" --region ",
{ "Ref" => "AWS::Region" },
" \\"
]
]
},
" --resource thisHereInstance"
]
]
}
For more info on the DSL, see:
- {CloudShaped.template}
- {CloudShaped::TemplateBuilder}
It's on GitHub. Fork it.