def setUp(self): self.ref = FindInMapAction()
class TestFindInMapCanResolveParameterRefs(TestCase): def setUp(self): self.ref = FindInMapAction() @patch.object(FindInMapAction, "can_handle") def test_cannot_handle(self, can_handle_mock): input = { "Fn::FindInMap": ["a", "b", "c"] } can_handle_mock.return_value = False output = self.ref.resolve_parameter_refs(input, {}) self.assertEqual(input, output) def test_value_not_list(self): input = { "Fn::FindInMap": "a string" } with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_value_not_list_of_length_three(self): input = { "Fn::FindInMap": ["a string"] } with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_mapping_not_string(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": [["MapA"], "TopKey2", "SecondKey1"] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_top_level_key_not_string(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapA", ["TopKey2"], "SecondKey1"] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_second_level_key_not_string(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapA", "TopKey1", ["SecondKey2"]] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_mapping_not_found(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapB", "TopKey2", "SecondKey1"] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_top_level_key_not_found(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapA", "TopKey3", "SecondKey1"] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_second_level_key_not_found(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapA", "TopKey1", "SecondKey1"] } output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_one_level_find_in_mappings(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = { "Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"] } expected = "value4" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output) def test_nested_find_in_mappings(self): mappings = { "MapA":{ "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "TopKey1" } } } input = { "Fn::FindInMap": ["MapA", {"Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"]}, "SecondKey2"] } expected = "value3" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output) def test_nested_find_in_multiple_mappings(self): mappings = { "MapA":{ "ATopKey1": { "ASecondKey2": "value3" } }, "MapB": { "BTopKey1": { "BSecondKey2": "ATopKey1" } } } input = { "Fn::FindInMap": ["MapA", {"Fn::FindInMap": ["MapB", "BTopKey1", "BSecondKey2"]}, "ASecondKey2"] } expected = "value3" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output)
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)
def _get_intrinsic_resolvers(self, mappings): return [ IntrinsicsResolver(self._parameters), IntrinsicsResolver( mappings, {FindInMapAction.intrinsic_name: FindInMapAction()}), ]
class TestFindInMapCanResolveParameterRefs(TestCase): def setUp(self): self.ref = FindInMapAction() @patch.object(FindInMapAction, "can_handle") def test_cannot_handle(self, can_handle_mock): input = {"Fn::FindInMap": ["a", "b", "c"]} can_handle_mock.return_value = False output = self.ref.resolve_parameter_refs(input, {}) self.assertEqual(input, output) def test_value_not_list(self): input = {"Fn::FindInMap": "a string"} with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_value_not_list_of_length_three(self): input = {"Fn::FindInMap": ["a string"]} with self.assertRaises(InvalidDocumentException): self.ref.resolve_parameter_refs(input, {}) def test_mapping_not_string(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": [["MapA"], "TopKey2", "SecondKey1"]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_top_level_key_not_string(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapA", ["TopKey2"], "SecondKey1"]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_second_level_key_not_string(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapA", "TopKey1", ["SecondKey2"]]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_mapping_not_found(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapB", "TopKey2", "SecondKey1"]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_top_level_key_not_found(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapA", "TopKey3", "SecondKey1"]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_second_level_key_not_found(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapA", "TopKey1", "SecondKey1"]} output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(input, output) def test_one_level_find_in_mappings(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "value4" } } } input = {"Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"]} expected = "value4" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output) def test_nested_find_in_mappings(self): mappings = { "MapA": { "TopKey1": { "SecondKey2": "value3" }, "TopKey2": { "SecondKey1": "TopKey1" } } } input = { "Fn::FindInMap": [ "MapA", { "Fn::FindInMap": ["MapA", "TopKey2", "SecondKey1"] }, "SecondKey2" ] } expected = "value3" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output) def test_nested_find_in_multiple_mappings(self): mappings = { "MapA": { "ATopKey1": { "ASecondKey2": "value3" } }, "MapB": { "BTopKey1": { "BSecondKey2": "ATopKey1" } } } input = { "Fn::FindInMap": [ "MapA", { "Fn::FindInMap": ["MapB", "BTopKey1", "BSecondKey2"] }, "ASecondKey2" ] } expected = "value3" output = self.ref.resolve_parameter_refs(input, mappings) self.assertEqual(expected, output)