Add cloudformation update from s3 support (#1377)
* Fix variable name typo * Make it possible to delete EC2 instances from cloudformation json * Add support for updating a cloudformation stack from an s3 template url
This commit is contained in:
parent
52ce8d378f
commit
92f5f7b263
@ -453,8 +453,8 @@ class ResourceMap(collections.Mapping):
|
|||||||
resource_name, resource_json, self, self._region_name)
|
resource_name, resource_json, self, self._region_name)
|
||||||
self._parsed_resources[resource_name] = new_resource
|
self._parsed_resources[resource_name] = new_resource
|
||||||
|
|
||||||
removed_resource_nams = set(old_template) - set(new_template)
|
removed_resource_names = set(old_template) - set(new_template)
|
||||||
for resource_name in removed_resource_nams:
|
for resource_name in removed_resource_names:
|
||||||
resource_json = old_template[resource_name]
|
resource_json = old_template[resource_name]
|
||||||
parse_and_delete_resource(
|
parse_and_delete_resource(
|
||||||
resource_name, resource_json, self, self._region_name)
|
resource_name, resource_json, self, self._region_name)
|
||||||
|
@ -161,11 +161,15 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
def update_stack(self):
|
def update_stack(self):
|
||||||
stack_name = self._get_param('StackName')
|
stack_name = self._get_param('StackName')
|
||||||
role_arn = self._get_param('RoleARN')
|
role_arn = self._get_param('RoleARN')
|
||||||
|
template_url = self._get_param('TemplateURL')
|
||||||
if self._get_param('UsePreviousTemplate') == "true":
|
if self._get_param('UsePreviousTemplate') == "true":
|
||||||
stack_body = self.cloudformation_backend.get_stack(
|
stack_body = self.cloudformation_backend.get_stack(
|
||||||
stack_name).template
|
stack_name).template
|
||||||
|
elif template_url:
|
||||||
|
stack_body = self._get_stack_from_s3_url(template_url)
|
||||||
else:
|
else:
|
||||||
stack_body = self._get_param('TemplateBody')
|
stack_body = self._get_param('TemplateBody')
|
||||||
|
|
||||||
parameters = dict([
|
parameters = dict([
|
||||||
(parameter['parameter_key'], parameter['parameter_value'])
|
(parameter['parameter_key'], parameter['parameter_value'])
|
||||||
for parameter
|
for parameter
|
||||||
|
@ -509,6 +509,22 @@ class Instance(TaggedEC2Resource, BotoInstance):
|
|||||||
instance.add_tag(tag["Key"], tag["Value"])
|
instance.add_tag(tag["Key"], tag["Value"])
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
ec2_backend = ec2_backends[region_name]
|
||||||
|
all_instances = ec2_backend.all_instances()
|
||||||
|
|
||||||
|
# the resource_name for instances is the stack name, logical id, and random suffix separated
|
||||||
|
# by hyphens. So to lookup the instances using the 'aws:cloudformation:logical-id' tag, we need to
|
||||||
|
# extract the logical-id from the resource_name
|
||||||
|
logical_id = resource_name.split('-')[1]
|
||||||
|
|
||||||
|
for instance in all_instances:
|
||||||
|
instance_tags = instance.get_tags()
|
||||||
|
for tag in instance_tags:
|
||||||
|
if tag['key'] == 'aws:cloudformation:logical-id' and tag['value'] == logical_id:
|
||||||
|
instance.delete(region_name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import boto3
|
|
||||||
import boto
|
|
||||||
import boto.s3
|
|
||||||
import boto.s3.key
|
|
||||||
from botocore.exceptions import ClientError
|
|
||||||
from moto import mock_cloudformation, mock_s3, mock_sqs
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sure # noqa
|
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
# Ensure 'assert_raises' context manager support for Python 2.6
|
# Ensure 'assert_raises' context manager support for Python 2.6
|
||||||
import tests.backport_assert_raises # noqa
|
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
import random
|
|
||||||
|
from moto import mock_cloudformation, mock_s3, mock_sqs, mock_ec2
|
||||||
|
|
||||||
dummy_template = {
|
dummy_template = {
|
||||||
"AWSTemplateFormatVersion": "2010-09-09",
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
@ -39,7 +34,6 @@ dummy_template = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dummy_template_yaml = """---
|
dummy_template_yaml = """---
|
||||||
AWSTemplateFormatVersion: 2010-09-09
|
AWSTemplateFormatVersion: 2010-09-09
|
||||||
Description: Stack1 with yaml template
|
Description: Stack1 with yaml template
|
||||||
@ -57,7 +51,6 @@ Resources:
|
|||||||
Value: Name tag for tests
|
Value: Name tag for tests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
dummy_template_yaml_with_short_form_func = """---
|
dummy_template_yaml_with_short_form_func = """---
|
||||||
AWSTemplateFormatVersion: 2010-09-09
|
AWSTemplateFormatVersion: 2010-09-09
|
||||||
Description: Stack1 with yaml template
|
Description: Stack1 with yaml template
|
||||||
@ -75,7 +68,6 @@ Resources:
|
|||||||
Value: Name tag for tests
|
Value: Name tag for tests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
dummy_template_yaml_with_ref = """---
|
dummy_template_yaml_with_ref = """---
|
||||||
AWSTemplateFormatVersion: 2010-09-09
|
AWSTemplateFormatVersion: 2010-09-09
|
||||||
Description: Stack1 with yaml template
|
Description: Stack1 with yaml template
|
||||||
@ -100,7 +92,6 @@ Resources:
|
|||||||
Value: !Ref TagName
|
Value: !Ref TagName
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
dummy_update_template = {
|
dummy_update_template = {
|
||||||
"AWSTemplateFormatVersion": "2010-09-09",
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
"Parameters": {
|
"Parameters": {
|
||||||
@ -131,12 +122,12 @@ dummy_output_template = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Outputs" : {
|
"Outputs": {
|
||||||
"StackVPC" : {
|
"StackVPC": {
|
||||||
"Description" : "The ID of the VPC",
|
"Description": "The ID of the VPC",
|
||||||
"Value" : "VPCID",
|
"Value": "VPCID",
|
||||||
"Export" : {
|
"Export": {
|
||||||
"Name" : "My VPC ID"
|
"Name": "My VPC ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +147,7 @@ dummy_import_template = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dummy_template_json = json.dumps(dummy_template)
|
dummy_template_json = json.dumps(dummy_template)
|
||||||
dummy_update_template_json = json.dumps(dummy_template)
|
dummy_update_template_json = json.dumps(dummy_update_template)
|
||||||
dummy_output_template_json = json.dumps(dummy_output_template)
|
dummy_output_template_json = json.dumps(dummy_output_template)
|
||||||
dummy_import_template_json = json.dumps(dummy_import_template)
|
dummy_import_template_json = json.dumps(dummy_import_template)
|
||||||
|
|
||||||
@ -172,6 +163,7 @@ def test_boto3_create_stack():
|
|||||||
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
|
cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
|
||||||
dummy_template)
|
dummy_template)
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
def test_boto3_create_stack_with_yaml():
|
def test_boto3_create_stack_with_yaml():
|
||||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
@ -283,6 +275,41 @@ def test_create_stack_from_s3_url():
|
|||||||
'TemplateBody'].should.equal(dummy_template)
|
'TemplateBody'].should.equal(dummy_template)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation
|
||||||
|
@mock_s3
|
||||||
|
@mock_ec2
|
||||||
|
def test_update_stack_from_s3_url():
|
||||||
|
s3 = boto3.client('s3')
|
||||||
|
s3_conn = boto3.resource('s3')
|
||||||
|
|
||||||
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
|
cf_conn.create_stack(
|
||||||
|
StackName="update_stack_from_url",
|
||||||
|
TemplateBody=dummy_template_json,
|
||||||
|
Tags=[{'Key': 'foo', 'Value': 'bar'}],
|
||||||
|
)
|
||||||
|
|
||||||
|
s3_conn.create_bucket(Bucket="foobar")
|
||||||
|
|
||||||
|
s3_conn.Object(
|
||||||
|
'foobar', 'template-key').put(Body=dummy_update_template_json)
|
||||||
|
key_url = s3.generate_presigned_url(
|
||||||
|
ClientMethod='get_object',
|
||||||
|
Params={
|
||||||
|
'Bucket': 'foobar',
|
||||||
|
'Key': 'template-key'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cf_conn.update_stack(
|
||||||
|
StackName="update_stack_from_url",
|
||||||
|
TemplateURL=key_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
cf_conn.get_template(StackName="update_stack_from_url")[
|
||||||
|
'TemplateBody'].should.equal(dummy_update_template)
|
||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
def test_describe_stack_pagination():
|
def test_describe_stack_pagination():
|
||||||
conn = boto3.client('cloudformation', region_name='us-east-1')
|
conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
@ -382,6 +409,7 @@ def test_delete_stack_from_resource():
|
|||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
def test_delete_stack_by_name():
|
def test_delete_stack_by_name():
|
||||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
cf_conn.create_stack(
|
cf_conn.create_stack(
|
||||||
@ -412,6 +440,7 @@ def test_describe_deleted_stack():
|
|||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
def test_describe_updated_stack():
|
def test_describe_updated_stack():
|
||||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||||
cf_conn.create_stack(
|
cf_conn.create_stack(
|
||||||
@ -502,6 +531,7 @@ def test_stack_tags():
|
|||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
def test_stack_events():
|
def test_stack_events():
|
||||||
cf = boto3.resource('cloudformation', region_name='us-east-1')
|
cf = boto3.resource('cloudformation', region_name='us-east-1')
|
||||||
stack = cf.create_stack(
|
stack = cf.create_stack(
|
||||||
@ -617,6 +647,7 @@ def test_export_names_must_be_unique():
|
|||||||
TemplateBody=dummy_output_template_json,
|
TemplateBody=dummy_output_template_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_sqs
|
@mock_sqs
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
def test_stack_with_imports():
|
def test_stack_with_imports():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user