def test_len_single_resource(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId1", "property2", "value2") self.assertEqual( 1, len(resource_refs)) # Each unique logicalId adds one to the len
def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1") self.supported_resource_refs.add("id1", "prop2", "value2") self.supported_resource_refs.add("id2", "prop3", "value3") self.input_sub_value = "Hello ${id1.prop1} ${id1.prop2}${id2.prop3} ${id1.prop1.arn} ${id1.prop2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World" self.expected_output_sub_value = "Hello ${value1} ${value2}${value3} ${value1.arn} ${value2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World"
def test_add_must_error_on_duplicate_value(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId", "property", "value1") with self.assertRaises(ValueError): resource_refs.add("logicalId", "property", "value2")
def test_get_must_return_correct_value(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId1", "property2", "value2") resource_refs.add("newLogicalId", "newProperty", "newValue") self.assertEqual("value1", resource_refs.get("logicalId1", "property1")) self.assertEqual("value2", resource_refs.get("logicalId1", "property2")) self.assertEqual("newValue", resource_refs.get("newLogicalId", "newProperty"))
def test_add_multiple_logical_ids(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId2", "property2", "value2") resource_refs.add("logicalId3", "property3", "value3") self.assertEqual({"property1": "value1"}, resource_refs.get_all("logicalId1")) self.assertEqual({"property2": "value2"}, resource_refs.get_all("logicalId2")) self.assertEqual({"property3": "value3"}, resource_refs.get_all("logicalId3"))
def test_get_must_return_correct_value(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId1", "property2", "value2") resource_refs.add("newLogicalId", "newProperty", "newValue") self.assertEquals("value1", resource_refs.get("logicalId1", "property1")) self.assertEquals("value2", resource_refs.get("logicalId1", "property2")) self.assertEquals("newValue", resource_refs.get("newLogicalId", "newProperty"))
def test_add_multiple_logical_ids(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId2", "property2", "value2") resource_refs.add("logicalId3", "property3", "value3") self.assertEquals({"property1": "value1"}, resource_refs.get_all("logicalId1")) self.assertEquals({"property2": "value2"}, resource_refs.get_all("logicalId2")) self.assertEquals({"property3": "value3"}, resource_refs.get_all("logicalId3"))
def test_get_on_non_existent_property(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") self.assertEquals(None, resource_refs.get("logicalId1", "SomeProperty")) self.assertEquals(None, resource_refs.get("SomeLogicalId", "property1"))
def test_add_multiple_properties_to_one_logicalId(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId", "property1", "value1") resource_refs.add("logicalId", "property2", "value2") resource_refs.add("logicalId", "property3", "value3") expected = { "property1": "value1", "property2": "value2", "property3": "value3" } self.assertEqual(expected, resource_refs.get_all("logicalId"))
def test_add_multiple_properties_to_one_logicalId(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId", "property1", "value1") resource_refs.add("logicalId", "property2", "value2") resource_refs.add("logicalId", "property3", "value3") expected = { "property1": "value1", "property2": "value2", "property3": "value3" } self.assertEquals(expected, resource_refs.get_all("logicalId"))
def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1")
class TestGetAttCanResolveResourceRefs(TestCase): def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1") def test_must_resolve_simple_refs(self): input = {"Fn::GetAtt": ["id1.prop1", "Arn"]} expected = {"Fn::GetAtt": ["value1", "Arn"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_resolve_refs_with_many_attributes(self): input = {"Fn::GetAtt": ["id1.prop1", "Arn1", "Arn2", "Arn3"]} expected = {"Fn::GetAtt": ["value1", "Arn1", "Arn2", "Arn3"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_resolve_with_splitted_resource_refs(self): input = { # Reference to resource `id1.prop1` is split into different elements "Fn::GetAtt": ["id1", "prop1", "Arn1", "Arn2", "Arn3"] } expected = {"Fn::GetAtt": ["value1", "Arn1", "Arn2", "Arn3"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_refs_without_attributes(self): input = { # No actual attributes. But since we have two entries in the array, we try to resolve them "Fn::GetAtt": ["id1", "prop1"] } expected = {"Fn::GetAtt": ["value1"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_refs_without_attributes_in_concatenated_form(self): input = { # No actual attributes. with just only one entry in array, the value never gets resolved "Fn::GetAtt": ["id1.prop1"] } expected = {"Fn::GetAtt": ["id1.prop1"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_invalid_value_array(self): input = { # No actual attributes "Fn::GetAtt": ["id1"] } expected = {"Fn::GetAtt": ["id1"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_invalid_value_type(self): input = { # No actual attributes "Fn::GetAtt": { "a": "b" } } expected = {"Fn::GetAtt": {"a": "b"}} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_missing_properties_with_dot_after(self): input = {"Fn::GetAtt": ["id1.", "foo"]} expected = {"Fn::GetAtt": ["id1.", "foo"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_missing_properties_with_dot_before(self): input = {"Fn::GetAtt": [".id1", "foo"]} expected = {"Fn::GetAtt": [".id1", "foo"]} getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) @patch.object(GetAttAction, "can_handle") def test_return_value_if_cannot_handle(self, can_handle_mock): input = {"Fn::GetAtt": ["id1", "prop1"]} expected = {"Fn::GetAtt": ["id1", "prop1"]} getatt = GetAttAction() can_handle_mock.return_value = False # Simulate failure to handle the input. Result should be same as input self.assertEqual( expected, getatt.resolve_resource_refs(input, self.supported_resource_refs))
def test_add_must_error_if_value_is_not_string(self): resource_refs = SupportedResourceReferences() with self.assertRaises(ValueError): resource_refs.add("logicalId", "property", {"a": "b"})
def test_len_multiple_resources(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId2", "property2", "value2") self.assertEquals(2, len(resource_refs))
class TestGetAttCanResolveResourceRefs(TestCase): def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1") def test_must_resolve_simple_refs(self): input = { "Fn::GetAtt": ["id1.prop1", "Arn"] } expected = { "Fn::GetAtt": ["value1", "Arn"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_resolve_refs_with_many_attributes(self): input = { "Fn::GetAtt": ["id1.prop1", "Arn1", "Arn2", "Arn3"] } expected = { "Fn::GetAtt": ["value1", "Arn1", "Arn2", "Arn3"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_resolve_with_splitted_resource_refs(self): input = { # Reference to resource `id1.prop1` is split into different elements "Fn::GetAtt": ["id1", "prop1", "Arn1", "Arn2", "Arn3"] } expected = { "Fn::GetAtt": ["value1", "Arn1", "Arn2", "Arn3"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_refs_without_attributes(self): input = { # No actual attributes. But since we have two entries in the array, we try to resolve them "Fn::GetAtt": ["id1", "prop1"] } expected = { "Fn::GetAtt": ["value1"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_refs_without_attributes_in_concatenated_form(self): input = { # No actual attributes. with just only one entry in array, the value never gets resolved "Fn::GetAtt": ["id1.prop1"] } expected = { "Fn::GetAtt": ["id1.prop1"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_invalid_value_array(self): input = { # No actual attributes "Fn::GetAtt": ["id1"] } expected = { "Fn::GetAtt": ["id1"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_invalid_value_type(self): input = { # No actual attributes "Fn::GetAtt": {"a": "b"} } expected = { "Fn::GetAtt": {"a": "b"} } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_missing_properties_with_dot_after(self): input = { "Fn::GetAtt": ["id1.", "foo"] } expected = { "Fn::GetAtt": ["id1.", "foo"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) def test_must_ignore_missing_properties_with_dot_before(self): input = { "Fn::GetAtt": [".id1", "foo"] } expected = { "Fn::GetAtt": [".id1", "foo"] } getatt = GetAttAction() output = getatt.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, output) @patch.object(GetAttAction, "can_handle") def test_return_value_if_cannot_handle(self, can_handle_mock): input = { "Fn::GetAtt": ["id1", "prop1"] } expected = { "Fn::GetAtt": ["id1", "prop1"] } getatt = GetAttAction() can_handle_mock.return_value = False # Simulate failure to handle the input. Result should be same as input self.assertEqual(expected, getatt.resolve_resource_refs(input, self.supported_resource_refs))
class TestSubCanResolveResourceRefs(TestCase): def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1") self.supported_resource_refs.add("id1", "prop2", "value2") self.supported_resource_refs.add("id2", "prop3", "value3") self.input_sub_value = "Hello ${id1.prop1} ${id1.prop2}${id2.prop3} ${id1.prop1.arn} ${id1.prop2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World" self.expected_output_sub_value = "Hello ${value1} ${value2}${value3} ${value1.arn} ${value2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World" def test_must_resolve_string_value(self): input = { "Fn::Sub": self.input_sub_value } expected = { "Fn::Sub": self.expected_output_sub_value } sub = SubAction() result = sub.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, result) def test_must_resolve_array_value(self): input = { "Fn::Sub": [self.input_sub_value, {"unknown":"a"}] } expected = { "Fn::Sub": [self.expected_output_sub_value, {"unknown": "a"}] } sub = SubAction() result = sub.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, result) def test_sub_all_refs_with_list_input(self): parameters = { "key1": "value1", "key2": "value2" } input = { "Fn::Sub": ["key1", "key2"] } expected = { "Fn::Sub": ["key1", "key2"] } sub = SubAction() result = sub.resolve_resource_refs(input, parameters) self.assertEqual(expected, result) def test_sub_all_refs_with_dict_input(self): parameters = { "key1": "value1", "key2": "value2" } input = { "Fn::Sub": {"a": "key1", "b": "key2"} } expected = { "Fn::Sub": {"a": "key1", "b": "key2"} } sub = SubAction() result = sub.resolve_resource_refs(input, parameters) self.assertEqual(expected, result) @patch.object(SubAction, "can_handle") def test_return_value_if_cannot_handle(self, can_handle_mock): parameters = { "key": "value" } input = { "Fn::Sub": "${key}" } expected = { "Fn::Sub": "${key}" } sub = SubAction() can_handle_mock.return_value = False # Simulate failure to handle the input. Result should be same as input self.assertEqual(expected, sub.resolve_resource_refs(input, parameters))
def setUp(self): self.supported_resource_refs = SupportedResourceReferences()
def test_add_must_error_when_property_is_absent(self): resource_refs = SupportedResourceReferences() with self.assertRaises(ValueError): resource_refs.add("logicalId", "", "value1")
class TestSamResourceReferableProperties(TestCase): class ResourceType1(Resource): resource_type = "resource_type1" property_types = {} class ResourceType2(Resource): resource_type = "resource_type2" property_types = {} class ResourceType3(Resource): resource_type = "resource_type3" property_types = {} def setUp(self): self.supported_resource_refs = SupportedResourceReferences() def test_must_get_property_for_available_resources(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "resource_type1", "prop2": "resource_type2", "prop3": "resource_type3" } sam_resource = NewSamResource("SamLogicalId") cfn_resources = [self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2")] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEqual("logicalId1", self.supported_resource_refs.get("SamLogicalId", "prop1")) self.assertEqual("logicalId2", self.supported_resource_refs.get("SamLogicalId", "prop2")) # there is no cfn resource of for "prop3" in the cfn_resources list self.assertEqual(None, self.supported_resource_refs.get("SamLogicalId", "prop3")) # Must add only for the given SAM resource self.assertEqual(1, len(self.supported_resource_refs)) def test_must_work_with_two_resources_of_same_type(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "resource_type1", "prop2": "resource_type2", "prop3": "resource_type3" } sam_resource1 = NewSamResource("SamLogicalId1") sam_resource2 = NewSamResource("SamLogicalId2") cfn_resources = [self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2") ] self.supported_resource_refs = \ sam_resource1.get_resource_references(cfn_resources, self.supported_resource_refs) self.supported_resource_refs = \ sam_resource2.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEqual("logicalId1", self.supported_resource_refs.get("SamLogicalId1", "prop1")) self.assertEqual("logicalId2", self.supported_resource_refs.get("SamLogicalId1", "prop2")) self.assertEqual("logicalId1", self.supported_resource_refs.get("SamLogicalId2", "prop1")) self.assertEqual("logicalId2", self.supported_resource_refs.get("SamLogicalId2", "prop2")) self.assertEqual(2, len(self.supported_resource_refs)) def test_must_skip_unknown_resource_types(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "foo", "prop2": "bar", } sam_resource = NewSamResource("SamLogicalId") # None of the CFN resource types are in the referable list cfn_resources = [self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2")] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEqual(0, len(self.supported_resource_refs)) def test_must_skip_if_no_supported_properties(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2")] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEqual(0, len(self.supported_resource_refs)) def test_must_skip_if_no_resources(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {"prop1": "resource_type1"} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEqual(0, len(self.supported_resource_refs)) def test_must_raise_if_input_is_absent(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {"prop1": "resource_type1"} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [self.ResourceType1("logicalId1")] with self.assertRaises(ValueError): sam_resource.get_resource_references(cfn_resources, None)
def test_get_all_on_non_existent_logical_id(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId", "property", "value1") self.assertEquals(None, resource_refs.get_all("some logical id"))
def test_len_multiple_resources(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId2", "property2", "value2") self.assertEqual(2, len(resource_refs))
def translate(self, sam_template, parameter_values): """Loads the SAM resources from the given SAM manifest, replaces them with their corresponding CloudFormation resources, and returns the resulting CloudFormation template. :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that some functionality that relies on resolving parameter references might not work as expected (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ # Create & Install plugins sam_plugins = prepare_plugins(self.plugins) parameter_values = self._add_default_parameter_values( sam_template, parameter_values) self.sam_parser.parse(sam_template=sam_template, parameter_values=parameter_values, sam_plugins=sam_plugins) template = copy.deepcopy(sam_template) macro_resolver = ResourceTypeResolver(sam_resources) intrinsics_resolver = IntrinsicsResolver(parameter_values) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate( sam_template, macro_resolver): try: macro = macro_resolver\ .resolve_resource_type(resource_dict)\ .from_dict(logical_id, resource_dict, sam_plugins=sam_plugins) kwargs = macro.resources_to_link(sam_template['Resources']) kwargs['managed_policy_map'] = self.managed_policy_map kwargs['intrinsics_resolver'] = intrinsics_resolver kwargs[ 'deployment_preference_collection'] = deployment_preference_collection translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references( translated, supported_resource_refs) # Some resources mutate their logical ids. Track those to change all references to them: if logical_id != macro.logical_id: changed_logical_ids[logical_id] = macro.logical_id del template['Resources'][logical_id] for resource in translated: if verify_unique_logical_id(resource, sam_template['Resources']): template['Resources'].update(resource.to_dict()) else: document_errors.append( DuplicateLogicalIdException( logical_id, resource.logical_id, resource.resource_type)) except (InvalidResourceException, InvalidEventException) as e: document_errors.append(e) if deployment_preference_collection.any_enabled(): template['Resources'].update(deployment_preference_collection. codedeploy_application.to_dict()) if not deployment_preference_collection.can_skip_service_role(): template['Resources'].update(deployment_preference_collection. codedeploy_iam_role.to_dict()) for logical_id in deployment_preference_collection.enabled_logical_ids( ): template['Resources'].update( deployment_preference_collection.deployment_group( logical_id).to_dict()) # Run the after-transform plugin target try: sam_plugins.act(LifeCycleEvents.after_transform_template, template) except (InvalidDocumentException, InvalidResourceException) as e: document_errors.append(e) # Cleanup if 'Transform' in template: del template['Transform'] if len(document_errors) is 0: template = intrinsics_resolver.resolve_sam_resource_id_refs( template, changed_logical_ids) template = intrinsics_resolver.resolve_sam_resource_refs( template, supported_resource_refs) return template else: raise InvalidDocumentException(document_errors)
class TestSamResourceReferableProperties(TestCase): class ResourceType1(Resource): resource_type = "resource_type1" property_types = {} class ResourceType2(Resource): resource_type = "resource_type2" property_types = {} class ResourceType3(Resource): resource_type = "resource_type3" property_types = {} def setUp(self): self.supported_resource_refs = SupportedResourceReferences() def test_must_get_property_for_available_resources(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "resource_type1", "prop2": "resource_type2", "prop3": "resource_type3" } sam_resource = NewSamResource("SamLogicalId") cfn_resources = [ self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2") ] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEquals( "logicalId1", self.supported_resource_refs.get("SamLogicalId", "prop1")) self.assertEquals( "logicalId2", self.supported_resource_refs.get("SamLogicalId", "prop2")) # there is no cfn resource of for "prop3" in the cfn_resources list self.assertEquals( None, self.supported_resource_refs.get("SamLogicalId", "prop3")) # Must add only for the given SAM resource self.assertEquals(1, len(self.supported_resource_refs)) def test_must_work_with_two_resources_of_same_type(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "resource_type1", "prop2": "resource_type2", "prop3": "resource_type3" } sam_resource1 = NewSamResource("SamLogicalId1") sam_resource2 = NewSamResource("SamLogicalId2") cfn_resources = [ self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2") ] self.supported_resource_refs = \ sam_resource1.get_resource_references(cfn_resources, self.supported_resource_refs) self.supported_resource_refs = \ sam_resource2.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEquals( "logicalId1", self.supported_resource_refs.get("SamLogicalId1", "prop1")) self.assertEquals( "logicalId2", self.supported_resource_refs.get("SamLogicalId1", "prop2")) self.assertEquals( "logicalId1", self.supported_resource_refs.get("SamLogicalId2", "prop1")) self.assertEquals( "logicalId2", self.supported_resource_refs.get("SamLogicalId2", "prop2")) self.assertEquals(2, len(self.supported_resource_refs)) def test_must_skip_unknown_resource_types(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = { "prop1": "foo", "prop2": "bar", } sam_resource = NewSamResource("SamLogicalId") # None of the CFN resource types are in the referable list cfn_resources = [ self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2") ] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEquals(0, len(self.supported_resource_refs)) def test_must_skip_if_no_supported_properties(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [ self.ResourceType1("logicalId1"), self.ResourceType2("logicalId2") ] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEquals(0, len(self.supported_resource_refs)) def test_must_skip_if_no_resources(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {"prop1": "resource_type1"} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [] self.supported_resource_refs = \ sam_resource.get_resource_references(cfn_resources, self.supported_resource_refs) self.assertEquals(0, len(self.supported_resource_refs)) def test_must_raise_if_input_is_absent(self): class NewSamResource(SamResourceMacro): resource_type = "foo" property_types = {} referable_properties = {"prop1": "resource_type1"} sam_resource = NewSamResource("SamLogicalId") cfn_resources = [self.ResourceType1("logicalId1")] with self.assertRaises(ValueError): sam_resource.get_resource_references(cfn_resources, None)
def test_get_all_on_non_existent_logical_id(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId", "property", "value1") self.assertEqual(None, resource_refs.get_all("some logical id"))
def translate(self, sam_template, parameter_values, feature_toggle=None): """Loads the SAM resources from the given SAM manifest, replaces them with their corresponding CloudFormation resources, and returns the resulting CloudFormation template. :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that some functionality that relies on resolving parameter references might not work as expected (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ self.feature_toggle = (feature_toggle if feature_toggle else FeatureToggle( FeatureToggleDefaultConfigProvider(), stage=None, account_id=None, region=None)) self.function_names = dict() self.redeploy_restapi_parameters = dict() sam_parameter_values = SamParameterValues(parameter_values) sam_parameter_values.add_default_parameter_values(sam_template) sam_parameter_values.add_pseudo_parameter_values(self.boto_session) parameter_values = sam_parameter_values.parameter_values # Create & Install plugins sam_plugins = prepare_plugins(self.plugins, parameter_values) self.sam_parser.parse(sam_template=sam_template, parameter_values=parameter_values, sam_plugins=sam_plugins) template = copy.deepcopy(sam_template) macro_resolver = ResourceTypeResolver(sam_resources) intrinsics_resolver = IntrinsicsResolver(parameter_values) mappings_resolver = IntrinsicsResolver( template.get("Mappings", {}), {FindInMapAction.intrinsic_name: FindInMapAction()}) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() shared_api_usage_plan = SharedApiUsagePlan() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate( sam_template, macro_resolver): try: macro = macro_resolver.resolve_resource_type( resource_dict).from_dict(logical_id, resource_dict, sam_plugins=sam_plugins) kwargs = macro.resources_to_link(sam_template["Resources"]) kwargs["managed_policy_map"] = self.managed_policy_map kwargs["intrinsics_resolver"] = intrinsics_resolver kwargs["mappings_resolver"] = mappings_resolver kwargs[ "deployment_preference_collection"] = deployment_preference_collection kwargs["conditions"] = template.get("Conditions") # add the value of FunctionName property if the function is referenced with the api resource self.redeploy_restapi_parameters[ "function_names"] = self._get_function_names( resource_dict, intrinsics_resolver) kwargs[ "redeploy_restapi_parameters"] = self.redeploy_restapi_parameters kwargs["shared_api_usage_plan"] = shared_api_usage_plan translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references( translated, supported_resource_refs) # Some resources mutate their logical ids. Track those to change all references to them: if logical_id != macro.logical_id: changed_logical_ids[logical_id] = macro.logical_id del template["Resources"][logical_id] for resource in translated: if verify_unique_logical_id(resource, sam_template["Resources"]): template["Resources"].update(resource.to_dict()) else: document_errors.append( DuplicateLogicalIdException( logical_id, resource.logical_id, resource.resource_type)) except (InvalidResourceException, InvalidEventException) as e: document_errors.append(e) if deployment_preference_collection.any_enabled(): template["Resources"].update(deployment_preference_collection. codedeploy_application.to_dict()) if not deployment_preference_collection.can_skip_service_role(): template["Resources"].update(deployment_preference_collection. codedeploy_iam_role.to_dict()) for logical_id in deployment_preference_collection.enabled_logical_ids( ): try: template["Resources"].update( deployment_preference_collection.deployment_group( logical_id).to_dict()) except InvalidResourceException as e: document_errors.append(e) # Run the after-transform plugin target try: sam_plugins.act(LifeCycleEvents.after_transform_template, template) except (InvalidDocumentException, InvalidResourceException) as e: document_errors.append(e) # Cleanup if "Transform" in template: del template["Transform"] if len(document_errors) == 0: template = intrinsics_resolver.resolve_sam_resource_id_refs( template, changed_logical_ids) template = intrinsics_resolver.resolve_sam_resource_refs( template, supported_resource_refs) return template else: raise InvalidDocumentException(document_errors)
class TestSubCanResolveResourceRefs(TestCase): def setUp(self): self.supported_resource_refs = SupportedResourceReferences() self.supported_resource_refs.add("id1", "prop1", "value1") self.supported_resource_refs.add("id1", "prop2", "value2") self.supported_resource_refs.add("id2", "prop3", "value3") self.input_sub_value = "Hello ${id1.prop1} ${id1.prop2}${id2.prop3} ${id1.prop1.arn} ${id1.prop2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World" self.expected_output_sub_value = "Hello ${value1} ${value2}${value3} ${value1.arn} ${value2.arn.name.foo} ${!id1.prop1} ${unknown} ${some.arn} World" def test_must_resolve_string_value(self): input = {"Fn::Sub": self.input_sub_value} expected = {"Fn::Sub": self.expected_output_sub_value} sub = SubAction() result = sub.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, result) def test_must_resolve_array_value(self): input = {"Fn::Sub": [self.input_sub_value, {"unknown": "a"}]} expected = { "Fn::Sub": [self.expected_output_sub_value, { "unknown": "a" }] } sub = SubAction() result = sub.resolve_resource_refs(input, self.supported_resource_refs) self.assertEqual(expected, result) def test_sub_all_refs_with_list_input(self): parameters = {"key1": "value1", "key2": "value2"} input = {"Fn::Sub": ["key1", "key2"]} expected = {"Fn::Sub": ["key1", "key2"]} sub = SubAction() result = sub.resolve_resource_refs(input, parameters) self.assertEqual(expected, result) def test_sub_all_refs_with_dict_input(self): parameters = {"key1": "value1", "key2": "value2"} input = {"Fn::Sub": {"a": "key1", "b": "key2"}} expected = {"Fn::Sub": {"a": "key1", "b": "key2"}} sub = SubAction() result = sub.resolve_resource_refs(input, parameters) self.assertEqual(expected, result) @patch.object(SubAction, "can_handle") def test_return_value_if_cannot_handle(self, can_handle_mock): parameters = {"key": "value"} input = {"Fn::Sub": "${key}"} expected = {"Fn::Sub": "${key}"} sub = SubAction() can_handle_mock.return_value = False # Simulate failure to handle the input. Result should be same as input self.assertEqual(expected, sub.resolve_resource_refs(input, parameters))
def test_len_single_resource(self): resource_refs = SupportedResourceReferences() resource_refs.add("logicalId1", "property1", "value1") resource_refs.add("logicalId1", "property2", "value2") self.assertEquals(1, len(resource_refs)) # Each unique logicalId adds one to the len