Add UpdateStack functionality for Cloudformation.
This commit is contained in:
parent
7f73d7e26d
commit
53fff2eb84
@ -14,19 +14,25 @@ class FakeStack(object):
|
|||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.template = template
|
self.template = template
|
||||||
|
self.template_dict = json.loads(self.template)
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.region_name = region_name
|
self.region_name = region_name
|
||||||
self.notification_arns = notification_arns if notification_arns else []
|
self.notification_arns = notification_arns if notification_arns else []
|
||||||
self.status = 'CREATE_COMPLETE'
|
self.status = 'CREATE_COMPLETE'
|
||||||
|
|
||||||
template_dict = json.loads(self.template)
|
self.description = self.template_dict.get('Description')
|
||||||
self.description = 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)
|
def _create_resource_map(self):
|
||||||
self.resource_map.create()
|
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)
|
def _create_output_map(self):
|
||||||
self.output_map.create()
|
output_map = OutputMap(self.resource_map, self.template_dict)
|
||||||
|
output_map.create()
|
||||||
|
return output_map
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stack_parameters(self):
|
def stack_parameters(self):
|
||||||
@ -40,6 +46,11 @@ class FakeStack(object):
|
|||||||
def stack_outputs(self):
|
def stack_outputs(self):
|
||||||
return self.output_map.values()
|
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):
|
class CloudFormationBackend(BaseBackend):
|
||||||
|
|
||||||
@ -86,10 +97,10 @@ class CloudFormationBackend(BaseBackend):
|
|||||||
# Lookup by stack name
|
# Lookup by stack name
|
||||||
return [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
|
return [stack for stack in self.stacks.values() if stack.name == name_or_stack_id][0]
|
||||||
|
|
||||||
# def update_stack(self, name, template):
|
def update_stack(self, name, template):
|
||||||
# stack = self.get_stack(name)
|
stack = self.get_stack(name)
|
||||||
# stack.template = template
|
stack.update(template)
|
||||||
# return stack
|
return stack
|
||||||
|
|
||||||
def list_stack_resources(self, stack_name_or_id):
|
def list_stack_resources(self, stack_name_or_id):
|
||||||
stack = self.get_stack(stack_name_or_id)
|
stack = self.get_stack(stack_name_or_id)
|
||||||
|
@ -156,7 +156,7 @@ def resource_name_property_from_type(resource_type):
|
|||||||
return NAME_TYPE_MAP.get(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, region_name, action='create'):
|
||||||
resource_type = resource_json['Type']
|
resource_type = resource_json['Type']
|
||||||
resource_class = resource_class_from_type(resource_type)
|
resource_class = resource_class_from_type(resource_type)
|
||||||
if not resource_class:
|
if not resource_class:
|
||||||
@ -183,7 +183,14 @@ def parse_resource(logical_id, resource_json, resources_map, region_name):
|
|||||||
logical_id,
|
logical_id,
|
||||||
random_suffix())
|
random_suffix())
|
||||||
|
|
||||||
|
if action == 'create':
|
||||||
resource = resource_class.create_from_cloudformation_json(resource_name, resource_json, region_name)
|
resource = resource_class.create_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||||
|
elif action == 'update':
|
||||||
|
resource = resource_class.update_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||||
|
elif action == 'delete':
|
||||||
|
resource_class.delete_from_cloudformation_json(resource_name, resource_json, region_name)
|
||||||
|
return None
|
||||||
|
|
||||||
resource.type = resource_type
|
resource.type = resource_type
|
||||||
resource.logical_resource_id = logical_id
|
resource.logical_resource_id = logical_id
|
||||||
return resource
|
return resource
|
||||||
@ -318,6 +325,39 @@ class ResourceMap(collections.Mapping):
|
|||||||
tags['aws:cloudformation:logical-id'] = resource
|
tags['aws:cloudformation:logical-id'] = resource
|
||||||
ec2_models.ec2_backends[self._region_name].create_tags([self[resource].physical_resource_id], tags)
|
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_resource(resource_name, resource_json, self, self._region_name, action='create')
|
||||||
|
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_resource(resource_name, resource_json, self, self._region_name, action='delete')
|
||||||
|
self._parsed_resources.pop(resource_name)
|
||||||
|
|
||||||
|
changed_resource_names = []
|
||||||
|
for resource_name in new_template:
|
||||||
|
if resource_name in old_template:
|
||||||
|
if new_template[resource_name] != old_template[resource_name]:
|
||||||
|
changed_resource_names.append(resource_name)
|
||||||
|
|
||||||
|
for resource_name in changed_resource_names:
|
||||||
|
resource_json = new_template[resource_name]
|
||||||
|
|
||||||
|
changed_resource = parse_resource(resource_name, resource_json, self, self._region_name, action='update')
|
||||||
|
self._parsed_resources[resource_name] = changed_resource
|
||||||
|
|
||||||
|
|
||||||
class OutputMap(collections.Mapping):
|
class OutputMap(collections.Mapping):
|
||||||
def __init__(self, resources, template):
|
def __init__(self, resources, template):
|
||||||
|
@ -88,22 +88,22 @@ class CloudFormationResponse(BaseResponse):
|
|||||||
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
||||||
return stack.template
|
return stack.template
|
||||||
|
|
||||||
# def update_stack(self):
|
def update_stack(self):
|
||||||
# stack_name = self._get_param('StackName')
|
stack_name = self._get_param('StackName')
|
||||||
# stack_body = self._get_param('TemplateBody')
|
stack_body = self._get_param('TemplateBody')
|
||||||
|
|
||||||
# stack = self.cloudformation_backend.update_stack(
|
stack = self.cloudformation_backend.update_stack(
|
||||||
# name=stack_name,
|
name=stack_name,
|
||||||
# template=stack_body,
|
template=stack_body,
|
||||||
# )
|
)
|
||||||
# stack_body = {
|
stack_body = {
|
||||||
# 'UpdateStackResponse': {
|
'UpdateStackResponse': {
|
||||||
# 'UpdateStackResult': {
|
'UpdateStackResult': {
|
||||||
# 'StackId': stack.name,
|
'StackId': stack.name,
|
||||||
# }
|
}
|
||||||
# }
|
}
|
||||||
# }
|
}
|
||||||
# return json.dumps(stack_body)
|
return json.dumps(stack_body)
|
||||||
|
|
||||||
def delete_stack(self):
|
def delete_stack(self):
|
||||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||||
|
@ -130,6 +130,24 @@ class Queue(object):
|
|||||||
visibility_timeout=properties.get('VisibilityTimeout'),
|
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
|
@property
|
||||||
def approximate_number_of_messages_delayed(self):
|
def approximate_number_of_messages_delayed(self):
|
||||||
return len([m for m in self._messages if m.delayed])
|
return len([m for m in self._messages if m.delayed])
|
||||||
|
@ -240,7 +240,6 @@ LIST_QUEUES_RESPONSE = """<ListQueuesResponse>
|
|||||||
<ListQueuesResult>
|
<ListQueuesResult>
|
||||||
{% for queue in queues %}
|
{% for queue in queues %}
|
||||||
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
||||||
<VisibilityTimeout>{{ queue.visibility_timeout }}</VisibilityTimeout>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ListQueuesResult>
|
</ListQueuesResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
|
@ -99,6 +99,119 @@ def test_stack_list_resources():
|
|||||||
queue.physical_resource_id.should.equal("my-queue")
|
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_ec2()
|
||||||
@mock_cloudformation()
|
@mock_cloudformation()
|
||||||
def test_stack_ec2_integration():
|
def test_stack_ec2_integration():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user