parent
fcc5a165ea
commit
4207a8e182
@ -475,19 +475,19 @@
|
||||
- [ ] continue_update_rollback
|
||||
- [X] create_change_set
|
||||
- [X] create_stack
|
||||
- [ ] create_stack_instances
|
||||
- [ ] create_stack_set
|
||||
- [X] create_stack_instances
|
||||
- [X] create_stack_set
|
||||
- [ ] delete_change_set
|
||||
- [X] delete_stack
|
||||
- [ ] delete_stack_instances
|
||||
- [ ] delete_stack_set
|
||||
- [X] delete_stack_instances
|
||||
- [X] delete_stack_set
|
||||
- [ ] describe_account_limits
|
||||
- [ ] describe_change_set
|
||||
- [ ] describe_stack_events
|
||||
- [ ] describe_stack_instance
|
||||
- [X] describe_stack_instance
|
||||
- [ ] describe_stack_resource
|
||||
- [ ] describe_stack_resources
|
||||
- [ ] describe_stack_set
|
||||
- [X] describe_stack_set
|
||||
- [ ] describe_stack_set_operation
|
||||
- [X] describe_stacks
|
||||
- [ ] estimate_template_cost
|
||||
@ -498,18 +498,18 @@
|
||||
- [ ] list_change_sets
|
||||
- [X] list_exports
|
||||
- [ ] list_imports
|
||||
- [ ] list_stack_instances
|
||||
- [X] list_stack_instances
|
||||
- [X] list_stack_resources
|
||||
- [ ] list_stack_set_operation_results
|
||||
- [ ] list_stack_set_operations
|
||||
- [ ] list_stack_sets
|
||||
- [X] list_stack_set_operations
|
||||
- [X] list_stack_sets
|
||||
- [X] list_stacks
|
||||
- [ ] set_stack_policy
|
||||
- [ ] signal_resource
|
||||
- [ ] stop_stack_set_operation
|
||||
- [X] update_stack
|
||||
- [ ] update_stack_instances
|
||||
- [ ] update_stack_set
|
||||
- [X] update_stack_instances
|
||||
- [X] update_stack_set
|
||||
- [ ] update_termination_protection
|
||||
- [ ] validate_template
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import yaml
|
||||
import uuid
|
||||
@ -12,12 +12,137 @@ from .parsing import ResourceMap, OutputMap
|
||||
from .utils import (
|
||||
generate_changeset_id,
|
||||
generate_stack_id,
|
||||
generate_stackset_arn,
|
||||
generate_stackset_id,
|
||||
yaml_tag_constructor,
|
||||
validate_template_cfn_lint,
|
||||
)
|
||||
from .exceptions import ValidationError
|
||||
|
||||
|
||||
class FakeStackSet(BaseModel):
|
||||
|
||||
def __init__(self, stackset_id, name, template, region='us-east-1', status='ACTIVE', description=None, parameters=None, tags=None, admin_role=None, execution_role=None):
|
||||
self.id = stackset_id
|
||||
self.arn = generate_stackset_arn(stackset_id, region)
|
||||
self.name = name
|
||||
self.template = template
|
||||
self.description = description
|
||||
self.parameters = parameters
|
||||
self.tags = tags
|
||||
self.admin_role = admin_role
|
||||
self.execution_role = execution_role
|
||||
self.status = status
|
||||
self.instances = FakeStackInstances(parameters, self.id, self.name)
|
||||
self.operations = []
|
||||
|
||||
@property
|
||||
def stack_instances(self):
|
||||
return self.instances.stack_instances
|
||||
|
||||
def _create_operation(self, operation_id, action, status):
|
||||
operation = {
|
||||
'OperationId': str(operation_id),
|
||||
'Action': action,
|
||||
'Status': status,
|
||||
'CreationTimestamp': datetime.now(),
|
||||
'EndTimestamp': datetime.now() + timedelta(minutes=2),
|
||||
}
|
||||
|
||||
self.operations += [operation]
|
||||
return operation
|
||||
|
||||
def delete(self):
|
||||
self.status = 'DELETED'
|
||||
|
||||
def update(self, template, description, parameters, tags, admin_role,
|
||||
execution_role, accounts, regions, operation_id=None):
|
||||
if not operation_id:
|
||||
operation_id = uuid.uuid4()
|
||||
|
||||
self.template = template if template else self.template
|
||||
self.description = description if description is not None else self.description
|
||||
self.parameters = parameters if parameters else self.parameters
|
||||
self.tags = tags if tags else self.tags
|
||||
self.admin_role = admin_role if admin_role else self.admin_role
|
||||
self.execution_role = execution_role if execution_role else self.execution_role
|
||||
|
||||
if accounts and regions:
|
||||
self.update_instances(accounts, regions, self.parameters)
|
||||
|
||||
operation = self._create_operation(operation_id=operation_id, action='UPDATE', status='SUCCEEDED')
|
||||
return operation
|
||||
|
||||
def create_stack_instances(self, accounts, regions, parameters, operation_id=None):
|
||||
if not operation_id:
|
||||
operation_id = uuid.uuid4()
|
||||
if not parameters:
|
||||
parameters = self.parameters
|
||||
|
||||
self.instances.create_instances(accounts, regions, parameters, operation_id)
|
||||
self._create_operation(operation_id=operation_id, action='CREATE', status='SUCCEEDED')
|
||||
|
||||
def delete_stack_instances(self, accounts, regions, operation_id=None):
|
||||
if not operation_id:
|
||||
operation_id = uuid.uuid4()
|
||||
|
||||
self.instances.delete(accounts, regions)
|
||||
|
||||
self._create_operation(operation_id=operation_id, action='DELETE', status='SUCCEEDED')
|
||||
|
||||
def update_instances(self, accounts, regions, parameters, operation_id=None):
|
||||
if not operation_id:
|
||||
operation_id = uuid.uuid4()
|
||||
|
||||
self.instances.update(accounts, regions, parameters)
|
||||
operation = self._create_operation(operation_id=operation_id, action='UPDATE', status='SUCCEEDED')
|
||||
return operation
|
||||
|
||||
|
||||
class FakeStackInstances(BaseModel):
|
||||
def __init__(self, parameters, stackset_id, stackset_name):
|
||||
self.parameters = parameters if parameters else {}
|
||||
self.stackset_id = stackset_id
|
||||
self.stack_name = "StackSet-{}".format(stackset_id)
|
||||
self.stackset_name = stackset_name
|
||||
self.stack_instances = []
|
||||
|
||||
def create_instances(self, accounts, regions, parameters, operation_id):
|
||||
new_instances = []
|
||||
for region in regions:
|
||||
for account in accounts:
|
||||
instance = {
|
||||
'StackId': generate_stack_id(self.stack_name, region, account),
|
||||
'StackSetId': self.stackset_id,
|
||||
'Region': region,
|
||||
'Account': account,
|
||||
'Status': "CURRENT",
|
||||
'ParameterOverrides': parameters if parameters else [],
|
||||
}
|
||||
new_instances.append(instance)
|
||||
self.stack_instances += new_instances
|
||||
return new_instances
|
||||
|
||||
def update(self, accounts, regions, parameters):
|
||||
for account in accounts:
|
||||
for region in regions:
|
||||
instance = self.get_instance(account, region)
|
||||
if parameters:
|
||||
instance['ParameterOverrides'] = parameters
|
||||
else:
|
||||
instance['ParameterOverrides'] = []
|
||||
|
||||
def delete(self, accounts, regions):
|
||||
for i, instance in enumerate(self.stack_instances):
|
||||
if instance['Region'] in regions and instance['Account'] in accounts:
|
||||
self.stack_instances.pop(i)
|
||||
|
||||
def get_instance(self, account, region):
|
||||
for i, instance in enumerate(self.stack_instances):
|
||||
if instance['Region'] == region and instance['Account'] == account:
|
||||
return self.stack_instances[i]
|
||||
|
||||
|
||||
class FakeStack(BaseModel):
|
||||
|
||||
def __init__(self, stack_id, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, cross_stack_resources=None, create_change_set=False):
|
||||
@ -146,10 +271,72 @@ class CloudFormationBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
self.stacks = OrderedDict()
|
||||
self.stacksets = OrderedDict()
|
||||
self.deleted_stacks = {}
|
||||
self.exports = OrderedDict()
|
||||
self.change_sets = OrderedDict()
|
||||
|
||||
def create_stack_set(self, name, template, parameters, tags=None, description=None, region='us-east-1', admin_role=None, execution_role=None):
|
||||
stackset_id = generate_stackset_id(name)
|
||||
new_stackset = FakeStackSet(
|
||||
stackset_id=stackset_id,
|
||||
name=name,
|
||||
template=template,
|
||||
parameters=parameters,
|
||||
description=description,
|
||||
tags=tags,
|
||||
admin_role=admin_role,
|
||||
execution_role=execution_role,
|
||||
)
|
||||
self.stacksets[stackset_id] = new_stackset
|
||||
return new_stackset
|
||||
|
||||
def get_stack_set(self, name):
|
||||
stacksets = self.stacksets.keys()
|
||||
for stackset in stacksets:
|
||||
if self.stacksets[stackset].name == name:
|
||||
return self.stacksets[stackset]
|
||||
raise ValidationError(name)
|
||||
|
||||
def delete_stack_set(self, name):
|
||||
stacksets = self.stacksets.keys()
|
||||
for stackset in stacksets:
|
||||
if self.stacksets[stackset].name == name:
|
||||
self.stacksets[stackset].delete()
|
||||
|
||||
def create_stack_instances(self, stackset_name, accounts, regions, parameters, operation_id=None):
|
||||
stackset = self.get_stack_set(stackset_name)
|
||||
|
||||
stackset.create_stack_instances(
|
||||
accounts=accounts,
|
||||
regions=regions,
|
||||
parameters=parameters,
|
||||
operation_id=operation_id,
|
||||
)
|
||||
return stackset
|
||||
|
||||
def update_stack_set(self, stackset_name, template=None, description=None,
|
||||
parameters=None, tags=None, admin_role=None, execution_role=None,
|
||||
accounts=None, regions=None, operation_id=None):
|
||||
stackset = self.get_stack_set(stackset_name)
|
||||
update = stackset.update(
|
||||
template=template,
|
||||
description=description,
|
||||
parameters=parameters,
|
||||
tags=tags,
|
||||
admin_role=admin_role,
|
||||
execution_role=execution_role,
|
||||
accounts=accounts,
|
||||
regions=regions,
|
||||
operation_id=operation_id
|
||||
)
|
||||
return update
|
||||
|
||||
def delete_stack_instances(self, stackset_name, accounts, regions, operation_id=None):
|
||||
stackset = self.get_stack_set(stackset_name)
|
||||
stackset.delete_stack_instances(accounts, regions, operation_id)
|
||||
return stackset
|
||||
|
||||
def create_stack(self, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None, create_change_set=False):
|
||||
stack_id = generate_stack_id(name)
|
||||
new_stack = FakeStack(
|
||||
|
@ -312,6 +312,151 @@ class CloudFormationResponse(BaseResponse):
|
||||
template = self.response_template(VALIDATE_STACK_RESPONSE_TEMPLATE)
|
||||
return template.render(description=description)
|
||||
|
||||
def create_stack_set(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
stack_body = self._get_param('TemplateBody')
|
||||
template_url = self._get_param('TemplateURL')
|
||||
# role_arn = self._get_param('RoleARN')
|
||||
parameters_list = self._get_list_prefix("Parameters.member")
|
||||
tags = dict((item['key'], item['value'])
|
||||
for item in self._get_list_prefix("Tags.member"))
|
||||
|
||||
# Copy-Pasta - Hack dict-comprehension
|
||||
parameters = dict([
|
||||
(parameter['parameter_key'], parameter['parameter_value'])
|
||||
for parameter
|
||||
in parameters_list
|
||||
])
|
||||
if template_url:
|
||||
stack_body = self._get_stack_from_s3_url(template_url)
|
||||
|
||||
stackset = self.cloudformation_backend.create_stack_set(
|
||||
name=stackset_name,
|
||||
template=stack_body,
|
||||
parameters=parameters,
|
||||
tags=tags,
|
||||
# role_arn=role_arn,
|
||||
)
|
||||
if self.request_json:
|
||||
return json.dumps({
|
||||
'CreateStackSetResponse': {
|
||||
'CreateStackSetResult': {
|
||||
'StackSetId': stackset.stackset_id,
|
||||
}
|
||||
}
|
||||
})
|
||||
else:
|
||||
template = self.response_template(CREATE_STACK_SET_RESPONSE_TEMPLATE)
|
||||
return template.render(stackset=stackset)
|
||||
|
||||
def create_stack_instances(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
accounts = self._get_multi_param('Accounts.member')
|
||||
regions = self._get_multi_param('Regions.member')
|
||||
parameters = self._get_multi_param('ParameterOverrides.member')
|
||||
self.cloudformation_backend.create_stack_instances(stackset_name, accounts, regions, parameters)
|
||||
template = self.response_template(CREATE_STACK_INSTANCES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def delete_stack_set(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
self.cloudformation_backend.delete_stack_set(stackset_name)
|
||||
template = self.response_template(DELETE_STACK_SET_RESPONSE_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def delete_stack_instances(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
accounts = self._get_multi_param('Accounts.member')
|
||||
regions = self._get_multi_param('Regions.member')
|
||||
self.cloudformation_backend.delete_stack_instances(stackset_name, accounts, regions)
|
||||
|
||||
template = self.response_template(DELETE_STACK_INSTANCES_TEMPLATE)
|
||||
return template.render()
|
||||
|
||||
def describe_stack_set(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||
|
||||
if not stackset.admin_role:
|
||||
stackset.admin_role = 'arn:aws:iam::123456789012:role/AWSCloudFormationStackSetAdministrationRole'
|
||||
if not stackset.execution_role:
|
||||
stackset.execution_role = 'AWSCloudFormationStackSetExecutionRole'
|
||||
|
||||
template = self.response_template(DESCRIBE_STACK_SET_RESPONSE_TEMPLATE)
|
||||
return template.render(stackset=stackset)
|
||||
|
||||
def describe_stack_instance(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
account = self._get_param('StackInstanceAccount')
|
||||
region = self._get_param('StackInstanceRegion')
|
||||
|
||||
instance = self.cloudformation_backend.get_stack_set(stackset_name).instances.get_instance(account, region)
|
||||
template = self.response_template(DESCRIBE_STACK_INSTANCE_TEMPLATE)
|
||||
rendered = template.render(instance=instance)
|
||||
return rendered
|
||||
|
||||
def list_stack_sets(self):
|
||||
stacksets = self.cloudformation_backend.stacksets
|
||||
template = self.response_template(LIST_STACK_SETS_TEMPLATE)
|
||||
return template.render(stacksets=stacksets)
|
||||
|
||||
def list_stack_instances(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||
template = self.response_template(LIST_STACK_INSTANCES_TEMPLATE)
|
||||
return template.render(stackset=stackset)
|
||||
|
||||
def list_stack_set_operations(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
|
||||
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
|
||||
return template.render(stackset=stackset)
|
||||
|
||||
def update_stack_set(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
operation_id = self._get_param('OperationId')
|
||||
description = self._get_param('Description')
|
||||
execution_role = self._get_param('ExecutionRoleName')
|
||||
admin_role = self._get_param('AdministrationRoleARN')
|
||||
accounts = self._get_multi_param('Accounts.member')
|
||||
regions = self._get_multi_param('Regions.member')
|
||||
template_body = self._get_param('TemplateBody')
|
||||
template_url = self._get_param('TemplateURL')
|
||||
if template_url:
|
||||
template_body = self._get_stack_from_s3_url(template_url)
|
||||
tags = dict((item['key'], item['value'])
|
||||
for item in self._get_list_prefix("Tags.member"))
|
||||
parameters_list = self._get_list_prefix("Parameters.member")
|
||||
parameters = dict([
|
||||
(parameter['parameter_key'], parameter['parameter_value'])
|
||||
for parameter
|
||||
in parameters_list
|
||||
])
|
||||
operation = self.cloudformation_backend.update_stack_set(
|
||||
stackset_name=stackset_name,
|
||||
template=template_body,
|
||||
description=description,
|
||||
parameters=parameters,
|
||||
tags=tags,
|
||||
admin_role=admin_role,
|
||||
execution_role=execution_role,
|
||||
accounts=accounts,
|
||||
regions=regions,
|
||||
operation_id=operation_id
|
||||
)
|
||||
|
||||
template = self.response_template(UPDATE_STACK_SET_RESPONSE_TEMPLATE)
|
||||
return template.render(operation=operation)
|
||||
|
||||
def update_stack_instances(self):
|
||||
stackset_name = self._get_param('StackSetName')
|
||||
accounts = self._get_multi_param('Accounts.member')
|
||||
regions = self._get_multi_param('Regions.member')
|
||||
parameters = self._get_multi_param('ParameterOverrides.member')
|
||||
operation = self.cloudformation_backend.get_stack_set(stackset_name).update_instances(accounts, regions, parameters)
|
||||
template = self.response_template(UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE)
|
||||
return template.render(operation=operation)
|
||||
|
||||
|
||||
VALIDATE_STACK_RESPONSE_TEMPLATE = """<ValidateTemplateResponse>
|
||||
<ValidateTemplateResult>
|
||||
@ -553,3 +698,183 @@ LIST_EXPORTS_RESPONSE = """<ListExportsResponse xmlns="http://cloudformation.ama
|
||||
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListExportsResponse>"""
|
||||
|
||||
CREATE_STACK_SET_RESPONSE_TEMPLATE = """<CreateStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<CreateStackSetResult>
|
||||
<StackSetId>{{ stackset.stackset_id }}</StackSetId>
|
||||
</CreateStackSetResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>f457258c-391d-41d1-861f-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateStackSetResponse>
|
||||
"""
|
||||
|
||||
DESCRIBE_STACK_SET_RESPONSE_TEMPLATE = """<DescribeStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<DescribeStackSetResult>
|
||||
<StackSet>
|
||||
<Capabilities/>
|
||||
<StackSetARN>{{ stackset.arn }}</StackSetARN>
|
||||
<ExecutionRoleName>{{ stackset.execution_role }}</ExecutionRoleName>
|
||||
<AdministrationRoleARN>{{ stackset.admin_role }}</AdministrationRoleARN>
|
||||
<StackSetId>{{ stackset.id }}</StackSetId>
|
||||
<TemplateBody>{{ stackset.template }}</TemplateBody>
|
||||
<StackSetName>{{ stackset.name }}</StackSetName>
|
||||
<Parameters>
|
||||
{% for param_name, param_value in stackset.parameters.items() %}
|
||||
<member>
|
||||
<ParameterKey>{{ param_name }}</ParameterKey>
|
||||
<ParameterValue>{{ param_value }}</ParameterValue>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Parameters>
|
||||
<Tags>
|
||||
{% for tag_key, tag_value in stackset.tags.items() %}
|
||||
<member>
|
||||
<Key>{{ tag_key }}</Key>
|
||||
<Value>{{ tag_value }}</Value>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Tags>
|
||||
<Status>{{ stackset.status }}</Status>
|
||||
</StackSet>
|
||||
</DescribeStackSetResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>d8b64e11-5332-46e1-9603-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DescribeStackSetResponse>"""
|
||||
|
||||
DELETE_STACK_SET_RESPONSE_TEMPLATE = """<DeleteStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<DeleteStackSetResult/>
|
||||
<ResponseMetadata>
|
||||
<RequestId>c35ec2d0-d69f-4c4d-9bd7-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteStackSetResponse>"""
|
||||
|
||||
CREATE_STACK_INSTANCES_TEMPLATE = """<CreateStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<CreateStackInstancesResult>
|
||||
<OperationId>1459ad6d-63cc-4c96-a73e-example</OperationId>
|
||||
</CreateStackInstancesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>6b29f7e3-69be-4d32-b374-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateStackInstancesResponse>
|
||||
"""
|
||||
|
||||
LIST_STACK_INSTANCES_TEMPLATE = """<ListStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<ListStackInstancesResult>
|
||||
<Summaries>
|
||||
{% for instance in stackset.stack_instances %}
|
||||
<member>
|
||||
<StackId>{{ instance.StackId }}</StackId>
|
||||
<StackSetId>{{ instance.StackSetId }}</StackSetId>
|
||||
<Region>{{ instance.Region }}</Region>
|
||||
<Account>{{ instance.Account }}</Account>
|
||||
<Status>{{ instance.Status }}</Status>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Summaries>
|
||||
</ListStackInstancesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>83c27e73-b498-410f-993c-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListStackInstancesResponse>
|
||||
"""
|
||||
|
||||
DELETE_STACK_INSTANCES_TEMPLATE = """<DeleteStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<DeleteStackInstancesResult>
|
||||
<OperationId>d76a070d-279a-45f3-b9b4-example</OperationId>
|
||||
</DeleteStackInstancesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>e5325090-66f6-4ecd-a531-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DeleteStackInstancesResponse>
|
||||
"""
|
||||
|
||||
DESCRIBE_STACK_INSTANCE_TEMPLATE = """<DescribeStackInstanceResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<DescribeStackInstanceResult>
|
||||
<StackInstance>
|
||||
<StackId>{{ instance.StackId }}</StackId>
|
||||
<StackSetId>{{ instance.StackSetId }}</StackSetId>
|
||||
{% if instance.ParameterOverrides %}
|
||||
<ParameterOverrides>
|
||||
{% for override in instance.ParameterOverrides %}
|
||||
{% if override['ParameterKey'] or override['ParameterValue'] %}
|
||||
<member>
|
||||
<ParameterKey>{{ override.ParameterKey }}</ParameterKey>
|
||||
<UsePreviousValue>false</UsePreviousValue>
|
||||
<ParameterValue>{{ override.ParameterValue }}</ParameterValue>
|
||||
</member>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ParameterOverrides>
|
||||
{% else %}
|
||||
<ParameterOverrides/>
|
||||
{% endif %}
|
||||
<Region>{{ instance.Region }}</Region>
|
||||
<Account>{{ instance.Account }}</Account>
|
||||
<Status>{{ instance.Status }}</Status>
|
||||
</StackInstance>
|
||||
</DescribeStackInstanceResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>c6c7be10-0343-4319-8a25-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</DescribeStackInstanceResponse>
|
||||
"""
|
||||
|
||||
LIST_STACK_SETS_TEMPLATE = """<ListStackSetsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<ListStackSetsResult>
|
||||
<Summaries>
|
||||
{% for key, value in stacksets.items() %}
|
||||
<member>
|
||||
<StackSetName>{{ value.name }}</StackSetName>
|
||||
<StackSetId>{{ value.id }}</StackSetId>
|
||||
<Status>{{ value.status }}</Status>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Summaries>
|
||||
</ListStackSetsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>4dcacb73-841e-4ed8-b335-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListStackSetsResponse>
|
||||
"""
|
||||
|
||||
UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE = """<UpdateStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<UpdateStackInstancesResult>
|
||||
<OperationId>{{ operation }}</OperationId>
|
||||
</UpdateStackInstancesResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>bdbf8e94-19b6-4ce4-af85-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</UpdateStackInstancesResponse>
|
||||
"""
|
||||
|
||||
UPDATE_STACK_SET_RESPONSE_TEMPLATE = """<UpdateStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<UpdateStackSetResult>
|
||||
<OperationId>{{ operation.OperationId }}</OperationId>
|
||||
</UpdateStackSetResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>adac907b-17e3-43e6-a254-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</UpdateStackSetResponse>
|
||||
"""
|
||||
|
||||
LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE = """<ListStackSetOperationsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
|
||||
<ListStackSetOperationsResult>
|
||||
<Summaries>
|
||||
{% for operation in stackset.operations %}
|
||||
<member>
|
||||
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
|
||||
<OperationId>{{ operation.OperationId }}</OperationId>
|
||||
<Action>{{ operation.Action }}</Action>
|
||||
<EndTimestamp>{{ operation.EndTimestamp }}</EndTimestamp>
|
||||
<Status>{{ operation.Status }}</Status>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Summaries>
|
||||
</ListStackSetOperationsResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>65b9d9be-08bb-4a43-9a21-example</RequestId>
|
||||
</ResponseMetadata>
|
||||
</ListStackSetOperationsResponse>
|
||||
"""
|
||||
|
@ -8,9 +8,9 @@ import os
|
||||
from cfnlint import decode, core
|
||||
|
||||
|
||||
def generate_stack_id(stack_name):
|
||||
def generate_stack_id(stack_name, region="us-east-1", account="123456789"):
|
||||
random_id = uuid.uuid4()
|
||||
return "arn:aws:cloudformation:us-east-1:123456789:stack/{0}/{1}".format(stack_name, random_id)
|
||||
return "arn:aws:cloudformation:{}:{}:stack/{}/{}".format(region, account, stack_name, random_id)
|
||||
|
||||
|
||||
def generate_changeset_id(changeset_name, region_name):
|
||||
@ -18,6 +18,15 @@ def generate_changeset_id(changeset_name, region_name):
|
||||
return 'arn:aws:cloudformation:{0}:123456789:changeSet/{1}/{2}'.format(region_name, changeset_name, random_id)
|
||||
|
||||
|
||||
def generate_stackset_id(stackset_name):
|
||||
random_id = uuid.uuid4()
|
||||
return '{}:{}'.format(stackset_name, random_id)
|
||||
|
||||
|
||||
def generate_stackset_arn(stackset_id, region_name):
|
||||
return 'arn:aws:cloudformation:{}:123456789012:stackset/{}'.format(region_name, stackset_id)
|
||||
|
||||
|
||||
def random_suffix():
|
||||
size = 12
|
||||
chars = list(range(10)) + ['A-Z']
|
||||
|
@ -184,6 +184,343 @@ dummy_import_template_json = json.dumps(dummy_import_template)
|
||||
dummy_redrive_template_json = json.dumps(dummy_redrive_template)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_describe_stack_instances():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
)
|
||||
usw2_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-west-2',
|
||||
)
|
||||
use1_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-east-1',
|
||||
)
|
||||
|
||||
usw2_instance['StackInstance'].should.have.key('Region').which.should.equal('us-west-2')
|
||||
usw2_instance['StackInstance'].should.have.key('Account').which.should.equal('123456789012')
|
||||
use1_instance['StackInstance'].should.have.key('Region').which.should.equal('us-east-1')
|
||||
use1_instance['StackInstance'].should.have.key('Account').which.should.equal('123456789012')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_list_stacksets_length():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set2",
|
||||
TemplateBody=dummy_template_yaml,
|
||||
)
|
||||
stacksets = cf_conn.list_stack_sets()
|
||||
stacksets.should.have.length_of(2)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_list_stacksets_contents():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
stacksets = cf_conn.list_stack_sets()
|
||||
stacksets['Summaries'][0].should.have.key('StackSetName').which.should.equal('test_stack_set')
|
||||
stacksets['Summaries'][0].should.have.key('Status').which.should.equal('ACTIVE')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_update_stack_instances():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
param = [
|
||||
{'ParameterKey': 'SomeParam', 'ParameterValue': 'StackSetValue'},
|
||||
{'ParameterKey': 'AnotherParam', 'ParameterValue': 'StackSetValue2'},
|
||||
]
|
||||
param_overrides = [
|
||||
{'ParameterKey': 'SomeParam', 'ParameterValue': 'OverrideValue'},
|
||||
{'ParameterKey': 'AnotherParam', 'ParameterValue': 'OverrideValue2'}
|
||||
]
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=param,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-1', 'us-west-2'],
|
||||
)
|
||||
cf_conn.update_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-west-1', 'us-west-2'],
|
||||
ParameterOverrides=param_overrides,
|
||||
)
|
||||
usw2_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-west-2',
|
||||
)
|
||||
usw1_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-west-1',
|
||||
)
|
||||
use1_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-east-1',
|
||||
)
|
||||
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][0]['ParameterKey'].should.equal(param_overrides[0]['ParameterKey'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][0]['ParameterValue'].should.equal(param_overrides[0]['ParameterValue'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][1]['ParameterKey'].should.equal(param_overrides[1]['ParameterKey'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][1]['ParameterValue'].should.equal(param_overrides[1]['ParameterValue'])
|
||||
|
||||
usw1_instance['StackInstance']['ParameterOverrides'][0]['ParameterKey'].should.equal(param_overrides[0]['ParameterKey'])
|
||||
usw1_instance['StackInstance']['ParameterOverrides'][0]['ParameterValue'].should.equal(param_overrides[0]['ParameterValue'])
|
||||
usw1_instance['StackInstance']['ParameterOverrides'][1]['ParameterKey'].should.equal(param_overrides[1]['ParameterKey'])
|
||||
usw1_instance['StackInstance']['ParameterOverrides'][1]['ParameterValue'].should.equal(param_overrides[1]['ParameterValue'])
|
||||
|
||||
use1_instance['StackInstance']['ParameterOverrides'].should.be.empty
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_delete_stack_instances():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
)
|
||||
|
||||
cf_conn.delete_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1'],
|
||||
RetainStacks=False,
|
||||
)
|
||||
|
||||
cf_conn.list_stack_instances(StackSetName="test_stack_set")['Summaries'].should.have.length_of(1)
|
||||
cf_conn.list_stack_instances(StackSetName="test_stack_set")['Summaries'][0]['Region'].should.equal(
|
||||
'us-west-2')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack_instances():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
)
|
||||
|
||||
cf_conn.list_stack_instances(StackSetName="test_stack_set")['Summaries'].should.have.length_of(2)
|
||||
cf_conn.list_stack_instances(StackSetName="test_stack_set")['Summaries'][0]['Account'].should.equal(
|
||||
'123456789012')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack_instances_with_param_overrides():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
param = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'StackSetValue'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'StackSetValue2'},
|
||||
]
|
||||
param_overrides = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'OverrideValue'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'OverrideValue2'}
|
||||
]
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=param,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
ParameterOverrides=param_overrides,
|
||||
)
|
||||
usw2_instance = cf_conn.describe_stack_instance(
|
||||
StackSetName="test_stack_set",
|
||||
StackInstanceAccount='123456789012',
|
||||
StackInstanceRegion='us-west-2',
|
||||
)
|
||||
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][0]['ParameterKey'].should.equal(param_overrides[0]['ParameterKey'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][1]['ParameterKey'].should.equal(param_overrides[1]['ParameterKey'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][0]['ParameterValue'].should.equal(param_overrides[0]['ParameterValue'])
|
||||
usw2_instance['StackInstance']['ParameterOverrides'][1]['ParameterValue'].should.equal(param_overrides[1]['ParameterValue'])
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_update_stack_set():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
param = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'StackSetValue'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'StackSetValue2'},
|
||||
]
|
||||
param_overrides = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'OverrideValue'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'OverrideValue2'}
|
||||
]
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=param,
|
||||
)
|
||||
cf_conn.update_stack_set(
|
||||
StackSetName='test_stack_set',
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=param_overrides,
|
||||
)
|
||||
stackset = cf_conn.describe_stack_set(StackSetName='test_stack_set')
|
||||
|
||||
stackset['StackSet']['Parameters'][0]['ParameterValue'].should.equal(param_overrides[0]['ParameterValue'])
|
||||
stackset['StackSet']['Parameters'][1]['ParameterValue'].should.equal(param_overrides[1]['ParameterValue'])
|
||||
stackset['StackSet']['Parameters'][0]['ParameterKey'].should.equal(param_overrides[0]['ParameterKey'])
|
||||
stackset['StackSet']['Parameters'][1]['ParameterKey'].should.equal(param_overrides[1]['ParameterKey'])
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_list_stack_set_operations():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.create_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
)
|
||||
cf_conn.update_stack_instances(
|
||||
StackSetName="test_stack_set",
|
||||
Accounts=['123456789012'],
|
||||
Regions=['us-east-1', 'us-west-2'],
|
||||
)
|
||||
|
||||
list_operation = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")
|
||||
list_operation['Summaries'].should.have.length_of(2)
|
||||
list_operation['Summaries'][-1]['Action'].should.equal('UPDATE')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_delete_stack_set():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
cf_conn.delete_stack_set(StackSetName='test_stack_set')
|
||||
|
||||
cf_conn.describe_stack_set(StackSetName="test_stack_set")['StackSet']['Status'].should.equal(
|
||||
'DELETED')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack_set():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_json,
|
||||
)
|
||||
|
||||
cf_conn.describe_stack_set(StackSetName="test_stack_set")['StackSet']['TemplateBody'].should.equal(
|
||||
dummy_template_json)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack_set_with_yaml():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack_set",
|
||||
TemplateBody=dummy_template_yaml,
|
||||
)
|
||||
|
||||
cf_conn.describe_stack_set(StackSetName="test_stack_set")['StackSet']['TemplateBody'].should.equal(
|
||||
dummy_template_yaml)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@mock_s3
|
||||
def test_create_stack_set_from_s3_url():
|
||||
s3 = boto3.client('s3')
|
||||
s3_conn = boto3.resource('s3')
|
||||
bucket = s3_conn.create_bucket(Bucket="foobar")
|
||||
|
||||
key = s3_conn.Object(
|
||||
'foobar', 'template-key').put(Body=dummy_template_json)
|
||||
key_url = s3.generate_presigned_url(
|
||||
ClientMethod='get_object',
|
||||
Params={
|
||||
'Bucket': 'foobar',
|
||||
'Key': 'template-key'
|
||||
}
|
||||
)
|
||||
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName='stack_from_url',
|
||||
TemplateURL=key_url,
|
||||
)
|
||||
cf_conn.describe_stack_set(StackSetName="stack_from_url")['StackSet']['TemplateBody'].should.equal(
|
||||
dummy_template_json)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack_set_with_ref_yaml():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
params = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'desc_ref'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'name_ref'},
|
||||
]
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack",
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=params
|
||||
)
|
||||
|
||||
cf_conn.describe_stack_set(StackSetName="test_stack")['StackSet']['TemplateBody'].should.equal(
|
||||
dummy_template_yaml_with_ref)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_describe_stack_set_params():
|
||||
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
|
||||
params = [
|
||||
{'ParameterKey': 'TagDescription', 'ParameterValue': 'desc_ref'},
|
||||
{'ParameterKey': 'TagName', 'ParameterValue': 'name_ref'},
|
||||
]
|
||||
cf_conn.create_stack_set(
|
||||
StackSetName="test_stack",
|
||||
TemplateBody=dummy_template_yaml_with_ref,
|
||||
Parameters=params
|
||||
)
|
||||
|
||||
cf_conn.describe_stack_set(StackSetName="test_stack")['StackSet']['Parameters'].should.equal(
|
||||
params)
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
def test_boto3_create_stack():
|
||||
|
Loading…
Reference in New Issue
Block a user