diff --git a/cloudformation b/cloudformation
new file mode 100644
index 00000000000..5ab2d1a93ec
--- /dev/null
+++ b/cloudformation
@@ -0,0 +1,243 @@
+#!/usr/bin/python -tt
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+DOCUMENTATION = '''
+---
+module: cloudformation
+short_description: create a AWS CloudFormation stack
+description:
+ - Launches an AWS CloudFormation stack and waits for it complete.
+version_added: "1.1"
+options:
+ stack_name:
+ description:
+ - name of the cloudformation stack
+ required: true
+ default: null
+ aliases: []
+ disable_rollback:
+ description:
+ - If a stacks fails to form, rollback will remove the stack
+ required: false
+ default: false
+ aliases: []
+ template_parameters:
+ description:
+ - a list of hashes of all the template variables for the stack
+ required: true
+ default: null
+ aliases: []
+ region:
+ description:
+ - The AWS region the stack will be launched in
+ required: true
+ default: null
+ aliases: []
+ state:
+ description:
+ - If state is "present", stack will be created. If state is "present" and if stack exists and template has changed, it will be updated.
+ If state is present, stack will be removed.
+ required: true
+ default: null
+ aliases: []
+ template:
+ description:
+ - the path of the cloudformation template
+ required: true
+ default: null
+ aliases: []
+ wait_for:
+ description:
+ - Wait while the stack is being created/updated/deleted.
+ required: false
+ default: true
+ aliases: []
+
+examples:
+
+ tasks:
+ - name: launch ansible cloudformation example
+ cloudformation: >
+ stack_name="ansible-cloudformation" state=present
+ region=us-east-1 disable_rollback=true
+ template=files/cloudformation-example.json
+ args:
+ template_parameters:
+ KeyName: jmartin
+ DiskType: ephemeral
+ InstanceType: m1.small
+ ClusterSize: 3
+
+requirements: [ "boto" ]
+author: James S. Martin
+'''
+
+import boto.cloudformation.connection
+import json
+
+
+class Region:
+ def __init__(self, region):
+ self.name = region
+ self.endpoint = 'cloudformation.%s.amazonaws.com' % region
+
+
+def boto_exception(err):
+
+ if hasattr(err, 'error_message'):
+ error = err.error_message
+ elif hasattr(err, 'message'):
+ error = err.message
+ else:
+ error = '%s: %s' % (Exception, err)
+ try:
+ error_msg = json.loads(error)
+ except:
+ error_msg = {'Error': error}
+ return error_msg
+
+
+def stack_operation(cfn, stack_name, operation):
+ existed = []
+ result = {}
+ operation_complete = False
+ while operation_complete == False:
+ try:
+ stack = cfn.describe_stacks(stack_name)[0]
+ existed.append('yes')
+ except:
+ if 'yes' in existed:
+ result = {'changed': True, 'output': 'Stack Deleted'}
+ result['events'] = map(str, list(stack.describe_events()))
+ else:
+ result = {'changed': True, 'output': 'Stack Not Found'}
+ break
+ if '%s_COMPLETE' % operation == stack.stack_status:
+ result['changed'] = True
+ result['events'] = map(str, list(stack.describe_events()))
+ result['output'] = 'Stack %s complete' % operation
+ break
+ elif '%s_FAILED' % operation == stack.stack_status:
+ result['changed'] = False
+ result['events'] = map(str, list(stack.describe_events()))
+ result['output'] = 'Stack %s failed' % operation
+ break
+ else:
+ time.sleep(5)
+ return result
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ stack_name=dict(required=True),
+ template_parameters=dict(required=False),
+ region=dict(required=True,
+ choices=['ap-northeast-1', 'ap-southeast-1',
+ 'ap-southeast-2', 'eu-west-1',
+ 'sa-east-1', 'us-east-1', 'us-west-1',
+ 'us-west-2']),
+ state=dict(default='present', choices=['present', 'absent']),
+ template=dict(default=None, required=True),
+ disable_rollback=dict(default=False),
+ wait_for=dict(default=True)
+ )
+ )
+
+ wait_for = module.params['wait_for']
+ state = module.params['state']
+ stack_name = module.params['stack_name']
+ region = Region(module.params['region'])
+ template_body = open(module.params['template'], 'r').read()
+ disable_rollback = module.params['disable_rollback']
+ template_parameters = module.params['template_parameters']
+
+ template_parameters_tup = [(k, v) for k, v in template_parameters.items()]
+ stack_outputs = {}
+ stack_outputs[module.params['region']] = {}
+ stack_outputs[module.params['region']][stack_name] = {}
+
+ try:
+ cfn = boto.cloudformation.connection.CloudFormationConnection(
+ region=region)
+ except boto.exception.NoAuthHandlerFound, e:
+ module.fail_json(msg=str(e))
+ update = False
+ stack_events = []
+ result = {}
+ operation = None
+ output = ''
+
+ if state == 'present':
+ try:
+ cfn.create_stack(stack_name, parameters=template_parameters_tup,
+ template_body=template_body,
+ disable_rollback=disable_rollback,
+ capabilities=['CAPABILITY_IAM'])
+ operation = 'CREATE'
+ except Exception, err:
+ error_msg = boto_exception(err)
+ if error_msg['Error']['Code'] == 'AlreadyExistsException':
+ update = True
+ else:
+ result = {'changed': False, 'output': error_msg}
+ module.fail_json(**result)
+ if not update:
+ result = stack_operation(cfn, stack_name, operation)
+ if update:
+ try:
+ cfn.update_stack(stack_name, parameters=template_parameters_tup,
+ template_body=template_body,
+ disable_rollback=disable_rollback,
+ capabilities=['CAPABILITY_IAM'])
+ operation = 'UPDATE'
+ except Exception, err:
+ error_msg = boto_exception(err)
+ if error_msg['Error']['Message'] == 'No updates are to be performed.':
+ output = error_msg['Error']['Message']
+ result = {'changed': False, 'output': output}
+ if operation == 'UPDATE':
+ result = stack_operation(cfn, stack_name, operation)
+
+ if state == 'present' or update:
+ stack = cfn.describe_stacks(stack_name)[0]
+ for output in stack.outputs:
+ stack_outputs[module.params['region']][stack_name][
+ output.key] = output.value
+ result['stack_outputs'] = stack_outputs
+
+# absent state is different because of the way delete_stack works.
+# problem is it it doesn't give an error if stack isn't found
+# so must describe the stack first
+
+ if state == 'absent':
+ try:
+ cfn.describe_stacks(stack_name)
+ operation = 'DELETE'
+ except Exception, err:
+ error_msg = boto_exception(err)
+ result = {'changed': False, 'output': error_msg}
+ module.fail_json(result)
+ if operation == 'DELETE':
+ cfn.delete_stack(stack_name)
+ result = stack_operation(cfn, stack_name, operation)
+
+ module.exit_json(**result)
+
+# this is magic, see lib/ansible/module_common.py
+#<>
+
+main()