From 24fa8f25a1ed39b80db34b56d89d6a8ef84f991b Mon Sep 17 00:00:00 2001 From: Sahil Shah <53784576+sahilshah6196@users.noreply.github.com> Date: Wed, 19 May 2021 03:30:25 -0400 Subject: [PATCH] SSM parameters in cloudformation (`AWS::SSM::Parameter::`) are not recognized and resolved (#3929) * Add ssm parsing support for cloudformation stacks * Start adding unit tests for ssm parameter parsing * Add tests for code update * Add tests to parse ssm parameters code * Fix black lint errors * Fix bug. * Need to specify region_name * region needs to be same * Use ssm_backends[region] instead of ssm_backend * StringList -> string * Linting * check if servermode tests are on * Typo Co-authored-by: Bert Blommers --- moto/cloudformation/parsing.py | 18 +++++++++ .../test_cloudformation/test_stack_parsing.py | 39 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index f92a402db..0b5f13aae 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -41,6 +41,7 @@ from moto.sagemaker import models as sagemaker_models # noqa from moto.sns import models as sns_models # noqa from moto.sqs import models as sqs_models # noqa from moto.stepfunctions import models as stepfunctions_models # noqa +from moto.ssm import models as ssm_models, ssm_backends # noqa # End ugly list of imports @@ -516,6 +517,23 @@ class ResourceMap(collections_abc.Mapping): parameter_slot = parameter_slots[key] value_type = parameter_slot.get("Type", "String") + + def _parse_ssm_parameter(value, value_type): + # The Value in SSM parameters is the SSM parameter path + # we need to use ssm_backend to retreive the + # actual value from parameter store + parameter = ssm_backends[self._region_name].get_parameter( + value, False + ) + actual_value = parameter.value + + if value_type.find("List") > 0: + return actual_value.split(",") + + return actual_value + + if value_type.startswith("AWS::SSM::Parameter::"): + value = _parse_ssm_parameter(value, value_type) if value_type == "CommaDelimitedList" or value_type.startswith("List"): value = value.split(",") diff --git a/tests/test_cloudformation/test_stack_parsing.py b/tests/test_cloudformation/test_stack_parsing.py index 7dc8b95e2..e1b1dc812 100644 --- a/tests/test_cloudformation/test_stack_parsing.py +++ b/tests/test_cloudformation/test_stack_parsing.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +import boto3 import json import yaml @@ -12,6 +13,7 @@ from moto.cloudformation.parsing import ( parse_condition, Export, ) +from moto import mock_ssm, settings from moto.sqs.models import Queue from moto.s3.models import FakeBucket from moto.cloudformation.utils import yaml_tag_constructor @@ -73,6 +75,13 @@ parameters = { } } +ssm_parameter = { + "Parameters": { + "SingleParamCfn": {"Type": "AWS::SSM::Parameter::Value"}, + "ListParamCfn": {"Type": "AWS::SSM::Parameter::Value>"}, + } +} + split_select_template = { "AWSTemplateFormatVersion": "2010-09-09", "Resources": { @@ -143,6 +152,9 @@ get_availability_zones_template = dict( ) parameters_template = dict(list(dummy_template.items()) + list(parameters.items())) +ssm_parameter_template = dict( + list(dummy_template.items()) + list(ssm_parameter.items()) +) dummy_template_json = json.dumps(dummy_template) name_type_template_json = json.dumps(name_type_template) @@ -151,6 +163,7 @@ bad_output_template_json = json.dumps(bad_outputs_template) get_attribute_outputs_template_json = json.dumps(get_attribute_outputs_template) get_availability_zones_template_json = json.dumps(get_availability_zones_template) parameters_template_json = json.dumps(parameters_template) +ssm_parameter_template_json = json.dumps(ssm_parameter_template) split_select_template_json = json.dumps(split_select_template) sub_template_json = json.dumps(sub_template) export_value_template_json = json.dumps(export_value_template) @@ -498,3 +511,29 @@ def test_short_form_func_in_yaml_teamplate(): ] for k, v in key_and_expects: template_dict.should.have.key(k).which.should.be.equal(v) + + +@mock_ssm +def test_ssm_parameter_parsing(): + client = boto3.client("ssm", region_name="us-west-1") + client.put_parameter(Name="/path/to/single/param", Value="string", Type="String") + client.put_parameter( + Name="/path/to/list/param", Value="comma,separated,string", Type="StringList" + ) + + if not settings.TEST_SERVER_MODE: + stack = FakeStack( + stack_id="test_id", + name="test_stack", + template=ssm_parameter_template_json, + parameters={ + "SingleParamCfn": "/path/to/single/param", + "ListParamCfn": "/path/to/list/param", + }, + region_name="us-west-1", + ) + + stack.resource_map.resolved_parameters["SingleParamCfn"].should.equal("string") + stack.resource_map.resolved_parameters["ListParamCfn"].should.equal( + ["comma", "separated", "string"] + )