diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index bce449604e..71eb9f70cd 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -2,10 +2,18 @@ from typing import Any, Union from samtranslator.model.exceptions import ExceptionWithMessage, InvalidResourceAttributeTypeException +from samtranslator.plugins.globals.merge_strategy import REPLACE, REPLACE_KEYS_MERGE_VALUES, MergeOp, MergeRule from samtranslator.public.intrinsics import is_intrinsics from samtranslator.public.sdk.resource import SamResourceType from samtranslator.swagger.swagger import SwaggerEditor +# Per-property merge schema. Paths not listed here default to CONCATENATE (today's behavior). +CUSTOM_STRATEGIES: dict[str, MergeRule] = { + "Function.Architectures": REPLACE, + "CapacityProvider.InstanceRequirements.Architectures": REPLACE, + "CapacityProvider.ManagedResourceTags": REPLACE_KEYS_MERGE_VALUES, +} + class Globals: """ @@ -308,7 +316,12 @@ def _parse(self, globals_dict): # type: ignore[no-untyped-def] ) # Store all Global properties in a map with key being the AWS::Serverless::* resource type - _globals[resource_type] = GlobalProperties(properties) + resource_schema = { + k.removeprefix(f"{section_name}."): v + for k, v in CUSTOM_STRATEGIES.items() + if k.startswith(f"{section_name}.") + } + _globals[resource_type] = GlobalProperties(properties, schema=resource_schema) return _globals @@ -436,8 +449,9 @@ class GlobalProperties: """ - def __init__(self, global_properties) -> None: # type: ignore[no-untyped-def] + def __init__(self, global_properties, schema=None) -> None: # type: ignore[no-untyped-def] self.global_properties = global_properties + self.schema = schema or {} def merge(self, local_properties): # type: ignore[no-untyped-def] """ @@ -445,15 +459,16 @@ def merge(self, local_properties): # type: ignore[no-untyped-def] :return local_properties: Dictionary of local properties """ - return self._do_merge(self.global_properties, local_properties) # type: ignore[no-untyped-call] + return self._do_merge(self.global_properties, local_properties, path="") # type: ignore[no-untyped-call] - def _do_merge(self, global_value, local_value): # type: ignore[no-untyped-def] + def _do_merge(self, global_value, local_value, path=""): # type: ignore[no-untyped-def] # noqa: PLR0911 """ Actually perform the merge operation for the given inputs. This method is used as part of the recursion. Therefore input values can be of any type. So is the output. :param global_value: Global value to be merged :param local_value: Local value to be merged + :param path: Dot-delimited path for schema lookup :return: Merged result """ @@ -468,9 +483,19 @@ def _do_merge(self, global_value, local_value): # type: ignore[no-untyped-def] return self._prefer_local(global_value, local_value) # type: ignore[no-untyped-call] if self.TOKEN.DICT == token_global == token_local: - return self._merge_dict(global_value, local_value) # type: ignore[no-untyped-call] + rule = self.schema.get(path) + if rule and rule.op == MergeOp.REPLACE: + return local_value + if rule and rule.op == MergeOp.REPLACE_KEYS_MERGE_VALUES and local_value: + return self._replace_keys_merge_values(global_value, local_value, path) + return self._merge_dict(global_value, local_value, path) # type: ignore[no-untyped-call] if self.TOKEN.LIST == token_global == token_local: + rule = self.schema.get(path) + if rule and rule.op == MergeOp.REPLACE: + return local_value + if rule and rule.op == MergeOp.MERGE_BY_KEY: + return self._merge_by_key(global_value, local_value, rule.key) return self._merge_lists(global_value, local_value) # type: ignore[no-untyped-call] raise TypeError(f"Unsupported type of objects. GlobalType={token_global}, LocalType={token_local}") @@ -486,12 +511,67 @@ def _merge_lists(self, global_list, local_list): # type: ignore[no-untyped-def] return global_list + local_list - def _merge_dict(self, global_dict, local_dict): # type: ignore[no-untyped-def] + def _merge_by_key(self, global_list: list[Any], local_list: list[Any], key: str | None) -> list[Any]: + """ + Merges two lists of dicts by a shared key field. Local entries override global entries + with the same key value. Non-dict items and items without the key are preserved. + + :param global_list: Global list of dicts + :param local_list: Local list of dicts + :param key: The dict key to match on + :return: Merged list + """ + # First-entry-wins: skip keys already seen so both passes use consistent precedence + local_by_key: dict[Any, Any] = {} + for item in local_list: + if isinstance(item, dict) and key in item and item[key] not in local_by_key: + local_by_key[item[key]] = item + seen_keys: set[Any] = set() + result = [] + + # Pass 1: walk globals, replace matched keys with local override (deduplicate) + for item in global_list: + if isinstance(item, dict) and key in item and item[key] in local_by_key: + if item[key] not in seen_keys: + result.append(local_by_key[item[key]]) + seen_keys.add(item[key]) + # else: duplicate global entry — drop it (already replaced once) + else: + result.append(item) + + # Pass 2: append local items not already seen (new keys + non-dict overflow) + for item in local_list: + if isinstance(item, dict) and key in item: + if item[key] not in seen_keys: + result.append(item) + seen_keys.add(item[key]) + else: + result.append(item) + + return result + + def _replace_keys_merge_values( + self, global_dict: dict[str, Any], local_dict: dict[str, Any], path: str + ) -> dict[str, Any]: + """ + Only local's key-set survives. Shared keys have their values deep-merged + via existing recursion. Global keys not in local are dropped. + """ + result: dict[str, Any] = {} + for key, local_val in local_dict.items(): + if key in global_dict: + result[key] = self._do_merge(global_dict[key], local_val, f"{path}.{key}") # type: ignore[no-untyped-call] + else: + result[key] = local_val + return result + + def _merge_dict(self, global_dict, local_dict, path_prefix=""): # type: ignore[no-untyped-def] """ Merges the two dictionaries together :param global_dict: Global dictionary to be merged :param local_dict: Local dictionary to be merged + :param path_prefix: Current dot-delimited path prefix for schema lookup :return: New merged dictionary with values shallow copied """ @@ -499,9 +579,10 @@ def _merge_dict(self, global_dict, local_dict): # type: ignore[no-untyped-def] global_dict = global_dict.copy() for key in local_dict: + child_path = f"{path_prefix}.{key}".lstrip(".") if key in global_dict: # Both local & global contains the same key. Let's do a merge. - global_dict[key] = self._do_merge(global_dict[key], local_dict[key]) # type: ignore[no-untyped-call] + global_dict[key] = self._do_merge(global_dict[key], local_dict[key], child_path) # type: ignore[no-untyped-call] else: # Key is not in globals, just in local. Copy it over diff --git a/samtranslator/plugins/globals/merge_strategy.py b/samtranslator/plugins/globals/merge_strategy.py new file mode 100644 index 0000000000..0d96732eea --- /dev/null +++ b/samtranslator/plugins/globals/merge_strategy.py @@ -0,0 +1,34 @@ +"""Per-property merge strategy types for the Globals merge engine.""" + +from dataclasses import dataclass +from enum import Enum + + +class MergeOp(Enum): + CONCATENATE = "concatenate" + REPLACE = "replace" + MERGE_BY_KEY = "merge_by_key" + REPLACE_KEYS_MERGE_VALUES = "replace_keys_merge_values" + + +@dataclass(frozen=True) +class MergeRule: + op: MergeOp + key: str | None = None + + def __post_init__(self) -> None: + if self.op == MergeOp.MERGE_BY_KEY and not self.key: + raise ValueError("MERGE_BY_KEY requires a 'key' field") + if self.op not in (MergeOp.MERGE_BY_KEY,) and self.key is not None: + raise ValueError(f"'key' is only valid with MERGE_BY_KEY, not {self.op.value}") + + +# Explicit default; not needed in CUSTOM_STRATEGIES (unlisted paths already concatenate). +CONCATENATE = MergeRule(MergeOp.CONCATENATE) +REPLACE = MergeRule(MergeOp.REPLACE) +REPLACE_KEYS_MERGE_VALUES = MergeRule(MergeOp.REPLACE_KEYS_MERGE_VALUES) + + +def merge_by_key(key: str) -> MergeRule: + """Factory for MERGE_BY_KEY rules. Merges list-of-dicts by the named key field.""" + return MergeRule(MergeOp.MERGE_BY_KEY, key=key) diff --git a/tests/plugins/globals/test_globals.py b/tests/plugins/globals/test_globals.py index 9178244668..a32a0d1b01 100644 --- a/tests/plugins/globals/test_globals.py +++ b/tests/plugins/globals/test_globals.py @@ -4,6 +4,7 @@ from parameterized import parameterized from samtranslator.model.exceptions import InvalidResourceAttributeTypeException from samtranslator.plugins.globals.globals import GlobalProperties, Globals, InvalidGlobalsSectionException +from samtranslator.plugins.globals.merge_strategy import REPLACE, REPLACE_KEYS_MERGE_VALUES, merge_by_key class GlobalPropertiesTestCases: @@ -171,6 +172,93 @@ class GlobalPropertiesTestCases: mixed_type_inputs_must_be_handled = {"global": {"a": "b"}, "local": [1, 2, 3], "expected_output": [1, 2, 3]} + # Merge strategy: REPLACE — local list fully replaces global list (flat and nested paths). + # Add new test cases here when new rules are added to CUSTOM_STRATEGIES. + list_with_replace_strategy_must_use_local = { + "global": {"Architectures": ["x86_64"], "VpcConfig": {"SecurityGroupIds": ["sg-global"]}}, + "local": {"Architectures": ["arm64"], "VpcConfig": {"SecurityGroupIds": ["sg-local"]}}, + "expected_output": {"Architectures": ["arm64"], "VpcConfig": {"SecurityGroupIds": ["sg-local"]}}, + "schema": {"Architectures": REPLACE, "VpcConfig.SecurityGroupIds": REPLACE}, + } + + # Merge strategy: MERGE_BY_KEY — deduplicates by key field, local overrides; non-dict items preserved. + list_with_merge_by_key_strategy = { + "global": {"Tags": [{"Key": "env", "Value": "dev"}, {"Key": "team", "Value": "lambda"}, "plain-string"]}, + "local": {"Tags": [{"Key": "env", "Value": "prod"}, {"Key": "app", "Value": "my"}]}, + "expected_output": { + "Tags": [ + {"Key": "env", "Value": "prod"}, + {"Key": "team", "Value": "lambda"}, + "plain-string", + {"Key": "app", "Value": "my"}, + ] + }, + "schema": {"Tags": merge_by_key("Key")}, + } + + # Regression: duplicate keys in global list must not produce duplicate results + list_with_merge_by_key_deduplicates_global_duplicates = { + "global": {"Tags": [{"Key": "env", "Value": "a"}, {"Key": "env", "Value": "b"}]}, + "local": {"Tags": [{"Key": "env", "Value": "c"}]}, + "expected_output": {"Tags": [{"Key": "env", "Value": "c"}]}, + "schema": {"Tags": merge_by_key("Key")}, + } + + # Regression: duplicate keys in local list must not produce duplicate results + list_with_merge_by_key_deduplicates_local_duplicates = { + "global": {"Tags": [{"Key": "team", "Value": "lambda"}]}, + "local": {"Tags": [{"Key": "env", "Value": "a"}, {"Key": "env", "Value": "b"}]}, + "expected_output": {"Tags": [{"Key": "team", "Value": "lambda"}, {"Key": "env", "Value": "a"}]}, + "schema": {"Tags": merge_by_key("Key")}, + } + + # Regression: local duplicates with global override — first-wins must be consistent + list_with_merge_by_key_local_duplicates_with_global_override = { + "global": {"Tags": [{"Key": "env", "Value": "g"}]}, + "local": {"Tags": [{"Key": "env", "Value": "a"}, {"Key": "env", "Value": "b"}]}, + "expected_output": {"Tags": [{"Key": "env", "Value": "a"}]}, + "schema": {"Tags": merge_by_key("Key")}, + } + + # Multiple strategies applied to different properties in one merge. + multiple_strategies_applied_per_property = { + "global": {"Architectures": ["x86_64"], "Tags": [{"Key": "env", "Value": "dev"}], "Layers": ["arn:layer1"]}, + "local": {"Architectures": ["arm64"], "Tags": [{"Key": "env", "Value": "prod"}], "Layers": ["arn:layer2"]}, + "expected_output": { + "Architectures": ["arm64"], + "Tags": [{"Key": "env", "Value": "prod"}], + "Layers": ["arn:layer1", "arn:layer2"], + }, + "schema": {"Architectures": REPLACE, "Tags": merge_by_key("Key")}, + } + + # REPLACE_KEYS_MERGE_VALUES: local's key-set wins; shared keys deep-merge values. + # Combined case: different keys dropped + shared dict deep-merged + shared scalar local-wins + replace_keys_merge_values_complex = { + "global": {"MRT": {"Propagate": True, "Tags": {"team": "plat", "env": "dev"}, "Meta": ["x"]}}, + "local": {"MRT": {"Propagate": False, "Tags": {"env": "prod", "app": "svc"}, "Meta": ["y"]}}, + "expected_output": { + "MRT": {"Propagate": False, "Tags": {"team": "plat", "env": "prod", "app": "svc"}, "Meta": ["x", "y"]} + }, + "schema": {"MRT": REPLACE_KEYS_MERGE_VALUES}, + } + + # Key-dropping: global-only keys removed; local-only keys kept; shared key deep-merges + replace_keys_merge_values_key_drop = { + "global": {"MRT": {"Propagate": True, "Tags": {"team": "plat"}}}, + "local": {"MRT": {"Tags": {"env": "prod"}}}, + "expected_output": {"MRT": {"Tags": {"team": "plat", "env": "prod"}}}, + "schema": {"MRT": REPLACE_KEYS_MERGE_VALUES}, + } + + # Empty local {} — inherits global (falsy guard, strategy not invoked) + replace_keys_merge_values_empty_local_inherits = { + "global": {"MRT": {"Propagate": True}}, + "local": {"MRT": {}}, + "expected_output": {"MRT": {"Propagate": True}}, + "schema": {"MRT": REPLACE_KEYS_MERGE_VALUES}, + } + class TestGlobalPropertiesMerge(TestCase): # Get all attributes of the test case object which is not a built-in method like __str__ @@ -180,7 +268,8 @@ def test_global_properties_merge(self, testcase): if not configuration: raise Exception("Invalid configuration for test case " + testcase) - global_properties = GlobalProperties(configuration["global"]) + schema = configuration.get("schema", {}) + global_properties = GlobalProperties(configuration["global"], schema=schema) actual = global_properties.merge(configuration["local"]) self.assertEqual(actual, configuration["expected_output"]) @@ -532,3 +621,24 @@ def test_openapi_postprocess(self): global_obj = Globals(self.template) global_obj.fix_openapi_definitions(test["input"]) self.assertEqual(test["input"], test["expected"], test["name"]) + + +class TestMergeSchemaWiring(TestCase): + """Tests that require the full Globals(template) pipeline (schema slicing by resource type). + + Add new test cases to GlobalPropertiesTestCases for merge behavior. + Only add here for wiring-specific tests (IgnoreGlobals interaction, resource-type routing). + """ + + def test_ignore_globals_skips_schema(self): + """IgnoreGlobals for a registered property means schema is never consulted.""" + template = {"Globals": {"Function": {"Architectures": ["arm64"], "Runtime": "python3.12"}}} + g = Globals(template) + result = g.merge( + "AWS::Serverless::Function", + {"Architectures": ["x86_64"]}, + logical_id="MyFunc", + ignore_globals=["Architectures"], + ) + self.assertEqual(result["Architectures"], ["x86_64"]) + self.assertEqual(result["Runtime"], "python3.12") diff --git a/tests/plugins/globals/test_merge_strategy.py b/tests/plugins/globals/test_merge_strategy.py new file mode 100644 index 0000000000..c2b2b06483 --- /dev/null +++ b/tests/plugins/globals/test_merge_strategy.py @@ -0,0 +1,100 @@ +"""Unit tests for merge_strategy.py types.""" + +import unittest + +from parameterized import parameterized +from samtranslator.plugins.globals.merge_strategy import ( + CONCATENATE, + REPLACE, + REPLACE_KEYS_MERGE_VALUES, + MergeOp, + MergeRule, + merge_by_key, +) + + +class TestMergeOp(unittest.TestCase): + @parameterized.expand( + [ + ("concatenate", MergeOp.CONCATENATE, "concatenate"), + ("replace", MergeOp.REPLACE, "replace"), + ("merge_by_key", MergeOp.MERGE_BY_KEY, "merge_by_key"), + ("replace_keys_merge_values", MergeOp.REPLACE_KEYS_MERGE_VALUES, "replace_keys_merge_values"), + ] + ) + def test_enum_values(self, _name, member, expected): + self.assertEqual(member.value, expected) + + +class TestMergeRule(unittest.TestCase): + @parameterized.expand( + [ + ("replace", MergeOp.REPLACE, None), + ("concatenate", MergeOp.CONCATENATE, None), + ("merge_by_key", MergeOp.MERGE_BY_KEY, "Key"), + ("replace_keys_merge_values", MergeOp.REPLACE_KEYS_MERGE_VALUES, None), + ] + ) + def test_valid_creation(self, _name, op, key): + rule = MergeRule(op, key=key) if key else MergeRule(op) + self.assertEqual(rule.op, op) + self.assertEqual(rule.key, key) + + @parameterized.expand( + [ + ("merge_by_key_no_key", MergeOp.MERGE_BY_KEY, None, "MERGE_BY_KEY requires a 'key' field"), + ("replace_with_key", MergeOp.REPLACE, "Bad", "only valid with MERGE_BY_KEY"), + ("concatenate_with_key", MergeOp.CONCATENATE, "Bad", "only valid with MERGE_BY_KEY"), + ( + "replace_keys_merge_values_with_key", + MergeOp.REPLACE_KEYS_MERGE_VALUES, + "Bad", + "only valid with MERGE_BY_KEY", + ), + ] + ) + def test_invalid_creation_raises(self, _name, op, key, expected_msg): + with self.assertRaises(ValueError) as ctx: + MergeRule(op, key=key) + self.assertIn(expected_msg, str(ctx.exception)) + + def test_frozen_immutable(self): + rule = MergeRule(MergeOp.REPLACE) + with self.assertRaises(AttributeError): + rule.op = MergeOp.CONCATENATE + + +class TestConvenienceConstructors(unittest.TestCase): + @parameterized.expand( + [ + ("CONCATENATE", CONCATENATE, MergeOp.CONCATENATE, None), + ("REPLACE", REPLACE, MergeOp.REPLACE, None), + ("REPLACE_KEYS_MERGE_VALUES", REPLACE_KEYS_MERGE_VALUES, MergeOp.REPLACE_KEYS_MERGE_VALUES, None), + ("MERGE_BY_KEY", merge_by_key("Key"), MergeOp.MERGE_BY_KEY, "Key"), + ] + ) + def test_constructor(self, _name, rule, expected_op, expected_key): + self.assertEqual(rule.op, expected_op) + self.assertEqual(rule.key, expected_key) + + +class TestSchemaKeyFormat(unittest.TestCase): + """Dot-notation schema keys support nested property paths.""" + + @parameterized.expand( + [ + ("top_level", "Architectures"), + ("one_level_nested", "VpcConfig.SecurityGroupIds"), + ("two_levels_nested", "VpcConfig.SubnetConfig.SubnetIds"), + ] + ) + def test_valid_dot_notation_keys(self, _name, key): + """Dot-separated paths are the schema key format — all valid.""" + schema = {key: REPLACE} + # Should not raise — dots are path separators, not errors + self.assertIn(key, schema) + self.assertEqual(schema[key], REPLACE) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/translator/input/capacity_provider_global_with_functions.yaml b/tests/translator/input/capacity_provider_global_with_functions.yaml index 55a4999665..0bb15961e0 100644 --- a/tests/translator/input/capacity_provider_global_with_functions.yaml +++ b/tests/translator/input/capacity_provider_global_with_functions.yaml @@ -163,6 +163,19 @@ Resources: MinExecutionEnvironments: 3 # support partial override AutoPublishAlias: Development + # CapacityProvider that overrides global Architectures (tests REPLACE merge strategy) + CpOverridesArchitectures: + Type: AWS::Serverless::CapacityProvider + Properties: + VpcConfig: + SecurityGroupIds: + - sg-12345678 + SubnetIds: + - subnet-12345678 + InstanceRequirements: + Architectures: + - arm64 + Outputs: CapacityProviderArn: Description: ARN of the created capacity provider diff --git a/tests/translator/input/capacity_provider_managed_resource_tags.yaml b/tests/translator/input/capacity_provider_managed_resource_tags.yaml index c02d0c5146..17c2b3a7b2 100644 --- a/tests/translator/input/capacity_provider_managed_resource_tags.yaml +++ b/tests/translator/input/capacity_provider_managed_resource_tags.yaml @@ -46,3 +46,16 @@ Resources: Tags: Environment: !Ref Environment Team: !Sub '${AWS::StackName}-tooling' + + # REPLACE_KEYS_MERGE_VALUES: local has only Tags → global Propagate key is dropped + CpOverrideStrategyDropsGlobalKey: + Type: AWS::Serverless::CapacityProvider + Properties: + VpcConfig: + SubnetIds: + - subnet-44444444 + SecurityGroupIds: + - sg-44444444 + ManagedResourceTags: + Tags: + App: my-service diff --git a/tests/translator/input/error_capacity_provider_managed_resource_tags_mutual_exclusion.yaml b/tests/translator/input/error_capacity_provider_managed_resource_tags_mutual_exclusion.yaml index 9e08fb4820..59c9f3f72b 100644 --- a/tests/translator/input/error_capacity_provider_managed_resource_tags_mutual_exclusion.yaml +++ b/tests/translator/input/error_capacity_provider_managed_resource_tags_mutual_exclusion.yaml @@ -18,14 +18,3 @@ Resources: Propagate: true Tags: Environment: Production - - CpExplicitTagsConflictWithGlobal: - Type: AWS::Serverless::CapacityProvider - Properties: - VpcConfig: - SubnetIds: - - subnet-33333333 - ManagedResourceTags: - Tags: - Environment: Production - Team: Tooling diff --git a/tests/translator/input/globals_merge_strategy_architectures.yaml b/tests/translator/input/globals_merge_strategy_architectures.yaml new file mode 100644 index 0000000000..137e491bc0 --- /dev/null +++ b/tests/translator/input/globals_merge_strategy_architectures.yaml @@ -0,0 +1,46 @@ +# Merge strategy translator-level tests. +# Add new test cases here when new rules are added to CUSTOM_STRATEGIES. +Globals: + Function: + Runtime: python3.12 + Handler: app.handler + Architectures: + - x86_64 + CapacityProvider: + InstanceRequirements: + Architectures: + - x86_64 + +Resources: + FunctionInheritsGlobalArch: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/code.zip + + FunctionOverridesArch: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/code.zip + Architectures: + - arm64 + + CpInheritsGlobalArch: + Type: AWS::Serverless::CapacityProvider + Properties: + VpcConfig: + SecurityGroupIds: + - sg-12345678 + SubnetIds: + - subnet-12345678 + + CpOverridesArch: + Type: AWS::Serverless::CapacityProvider + Properties: + VpcConfig: + SecurityGroupIds: + - sg-12345678 + SubnetIds: + - subnet-12345678 + InstanceRequirements: + Architectures: + - arm64 diff --git a/tests/translator/output/aws-cn/capacity_provider_global_with_functions.json b/tests/translator/output/aws-cn/capacity_provider_global_with_functions.json index cb8ad07016..fb45916d9d 100644 --- a/tests/translator/output/aws-cn/capacity_provider_global_with_functions.json +++ b/tests/translator/output/aws-cn/capacity_provider_global_with_functions.json @@ -92,6 +92,76 @@ } }, "Resources": { + "CpOverridesArchitectures": { + "Properties": { + "CapacityProviderScalingConfig": { + "ScalingMode": "Manual", + "ScalingPolicies": [ + { + "PredefinedMetricType": "LambdaCapacityProviderAverageCPUUtilization", + "TargetValue": { + "Fn::If": [ + "IsProd", + 80.0, + 70.0 + ] + } + } + ] + }, + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/some-kms-key", + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CustomPermissionsCapacityProviderOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "Environment", + "Value": { + "Ref": "Environment" + } + }, + { + "Key": "Team", + "Value": { + "Ref": "Team" + } + }, + { + "Key": "ManagedBy", + "Value": "SAM" + }, + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + { + "Ref": "SecurityGroupId" + }, + "sg-12345678" + ], + "SubnetIds": [ + { + "Ref": "SubnetId" + }, + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, "CustomPermissionsCapacityProviderOperatorRole": { "Properties": { "AssumeRolePolicyDocument": { diff --git a/tests/translator/output/aws-cn/capacity_provider_managed_resource_tags.json b/tests/translator/output/aws-cn/capacity_provider_managed_resource_tags.json index 5cce90880f..ce336beb56 100644 --- a/tests/translator/output/aws-cn/capacity_provider_managed_resource_tags.json +++ b/tests/translator/output/aws-cn/capacity_provider_managed_resource_tags.json @@ -128,6 +128,72 @@ }, "Type": "AWS::IAM::Role" }, + "CpOverrideStrategyDropsGlobalKey": { + "Properties": { + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverrideStrategyDropsGlobalKeyOperatorRole", + "Arn" + ] + } + }, + "PropagateTags": { + "ExplicitTags": [ + { + "Key": "App", + "Value": "my-service" + } + ], + "Mode": "Explicit" + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-44444444" + ], + "SubnetIds": [ + "subnet-44444444" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverrideStrategyDropsGlobalKeyOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, "CpOverrideWithExplicitTags": { "Properties": { "PermissionsConfig": { diff --git a/tests/translator/output/aws-cn/globals_merge_strategy_architectures.json b/tests/translator/output/aws-cn/globals_merge_strategy_architectures.json new file mode 100644 index 0000000000..ff6a38ac02 --- /dev/null +++ b/tests/translator/output/aws-cn/globals_merge_strategy_architectures.json @@ -0,0 +1,240 @@ +{ + "Resources": { + "CpInheritsGlobalArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "x86_64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpInheritsGlobalArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpInheritsGlobalArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "CpOverridesArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverridesArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverridesArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionInheritsGlobalArch": { + "Properties": { + "Architectures": [ + "x86_64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionInheritsGlobalArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionInheritsGlobalArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionOverridesArch": { + "Properties": { + "Architectures": [ + "arm64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionOverridesArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionOverridesArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/capacity_provider_global_with_functions.json b/tests/translator/output/aws-us-gov/capacity_provider_global_with_functions.json index 12f0593931..72fd03e6c3 100644 --- a/tests/translator/output/aws-us-gov/capacity_provider_global_with_functions.json +++ b/tests/translator/output/aws-us-gov/capacity_provider_global_with_functions.json @@ -92,6 +92,76 @@ } }, "Resources": { + "CpOverridesArchitectures": { + "Properties": { + "CapacityProviderScalingConfig": { + "ScalingMode": "Manual", + "ScalingPolicies": [ + { + "PredefinedMetricType": "LambdaCapacityProviderAverageCPUUtilization", + "TargetValue": { + "Fn::If": [ + "IsProd", + 80.0, + 70.0 + ] + } + } + ] + }, + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/some-kms-key", + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CustomPermissionsCapacityProviderOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "Environment", + "Value": { + "Ref": "Environment" + } + }, + { + "Key": "Team", + "Value": { + "Ref": "Team" + } + }, + { + "Key": "ManagedBy", + "Value": "SAM" + }, + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + { + "Ref": "SecurityGroupId" + }, + "sg-12345678" + ], + "SubnetIds": [ + { + "Ref": "SubnetId" + }, + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, "CustomPermissionsCapacityProviderOperatorRole": { "Properties": { "AssumeRolePolicyDocument": { diff --git a/tests/translator/output/aws-us-gov/capacity_provider_managed_resource_tags.json b/tests/translator/output/aws-us-gov/capacity_provider_managed_resource_tags.json index 93cbb50bcf..2b77e99479 100644 --- a/tests/translator/output/aws-us-gov/capacity_provider_managed_resource_tags.json +++ b/tests/translator/output/aws-us-gov/capacity_provider_managed_resource_tags.json @@ -128,6 +128,72 @@ }, "Type": "AWS::IAM::Role" }, + "CpOverrideStrategyDropsGlobalKey": { + "Properties": { + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverrideStrategyDropsGlobalKeyOperatorRole", + "Arn" + ] + } + }, + "PropagateTags": { + "ExplicitTags": [ + { + "Key": "App", + "Value": "my-service" + } + ], + "Mode": "Explicit" + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-44444444" + ], + "SubnetIds": [ + "subnet-44444444" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverrideStrategyDropsGlobalKeyOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, "CpOverrideWithExplicitTags": { "Properties": { "PermissionsConfig": { diff --git a/tests/translator/output/aws-us-gov/globals_merge_strategy_architectures.json b/tests/translator/output/aws-us-gov/globals_merge_strategy_architectures.json new file mode 100644 index 0000000000..a3a4d0b7ab --- /dev/null +++ b/tests/translator/output/aws-us-gov/globals_merge_strategy_architectures.json @@ -0,0 +1,240 @@ +{ + "Resources": { + "CpInheritsGlobalArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "x86_64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpInheritsGlobalArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpInheritsGlobalArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "CpOverridesArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverridesArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverridesArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionInheritsGlobalArch": { + "Properties": { + "Architectures": [ + "x86_64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionInheritsGlobalArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionInheritsGlobalArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionOverridesArch": { + "Properties": { + "Architectures": [ + "arm64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionOverridesArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionOverridesArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/capacity_provider_global_with_functions.json b/tests/translator/output/capacity_provider_global_with_functions.json index 936e27ca98..03de028bb0 100644 --- a/tests/translator/output/capacity_provider_global_with_functions.json +++ b/tests/translator/output/capacity_provider_global_with_functions.json @@ -92,6 +92,76 @@ } }, "Resources": { + "CpOverridesArchitectures": { + "Properties": { + "CapacityProviderScalingConfig": { + "ScalingMode": "Manual", + "ScalingPolicies": [ + { + "PredefinedMetricType": "LambdaCapacityProviderAverageCPUUtilization", + "TargetValue": { + "Fn::If": [ + "IsProd", + 80.0, + 70.0 + ] + } + } + ] + }, + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/some-kms-key", + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CustomPermissionsCapacityProviderOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "Environment", + "Value": { + "Ref": "Environment" + } + }, + { + "Key": "Team", + "Value": { + "Ref": "Team" + } + }, + { + "Key": "ManagedBy", + "Value": "SAM" + }, + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + { + "Ref": "SecurityGroupId" + }, + "sg-12345678" + ], + "SubnetIds": [ + { + "Ref": "SubnetId" + }, + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, "CustomPermissionsCapacityProviderOperatorRole": { "Properties": { "AssumeRolePolicyDocument": { diff --git a/tests/translator/output/capacity_provider_managed_resource_tags.json b/tests/translator/output/capacity_provider_managed_resource_tags.json index 09af81aac6..c4cbd291b8 100644 --- a/tests/translator/output/capacity_provider_managed_resource_tags.json +++ b/tests/translator/output/capacity_provider_managed_resource_tags.json @@ -128,6 +128,72 @@ }, "Type": "AWS::IAM::Role" }, + "CpOverrideStrategyDropsGlobalKey": { + "Properties": { + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverrideStrategyDropsGlobalKeyOperatorRole", + "Arn" + ] + } + }, + "PropagateTags": { + "ExplicitTags": [ + { + "Key": "App", + "Value": "my-service" + } + ], + "Mode": "Explicit" + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-44444444" + ], + "SubnetIds": [ + "subnet-44444444" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverrideStrategyDropsGlobalKeyOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, "CpOverrideWithExplicitTags": { "Properties": { "PermissionsConfig": { diff --git a/tests/translator/output/error_capacity_provider_managed_resource_tags_mutual_exclusion.json b/tests/translator/output/error_capacity_provider_managed_resource_tags_mutual_exclusion.json index 9b28acea69..235b125bdd 100644 --- a/tests/translator/output/error_capacity_provider_managed_resource_tags_mutual_exclusion.json +++ b/tests/translator/output/error_capacity_provider_managed_resource_tags_mutual_exclusion.json @@ -1,11 +1,9 @@ { "_autoGeneratedBreakdownErrorMessage": [ "Invalid Serverless Application Specification document. ", - "Number of errors found: 2. ", + "Number of errors found: 1. ", "Resource with id [CpBothSet] is invalid. ", - "Cannot specify 'ManagedResourceTags.Propagate=True' and 'ManagedResourceTags.Tags' together. ", - "Resource with id [CpExplicitTagsConflictWithGlobal] is invalid. ", "Cannot specify 'ManagedResourceTags.Propagate=True' and 'ManagedResourceTags.Tags' together." ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [CpBothSet] is invalid. Cannot specify 'ManagedResourceTags.Propagate=True' and 'ManagedResourceTags.Tags' together. Resource with id [CpExplicitTagsConflictWithGlobal] is invalid. Cannot specify 'ManagedResourceTags.Propagate=True' and 'ManagedResourceTags.Tags' together." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [CpBothSet] is invalid. Cannot specify 'ManagedResourceTags.Propagate=True' and 'ManagedResourceTags.Tags' together." } diff --git a/tests/translator/output/globals_merge_strategy_architectures.json b/tests/translator/output/globals_merge_strategy_architectures.json new file mode 100644 index 0000000000..4b0d18efc0 --- /dev/null +++ b/tests/translator/output/globals_merge_strategy_architectures.json @@ -0,0 +1,240 @@ +{ + "Resources": { + "CpInheritsGlobalArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "x86_64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpInheritsGlobalArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpInheritsGlobalArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "CpOverridesArch": { + "Properties": { + "InstanceRequirements": { + "Architectures": [ + "arm64" + ] + }, + "PermissionsConfig": { + "CapacityProviderOperatorRoleArn": { + "Fn::GetAtt": [ + "CpOverridesArchOperatorRole", + "Arn" + ] + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "VpcConfig": { + "SecurityGroupIds": [ + "sg-12345678" + ], + "SubnetIds": [ + "subnet-12345678" + ] + } + }, + "Type": "AWS::Lambda::CapacityProvider" + }, + "CpOverridesArchOperatorRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/AWSLambdaManagedEC2ResourceOperator" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionInheritsGlobalArch": { + "Properties": { + "Architectures": [ + "x86_64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionInheritsGlobalArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionInheritsGlobalArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "FunctionOverridesArch": { + "Properties": { + "Architectures": [ + "arm64" + ], + "Code": { + "S3Bucket": "bucket", + "S3Key": "code.zip" + }, + "Handler": "app.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionOverridesArchRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "FunctionOverridesArchRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +}