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 import boto |
| 23 from boto.cloudformation.stack import Stack, StackSummary, StackEvent |
| 24 from boto.cloudformation.stack import StackResource, StackResourceSummary |
| 25 from boto.cloudformation.template import Template |
| 26 from boto.connection import AWSQueryConnection |
| 27 from boto.regioninfo import RegionInfo |
| 28 from boto.compat import json |
| 29 |
| 30 |
| 31 class CloudFormationConnection(AWSQueryConnection): |
| 32 |
| 33 """ |
| 34 A Connection to the CloudFormation Service. |
| 35 """ |
| 36 APIVersion = boto.config.get('Boto', 'cfn_version', '2010-05-15') |
| 37 DefaultRegionName = boto.config.get('Boto', 'cfn_region_name', 'us-east-1') |
| 38 DefaultRegionEndpoint = boto.config.get('Boto', 'cfn_region_endpoint', |
| 39 'cloudformation.us-east-1.amazonaws.
com') |
| 40 |
| 41 valid_states = ("CREATE_IN_PROGRESS", "CREATE_FAILED", "CREATE_COMPLETE", |
| 42 "ROLLBACK_IN_PROGRESS", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE", |
| 43 "DELETE_IN_PROGRESS", "DELETE_FAILED", "DELETE_COMPLETE") |
| 44 |
| 45 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, |
| 46 is_secure=True, port=None, proxy=None, proxy_port=None, |
| 47 proxy_user=None, proxy_pass=None, debug=0, |
| 48 https_connection_factory=None, region=None, path='/', |
| 49 converter=None, security_token=None, validate_certs=True): |
| 50 if not region: |
| 51 region = RegionInfo(self, self.DefaultRegionName, |
| 52 self.DefaultRegionEndpoint, CloudFormationConnection) |
| 53 self.region = region |
| 54 AWSQueryConnection.__init__(self, aws_access_key_id, |
| 55 aws_secret_access_key, |
| 56 is_secure, port, proxy, proxy_port, |
| 57 proxy_user, proxy_pass, |
| 58 self.region.endpoint, debug, |
| 59 https_connection_factory, path, |
| 60 security_token, |
| 61 validate_certs=validate_certs) |
| 62 |
| 63 def _required_auth_capability(self): |
| 64 return ['hmac-v4'] |
| 65 |
| 66 def encode_bool(self, v): |
| 67 v = bool(v) |
| 68 return {True: "true", False: "false"}[v] |
| 69 |
| 70 def _build_create_or_update_params(self, stack_name, template_body, |
| 71 template_url, parameters, |
| 72 notification_arns, disable_rollback, |
| 73 timeout_in_minutes, capabilities, tags): |
| 74 """ |
| 75 Helper that creates JSON parameters needed by a Stack Create or |
| 76 Stack Update call. |
| 77 |
| 78 :type stack_name: string |
| 79 :param stack_name: The name of the Stack, must be unique amoung running |
| 80 Stacks |
| 81 |
| 82 :type template_body: string |
| 83 :param template_body: The template body (JSON string) |
| 84 |
| 85 :type template_url: string |
| 86 :param template_url: An S3 URL of a stored template JSON document. If |
| 87 both the template_body and template_url are |
| 88 specified, the template_body takes precedence |
| 89 |
| 90 :type parameters: list of tuples |
| 91 :param parameters: A list of (key, value) pairs for template input |
| 92 parameters. |
| 93 |
| 94 :type notification_arns: list of strings |
| 95 :param notification_arns: A list of SNS topics to send Stack event |
| 96 notifications to. |
| 97 |
| 98 :type disable_rollback: bool |
| 99 :param disable_rollback: Indicates whether or not to rollback on |
| 100 failure. |
| 101 |
| 102 :type timeout_in_minutes: int |
| 103 :param timeout_in_minutes: Maximum amount of time to let the Stack |
| 104 spend creating itself. If this timeout is exceeded, |
| 105 the Stack will enter the CREATE_FAILED state. |
| 106 |
| 107 :type capabilities: list |
| 108 :param capabilities: The list of capabilities you want to allow in |
| 109 the stack. Currently, the only valid capability is |
| 110 'CAPABILITY_IAM'. |
| 111 |
| 112 :type tags: dict |
| 113 :param tags: A dictionary of (key, value) pairs of tags to |
| 114 associate with this stack. |
| 115 |
| 116 :rtype: dict |
| 117 :return: JSON parameters represented as a Python dict. |
| 118 """ |
| 119 params = {'ContentType': "JSON", 'StackName': stack_name, |
| 120 'DisableRollback': self.encode_bool(disable_rollback)} |
| 121 if template_body: |
| 122 params['TemplateBody'] = template_body |
| 123 if template_url: |
| 124 params['TemplateURL'] = template_url |
| 125 if template_body and template_url: |
| 126 boto.log.warning("If both TemplateBody and TemplateURL are" |
| 127 " specified, only TemplateBody will be honored by the API") |
| 128 if len(parameters) > 0: |
| 129 for i, (key, value) in enumerate(parameters): |
| 130 params['Parameters.member.%d.ParameterKey' % (i + 1)] = key |
| 131 params['Parameters.member.%d.ParameterValue' % (i + 1)] = value |
| 132 if capabilities: |
| 133 for i, value in enumerate(capabilities): |
| 134 params['Capabilities.member.%d' % (i + 1)] = value |
| 135 if tags: |
| 136 for i, (key, value) in enumerate(tags.items()): |
| 137 params['Tags.member.%d.Key' % (i + 1)] = key |
| 138 params['Tags.member.%d.Value' % (i + 1)] = value |
| 139 if len(notification_arns) > 0: |
| 140 self.build_list_params(params, notification_arns, |
| 141 "NotificationARNs.member") |
| 142 if timeout_in_minutes: |
| 143 params['TimeoutInMinutes'] = int(timeout_in_minutes) |
| 144 return params |
| 145 |
| 146 def create_stack(self, stack_name, template_body=None, template_url=None, |
| 147 parameters=[], notification_arns=[], disable_rollback=False, |
| 148 timeout_in_minutes=None, capabilities=None, tags=None): |
| 149 """ |
| 150 Creates a CloudFormation Stack as specified by the template. |
| 151 |
| 152 :type stack_name: string |
| 153 :param stack_name: The name of the Stack, must be unique amoung running |
| 154 Stacks |
| 155 |
| 156 :type template_body: string |
| 157 :param template_body: The template body (JSON string) |
| 158 |
| 159 :type template_url: string |
| 160 :param template_url: An S3 URL of a stored template JSON document. If |
| 161 both the template_body and template_url are |
| 162 specified, the template_body takes precedence |
| 163 |
| 164 :type parameters: list of tuples |
| 165 :param parameters: A list of (key, value) pairs for template input |
| 166 parameters. |
| 167 |
| 168 :type notification_arns: list of strings |
| 169 :param notification_arns: A list of SNS topics to send Stack event |
| 170 notifications to. |
| 171 |
| 172 :type disable_rollback: bool |
| 173 :param disable_rollback: Indicates whether or not to rollback on |
| 174 failure. |
| 175 |
| 176 :type timeout_in_minutes: int |
| 177 :param timeout_in_minutes: Maximum amount of time to let the Stack |
| 178 spend creating itself. If this timeout is exceeded, |
| 179 the Stack will enter the CREATE_FAILED state. |
| 180 |
| 181 :type capabilities: list |
| 182 :param capabilities: The list of capabilities you want to allow in |
| 183 the stack. Currently, the only valid capability is |
| 184 'CAPABILITY_IAM'. |
| 185 |
| 186 :type tags: dict |
| 187 :param tags: A dictionary of (key, value) pairs of tags to |
| 188 associate with this stack. |
| 189 |
| 190 :rtype: string |
| 191 :return: The unique Stack ID. |
| 192 """ |
| 193 params = self._build_create_or_update_params(stack_name, |
| 194 template_body, template_url, parameters, notification_arns, |
| 195 disable_rollback, timeout_in_minutes, capabilities, tags) |
| 196 response = self.make_request('CreateStack', params, '/', 'POST') |
| 197 body = response.read() |
| 198 if response.status == 200: |
| 199 body = json.loads(body) |
| 200 return body['CreateStackResponse']['CreateStackResult']['StackId'] |
| 201 else: |
| 202 boto.log.error('%s %s' % (response.status, response.reason)) |
| 203 boto.log.error('%s' % body) |
| 204 raise self.ResponseError(response.status, response.reason, body) |
| 205 |
| 206 def update_stack(self, stack_name, template_body=None, template_url=None, |
| 207 parameters=[], notification_arns=[], disable_rollback=False, |
| 208 timeout_in_minutes=None, capabilities=None, tags=None): |
| 209 """ |
| 210 Updates a CloudFormation Stack as specified by the template. |
| 211 |
| 212 :type stack_name: string |
| 213 :param stack_name: The name of the Stack, must be unique amoung running |
| 214 Stacks. |
| 215 |
| 216 :type template_body: string |
| 217 :param template_body: The template body (JSON string) |
| 218 |
| 219 :type template_url: string |
| 220 :param template_url: An S3 URL of a stored template JSON document. If |
| 221 both the template_body and template_url are |
| 222 specified, the template_body takes precedence. |
| 223 |
| 224 :type parameters: list of tuples |
| 225 :param parameters: A list of (key, value) pairs for template input |
| 226 parameters. |
| 227 |
| 228 :type notification_arns: list of strings |
| 229 :param notification_arns: A list of SNS topics to send Stack event |
| 230 notifications to. |
| 231 |
| 232 :type disable_rollback: bool |
| 233 :param disable_rollback: Indicates whether or not to rollback on |
| 234 failure. |
| 235 |
| 236 :type timeout_in_minutes: int |
| 237 :param timeout_in_minutes: Maximum amount of time to let the Stack |
| 238 spend creating itself. If this timeout is exceeded, |
| 239 the Stack will enter the CREATE_FAILED state |
| 240 |
| 241 :type capabilities: list |
| 242 :param capabilities: The list of capabilities you want to allow in |
| 243 the stack. Currently, the only valid capability is |
| 244 'CAPABILITY_IAM'. |
| 245 |
| 246 :type tags: dict |
| 247 :param tags: A dictionary of (key, value) pairs of tags to |
| 248 associate with this stack. |
| 249 |
| 250 :rtype: string |
| 251 :return: The unique Stack ID. |
| 252 """ |
| 253 params = self._build_create_or_update_params(stack_name, |
| 254 template_body, template_url, parameters, notification_arns, |
| 255 disable_rollback, timeout_in_minutes, capabilities, tags) |
| 256 response = self.make_request('UpdateStack', params, '/', 'POST') |
| 257 body = response.read() |
| 258 if response.status == 200: |
| 259 body = json.loads(body) |
| 260 return body['UpdateStackResponse']['UpdateStackResult']['StackId'] |
| 261 else: |
| 262 boto.log.error('%s %s' % (response.status, response.reason)) |
| 263 boto.log.error('%s' % body) |
| 264 raise self.ResponseError(response.status, response.reason, body) |
| 265 |
| 266 def delete_stack(self, stack_name_or_id): |
| 267 params = {'ContentType': "JSON", 'StackName': stack_name_or_id} |
| 268 # TODO: change this to get_status ? |
| 269 response = self.make_request('DeleteStack', params, '/', 'GET') |
| 270 body = response.read() |
| 271 if response.status == 200: |
| 272 return json.loads(body) |
| 273 else: |
| 274 boto.log.error('%s %s' % (response.status, response.reason)) |
| 275 boto.log.error('%s' % body) |
| 276 raise self.ResponseError(response.status, response.reason, body) |
| 277 |
| 278 def describe_stack_events(self, stack_name_or_id=None, next_token=None): |
| 279 params = {} |
| 280 if stack_name_or_id: |
| 281 params['StackName'] = stack_name_or_id |
| 282 if next_token: |
| 283 params['NextToken'] = next_token |
| 284 return self.get_list('DescribeStackEvents', params, [('member', |
| 285 StackEvent)]) |
| 286 |
| 287 def describe_stack_resource(self, stack_name_or_id, logical_resource_id): |
| 288 params = {'ContentType': "JSON", 'StackName': stack_name_or_id, |
| 289 'LogicalResourceId': logical_resource_id} |
| 290 response = self.make_request('DescribeStackResource', params, |
| 291 '/', 'GET') |
| 292 body = response.read() |
| 293 if response.status == 200: |
| 294 return json.loads(body) |
| 295 else: |
| 296 boto.log.error('%s %s' % (response.status, response.reason)) |
| 297 boto.log.error('%s' % body) |
| 298 raise self.ResponseError(response.status, response.reason, body) |
| 299 |
| 300 def describe_stack_resources(self, stack_name_or_id=None, |
| 301 logical_resource_id=None, |
| 302 physical_resource_id=None): |
| 303 params = {} |
| 304 if stack_name_or_id: |
| 305 params['StackName'] = stack_name_or_id |
| 306 if logical_resource_id: |
| 307 params['LogicalResourceId'] = logical_resource_id |
| 308 if physical_resource_id: |
| 309 params['PhysicalResourceId'] = physical_resource_id |
| 310 return self.get_list('DescribeStackResources', params, |
| 311 [('member', StackResource)]) |
| 312 |
| 313 def describe_stacks(self, stack_name_or_id=None): |
| 314 params = {} |
| 315 if stack_name_or_id: |
| 316 params['StackName'] = stack_name_or_id |
| 317 return self.get_list('DescribeStacks', params, [('member', Stack)]) |
| 318 |
| 319 def get_template(self, stack_name_or_id): |
| 320 params = {'ContentType': "JSON", 'StackName': stack_name_or_id} |
| 321 response = self.make_request('GetTemplate', params, '/', 'GET') |
| 322 body = response.read() |
| 323 if response.status == 200: |
| 324 return json.loads(body) |
| 325 else: |
| 326 boto.log.error('%s %s' % (response.status, response.reason)) |
| 327 boto.log.error('%s' % body) |
| 328 raise self.ResponseError(response.status, response.reason, body) |
| 329 |
| 330 def list_stack_resources(self, stack_name_or_id, next_token=None): |
| 331 params = {'StackName': stack_name_or_id} |
| 332 if next_token: |
| 333 params['NextToken'] = next_token |
| 334 return self.get_list('ListStackResources', params, |
| 335 [('member', StackResourceSummary)]) |
| 336 |
| 337 def list_stacks(self, stack_status_filters=[], next_token=None): |
| 338 params = {} |
| 339 if next_token: |
| 340 params['NextToken'] = next_token |
| 341 if len(stack_status_filters) > 0: |
| 342 self.build_list_params(params, stack_status_filters, |
| 343 "StackStatusFilter.member") |
| 344 |
| 345 return self.get_list('ListStacks', params, |
| 346 [('member', StackSummary)]) |
| 347 |
| 348 def validate_template(self, template_body=None, template_url=None): |
| 349 params = {} |
| 350 if template_body: |
| 351 params['TemplateBody'] = template_body |
| 352 if template_url: |
| 353 params['TemplateURL'] = template_url |
| 354 if template_body and template_url: |
| 355 boto.log.warning("If both TemplateBody and TemplateURL are" |
| 356 " specified, only TemplateBody will be honored by the API") |
| 357 return self.get_object('ValidateTemplate', params, Template, |
| 358 verb="POST") |
OLD | NEW |