def to_statement(self, parameter_values):
        """
        With the given values for each parameter, this method will return a policy statement that can be used
        directly with IAM.

        :param dict parameter_values: Dict containing values for each parameter defined in the template
        :return dict: Dictionary containing policy statement
        :raises InvalidParameterValues: If parameter values is not a valid dictionary or does not contain values for all parameters
        :raises InsufficientParameterValues: If the parameter values don't have values for all required parameters
        """

        missing = self.missing_parameter_values(parameter_values)
        if len(missing) > 0:
            # str() of elements of list to prevent any `u` prefix from being displayed in user-facing error message
            raise InsufficientParameterValues(
                "Following required parameters of template '{}' don't have values: {}"
                .format(self.name, [str(m) for m in missing]))

        # Select only necessary parameter_values. this is to prevent malicious or accidental
        # injection of values for parameters not intended in the template. This is important because "Ref" resolution
        # will substitute any references for which a value is provided.
        necessary_parameter_values = {
            name: value
            for name, value in parameter_values.items()
            if name in self.parameters
        }

        # Only "Ref" is supported
        supported_intrinsics = {RefAction.intrinsic_name: RefAction()}

        resolver = IntrinsicsResolver(necessary_parameter_values,
                                      supported_intrinsics)
        definition_copy = copy.deepcopy(self.definition)

        return resolver.resolve_parameter_refs(definition_copy)
    def to_statement(self, parameter_values):
        """
        With the given values for each parameter, this method will return a policy statement that can be used
        directly with IAM.

        :param dict parameter_values: Dict containing values for each parameter defined in the template
        :return dict: Dictionary containing policy statement
        :raises InvalidParameterValues: If parameter values is not a valid dictionary or does not contain values for all parameters
        :raises InsufficientParameterValues: If the parameter values don't have values for all required parameters
        """

        missing = self.missing_parameter_values(parameter_values)
        if len(missing) > 0:
            # str() of elements of list to prevent any `u` prefix from being displayed in user-facing error message
            raise InsufficientParameterValues("Following required parameters of template '{}' don't have values: {}"
                                              .format(self.name, [str(m) for m in missing]))

        # Select only necessary parameter_values. this is to prevent malicious or accidental
        # injection of values for parameters not intended in the template. This is important because "Ref" resolution
        # will substitute any references for which a value is provided.
        necessary_parameter_values = {name: value for name, value in parameter_values.items()
                                      if name in self.parameters}

        # Only "Ref" is supported
        supported_intrinsics = {
            RefAction.intrinsic_name: RefAction()
        }

        resolver = IntrinsicsResolver(necessary_parameter_values, supported_intrinsics)
        definition_copy = copy.deepcopy(self.definition)

        return resolver.resolve_parameter_refs(definition_copy)
    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_sam_resource_refs = Mock()  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_sam_resource_refs(input, {}), expected)
        resolver._try_resolve_sam_resource_refs.assert_not_called()
    def setUp(self):
        self.parameter_values = {
            "param1": "value1",
            "param2": "value2",
            "param3": "value3"
        }

        self.resolver = IntrinsicsResolver(self.parameter_values)
    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_sam_resource_refs = Mock()  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_sam_resource_refs(input, {}), expected)
        resolver._try_resolve_sam_resource_refs.assert_not_called()
    def _resolve_parameters(template_dict, parameter_overrides):
        """
        In the given template, apply parameter values to resolve intrinsic functions

        Parameters
        ----------
        template_dict : dict
            SAM Template

        parameter_overrides : dict
            Values for template parameters provided by user

        Returns
        -------
        dict
            Resolved SAM template
        """

        parameter_values = SamBaseProvider._get_parameter_values(template_dict, parameter_overrides)

        supported_intrinsics = {action.intrinsic_name: action() for action in SamBaseProvider._SUPPORTED_INTRINSICS}

        # Intrinsics resolver will mutate the original template
        return IntrinsicsResolver(parameters=parameter_values, supported_intrinsics=supported_intrinsics)\
            .resolve_parameter_refs(template_dict)
Beispiel #7
0
class TestCodeUri(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {"foo": "bar"},
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_code_uri(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code, {"S3Key": "foo.zip", "S3Bucket": "foobar"})

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_zip_file(self):
        function = SamFunction("foo")
        function.InlineCode = "hello world"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code, {"ZipFile": "hello world"})

    def test_with_no_code_uri_or_zipfile(self):
        function = SamFunction("foo")
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)
class TestApiTags(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {
            "foo": "bar"
        },
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_no_tags(self):
        api = SamApi("foo")
        api.Tags = {}

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [x for x in resources if isinstance(x, ApiGatewayStage)]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].Tags, [])

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_tags(self):
        api = SamApi("foo")
        api.Tags = {"MyKey": "MyValue"}

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [x for x in resources if isinstance(x, ApiGatewayStage)]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].Tags, [{
            "Key": "MyKey",
            "Value": "MyValue"
        }])
class TestHttpApiDescription(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {
            "foo": "bar"
        },
    }

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_no_description(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.DefinitionUri = "s3://foobar/foo.zip"

        resources = sam_http_api.to_cloudformation(**self.kwargs)
        rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
        self.assertEqual(rest_api[0].Description, None)

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_description(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.DefinitionUri = "s3://foobar/foo.zip"
        sam_http_api.Description = "my description"

        resources = sam_http_api.to_cloudformation(**self.kwargs)
        rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
        self.assertEqual(rest_api[0].Description, "my description")
class TestVersionDescription(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {
            "foo": "bar"
        },
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_version_description(self):
        function = SamFunction("foo")
        test_description = "foobar"

        function.Runtime = "foo"
        function.Handler = "bar"
        function.CodeUri = "s3://foobar/foo.zip"
        function.VersionDescription = test_description
        function.AutoPublishAlias = "live"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generateFunctionVersion = [
            x for x in cfnResources if isinstance(x, LambdaVersion)
        ]
        self.assertEqual(generateFunctionVersion[0].Description,
                         test_description)
Beispiel #11
0
class TestApiTags(TestCase):
    kwargs = {
        'intrinsics_resolver': IntrinsicsResolver({}),
        'event_resources': [],
        'managed_policy_map': {
            "foo": "bar"
        }
    }

    def test_with_no_tags(self):
        api = SamApi("foo")
        api.Tags = {}

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [x for x in resources if isinstance(x, ApiGatewayStage)]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].Tags, [])

    def test_with_tags(self):
        api = SamApi("foo")
        api.Tags = {'MyKey': 'MyValue'}

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [x for x in resources if isinstance(x, ApiGatewayStage)]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].Tags, [{
            'Key': 'MyKey',
            'Value': 'MyValue'
        }])
    def setUp(self):
        self.parameter_values = {
            "param1": "value1",
            "param2": "value2",
            "param3": "value3"
        }

        self.resolver = IntrinsicsResolver(self.parameter_values)
class TestResourceReferenceResolution(TestCase):
    def setUp(self):
        self.resolver = IntrinsicsResolver({})

    @patch.object(IntrinsicsResolver, "_try_resolve_sam_resource_refs")
    @patch.object(IntrinsicsResolver, "_traverse")
    def test_resolve_sam_resource_refs(self, traverse_mock, try_resolve_mock):
        input = "foo"
        supported_refs = Mock()

        self.resolver.resolve_sam_resource_refs(input, supported_refs)
        traverse_mock.assert_called_once_with(input, supported_refs,
                                              try_resolve_mock)

    def test_resolve_sam_resource_refs_on_supported_intrinsic(self):
        action_mock = Mock()
        self.resolver.supported_intrinsics = {"foo": action_mock}
        input = {"foo": "bar"}
        supported_refs = Mock()

        self.resolver._try_resolve_sam_resource_refs(input, supported_refs)
        action_mock.resolve_resource_refs.assert_called_once_with(
            input, supported_refs)

    def test_resolve_sam_resource_refs_on_unknown_intrinsic(self):
        action_mock = Mock()
        self.resolver.supported_intrinsics = {"foo": action_mock}
        input = {"a": "b"}
        expected = {"a": "b"}
        supported_refs = Mock()

        result = self.resolver._try_resolve_sam_resource_refs(
            input, supported_refs)
        self.assertEqual(result, expected)
        action_mock.resolve_sam_resource_refs.assert_not_called()

    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_sam_resource_refs = Mock(
        )  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_sam_resource_refs(input, {}),
                         expected)
        resolver._try_resolve_sam_resource_refs.assert_not_called()
