Operations (#4)

Added stop, list operation results, and describe operation
This commit is contained in:
John Corrales 2019-01-14 22:01:53 -08:00 committed by GitHub
parent 4207a8e182
commit 5fb43ee7b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 189 additions and 14 deletions

View File

@ -470,7 +470,7 @@
- [ ] upgrade_applied_schema
- [ ] upgrade_published_schema
## cloudformation - 21% implemented
## cloudformation - 56% implemented
- [ ] cancel_update_stack
- [ ] continue_update_rollback
- [X] create_change_set
@ -488,7 +488,7 @@
- [ ] describe_stack_resource
- [ ] describe_stack_resources
- [X] describe_stack_set
- [ ] describe_stack_set_operation
- [X] describe_stack_set_operation
- [X] describe_stacks
- [ ] estimate_template_cost
- [X] execute_change_set
@ -500,13 +500,13 @@
- [ ] list_imports
- [X] list_stack_instances
- [X] list_stack_resources
- [ ] list_stack_set_operation_results
- [X] list_stack_set_operation_results
- [X] list_stack_set_operations
- [X] list_stack_sets
- [X] list_stacks
- [ ] set_stack_policy
- [ ] signal_resource
- [ ] stop_stack_set_operation
- [X] stop_stack_set_operation
- [X] update_stack
- [X] update_stack_instances
- [X] update_stack_set

View File

@ -22,7 +22,10 @@ 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):
def __init__(self, stackset_id, name, template, region='us-east-1',
status='ACTIVE', description=None, parameters=None, tags=None,
admin_role='AWSCloudFormationStackSetAdministrationRole',
execution_role='AWSCloudFormationStackSetExecutionRole'):
self.id = stackset_id
self.arn = generate_stackset_arn(stackset_id, region)
self.name = name
@ -34,24 +37,33 @@ class FakeStackSet(BaseModel):
self.execution_role = execution_role
self.status = status
self.instances = FakeStackInstances(parameters, self.id, self.name)
self.stack_instances = self.instances.stack_instances
self.operations = []
@property
def stack_instances(self):
return self.instances.stack_instances
def _create_operation(self, operation_id, action, status):
def _create_operation(self, operation_id, action, status, accounts=[], regions=[]):
operation = {
'OperationId': str(operation_id),
'Action': action,
'Status': status,
'CreationTimestamp': datetime.now(),
'EndTimestamp': datetime.now() + timedelta(minutes=2),
'Instances': [{account: region} for account in accounts for region in regions],
}
self.operations += [operation]
return operation
def get_operation(self, operation_id):
for operation in self.operations:
if operation_id == operation['OperationId']:
return operation
raise ValidationError(operation_id)
def update_operation(self, operation_id, status):
operation = self.get_operation(operation_id)
operation['Status'] = status
return operation_id
def delete(self):
self.status = 'DELETED'
@ -70,7 +82,9 @@ class FakeStackSet(BaseModel):
if accounts and regions:
self.update_instances(accounts, regions, self.parameters)
operation = self._create_operation(operation_id=operation_id, action='UPDATE', status='SUCCEEDED')
operation = self._create_operation(operation_id=operation_id,
action='UPDATE', status='SUCCEEDED', accounts=accounts,
regions=regions)
return operation
def create_stack_instances(self, accounts, regions, parameters, operation_id=None):
@ -80,7 +94,8 @@ class FakeStackSet(BaseModel):
parameters = self.parameters
self.instances.create_instances(accounts, regions, parameters, operation_id)
self._create_operation(operation_id=operation_id, action='CREATE', status='SUCCEEDED')
self._create_operation(operation_id=operation_id, action='CREATE',
status='SUCCEEDED', accounts=accounts, regions=regions)
def delete_stack_instances(self, accounts, regions, operation_id=None):
if not operation_id:
@ -88,14 +103,17 @@ class FakeStackSet(BaseModel):
self.instances.delete(accounts, regions)
self._create_operation(operation_id=operation_id, action='DELETE', status='SUCCEEDED')
self._create_operation(operation_id=operation_id, action='DELETE',
status='SUCCEEDED', accounts=accounts, regions=regions)
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')
operation = self._create_operation(operation_id=operation_id,
action='UPDATE', status='SUCCEEDED', accounts=accounts,
regions=regions)
return operation

View File

