diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index 4a033b6d2..2b3dfee47 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -206,6 +206,17 @@ class CloudFormationBackend(BaseBackend): if stack.name == name_or_stack_id: self.delete_stack(stack.stack_id) + def list_exports(self, token): + all_exports = [x for x in self.exports.values()] + if token is None: + exports = all_exports[0:100] + next_token = '100' if len(all_exports) > 100 else None + else: + token = int(token) + exports = all_exports[token:token + 100] + next_token = str(token + 100) if len(all_exports) > token + 100 else None + return exports, next_token + cloudformation_backends = {} for region in boto.cloudformation.regions(): diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 60f647efa..d66a172a8 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -210,6 +210,12 @@ class CloudFormationResponse(BaseResponse): template = self.response_template(DELETE_STACK_RESPONSE_TEMPLATE) return template.render() + def list_exports(self): + token = self._get_param('NextToken') + exports, next_token = self.cloudformation_backend.list_exports(token=token) + template = self.response_template(LIST_EXPORTS_RESPONSE) + return template.render(exports=exports, next_token=next_token) + CREATE_STACK_RESPONSE_TEMPLATE = """ @@ -410,3 +416,23 @@ DELETE_STACK_RESPONSE_TEMPLATE = """ """ + +LIST_EXPORTS_RESPONSE = """ + + + {% for export in exports %} + + {{ export.exporting_stack_id }} + {{ export.name }} + {{ export.value }} + + {% endfor %} + + {% if next_token %} + {{ next_token }} + {% endif %} + + + 5ccc7dcd-744c-11e5-be70-example + +""" diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 85815e9f8..8b4d72ad3 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -57,8 +57,31 @@ dummy_update_template = { } } +dummy_output_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Stack 1", + "Resources": { + "Instance": { + "Type": "AWS::EC2::Instance", + "Properties": { + "ImageId": "ami-08111162" + } + } + }, + "Outputs" : { + "StackVPC" : { + "Description" : "The ID of the VPC", + "Value" : "VPCID", + "Export" : { + "Name" : "My VPC ID" + } + } + } +} + dummy_template_json = json.dumps(dummy_template) dummy_update_template_json = json.dumps(dummy_template) +dummy_output_template_json = json.dumps(dummy_output_template) @mock_cloudformation @@ -408,3 +431,56 @@ def test_stack_events(): assert False, "Too many stack events" list(stack_events_to_look_for).should.be.empty + + +@mock_cloudformation +def test_list_exports(): + cf_client = boto3.client('cloudformation', region_name='us-east-1') + cf_resource = boto3.resource('cloudformation', region_name='us-east-1') + stack = cf_resource.create_stack( + StackName="test_stack", + TemplateBody=dummy_output_template_json, + ) + output_value = 'VPCID' + exports = cf_client.list_exports()['Exports'] + + stack.outputs.should.have.length_of(1) + stack.outputs[0]['OutputValue'].should.equal(output_value) + + exports.should.have.length_of(1) + exports[0]['ExportingStackId'].should.equal(stack.stack_id) + exports[0]['Name'].should.equal('My VPC ID') + exports[0]['Value'].should.equal(output_value) + + +@mock_cloudformation +def test_list_exports_with_token(): + cf = boto3.client('cloudformation', region_name='us-east-1') + for i in range(101): + cf.create_stack( + StackName="test_stack", + TemplateBody=dummy_output_template_json, + ) + exports = cf.list_exports() + exports['Exports'].should.have.length_of(100) + exports.get('NextToken').should_not.be.none + + more_exports = cf.list_exports(NextToken=exports['NextToken']) + more_exports['Exports'].should.have.length_of(1) + more_exports.get('NextToken').should.be.none + + +@mock_cloudformation +def test_delete_stack_with_export(): + cf = boto3.client('cloudformation', region_name='us-east-1') + stack = cf.create_stack( + StackName="test_stack", + TemplateBody=dummy_output_template_json, + ) + + stack_id = stack['StackId'] + exports = cf.list_exports()['Exports'] + exports.should.have.length_of(1) + + cf.delete_stack(StackName=stack_id) + cf.list_exports()['Exports'].should.have.length_of(0)