class TestResourceReferenceResolution(TestCase):

    def setUp(self):
        self.resolver = IntrinsicsResolver({})

    @patch.object(IntrinsicsResolver, "_try_resolve_sam_resource_refs")
    @patch.object(IntrinsicsResolver, "_traverse")
    def test_resolve_sam_resource_refs(self, traverse_mock, try_resolve_mock):
        input = "foo"
        supported_refs = Mock()

        self.resolver.resolve_sam_resource_refs(input, supported_refs)
        traverse_mock.assert_called_once_with(input, supported_refs, try_resolve_mock)

    def test_resolve_sam_resource_refs_on_supported_intrinsic(self):
        action_mock = Mock()
        self.resolver.supported_intrinsics = {
            "foo": action_mock
        }
        input = {"foo": "bar"}
        supported_refs = Mock()

        self.resolver._try_resolve_sam_resource_refs(input, supported_refs)
        action_mock.resolve_resource_refs.assert_called_once_with(input, supported_refs)

    def test_resolve_sam_resource_refs_on_unknown_intrinsic(self):
        action_mock = Mock()
        self.resolver.supported_intrinsics = {
            "foo": action_mock
        }
        input = {"a": "b"}
        expected = {"a": "b"}
        supported_refs = Mock()

        result = self.resolver._try_resolve_sam_resource_refs(input, supported_refs)
        self.assertEqual(result, expected)
        action_mock.resolve_sam_resource_refs.assert_not_called()

    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_sam_resource_refs = Mock()  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_sam_resource_refs(input, {}), expected)
        resolver._try_resolve_sam_resource_refs.assert_not_called()
    def test_configure_supported_intrinsics_must_error_for_non_action_value(self):
        class SomeAction(Action):
            intrinsic_name = "Foo"

        # All intrinsics must have a value to be subclass of "Action"
        supported_intrinsics = {"A": "B", "Foo": SomeAction()}
        with self.assertRaises(TypeError):
            IntrinsicsResolver({}, supported_intrinsics)
    def test_by_default_all_intrinsics_must_be_supported(self):
        # Just make sure we never remove support for some intrinsic
        resolver = IntrinsicsResolver({})

        # This needs to be updated when new intrinsics are added
        self.assertEqual(3, len(resolver.supported_intrinsics))
        self.assertTrue("Ref" in resolver.supported_intrinsics)
        self.assertTrue("Fn::Sub" in resolver.supported_intrinsics)
        self.assertTrue("Fn::GetAtt" in resolver.supported_intrinsics)
    def test_configure_supported_intrinsics(self):
        class SomeAction(Action):
            intrinsic_name = "IntrinsicName"

        action = SomeAction()
        supported_intrinsics = {"ThisCanBeAnyIntrinsicName": action}
        resolver = IntrinsicsResolver({}, supported_intrinsics)

        self.assertEqual(resolver.supported_intrinsics, {"ThisCanBeAnyIntrinsicName": action})
class TestOpenApi(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {
            "foo": "bar"
        },
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_open_api_3_no_stage(self):
        api = SamApi("foo")
        api.OpenApiVersion = "3.0"

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [
            x for x in resources if isinstance(x, ApiGatewayDeployment)
        ]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].StageName, None)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_open_api_2_no_stage(self):
        api = SamApi("foo")
        api.OpenApiVersion = "3.0"

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [
            x for x in resources if isinstance(x, ApiGatewayDeployment)
        ]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].StageName, None)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_open_api_bad_value(self):
        api = SamApi("foo")
        api.OpenApiVersion = "5.0"
        with pytest.raises(InvalidResourceException):
            api.to_cloudformation(**self.kwargs)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_swagger_no_stage(self):
        api = SamApi("foo")

        resources = api.to_cloudformation(**self.kwargs)
        deployment = [
            x for x in resources if isinstance(x, ApiGatewayDeployment)
        ]

        self.assertEqual(deployment.__len__(), 1)
        self.assertEqual(deployment[0].StageName, "Stage")
