Merge pull request #2180 from jsmartin/cloudformation
CloudFormation support.
This commit is contained in:
commit
69a199727c
3 changed files with 687 additions and 0 deletions
45
examples/playbooks/cloudformation.yaml
Normal file
45
examples/playbooks/cloudformation.yaml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
# This playbook demonstrates how to use the ansible cloudformation module to launch an AWS CloudFormation stack.
|
||||||
|
#
|
||||||
|
# This module requires that the boto python library is installed, and that you have your AWS credentials
|
||||||
|
# in $HOME/.boto
|
||||||
|
|
||||||
|
#The thought here is to bring up a bare infrastructure with CloudFormation, but use ansible to configure it.
|
||||||
|
#I generally do this in 2 different playbook runs as to allow the ec2.py inventory to be updated.
|
||||||
|
|
||||||
|
#This module also uses "complex arguments" which were introduced in ansible 1.1 allowing you to specify the
|
||||||
|
#Cloudformation template parameters
|
||||||
|
|
||||||
|
#This example launches a 3 node AutoScale group, with a security group, and an InstanceProfile with root permissions.
|
||||||
|
|
||||||
|
#If a stack does not exist, it will be created. If it does exist and the template file has changed, the stack will be updated.
|
||||||
|
#If the parameters are different, the stack will also be updated.
|
||||||
|
|
||||||
|
#CloudFormation stacks can take awhile to provision, if you are curious about its status, use the AWS
|
||||||
|
#web console or one of the CloudFormation CLI's.
|
||||||
|
|
||||||
|
#Example update -- try first launching the stack with 3 as the ClusterSize. After it is launched, change it to 4
|
||||||
|
#and run the playbook again.
|
||||||
|
|
||||||
|
- name: provision stack
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
# Launch the cloudformation-example.json template. Register the output.
|
||||||
|
|
||||||
|
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
|
||||||
|
register: stack
|
||||||
|
- name: show stack outputs
|
||||||
|
debug: msg="My stack outputs are ${stack.stack_outputs}"
|
399
examples/playbooks/files/cloudformation-example.json
Normal file
399
examples/playbooks/files/cloudformation-example.json
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
{
|
||||||
|
"Outputs" : {
|
||||||
|
"ClusterSecGroup" : {
|
||||||
|
"Description" : "Name of RegionalManagerSecGroup",
|
||||||
|
"Value" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AWSTemplateFormatVersion" : "2010-09-09",
|
||||||
|
"Description" : "Launches an example cluster",
|
||||||
|
"Mappings" : {
|
||||||
|
"ebs" : {
|
||||||
|
"ap-northeast-1" : {
|
||||||
|
"AMI" : "ami-4e6cd34f"
|
||||||
|
},
|
||||||
|
"ap-southeast-1" : {
|
||||||
|
"AMI" : "ami-a6a7e7f4"
|
||||||
|
},
|
||||||
|
"eu-west-1" : {
|
||||||
|
"AMI" : "ami-c37474b7"
|
||||||
|
},
|
||||||
|
"sa-east-1" : {
|
||||||
|
"AMI" : "ami-1e08d103"
|
||||||
|
},
|
||||||
|
"us-east-1" : {
|
||||||
|
"AMI" : "ami-1624987f"
|
||||||
|
},
|
||||||
|
"us-west-1" : {
|
||||||
|
"AMI" : "ami-1bf9de5e"
|
||||||
|
},
|
||||||
|
"us-west-2" : {
|
||||||
|
"AMI" : "ami-2a31bf1a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ephemeral" : {
|
||||||
|
"ap-northeast-1" : {
|
||||||
|
"AMI" : "ami-5a6cd35b"
|
||||||
|
},
|
||||||
|
"ap-southeast-1" : {
|
||||||
|
"AMI" : "ami-a8a7e7fa"
|
||||||
|
},
|
||||||
|
"eu-west-1" : {
|
||||||
|
"AMI" : "ami-b57474c1"
|
||||||
|
},
|
||||||
|
"sa-east-1" : {
|
||||||
|
"AMI" : "ami-1608d10b"
|
||||||
|
},
|
||||||
|
"us-east-1" : {
|
||||||
|
"AMI" : "ami-e8249881"
|
||||||
|
},
|
||||||
|
"us-west-1" : {
|
||||||
|
"AMI" : "ami-21f9de64"
|
||||||
|
},
|
||||||
|
"us-west-2" : {
|
||||||
|
"AMI" : "ami-2e31bf1e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Parameters" : {
|
||||||
|
"ClusterSize" : {
|
||||||
|
"Description" : "Number of nodes in the cluster",
|
||||||
|
"Type" : "String"
|
||||||
|
},
|
||||||
|
"DiskType" : {
|
||||||
|
"AllowedValues" : [
|
||||||
|
"ephemeral",
|
||||||
|
"ebs"
|
||||||
|
],
|
||||||
|
"Default" : "ephemeral",
|
||||||
|
"Description" : "Type of Disk to use ( ephemeral/ebs )",
|
||||||
|
"Type" : "String"
|
||||||
|
},
|
||||||
|
"InstanceType" : {
|
||||||
|
"AllowedValues" : [
|
||||||
|
"t1.micro",
|
||||||
|
"m1.small",
|
||||||
|
"m1.medium",
|
||||||
|
"m1.large",
|
||||||
|
"m1.xlarge",
|
||||||
|
"m2.xlarge",
|
||||||
|
"m2.2xlarge",
|
||||||
|
"m2.4xlarge",
|
||||||
|
"c1.medium",
|
||||||
|
"c1.xlarge",
|
||||||
|
"cc1.4xlarge"
|
||||||
|
],
|
||||||
|
"ConstraintDescription" : "must be valid instance type. ",
|
||||||
|
"Default" : "m1.large",
|
||||||
|
"Description" : "Type of EC2 instance for cluster",
|
||||||
|
"Type" : "String"
|
||||||
|
},
|
||||||
|
"KeyName" : {
|
||||||
|
"Description" : "Name of an existing EC2 KeyPair to enable SSH access to the cluster",
|
||||||
|
"Type" : "String"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Resources" : {
|
||||||
|
"ApplicationWaitCondition" : {
|
||||||
|
"DependsOn" : "ClusterServerGroup",
|
||||||
|
"Properties" : {
|
||||||
|
"Handle" : {
|
||||||
|
"Ref" : "ApplicationWaitHandle"
|
||||||
|
},
|
||||||
|
"Timeout" : "4500"
|
||||||
|
},
|
||||||
|
"Type" : "AWS::CloudFormation::WaitCondition"
|
||||||
|
},
|
||||||
|
"ApplicationWaitHandle" : {
|
||||||
|
"Type" : "AWS::CloudFormation::WaitConditionHandle"
|
||||||
|
},
|
||||||
|
"CFNInitUser" : {
|
||||||
|
"Properties" : {
|
||||||
|
"Path" : "/",
|
||||||
|
"Policies" : [
|
||||||
|
{
|
||||||
|
"PolicyDocument" : {
|
||||||
|
"Statement" : [
|
||||||
|
{
|
||||||
|
"Action" : [
|
||||||
|
"cloudformation:DescribeStackResource",
|
||||||
|
"s3:GetObject"
|
||||||
|
],
|
||||||
|
"Effect" : "Allow",
|
||||||
|
"Resource" : "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PolicyName" : "AccessForCFNInit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Type" : "AWS::IAM::User"
|
||||||
|
},
|
||||||
|
"CFNKeys" : {
|
||||||
|
"Properties" : {
|
||||||
|
"UserName" : {
|
||||||
|
"Ref" : "CFNInitUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Type" : "AWS::IAM::AccessKey"
|
||||||
|
},
|
||||||
|
"ClusterCommunication1" : {
|
||||||
|
"Properties" : {
|
||||||
|
"FromPort" : "-1",
|
||||||
|
"GroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"IpProtocol" : "icmp",
|
||||||
|
"SourceSecurityGroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"ToPort" : "-1"
|
||||||
|
},
|
||||||
|
"Type" : "AWS::EC2::SecurityGroupIngress"
|
||||||
|
},
|
||||||
|
"ClusterCommunication2" : {
|
||||||
|
"Properties" : {
|
||||||
|
"FromPort" : "1",
|
||||||
|
"GroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"IpProtocol" : "tcp",
|
||||||
|
"SourceSecurityGroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"ToPort" : "65356"
|
||||||
|
},
|
||||||
|
"Type" : "AWS::EC2::SecurityGroupIngress"
|
||||||
|
},
|
||||||
|
"ClusterCommunication3" : {
|
||||||
|
"Properties" : {
|
||||||
|
"FromPort" : "1",
|
||||||
|
"GroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"IpProtocol" : "udp",
|
||||||
|
"SourceSecurityGroupName" : {
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
},
|
||||||
|
"ToPort" : "65356"
|
||||||
|
},
|
||||||
|
"Type" : "AWS::EC2::SecurityGroupIngress"
|
||||||
|
},
|
||||||
|
"InstanceSecurityGroup" : {
|
||||||
|
"Properties" : {
|
||||||
|
"GroupDescription" : "Enable SSH access via port 22",
|
||||||
|
"SecurityGroupIngress" : [
|
||||||
|
{
|
||||||
|
"CidrIp" : "0.0.0.0/0",
|
||||||
|
"FromPort" : "22",
|
||||||
|
"IpProtocol" : "tcp",
|
||||||
|
"ToPort" : "22"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Type" : "AWS::EC2::SecurityGroup"
|
||||||
|
},
|
||||||
|
"LaunchConfig" : {
|
||||||
|
"Properties" : {
|
||||||
|
"IamInstanceProfile" : {
|
||||||
|
"Ref" : "RootInstanceProfile"
|
||||||
|
},
|
||||||
|
"ImageId" : {
|
||||||
|
"Fn::FindInMap" : [
|
||||||
|
{
|
||||||
|
"Ref" : "DiskType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Ref" : "AWS::Region"
|
||||||
|
},
|
||||||
|
"AMI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"InstanceType" : {
|
||||||
|
"Ref" : "InstanceType"
|
||||||
|
},
|
||||||
|
"KeyName" : {
|
||||||
|
"Ref" : "KeyName"
|
||||||
|
},
|
||||||
|
"SecurityGroups" : [
|
||||||
|
{
|
||||||
|
"Ref" : "InstanceSecurityGroup"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UserData" : {
|
||||||
|
"Fn::Base64" : {
|
||||||
|
"Fn::Join" : [
|
||||||
|
"\n",
|
||||||
|
[
|
||||||
|
"#!/bin/bash -v",
|
||||||
|
"exec > >(tee /var/log/cfn-data.log|logger -t user-data -s 2>/dev/console) 2>&1",
|
||||||
|
"",
|
||||||
|
"sleep 10",
|
||||||
|
"",
|
||||||
|
"function retry {",
|
||||||
|
" nTrys=0",
|
||||||
|
" maxTrys=5",
|
||||||
|
" status=256",
|
||||||
|
" until [ $status == 0 ] ; do",
|
||||||
|
" $1",
|
||||||
|
" status=$?",
|
||||||
|
" nTrys=$(($nTrys + 1))",
|
||||||
|
" if [ $nTrys -gt $maxTrys ] ; then",
|
||||||
|
" echo \"Number of re-trys exceeded. Exit code: $status\"",
|
||||||
|
" exit $status",
|
||||||
|
" fi",
|
||||||
|
" if [ $status != 0 ] ; then",
|
||||||
|
" echo \"Failed (exit code $status)... retry $nTrys\"",
|
||||||
|
" sleep 10",
|
||||||
|
" fi",
|
||||||
|
" done",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"yum update -y aws-cfn-bootstrap",
|
||||||
|
"",
|
||||||
|
"#for all the stuff that complains about sudo and tty",
|
||||||
|
"sed -i 's,Defaults requiretty,#Defaults requiretty,g' /etc/sudoers",
|
||||||
|
"",
|
||||||
|
"function error_exit",
|
||||||
|
"{",
|
||||||
|
{
|
||||||
|
"Fn::Join" : [
|
||||||
|
"",
|
||||||
|
[
|
||||||
|
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
|
||||||
|
{
|
||||||
|
"Ref" : "ApplicationWaitHandle"
|
||||||
|
},
|
||||||
|
"'"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"}",
|
||||||
|
"yum update -y aws-cfn-bootstrap",
|
||||||
|
"#this runs the first stage of cfinit",
|
||||||
|
{
|
||||||
|
"Fn::Join" : [
|
||||||
|
"",
|
||||||
|
[
|
||||||
|
"#/opt/aws/bin/cfn-init -c ascending -v --region ",
|
||||||
|
{
|
||||||
|
"Ref" : "AWS::Region"
|
||||||
|
},
|
||||||
|
" -s ",
|
||||||
|
{
|
||||||
|
"Ref" : "AWS::StackName"
|
||||||
|
},
|
||||||
|
" -r ",
|
||||||
|
"LaunchConfig",
|
||||||
|
" --access-key ",
|
||||||
|
{
|
||||||
|
"Ref" : "CFNKeys"
|
||||||
|
},
|
||||||
|
" --secret-key ",
|
||||||
|
{
|
||||||
|
"Fn::GetAtt" : [
|
||||||
|
"CFNKeys",
|
||||||
|
"SecretAccessKey"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
" || error_exit 'Failed to initialize client using cfn-init'"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"result_code=$?",
|
||||||
|
{
|
||||||
|
"Fn::Join" : [
|
||||||
|
"",
|
||||||
|
[
|
||||||
|
"/opt/aws/bin/cfn-signal -e $result_code '",
|
||||||
|
{
|
||||||
|
"Ref" : "ApplicationWaitHandle"
|
||||||
|
},
|
||||||
|
"'"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Type" : "AWS::AutoScaling::LaunchConfiguration"
|
||||||
|
},
|
||||||
|
"ClusterServerGroup" : {
|
||||||
|
"Properties" : {
|
||||||
|
"AvailabilityZones" : {
|
||||||
|
"Fn::GetAZs" : ""
|
||||||
|
},
|
||||||
|
"LaunchConfigurationName" : {
|
||||||
|
"Ref" : "LaunchConfig"
|
||||||
|
},
|
||||||
|
"MaxSize" : {
|
||||||
|
"Ref" : "ClusterSize"
|
||||||
|
},
|
||||||
|
"MinSize" : {
|
||||||
|
"Ref" : "ClusterSize"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Type" : "AWS::AutoScaling::AutoScalingGroup"
|
||||||
|
},
|
||||||
|
"RolePolicies" : {
|
||||||
|
"Properties" : {
|
||||||
|
"PolicyDocument" : {
|
||||||
|
"Statement" : [
|
||||||
|
{
|
||||||
|
"Action" : "*",
|
||||||
|
"Effect" : "Allow",
|
||||||
|
"Resource" : "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"PolicyName" : "root",
|
||||||
|
"Roles" : [
|
||||||
|
{
|
||||||
|
"Ref" : "RootRole"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Type" : "AWS::IAM::Policy"
|
||||||
|
},
|
||||||
|
"RootInstanceProfile" : {
|
||||||
|
"Properties" : {
|
||||||
|
"Path" : "/",
|
||||||
|
"Roles" : [
|
||||||
|
{
|
||||||
|
"Ref" : "RootRole"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Type" : "AWS::IAM::InstanceProfile"
|
||||||
|
},
|
||||||
|
"RootRole" : {
|
||||||
|
"Properties" : {
|
||||||
|
"AssumeRolePolicyDocument" : {
|
||||||
|
"Statement" : [
|
||||||
|
{
|
||||||
|
"Action" : [
|
||||||
|
"sts:AssumeRole"
|
||||||
|
],
|
||||||
|
"Effect" : "Allow",
|
||||||
|
"Principal" : {
|
||||||
|
"Service" : [
|
||||||
|
"ec2.amazonaws.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Path" : "/"
|
||||||
|
},
|
||||||
|
"Type" : "AWS::IAM::Role"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
243
library/cloudformation
Normal file
243
library/cloudformation
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||||
|
|
||||||
|
main()
|
Loading…
Reference in a new issue