diff --git a/moto/cloudformation/exceptions.py b/moto/cloudformation/exceptions.py index 10669ca56..83faec78e 100644 --- a/moto/cloudformation/exceptions.py +++ b/moto/cloudformation/exceptions.py @@ -11,7 +11,7 @@ class UnformattedGetAttTemplateException(Exception): class ValidationError(BadRequest): - def __init__(self, name_or_id, message=None): + def __init__(self, name_or_id=None, message=None): if message is None: message = "Stack with id {0} does not exist".format(name_or_id) diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index 76ef3de97..2ca7ee339 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -745,24 +745,25 @@ class CloudFormationBackend(BaseBackend): if change_set is None: raise ValidationError(stack_name) + stack = self.stacks[change_set.stack_id] + # TODO: handle execution errors and implement rollback if change_set.change_set_type == "CREATE": - change_set.stack._add_stack_event( + stack._add_stack_event( "CREATE_IN_PROGRESS", resource_status_reason="User Initiated" ) change_set.apply() - change_set.stack._add_stack_event("CREATE_COMPLETE") + stack._add_stack_event("CREATE_COMPLETE") else: - change_set.stack._add_stack_event("UPDATE_IN_PROGRESS") + stack._add_stack_event("UPDATE_IN_PROGRESS") change_set.apply() - change_set.stack._add_stack_event("UPDATE_COMPLETE") + stack._add_stack_event("UPDATE_COMPLETE") # set the execution status of the changeset change_set.execution_status = "EXECUTE_COMPLETE" # set the status of the stack - self.stacks[ - change_set.stack_id - ].status = f"{change_set.change_set_type}_COMPLETE" + stack.status = f"{change_set.change_set_type}_COMPLETE" + stack.template = change_set.template return True def describe_stacks(self, name_or_stack_id): diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 9f711eedd..f9b7d16bf 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -324,7 +324,12 @@ class CloudFormationResponse(BaseResponse): stack_body = self._get_param("TemplateBody") if stack_name: - stack_body = self.cloudformation_backend.get_stack(stack_name).template + stack = self.cloudformation_backend.get_stack(stack_name) + if stack.status == "REVIEW_IN_PROGRESS": + raise ValidationError( + message="GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks.", + ) + stack_body = stack.template elif template_url: stack_body = self._get_stack_from_s3_url(template_url) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 248f7a3f9..6bcd17b6e 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -862,42 +862,61 @@ def test_get_template_summary(): s3 = boto3.client("s3", region_name="us-east-1") s3_conn = boto3.resource("s3", region_name="us-east-1") + # json template conn = boto3.client("cloudformation", region_name="us-east-1") result = conn.get_template_summary(TemplateBody=json.dumps(dummy_template3)) - result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) result["Version"].should.equal("2010-09-09") result["Description"].should.equal("Stack 3") + # existing stack conn.create_stack(StackName="test_stack", TemplateBody=json.dumps(dummy_template3)) - result = conn.get_template_summary(StackName="test_stack") - result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) result["Version"].should.equal("2010-09-09") result["Description"].should.equal("Stack 3") + # json template from s3 s3_conn.create_bucket(Bucket="foobar") s3_conn.Object("foobar", "template-key").put(Body=json.dumps(dummy_template3)) - key_url = s3.generate_presigned_url( ClientMethod="get_object", Params={"Bucket": "foobar", "Key": "template-key"} ) - conn.create_stack(StackName="stack_from_url", TemplateURL=key_url) result = conn.get_template_summary(TemplateURL=key_url) result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) result["Version"].should.equal("2010-09-09") result["Description"].should.equal("Stack 3") + # yaml template conn = boto3.client("cloudformation", region_name="us-east-1") result = conn.get_template_summary(TemplateBody=dummy_template_yaml) - result["ResourceTypes"].should.equal(["AWS::EC2::Instance"]) result["Version"].should.equal("2010-09-09") result["Description"].should.equal("Stack1 with yaml template") +@mock_cloudformation +def test_get_template_summary_for_stack_createed_by_changeset_execution(): + conn = boto3.client("cloudformation", region_name="us-east-1") + conn.create_change_set( + StackName="stack_from_changeset", + TemplateBody=json.dumps(dummy_template3), + ChangeSetName="test_changeset", + ChangeSetType="CREATE", + ) + with pytest.raises( + ClientError, + match="GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks", + ): + conn.get_template_summary(StackName="stack_from_changeset") + conn.execute_change_set(ChangeSetName="test_changeset") + result = conn.get_template_summary(StackName="stack_from_changeset") + result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) + result["Version"].should.equal("2010-09-09") + result["Description"].should.equal("Stack 3") + + @mock_cloudformation def test_boto3_create_stack_with_ref_yaml(): cf_conn = boto3.client("cloudformation", region_name="us-east-1")