class TestAssumeRolePolicyDocument(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {"foo": "bar"},
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_assume_role_policy_document(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"

        assume_role_policy_document = {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": ["sts:AssumeRole"],
                    "Effect": "Allow",
                    "Principal": {"Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]},
                }
            ],
        }

        function.AssumeRolePolicyDocument = assume_role_policy_document

        cfnResources = function.to_cloudformation(**self.kwargs)
        generateFunctionVersion = [x for x in cfnResources if isinstance(x, IAMRole)]
        self.assertEqual(generateFunctionVersion[0].AssumeRolePolicyDocument, assume_role_policy_document)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_without_assume_role_policy_document(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"

        assume_role_policy_document = {
            "Version": "2012-10-17",
            "Statement": [
                {"Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": {"Service": ["lambda.amazonaws.com"]}}
            ],
        }

        cfnResources = function.to_cloudformation(**self.kwargs)
        generateFunctionVersion = [x for x in cfnResources if isinstance(x, IAMRole)]
        self.assertEqual(generateFunctionVersion[0].AssumeRolePolicyDocument, assume_role_policy_document)
class TestCodeUri(TestCase):
    kwargs = {
        'intrinsics_resolver': IntrinsicsResolver({}),
        'event_resources': [],
        'managed_policy_map': {
            "foo": "bar"
        }
    }

    def test_with_code_uri(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [
            x for x in cfnResources if isinstance(x, LambdaFunction)
        ]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code, {
            "S3Key": "foo.zip",
            "S3Bucket": "foobar",
        })

    def test_with_zip_file(self):
        function = SamFunction("foo")
        function.InlineCode = "hello world"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [
            x for x in cfnResources if isinstance(x, LambdaFunction)
        ]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code,
                         {"ZipFile": "hello world"})

    def test_with_no_code_uri_or_zipfile(self):
        function = SamFunction("foo")
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)
class TestLayers(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {"foo": "bar"},
    }

    def test_basic_layer(self):
        layer = SamLayerVersion("foo")
        layer.ContentUri = "s3://foobar/foo.zip"
        cfnResources = layer.to_cloudformation(**self.kwargs)
        generatedLayerList = [x for x in cfnResources if isinstance(x, LambdaLayerVersion)]
        self.assertEqual(cfnResources.__len__(), 1)
        self.assertTrue(isinstance(cfnResources[0], LambdaLayerVersion))
        self.assertEqual(cfnResources[0].Content, {"S3Key": "foo.zip", "S3Bucket": "foobar"})

    def test_invalid_compatible_architectures(self):
        layer = SamLayerVersion("foo")
        layer.ContentUri = "s3://foobar/foo.zip"
        invalid_architectures = [["arm"], [1], "arm", 1, True]
        for architecturea in invalid_architectures:
            layer.CompatibleArchitectures = architecturea
            with pytest.raises(InvalidResourceException):
                layer.to_cloudformation(**self.kwargs)
 def test_throw_on_empty_parameters(self):
     with self.assertRaises(InvalidDocumentException):
         IntrinsicsResolver(None).resolve_parameter_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
        """
        sam_parameter_values = SamParameterValues(parameter_values)
        sam_parameter_values.add_default_parameter_values(sam_template)
        sam_parameter_values.add_pseudo_parameter_values()
        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)
        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) == 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)
Beispiel #24
0
    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)
 def _get_intrinsic_resolvers(self, mappings):
     return [
         IntrinsicsResolver(self._parameters),
         IntrinsicsResolver(
             mappings, {FindInMapAction.intrinsic_name: FindInMapAction()}),
     ]
 def test_throw_on_non_dict_parameters(self):
     with self.assertRaises(InvalidDocumentException):
         IntrinsicsResolver([1, 2, 3]).resolve_parameter_refs({})
class TestParameterReferenceResolution(TestCase):
    def setUp(self):
        self.parameter_values = {
            "param1": "value1",
            "param2": "value2",
            "param3": "value3"
        }

        self.resolver = IntrinsicsResolver(self.parameter_values)

    def test_must_resolve_top_level_direct_refs(self):
        input = {
            "key1": {
                "Ref": "param1"
            },
            "key2": {
                "Ref": "param2"
            },
            "key3": {
                "a": "b"
            }
        }

        expected = {
            "key1": self.parameter_values["param1"],
            "key2": self.parameter_values["param2"],
            "key3": {
                "a": "b"
            },
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_nested_refs(self):
        input = {
            "key1": {
                "sub1": {
                    "sub2": {
                        "sub3": {
                            "Ref": "param1"
                        },
                        "list": [1, "b", {
                            "Ref": "param2"
                        }]
                    }
                }
            }
        }

        expected = {
            "key1": {
                "sub1": {
                    "sub2": {
                        "sub3": self.parameter_values["param1"],
                        "list": [1, "b", self.parameter_values["param2"]]
                    }
                }
            }
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_direct_refs(self):
        input = {"Ref": "param1"}
        expected = self.parameter_values["param1"]

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_array_refs(self):
        input = ["foo", 1, 2, {"Ref": "param1"}]
        expected = ["foo", 1, 2, self.parameter_values["param1"]]

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_skip_unknown_refs(self):
        input = {"key1": {"Ref": "someresource"}, "key2": {"Ref": "param1"}}

        expected = {
            "key1": {
                "Ref": "someresource"
            },
            "key2": self.parameter_values["param1"]
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_inside_sub_strings(self):
        input = {
            "Fn::Sub": "prefix ${param1} ${param2} ${param3} ${param1} suffix"
        }

        expected = {"Fn::Sub": "prefix value1 value2 value3 value1 suffix"}

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_skip_over_sub_literals(self):
        input = {"Fn::Sub": "prefix ${!MustNotBeReplaced} suffix"}

        expected = {"Fn::Sub": "prefix ${!MustNotBeReplaced} suffix"}

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_refs_inside_other_intrinsics(self):
        input = {
            "key1": {
                "Fn::Join": ["-", [{
                    "Ref": "param1"
                }, "some other value"]]
            }
        }

        expected = {
            "key1": {
                "Fn::Join":
                ["-", [self.parameter_values["param1"], "some other value"]]
            }
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_skip_invalid_values_for_ref(self):
        input = {"Ref": ["ref cannot have list value"]}

        expected = {"Ref": ["ref cannot have list value"]}
        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_skip_invalid_values_for_sub(self):
        input = {
            # Invalid Sub resource, must never be parsed, and must not error out
            "Fn::Sub": [{
                "a": "b"
            }]
        }

        expected = {"Fn::Sub": [{"a": "b"}]}

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_throw_on_empty_parameters(self):
        with self.assertRaises(TypeError):
            IntrinsicsResolver(None).resolve_parameter_refs({})

    def test_throw_on_non_dict_parameters(self):
        with self.assertRaises(TypeError):
            IntrinsicsResolver([1, 2, 3]).resolve_parameter_refs({})

    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_parameter_refs = Mock(
        )  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_parameter_refs(input), expected)
        resolver._try_resolve_parameter_refs.assert_not_called()
class TestHttpApiDescription(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {"foo": "bar"},
    }

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_no_description(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.DefinitionBody = {
            "openapi": "3.0.1",
            "paths": {"/foo": {}, "/bar": {}},
            "info": {"description": "existing description"},
        }

        resources = sam_http_api.to_cloudformation(**self.kwargs)
        http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
        self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "existing description")

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_no_definition_body(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.Description = "my description"

        with self.assertRaises(InvalidResourceException) as context:
            sam_http_api.to_cloudformation(**self.kwargs)
        self.assertEqual(
            context.exception.message,
            "Resource with id [foo] is invalid. "
            "Description works only with inline OpenApi specified in the 'DefinitionBody' property.",
        )

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_description_defined_in_definition_body(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.DefinitionBody = {
            "openapi": "3.0.1",
            "paths": {"/foo": {}, "/bar": {}},
            "info": {"description": "existing description"},
        }
        sam_http_api.Description = "new description"

        with self.assertRaises(InvalidResourceException) as context:
            sam_http_api.to_cloudformation(**self.kwargs)
        self.assertEqual(
            context.exception.message,
            "Resource with id [foo] is invalid. "
            "Unable to set Description because it is already defined within inline OpenAPI specified in the "
            "'DefinitionBody' property.",
        )

    @patch("boto3.session.Session.region_name", "eu-central-1")
    def test_with_description_not_defined_in_definition_body(self):
        sam_http_api = SamHttpApi("foo")
        sam_http_api.DefinitionBody = {"openapi": "3.0.1", "paths": {"/foo": {}}, "info": {}}
        sam_http_api.Description = "new description"

        resources = sam_http_api.to_cloudformation(**self.kwargs)
        http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)]
        self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description")
 def setUp(self):
     self.resolver = IntrinsicsResolver({})
 def setUp(self):
     self.resolver = IntrinsicsResolver({})
class TestParameterReferenceResolution(TestCase):

    def setUp(self):
        self.parameter_values = {
            "param1": "value1",
            "param2": "value2",
            "param3": "value3"
        }

        self.resolver = IntrinsicsResolver(self.parameter_values)

    def test_must_resolve_top_level_direct_refs(self):
        input = {
            "key1": {
                "Ref": "param1"
            },
            "key2": {
                "Ref": "param2"
            },
            "key3": {
                "a": "b"
            }
        }

        expected = {
            "key1": self.parameter_values["param1"],
            "key2": self.parameter_values["param2"],
            "key3": {
                "a": "b"
            }
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_nested_refs(self):
        input = {
            "key1": {
                "sub1": {
                    "sub2": {
                        "sub3": {
                            "Ref": "param1"
                        },
                        "list": [1, "b", {"Ref": "param2"}]
                    }
                }
            }
        }

        expected = {
            "key1": {
                "sub1": {
                    "sub2": {
                        "sub3": self.parameter_values["param1"],
                        "list": [1, "b", self.parameter_values["param2"]]
                    }
                }
            }
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_direct_refs(self):
        input = {"Ref": "param1"}
        expected = self.parameter_values["param1"]

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_array_refs(self):
        input = ["foo", 1, 2, {"Ref": "param1"}]
        expected = ["foo", 1, 2, self.parameter_values["param1"]]

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_skip_unknown_refs(self):
        input = {
            "key1": {
                "Ref": "someresource"
            },
            "key2": {
                "Ref": "param1"
            }
        }

        expected = {
            "key1": {
                "Ref": "someresource"
            },
            "key2": self.parameter_values["param1"]
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_inside_sub_strings(self):
        input = {
            "Fn::Sub": "prefix ${param1} ${param2} ${param3} ${param1} suffix"
        }

        expected = {
            "Fn::Sub": "prefix value1 value2 value3 value1 suffix"
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_skip_over_sub_literals(self):
        input = {
            "Fn::Sub": "prefix ${!MustNotBeReplaced} suffix"
        }

        expected = {
            "Fn::Sub": "prefix ${!MustNotBeReplaced} suffix"
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_must_resolve_refs_inside_other_intrinsics(self):
        input = {
            "key1": {
                "Fn::Join": ["-", [{"Ref": "param1"}, "some other value"]]
            }
        }

        expected = {
            "key1": {
                "Fn::Join": ["-", [self.parameter_values["param1"], "some other value"]]
            }
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_skip_invalid_values_for_ref(self):
        input = {
            "Ref": ["ref cannot have list value"]
        }

        expected = {
            "Ref": ["ref cannot have list value"]
        }
        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_skip_invalid_values_for_sub(self):
        input = {
            # Invalid Sub resource, must never be parsed, and must not error out
            "Fn::Sub": [{"a": "b"}]
        }

        expected = {
            "Fn::Sub": [{"a": "b"}]
        }

        output = self.resolver.resolve_parameter_refs(input)
        self.assertEqual(output, expected)

    def test_throw_on_empty_parameters(self):
        with self.assertRaises(TypeError):
            IntrinsicsResolver(None).resolve_parameter_refs({})

    def test_throw_on_non_dict_parameters(self):
        with self.assertRaises(TypeError):
            IntrinsicsResolver([1,2,3]).resolve_parameter_refs({})

    def test_short_circuit_on_empty_parameters(self):
        resolver = IntrinsicsResolver({})
        resolver._try_resolve_parameter_refs = Mock()  # Mock other methods to detect any actual calls
        input = {"Ref": "foo"}
        expected = {"Ref": "foo"}

        self.assertEqual(resolver.resolve_parameter_refs(input), expected)
        resolver._try_resolve_parameter_refs.assert_not_called()
 def test_throw_on_non_dict_parameters(self):
     with self.assertRaises(TypeError):
         IntrinsicsResolver([1, 2, 3]).resolve_parameter_refs({})
 def test_throw_on_empty_parameters(self):
     with self.assertRaises(TypeError):
         IntrinsicsResolver(None).resolve_parameter_refs({})
class TestArchitecture(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {"foo": "bar"},
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_unknown_architectures(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"
        invalid_architectures = [["arm"], [1], "arm", 1, {"my": "value"}, True, [], {}]
        for architecture in invalid_architectures:
            function.Architectures = architecture
            with pytest.raises(InvalidResourceException) as e:
                function.to_cloudformation(**self.kwargs)
            self.assertEqual(
                str(e.value.message),
                "Resource with id [foo] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`.",
            )

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_multiple_architectures(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"
        function.Architectures = ["arm64", "x86_64"]

        with pytest.raises(InvalidResourceException) as e:
            function.to_cloudformation(**self.kwargs)
        self.assertEqual(
            str(e.value.message),
            "Resource with id [foo] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`.",
        )

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_validate_architecture_with_intrinsic(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"
        function.Architectures = {"Ref": "MyRef"}

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Architectures, {"Ref": "MyRef"})

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_valid_architectures(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"
        valid_architectures = (["arm64"], ["x86_64"])

        for architecture in valid_architectures:
            function.Architectures = architecture
            cfnResources = function.to_cloudformation(**self.kwargs)
            generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)]
            self.assertEqual(generatedFunctionList.__len__(), 1)
            self.assertEqual(generatedFunctionList[0].Architectures, architecture)
    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 TestCodeUriandImageUri(TestCase):
    kwargs = {
        "intrinsics_resolver": IntrinsicsResolver({}),
        "event_resources": [],
        "managed_policy_map": {
            "foo": "bar"
        },
    }

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_code_uri(self):
        function = SamFunction("foo")
        function.CodeUri = "s3://foobar/foo.zip"
        function.Runtime = "foo"
        function.Handler = "bar"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [
            x for x in cfnResources if isinstance(x, LambdaFunction)
        ]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code, {
            "S3Key": "foo.zip",
            "S3Bucket": "foobar"
        })

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_zip_file(self):
        function = SamFunction("foo")
        function.InlineCode = "hello world"
        function.Runtime = "foo"
        function.Handler = "bar"

        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [
            x for x in cfnResources if isinstance(x, LambdaFunction)
        ]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code,
                         {"ZipFile": "hello world"})

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_no_code_uri_or_zipfile_or_no_image_uri(self):
        function = SamFunction("foo")
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_image_uri(self):
        function = SamFunction("foo")
        function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest"
        function.PackageType = IMAGE
        cfnResources = function.to_cloudformation(**self.kwargs)
        generatedFunctionList = [
            x for x in cfnResources if isinstance(x, LambdaFunction)
        ]
        self.assertEqual(generatedFunctionList.__len__(), 1)
        self.assertEqual(generatedFunctionList[0].Code,
                         {"ImageUri": function.ImageUri})

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_image_uri_layers_runtime_handler(self):
        function = SamFunction("foo")
        function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest"
        function.Layers = ["Layer1"]
        function.Runtime = "foo"
        function.Handler = "bar"
        function.PackageType = IMAGE
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_image_uri_package_type_zip(self):
        function = SamFunction("foo")
        function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest"
        function.PackageType = ZIP
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_image_uri_invalid_package_type(self):
        function = SamFunction("foo")
        function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest"
        function.PackageType = "fake"
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)

    @patch("boto3.session.Session.region_name", "ap-southeast-1")
    def test_with_image_uri_and_code_uri(self):
        function = SamFunction("foo")
        function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest"
        function.CodeUri = "s3://foobar/foo.zip"
        with pytest.raises(InvalidResourceException):
            function.to_cloudformation(**self.kwargs)
    def test_configure_supported_intrinsics_must_error_for_non_dict_input(
            self):

        with self.assertRaises(TypeError):
            IntrinsicsResolver({}, [1, 2, 3])