@ -412,6 +412,30 @@ class CloudFormationResponse(BaseResponse):
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
return template.render(stackset=stackset)
def stop_stack_set_operation(self):
stackset_name = self._get_param('StackSetName')
operation_id = self._get_param('OperationId')
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
stackset.update_operation(operation_id, 'STOPPED')
template = self.response_template(STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE)
return template.render()
def describe_stack_set_operation(self):
stackset_name = self._get_param('StackSetName')
operation_id = self._get_param('OperationId')
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
operation = stackset.get_operation(operation_id)
template = self.response_template(DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE)
return template.render(stackset=stackset, operation=operation)
def list_stack_set_operation_results(self):
stackset_name = self._get_param('StackSetName')
operation_id = self._get_param('OperationId')
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
operation = stackset.get_operation(operation_id)
template = self.response_template(LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE)
return template.render(operation=operation)
def update_stack_set(self):
stackset_name = self._get_param('StackSetName')
operation_id = self._get_param('OperationId')
@ -878,3 +902,56 @@ LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE = """<ListStackSetOperationsResponse
</ResponseMetadata>
</ListStackSetOperationsResponse>
"""
STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE = """<StopStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<StopStackSetOperationResult/>
<ResponseMetadata>
<RequestId>2188554a-07c6-4396-b2c5-example</RequestId>
</ResponseMetadata> </StopStackSetOperationResponse>
"""
DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """<DescribeStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DescribeStackSetOperationResult>
<StackSetOperation>
<ExecutionRoleName>{{ stackset.execution_role }}</ExecutionRoleName>
<AdministrationRoleARN>arn:aws:iam::123456789012:role/{{ stackset.admin_role }}</AdministrationRoleARN>
<StackSetId>{{ stackset.id }}</StackSetId>
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
<OperationId>{{ operation.OperationId }}</OperationId>
<Action>{{ operation.Action }}</Action>
<OperationPreferences>
<RegionOrder/>
</OperationPreferences>
<EndTimestamp>{{ operation.EndTimestamp }}</EndTimestamp>
<Status>{{ operation.Status }}</Status>
</StackSetOperation>
</DescribeStackSetOperationResult>
<ResponseMetadata>
<RequestId>2edc27b6-9ce2-486a-a192-example</RequestId>
</ResponseMetadata>
</DescribeStackSetOperationResponse>
"""
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """<ListStackSetOperationResultsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetOperationResultsResult>
<Summaries>
{% for instance in operation.Instances %}
{% for account, region in instance.items() %}
<member>
<AccountGateResult>
<StatusReason>Function not found: arn:aws:lambda:us-west-2:123456789012:function:AWSCloudFormationStackSetAccountGate</StatusReason>
<Status>SKIPPED</Status>
</AccountGateResult>
<Region>{{ region }}</Region>
<Account>{{ account }}</Account>
<Status>{{ operation.Status }}</Status>
</member>
{% endfor %}
{% endfor %}
</Summaries>
</ListStackSetOperationResultsResult>
<ResponseMetadata>
<RequestId>ac05a9ce-5f98-4197-a29b-example</RequestId>
</ResponseMetadata>
</ListStackSetOperationResultsResponse>
"""

View File

@ -240,6 +240,86 @@ def test_boto3_list_stacksets_contents():
stacksets['Summaries'][0].should.have.key('Status').which.should.equal('ACTIVE')
@mock_cloudformation
def test_boto3_stop_stack_set_operation():
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-1', 'us-west-2'],
)
operation_id = cf_conn.list_stack_set_operations(
StackSetName="test_stack_set")['Summaries'][-1]['OperationId']
cf_conn.stop_stack_set_operation(
StackSetName="test_stack_set",
OperationId=operation_id
)
list_operation = cf_conn.list_stack_set_operations(
StackSetName="test_stack_set"
)
list_operation['Summaries'][-1]['Status'].should.equal('STOPPED')
@mock_cloudformation
def test_boto3_describe_stack_set_operation():
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-1', 'us-west-2'],
)
operation_id = cf_conn.list_stack_set_operations(
StackSetName="test_stack_set")['Summaries'][-1]['OperationId']
cf_conn.stop_stack_set_operation(
StackSetName="test_stack_set",
OperationId=operation_id
)
response = cf_conn.describe_stack_set_operation(
StackSetName="test_stack_set",
OperationId=operation_id,
)
response['StackSetOperation']['Status'].should.equal('STOPPED')
response['StackSetOperation']['Action'].should.equal('CREATE')
@mock_cloudformation
def test_boto3_list_stack_set_operation_results():
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-1', 'us-west-2'],
)
operation_id = cf_conn.list_stack_set_operations(
StackSetName="test_stack_set")['Summaries'][-1]['OperationId']
cf_conn.stop_stack_set_operation(
StackSetName="test_stack_set",
OperationId=operation_id
)
response = cf_conn.list_stack_set_operation_results(
StackSetName="test_stack_set",
OperationId=operation_id,
)
response['Summaries'].should.have.length_of(3)
response['Summaries'][0].should.have.key('Account').which.should.equal('123456789012')
response['Summaries'][1].should.have.key('Status').which.should.equal('STOPPED')
@mock_cloudformation
def test_boto3_update_stack_instances():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')