Created
November 28, 2013 01:54
-
-
Save marshallbrekka/7686151 to your computer and use it in GitHub Desktop.
CloudFormation Template Generation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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"))]})) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey Marshall, what is the license on this code?
Cheers.