Cryptocracy

A blog

Dry Run CloudFormation Updates Using Ansible

At ScaleSummit last week, I mentioned that I had some unreleased patches to Ansible’s Cloudformation module to implement a dry-run mode. This piqued some interest, so I’ve cleaned it up a little and am sharing it here.

First, a disclaimer: I’ve spent precious little time with Python in the last decade, and my primary goal was to write code that worked. I wouldn’t say I’m happy with it, but it works well enough for me. Feel free to take it and run with it.

If you just want the code, you’ll find it on this branch; otherwise read on for details…

Dry-Run Mode, You Say?

CloudFormation (the web service) doesn’t have a dry-run mode – you can’t pass it a set of inputs and find out what it would do. Still, we can build a useful approximation by taking inspiration from Chef’s naming of this feature, “Why-Run?” CloudFormation records the inputs (template and parameters) used for each update, and we can compare these to the inputs we plan to use. The changes we find are the reasons why CloudFormation might do something during an update.

What this can’t tell us is what resources will be updated1, whether those updates will involve replacement of the resource, whether our template is nonsense2, etc.

Will this be useful to you? If your templates are simple enough that a list of modified inputs is enough for you to reason about the outcome, you’ll find this useful.

What Does It Do?

When run in check mode (ansible-playbook --check), a CloudFormation task will be output like so:

TASK: [create example-test cfn stack] **************************************
changed: [example] => {"changed": true, "output": "Stack would be updated, changes: [{'Template': {u'Resources': {u'DummyWaitHandle2': 'Added', u'DummyWaitHandle': 'Removed'}}}, {u'SnapshotID': {'to': u'test-20150329', 'from': u'test-20150330'}}]", "stack_outputs": {"DBInstanceId": "rdz5lp9i8ovmog", "FrontendELB": "example-Frontend-1VIHBAZL8WUA1", "FrontendHostname": "example-Frontend-1VIHBAZL8WUA1-1582281064.eu-west-1.elb.amazonaws.com", "SecurityGroup": "example-test-SecurityGroup-16PP0KV5SCAS9", "SecurityGroup2": "example-test-SecurityGroup2-I2ENEANIVZPF"}}

This is not particularly readable when wrapped in a terminal window – still, it’s an improvement on nothing. I couldn’t readily find any way of producing nicely formatted output. I originally wanted to show a unified diff of the template changes, but that was completely unreadable. I did find a --diff option in Ansible, but this apparently only works for template changes and I couldn’t figure out how to use that here.

I’ve also added a described state, which just exposes a stack’s parameters. I believe the idiomatic solution to this in Ansible would be a lookup plugin3, but I prefer this way.

What (Else) Does It Do Badly?

When I mentioned this at ScaleSummit, I didn’t include a diff of the template – only an indication that the template had changed. Verifying parameter changes was valuable to me, but ScaleSummit prodded me into taking another look at template comparison.

So, the template diff mindlessly compares two json documents – it doesn’t understand anything about the semantics of CloudFormation templates. It descends recursively into dicts to find changes, but arrays are shown as the stringified before/after copies. This makes changes to some resource types (eg, security groups) very noisy.

Room For Improvement

  • Show only added/removed/changed array elements
  • Limit template diff to a certain depth
  • Show unified diff via --diff option (if possible)
  • Validate local template
  • Check mode for stack creation should show the parameters which will be used
  • Fake up outputs that will be created by a stack

EDIT: I found the motivation to fix a few of those warts.

What’s Next?

I mostly improve this as something annoys me when I happen to have time for a diversion. While Ansible has made my use of CloudFormation somewhat less vexing, it’s not a huge step forward and I’m hoping to replace this with Terraform in the near future.

My gut feeling is that this won’t be accepted upstream as-is, but I haven’t tried. Leave a comment if you want to encourage me to upstream it, or feel free to tidy it up and open a PR yourself.


  1. We can be confident that any resources with template changes will update, but we don’t know what others will be updated in consequence. ie, we suffer from false negatives.

  2. Though we could use the validate_template function for this.

  3. See this post by Dean Wilson.

Comments