Skip to content

Instantly share code, notes, and snippets.

@marshallbrekka
Created November 28, 2013 01:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marshallbrekka/7686151 to your computer and use it in GitHub Desktop.
Save marshallbrekka/7686151 to your computer and use it in GitHub Desktop.
CloudFormation Template Generation
(defn keys->strings
"Given a map converts all keys that are keywords from dash-separated to
camel-case using key->string."
[m]
(let [f (fn [[k v]] [(key->string k) v])
walk-fn (fn [x]
(if (map? x)
(->> (map f x)
(into {}))
x))]
(walk/postwalk walk-fn m)))
(defn pieces->map
"Given a list of resources or outputs with the form [name map] returns a map
where name is the key and map is the value."
[p]
(if (seq p)
(let [names (map first p)]
(->> (map last p)
(keys->strings)
(zipmap names)))
{}))
(defn aws-refer
"Helper for cloudformation template function Ref."
[name]
{"Ref" name})
(defn aws-fn [fn-name & args]
{(->> (key->string fn-name)
(str "Fn::"))
(if (= 1 (count args))
(first args)
args)})
(defn ->tags [keys div tags]
"Turns a list of tag definitions into an aws tag list.
Tags is a list with the repeating format \"key value\" or
\"key value propagate-at-launch?\". The format must be consistant
for the entire list.
Div is the number of elements in each tag definition (either 2 or 3).
Keys is a list of keys that will become the keys for the parts of a
tag definition."
(->> (partition div tags)
(map #(zipmap keys %))))
(defn tags
"Helper to create aws tags. It should accept the repeating format
\"key value\" or \"key value propagate-at-launch?\" for scaling
group tags.
Ex: non-scaling goup (tags :stack-name \"my-stack\"
:purpose \"database\")
Ex: scaling group (tags :stack-name \"my-stack\" true
:purpose \"database\" false)"
[& kvs]
(let [arg-count (count kvs)]
(if (and
(-> (mod arg-count 3) (= 0))
(->> (nthnext kvs 2)
(take-nth 3)
(every? #(or (true? %) (false? %)))))
(->tags [:key :value :propagate-at-launch] 3 kvs)
(do (-> (mod arg-count 2)
(= 0)
(assert))
(->tags [:key :value] 2 kvs)))))
(defn resource
"Creates a resource item.
Takes the resource name, the resource type, and a map of properties.
Can optionally supply sections other than resource properties (such as
metadata) via named arguments.
Ex:
(resource \"WebLC\" \"AWS::AutoScaling::LaunchConfiguration\"
{:image-id \"ami-e565ba8c\"
:instance-type \"m1.large\"
...}
:metadata {\"AWS::CloudFormation::Init\"
{\"config\" : {...}}})
is equivalent to the aws template declaration in the resources section
{\"WebLC\" : {
\"Type\" : \"AWS::AutoScaling::LaunchConfiguration\",
\"Properties\" : {
\"ImageId\" : \"ami-e565ba8c\",
\"InstanceType \"m1.large\"
...},
\"Metadata\" : {
\"AWS::CloudFormation::Init\" : {
\"config\" : {...}}}
}
}"
[name type properties & kv]
(->> (apply hash-map
:properties properties
:type type
kv)
(vector name)))
(defn output
"Constructs an output delcaration in the same format that (resource) returns.
Takes the name of the output, the value to return, and the optional description."
[name value & [description]]
(->> (when description
[:description description])
(hash-map :value value)
(vector name)))
(defn parts->template
"Takes an aws template format version, a list of resources and a list of outputs,
and returns a json cloudformation template."
[format-version resources outputs]
(-> (hash-map "AWSTemplateFormatVersion" format-version
"Resources" (pieces->map resources)
"Outputs" (pieces->map outputs))
(json/generate-string)))
;;;;; Example Usage ;;;;;
;; Create a subnet resource
(resource "my-awesome-subnet" "AWS::EC2::Subnet"
{:availability-zone "desired-aws-zone"
:cidr-block "some-cidr-block"
:vpc-id "your-vpc-id"})
;; Fn that creates a cname, in this example i use the aws fn Fn::GetAtt
(defn resource-cname [resource-name stack-role & [{:keys [type resource-property identifier]}]]
(resource (str resource-name "CNAME") "AWS::Route53::RecordSet"
{:type (or type "A")
"TTL" "300"
:hosted-zone-name (str hosted-name ".")
:name (secure-domain stack-role {:identifier identifier :cname true})
:resource-records [(aws-fn :get-att resource-name
(or resource-property "PrivateIp"))]}))
@fcuozzo
Copy link

fcuozzo commented May 14, 2014

Hey Marshall, what is the license on this code?
Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment