Merge pull request #376 from spulec/cloudformation-update-stack
Cloudformation update stack
This commit is contained in:
commit
a854efbf56
@ -14,19 +14,25 @@ class FakeStack(object):
|
||||
self.stack_id = stack_id
|
||||
self.name = name
|
||||
self.template = template
|
||||
self.template_dict = json.loads(self.template)
|
||||
self.parameters = parameters
|
||||
self.region_name = region_name
|
||||
self.notification_arns = notification_arns if notification_arns else []
|
||||
self.status = 'CREATE_COMPLETE'
|
||||
|
||||
template_dict = json.loads(self.template)
|
||||
self.description = template_dict.get('Description')
|
||||
self.description = self.template_dict.get('Description')
|
||||
self.resource_map = self._create_resource_map()
|
||||
self.output_map = self._create_output_map()
|
||||
|
||||
self.resource_map = ResourceMap(stack_id, name, parameters, region_name, template_dict)
|
||||
self.resource_map.create()
|
||||
def _create_resource_map(self):
|
||||
resource_map = ResourceMap(self.stack_id, self.name, self.parameters, self.region_name, self.template_dict)
|
||||
resource_map.create()
|
||||
return resource_map
|
||||
|
||||
self.output_map = OutputMap(self.resource_map, template_dict)
|
||||
self.output_map.create()
|
||||
def _create_output_map(self):
|
||||
output_map = OutputMap(self.resource_map, self.template_dict)
|
||||
output_map.create()
|
||||
return output_map
|
||||
|
||||
@property
|
||||
def stack_parameters(self):
|
||||
@ -40,6 +46,11 @@ class FakeStack(object):
|
||||
def stack_outputs(self):
|
||||
return self.output_map.values()
|
||||
|
||||
def update(self, template):
|
||||
self.template = template
|
||||
self.resource_map.update(json.loads(template))
|
||||
self.output_map = self._create_output_map()
|
||||
|
||||
|
||||
class CloudFormationBackend(BaseBackend):
|
||||
|
||||
@ -86,10 +97,10 @@ class CloudFormationBackend(BaseBackend):
|
||||
# Lookup by stack name
|
||||
return [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
|
||||
|
||||
# def update_stack(self, name, template):
|
||||
# stack = self.get_stack(name)
|
||||
# stack.template = template
|
||||
# return stack
|
||||
def update_stack(self, name, template):
|
||||
stack = self.get_stack(name)
|
||||
stack.update(template)
|
||||
return stack
|
||||
|
||||
def list_stack_resources(self, stack_name_or_id):
|
||||
stack = self.get_stack(stack_name_or_id)
|
||||
|
@ -156,17 +156,12 @@ def resource_name_property_from_type(resource_type):
|
||||
return NAME_TYPE_MAP.get(resource_type)
|
||||
|
||||
|
||||
def parse_resource(logical_id, resource_json, resources_map, region_name):
|
||||
def parse_resource(logical_id, resource_json, resources_map):
|
||||
resource_type = resource_json['Type']
|
||||
resource_class = resource_class_from_type(resource_type)
|
||||
if not resource_class:
|
||||
return None
|
||||
|
||||
condition = resource_json.get('Condition')
|
||||
if condition and not resources_map[condition]:
|
||||
# If this has a False condition, don't create the resource
|
||||
return None
|
||||
|
||||
resource_json = clean_json(resource_json, resources_map)
|
||||
resource_name_property = resource_name_property_from_type(resource_type)
|
||||
if resource_name_property:
|
||||
@ -182,13 +177,38 @@ def parse_resource(logical_id, resource_json, resources_map, region_name):
|
||||
resource_name = '{0}-{1}-{2}'.format(resources_map.get('AWS::StackName'),
|
||||
logical_id,
|
||||
random_suffix())
|
||||
return resource_class, resource_json, resource_name
|
||||
|
||||
|
||||
def parse_and_create_resource(logical_id, resource_json, resources_map, region_name):
|
||||
condition = resource_json.get('Condition')
|
||||
if condition and not resources_map[condition]:
|
||||
# If this has a False condition, don't create the resource
|
||||
return None
|
||||
|
||||
resource_type = resource_json['Type']
|
||||
resource_tuple = parse_resource(logical_id, resource_json, resources_map)
|
||||
if not resource_tuple:
|
||||
return None
|
||||
resource_class, resource_json, resource_name = resource_tuple
|
||||
resource = resource_class.create_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||
resource.type = resource_type
|
||||
resource.logical_resource_id = logical_id
|
||||
return resource
|
||||
|
||||
|
||||
def parse_and_update_resource(logical_id, resource_json, resources_map, region_name):
|
||||
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||
resource = resource_class.update_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||
return resource
|
||||
|
||||
|
||||
def parse_and_delete_resource(logical_id, resource_json, resources_map, region_name):
|
||||
resource_class, resource_json, resource_name = parse_resource(logical_id, resource_json, resources_map)
|
||||
resource_class.delete_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||
return None
|
||||
|
||||
|
||||
def parse_condition(condition, resources_map, condition_map):
|
||||
if isinstance(condition, bool):
|
||||
return condition
|
||||
@ -258,7 +278,7 @@ class ResourceMap(collections.Mapping):
|
||||
return self._parsed_resources[resource_logical_id]
|
||||
else:
|
||||
resource_json = self._resource_json_map.get(resource_logical_id)
|
||||
new_resource = parse_resource(resource_logical_id, resource_json, self, self._region_name)
|
||||
new_resource = parse_and_create_resource(resource_logical_id, resource_json, self, self._region_name)
|
||||
self._parsed_resources[resource_logical_id] = new_resource
|
||||
return new_resource
|
||||
|
||||
@ -318,6 +338,34 @@ class ResourceMap(collections.Mapping):
|
||||
tags['aws:cloudformation:logical-id'] = resource
|
||||
ec2_models.ec2_backends[self._region_name].create_tags([self[resource].physical_resource_id], tags)
|
||||
|
||||
def update(self, template):
|
||||
self.load_mapping()
|
||||
self.load_parameters()
|
||||
self.load_conditions()
|
||||
|
||||
old_template = self._resource_json_map
|
||||
new_template = template['Resources']
|
||||
self._resource_json_map = new_template
|
||||
|
||||
new_resource_names = set(new_template) - set(old_template)
|
||||
for resource_name in new_resource_names:
|
||||
resource_json = new_template[resource_name]
|
||||
new_resource = parse_and_create_resource(resource_name, resource_json, self, self._region_name)
|
||||
self._parsed_resources[resource_name] = new_resource
|
||||
|
||||
removed_resource_nams = set(old_template) - set(new_template)
|
||||
for resource_name in removed_resource_nams:
|
||||
resource_json = old_template[resource_name]
|
||||
parse_and_delete_resource(resource_name, resource_json, self, self._region_name)
|
||||
self._parsed_resources.pop(resource_name)
|
||||
|
||||
for resource_name in new_template:
|
||||
if resource_name in old_template and new_template[resource_name] != old_template[resource_name]:
|
||||
resource_json = new_template[resource_name]
|
||||
|
||||
changed_resource = parse_and_update_resource(resource_name, resource_json, self, self._region_name)
|
||||
self._parsed_resources[resource_name] = changed_resource
|
||||
|
||||
|
||||
class OutputMap(collections.Mapping):
|
||||
def __init__(self, resources, template):
|
||||
|
@ -88,22 +88,22 @@ class CloudFormationResponse(BaseResponse):
|
||||
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
||||
return stack.template
|
||||
|
||||
# def update_stack(self):
|
||||
# stack_name = self._get_param('StackName')
|
||||
# stack_body = self._get_param('TemplateBody')
|
||||
def update_stack(self):
|
||||
stack_name = self._get_param('StackName')
|
||||
stack_body = self._get_param('TemplateBody')
|
||||
|
||||
# stack = self.cloudformation_backend.update_stack(
|
||||
# name=stack_name,
|
||||
# template=stack_body,
|
||||
# )
|
||||
# stack_body = {
|
||||
# 'UpdateStackResponse': {
|
||||
# 'UpdateStackResult': {
|
||||
# 'StackId': stack.name,
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# return json.dumps(stack_body)
|
||||
stack = self.cloudformation_backend.update_stack(
|
||||
name=stack_name,
|
||||
template=stack_body,
|
||||
)
|
||||
stack_body = {
|
||||
'UpdateStackResponse': {
|
||||
'UpdateStackResult': {
|
||||
'StackId': stack.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.dumps(stack_body)
|
||||
|
||||
def delete_stack(self):
|
||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||
|
@ -130,6 +130,24 @@ class Queue(object):
|
||||
visibility_timeout=properties.get('VisibilityTimeout'),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
queue_name = properties['QueueName']
|
||||
|
||||
sqs_backend = sqs_backends[region_name]
|
||||
queue = sqs_backend.get_queue(queue_name)
|
||||
if 'VisibilityTimeout' in properties:
|
||||
queue.visibility_timeout = int(properties['VisibilityTimeout'])
|
||||
return queue
|
||||
|
||||
@classmethod
|
||||
def delete_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
queue_name = properties['QueueName']
|
||||
sqs_backend = sqs_backends[region_name]
|
||||
sqs_backend.delete_queue(queue_name)
|
||||
|
||||
@property
|
||||
def approximate_number_of_messages_delayed(self):
|
||||
return len([m for m in self._messages if m.delayed])
|
||||
|
@ -240,7 +240,6 @@ LIST_QUEUES_RESPONSE = """<ListQueuesResponse>
|
||||
<ListQueuesResult>
|
||||
{% for queue in queues %}
|
||||
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
||||
<VisibilityTimeout>{{ queue.visibility_timeout }}</VisibilityTimeout>
|
||||
{% endfor %}
|
||||
</ListQueuesResult>
|
||||
<ResponseMetadata>
|
||||
|
@ -99,6 +99,119 @@ def test_stack_list_resources():
|
||||
queue.physical_resource_id.should.equal("my-queue")
|
||||
|
||||
|
||||
@mock_cloudformation()
|
||||
@mock_sqs()
|
||||
def test_update_stack():
|
||||
sqs_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"QueueGroup": {
|
||||
|
||||
"Type": "AWS::SQS::Queue",
|
||||
"Properties": {
|
||||
"QueueName": "my-queue",
|
||||
"VisibilityTimeout": 60,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
|
||||
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=sqs_template_json,
|
||||
)
|
||||
|
||||
sqs_conn = boto.sqs.connect_to_region("us-west-1")
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(1)
|
||||
queues[0].get_attributes('VisibilityTimeout')['VisibilityTimeout'].should.equal('60')
|
||||
|
||||
sqs_template['Resources']['QueueGroup']['Properties']['VisibilityTimeout'] = 100
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
conn.update_stack("test_stack", sqs_template_json)
|
||||
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(1)
|
||||
queues[0].get_attributes('VisibilityTimeout')['VisibilityTimeout'].should.equal('100')
|
||||
|
||||
|
||||
@mock_cloudformation()
|
||||
@mock_sqs()
|
||||
def test_update_stack_and_remove_resource():
|
||||
sqs_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"QueueGroup": {
|
||||
|
||||
"Type": "AWS::SQS::Queue",
|
||||
"Properties": {
|
||||
"QueueName": "my-queue",
|
||||
"VisibilityTimeout": 60,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
|
||||
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=sqs_template_json,
|
||||
)
|
||||
|
||||
sqs_conn = boto.sqs.connect_to_region("us-west-1")
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(1)
|
||||
|
||||
sqs_template['Resources'].pop('QueueGroup')
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
conn.update_stack("test_stack", sqs_template_json)
|
||||
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(0)
|
||||
|
||||
|
||||
@mock_cloudformation()
|
||||
@mock_sqs()
|
||||
def test_update_stack_and_add_resource():
|
||||
sqs_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {},
|
||||
}
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
|
||||
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||
conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=sqs_template_json,
|
||||
)
|
||||
|
||||
sqs_conn = boto.sqs.connect_to_region("us-west-1")
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(0)
|
||||
|
||||
sqs_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"QueueGroup": {
|
||||
|
||||
"Type": "AWS::SQS::Queue",
|
||||
"Properties": {
|
||||
"QueueName": "my-queue",
|
||||
"VisibilityTimeout": 60,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
sqs_template_json = json.dumps(sqs_template)
|
||||
conn.update_stack("test_stack", sqs_template_json)
|
||||
|
||||
queues = sqs_conn.get_all_queues()
|
||||
queues.should.have.length_of(1)
|
||||
|
||||
|
||||
@mock_ec2()
|
||||
@mock_cloudformation()
|
||||
def test_stack_ec2_integration():
|
||||
|
Loading…
Reference in New Issue
Block a user