OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/ |
| 2 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a |
| 4 # copy of this software and associated documentation files (the |
| 5 # "Software"), to deal in the Software without restriction, including |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 # IN THE SOFTWARE. |
| 21 |
| 22 try: |
| 23 import simplejson as json |
| 24 except: |
| 25 import json |
| 26 |
| 27 import boto |
| 28 from boto.cloudformation.stack import Stack, StackSummary, StackEvent |
| 29 from boto.cloudformation.stack import StackResource, StackResourceSummary |
| 30 from boto.cloudformation.template import Template |
| 31 from boto.connection import AWSQueryConnection |
| 32 from boto.regioninfo import RegionInfo |
| 33 |
| 34 class CloudFormationConnection(AWSQueryConnection): |
| 35 |
| 36 """ |
| 37 A Connection to the CloudFormation Service. |
| 38 """ |
| 39 DefaultRegionName = 'us-east-1' |
| 40 DefaultRegionEndpoint = 'cloudformation.us-east-1.amazonaws.com' |
| 41 APIVersion = '2010-05-15' |
| 42 |
| 43 valid_states = ("CREATE_IN_PROGRESS", "CREATE_FAILED", "CREATE_COMPLETE", |
| 44 "ROLLBACK_IN_PROGRESS", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE", |
| 45 "DELETE_IN_PROGRESS", "DELETE_FAILED", "DELETE_COMPLETE") |
| 46 |
| 47 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, |
| 48 is_secure=True, port=None, proxy=None, proxy_port=None, |
| 49 proxy_user=None, proxy_pass=None, debug=0, |
| 50 https_connection_factory=None, region=None, path='/', converter
=None): |
| 51 if not region: |
| 52 region = RegionInfo(self, self.DefaultRegionName, |
| 53 self.DefaultRegionEndpoint, CloudFormationConnection) |
| 54 self.region = region |
| 55 AWSQueryConnection.__init__(self, aws_access_key_id, aws_secret_access_k
ey, |
| 56 is_secure, port, proxy, proxy_port, proxy_us
er, proxy_pass, |
| 57 self.region.endpoint, debug, https_connectio
n_factory, path) |
| 58 |
| 59 def _required_auth_capability(self): |
| 60 return ['cloudformation'] |
| 61 |
| 62 def encode_bool(self, v): |
| 63 v = bool(v) |
| 64 return {True: "true", False: "false"}[v] |
| 65 |
| 66 def create_stack(self, stack_name, template_body=None, template_url=None, |
| 67 parameters=[], notification_arns=[], disable_rollback=False, |
| 68 timeout_in_minutes=None): |
| 69 """ |
| 70 Creates a CloudFormation Stack as specified by the template. |
| 71 |
| 72 :type stack_name: string |
| 73 :param stack_name: The name of the Stack, must be unique amoung running |
| 74 Stacks |
| 75 |
| 76 :type template_body: string |
| 77 :param template_body: The template body (JSON string) |
| 78 |
| 79 :type template_url: string |
| 80 :param template_url: An S3 URL of a stored template JSON document. If |
| 81 both the template_body and template_url are |
| 82 specified, the template_body takes precedence |
| 83 |
| 84 :type parameters: list of tuples |
| 85 :param parameters: A list of (key, value) pairs for template input |
| 86 parameters. |
| 87 |
| 88 :type notification_arns: list of strings |
| 89 :param notification_arns: A list of SNS topics to send Stack event |
| 90 notifications to |
| 91 |
| 92 :type disable_rollback: bool |
| 93 :param disable_rollback: Indicates whether or not to rollback on |
| 94 failure |
| 95 |
| 96 :type timeout_in_minutes: int |
| 97 :param timeout_in_minutes: Maximum amount of time to let the Stack |
| 98 spend creating itself. If this timeout is exceeded, |
| 99 the Stack will enter the CREATE_FAILED state |
| 100 |
| 101 :rtype: string |
| 102 :return: The unique Stack ID |
| 103 """ |
| 104 params = {'ContentType': "JSON", 'StackName': stack_name, |
| 105 'DisableRollback': self.encode_bool(disable_rollback)} |
| 106 if template_body: |
| 107 params['TemplateBody'] = template_body |
| 108 if template_url: |
| 109 params['TemplateURL'] = template_url |
| 110 if template_body and template_url: |
| 111 boto.log.warning("If both TemplateBody and TemplateURL are" |
| 112 " specified, only TemplateBody will be honored by the API") |
| 113 if len(parameters) > 0: |
| 114 for i, (key, value) in enumerate(parameters): |
| 115 params['Parameters.member.%d.ParameterKey' % (i+1)] = key |
| 116 params['Parameters.member.%d.ParameterValue' % (i+1)] = value |
| 117 if len(notification_arns) > 0: |
| 118 self.build_list_params(params, notification_arns, "NotificationARNs.
member") |
| 119 if timeout_in_minutes: |
| 120 params['TimeoutInMinutes'] = int(timeout_in_minutes) |
| 121 |
| 122 response = self.make_request('CreateStack', params, '/', 'POST') |
| 123 body = response.read() |
| 124 if response.status == 200: |
| 125 body = json.loads(body) |
| 126 return body['CreateStackResponse']['CreateStackResult']['StackId'] |
| 127 else: |
| 128 boto.log.error('%s %s' % (response.status, response.reason)) |
| 129 boto.log.error('%s' % body) |
| 130 raise self.ResponseError(response.status, response.reason, body) |
| 131 |
| 132 def delete_stack(self, stack_name_or_id): |
| 133 params = {'ContentType': "JSON", 'StackName': stack_name_or_id} |
| 134 # TODO: change this to get_status ? |
| 135 response = self.make_request('DeleteStack', params, '/', 'GET') |
| 136 body = response.read() |
| 137 if response.status == 200: |
| 138 return json.loads(body) |
| 139 else: |
| 140 boto.log.error('%s %s' % (response.status, response.reason)) |
| 141 boto.log.error('%s' % body) |
| 142 raise self.ResponseError(response.status, response.reason, body) |
| 143 |
| 144 def describe_stack_events(self, stack_name_or_id=None, next_token=None): |
| 145 params = {} |
| 146 if stack_name_or_id: |
| 147 params['StackName'] = stack_name_or_id |
| 148 if next_token: |
| 149 params['NextToken'] = next_token |
| 150 return self.get_list('DescribeStackEvents', params, [('member', |
| 151 StackEvent)]) |
| 152 |
| 153 def describe_stack_resource(self, stack_name_or_id, logical_resource_id): |
| 154 params = {'ContentType': "JSON", 'StackName': stack_name_or_id, |
| 155 'LogicalResourceId': logical_resource_id} |
| 156 response = self.make_request('DescribeStackResource', params, '/', 'GET'
) |
| 157 body = response.read() |
| 158 if response.status == 200: |
| 159 return json.loads(body) |
| 160 else: |
| 161 boto.log.error('%s %s' % (response.status, response.reason)) |
| 162 boto.log.error('%s' % body) |
| 163 raise self.ResponseError(response.status, response.reason, body) |
| 164 |
| 165 def describe_stack_resources(self, stack_name_or_id=None, |
| 166 logical_resource_id=None, |
| 167 physical_resource_id=None): |
| 168 params = {} |
| 169 if stack_name_or_id: |
| 170 params['StackName'] = stack_name_or_id |
| 171 if logical_resource_id: |
| 172 params['LogicalResourceId'] = logical_resource_id |
| 173 if physical_resource_id: |
| 174 params['PhysicalResourceId'] = physical_resource_id |
| 175 return self.get_list('DescribeStackResources', params, [('member', |
| 176 StackResource)]) |
| 177 |
| 178 def describe_stacks(self, stack_name_or_id=None): |
| 179 params = {} |
| 180 if stack_name_or_id: |
| 181 params['StackName'] = stack_name_or_id |
| 182 return self.get_list('DescribeStacks', params, [('member', Stack)]) |
| 183 |
| 184 def get_template(self, stack_name_or_id): |
| 185 params = {'ContentType': "JSON", 'StackName': stack_name_or_id} |
| 186 response = self.make_request('GetTemplate', params, '/', 'GET') |
| 187 body = response.read() |
| 188 if response.status == 200: |
| 189 return json.loads(body) |
| 190 else: |
| 191 boto.log.error('%s %s' % (response.status, response.reason)) |
| 192 boto.log.error('%s' % body) |
| 193 raise self.ResponseError(response.status, response.reason, body) |
| 194 |
| 195 def list_stack_resources(self, stack_name_or_id, next_token=None): |
| 196 params = {'StackName': stack_name_or_id} |
| 197 if next_token: |
| 198 params['NextToken'] = next_token |
| 199 return self.get_list('ListStackResources', params, [('member', |
| 200 StackResourceSummary)]) |
| 201 |
| 202 def list_stacks(self, stack_status_filters=[], next_token=None): |
| 203 params = {} |
| 204 if next_token: |
| 205 params['NextToken'] = next_token |
| 206 if len(stack_status_filters) > 0: |
| 207 self.build_list_params(params, stack_status_filters, |
| 208 "StackStatusFilter.member") |
| 209 |
| 210 return self.get_list('ListStacks', params, [('member', |
| 211 StackSummary)]) |
| 212 |
| 213 def validate_template(self, template_body=None, template_url=None): |
| 214 params = {} |
| 215 if template_body: |
| 216 params['TemplateBody'] = template_body |
| 217 if template_url: |
| 218 params['TemplateUrl'] = template_url |
| 219 if template_body and template_url: |
| 220 boto.log.warning("If both TemplateBody and TemplateURL are" |
| 221 " specified, only TemplateBody will be honored by the API") |
| 222 return self.get_object('ValidateTemplate', params, Template, |
| 223 verb="POST") |
OLD | NEW |