diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index d3f0976a1..7bb169a07 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -467,6 +467,28 @@ class CloudFormationBackend(BaseBackend): self.exports = OrderedDict() self.change_sets = OrderedDict() + def _resolve_update_parameters(self, instance, incoming_params): + parameters = dict( + [ + (parameter["parameter_key"], parameter["parameter_value"]) + for parameter in incoming_params + if "parameter_value" in parameter + ] + ) + previous = dict( + [ + ( + parameter["parameter_key"], + instance.parameters[parameter["parameter_key"]], + ) + for parameter in incoming_params + if "use_previous_value" in parameter + ] + ) + parameters.update(previous) + + return parameters + def create_stack_set( self, name, @@ -494,6 +516,8 @@ class CloudFormationBackend(BaseBackend): def get_stack_set(self, name): stacksets = self.stacksets.keys() + if name in stacksets: + return self.stacksets[name] for stackset in stacksets: if self.stacksets[stackset].name == name: return self.stacksets[stackset] @@ -501,6 +525,8 @@ class CloudFormationBackend(BaseBackend): def delete_stack_set(self, name): stacksets = self.stacksets.keys() + if name in stacksets: + self.stacksets[name].delete() for stackset in stacksets: if self.stacksets[stackset].name == name: self.stacksets[stackset].delete() @@ -532,10 +558,13 @@ class CloudFormationBackend(BaseBackend): operation_id=None, ): stackset = self.get_stack_set(stackset_name) + resolved_parameters = self._resolve_update_parameters( + instance=stackset, incoming_params=parameters + ) update = stackset.update( template=template, description=description, - parameters=parameters, + parameters=resolved_parameters, tags=tags, admin_role=admin_role, execution_role=execution_role, @@ -711,7 +740,10 @@ class CloudFormationBackend(BaseBackend): def update_stack(self, name, template, role_arn=None, parameters=None, tags=None): stack = self.get_stack(name) - stack.update(template, role_arn, parameters=parameters, tags=tags) + resolved_parameters = self._resolve_update_parameters( + instance=stack, incoming_params=parameters + ) + stack.update(template, role_arn, parameters=resolved_parameters, tags=tags) return stack def list_stack_resources(self, stack_name_or_id): diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index c91af0e38..1667d59db 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -315,24 +315,6 @@ class CloudFormationResponse(BaseResponse): stack_body = self._get_stack_from_s3_url(template_url) incoming_params = self._get_list_prefix("Parameters.member") - parameters = dict( - [ - (parameter["parameter_key"], parameter["parameter_value"]) - for parameter in incoming_params - if "parameter_value" in parameter - ] - ) - previous = dict( - [ - ( - parameter["parameter_key"], - stack.parameters[parameter["parameter_key"]], - ) - for parameter in incoming_params - if "use_previous_value" in parameter - ] - ) - parameters.update(previous) # boto3 is supposed to let you clear the tags by passing an empty value, but the request body doesn't # end up containing anything we can use to differentiate between passing an empty value versus not # passing anything. so until that changes, moto won't be able to clear tags, only update them. @@ -357,7 +339,7 @@ class CloudFormationResponse(BaseResponse): name=stack_name, template=stack_body, role_arn=role_arn, - parameters=parameters, + parameters=incoming_params, tags=tags, ) if self.request_json: @@ -560,17 +542,12 @@ class CloudFormationResponse(BaseResponse): 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, + parameters=parameters_list, tags=tags, admin_role=admin_role, execution_role=execution_role, diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 977b70833..36e716d79 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -558,6 +558,43 @@ def test_update_stack_set(): ) +@mock_cloudformation +def test_update_stack_set_with_previous_value(): + 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", "UsePreviousValue": True}, + ] + 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[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") @@ -588,7 +625,7 @@ def test_boto3_bad_list_stack_resources(): @mock_cloudformation -def test_boto3_delete_stack_set(): +def test_boto3_delete_stack_set_by_name(): cf_conn = boto3.client("cloudformation", region_name="us-east-1") cf_conn.create_stack_set( StackSetName="test_stack_set", TemplateBody=dummy_template_json @@ -600,6 +637,20 @@ def test_boto3_delete_stack_set(): ].should.equal("DELETED") +@mock_cloudformation +def test_boto3_delete_stack_set_by_id(): + cf_conn = boto3.client("cloudformation", region_name="us-east-1") + response = cf_conn.create_stack_set( + StackSetName="test_stack_set", TemplateBody=dummy_template_json + ) + stack_set_id = response["StackSetId"] + cf_conn.delete_stack_set(StackSetName=stack_set_id) + + 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") @@ -680,6 +731,19 @@ def test_boto3_describe_stack_set_params(): ].should.equal(params) +@mock_cloudformation +def test_boto3_describe_stack_set_by_id(): + cf_conn = boto3.client("cloudformation", region_name="us-east-1") + response = cf_conn.create_stack_set( + StackSetName="test_stack", TemplateBody=dummy_template_json, + ) + + stack_set_id = response["StackSetId"] + cf_conn.describe_stack_set(StackSetName=stack_set_id)["StackSet"][ + "TemplateBody" + ].should.equal(dummy_template_json) + + @mock_cloudformation def test_boto3_create_stack(): cf_conn = boto3.client("cloudformation", region_name="us-east-1")