From 4579ca3a37cb450481f05f8532781dc641a4f069 Mon Sep 17 00:00:00 2001 From: Allen Helton Date: Thu, 11 Jun 2026 06:31:05 -0500 Subject: [PATCH] fix Globals.Function Architectures override behavior Signed-off-by: Allen Helton --- samtranslator/plugins/globals/globals.py | 27 +++++++++++++++-- tests/plugins/globals/test_globals.py | 30 ++++++++++++++++++- .../input/globals_for_function.yaml | 2 ++ .../output/aws-cn/globals_for_function.json | 2 +- .../aws-us-gov/globals_for_function.json | 2 +- .../output/globals_for_function.json | 2 +- 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index 83940b03e4..1c72470e86 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -306,8 +306,12 @@ def _parse(self, globals_dict): # type: ignore[no-untyped-def] f"Must be one of the following values - {supported_displayed}", ) + override_properties = [] + if resource_type == SamResourceType.Function.value: + override_properties = ["Architectures"] + # Store all Global properties in a map with key being the AWS::Serverless::* resource type - _globals[resource_type] = GlobalProperties(properties) + _globals[resource_type] = GlobalProperties(properties, override_properties) return _globals @@ -433,10 +437,16 @@ class GlobalProperties: ``` (in other words, Deployments will be turned off for the Function) + **Overridable Properties** + Some top-level resource properties must replace the global value instead of following the default merge behavior. + For example, Function Architectures is a list, but Lambda accepts only one architecture value. If both global and + local Architectures are set, the local value must override the global value instead of concatenating lists. + """ - def __init__(self, global_properties) -> None: # type: ignore[no-untyped-def] + def __init__(self, global_properties, override_properties=None) -> None: # type: ignore[no-untyped-def] self.global_properties = global_properties + self.override_properties = override_properties or [] def merge(self, local_properties): # type: ignore[no-untyped-def] """ @@ -444,7 +454,18 @@ 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] + global_properties = self._drop_overridden_globals(self.global_properties, local_properties) + return self._do_merge(global_properties, local_properties) # type: ignore[no-untyped-call] + + def _drop_overridden_globals(self, global_properties, local_properties): # type: ignore[no-untyped-def] + if not self.override_properties or not isinstance(global_properties, dict) or not isinstance(local_properties, dict): + return global_properties + + global_properties = global_properties.copy() + for key in self.override_properties: + if key in global_properties and key in local_properties: + del global_properties[key] + return global_properties def _do_merge(self, global_value, local_value): # type: ignore[no-untyped-def] """ diff --git a/tests/plugins/globals/test_globals.py b/tests/plugins/globals/test_globals.py index 9178244668..83dd64ad0d 100644 --- a/tests/plugins/globals/test_globals.py +++ b/tests/plugins/globals/test_globals.py @@ -171,6 +171,34 @@ class GlobalPropertiesTestCases: mixed_type_inputs_must_be_handled = {"global": {"a": "b"}, "local": [1, 2, 3], "expected_output": [1, 2, 3]} + architectures_in_local_must_override_global_instead_of_concatenating = { + "global": {"Architectures": ["x86_64"]}, + "local": {"Architectures": ["arm64"]}, + "override_properties": ["Architectures"], + "expected_output": {"Architectures": ["arm64"]}, + } + + architectures_in_global_must_be_used_when_local_does_not_set_it = { + "global": {"Architectures": ["arm64"], "Runtime": "python3.12"}, + "local": {"Runtime": "nodejs20.x"}, + "override_properties": ["Architectures"], + "expected_output": {"Architectures": ["arm64"], "Runtime": "nodejs20.x"}, + } + + override_property_in_local_only_must_not_error = { + "global": {"Runtime": "python3.12"}, + "local": {"Architectures": ["arm64"]}, + "override_properties": ["Architectures"], + "expected_output": {"Runtime": "python3.12", "Architectures": ["arm64"]}, + } + + override_properties_only_apply_at_root = { + "global": {"Nested": {"Architectures": ["x86_64"]}}, + "local": {"Nested": {"Architectures": ["arm64"]}}, + "override_properties": ["Architectures"], + "expected_output": {"Nested": {"Architectures": ["x86_64", "arm64"]}}, + } + class TestGlobalPropertiesMerge(TestCase): # Get all attributes of the test case object which is not a built-in method like __str__ @@ -180,7 +208,7 @@ def test_global_properties_merge(self, testcase): if not configuration: raise Exception("Invalid configuration for test case " + testcase) - global_properties = GlobalProperties(configuration["global"]) + global_properties = GlobalProperties(configuration["global"], configuration.get("override_properties")) actual = global_properties.merge(configuration["local"]) self.assertEqual(actual, configuration["expected_output"]) diff --git a/tests/translator/input/globals_for_function.yaml b/tests/translator/input/globals_for_function.yaml index 96e24eeb85..1a2c4f10fd 100644 --- a/tests/translator/input/globals_for_function.yaml +++ b/tests/translator/input/globals_for_function.yaml @@ -65,6 +65,8 @@ Resources: PermissionsBoundary: arn:aws:1234:iam:boundary/OverridePermissionsBoundary Layers: - !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:layer:MyLayer2:2 + Architectures: + - arm64 SnapStart: ApplyOn: None ReservedConcurrentExecutions: 100 diff --git a/tests/translator/output/aws-cn/globals_for_function.json b/tests/translator/output/aws-cn/globals_for_function.json index cc5467a092..32e7630716 100644 --- a/tests/translator/output/aws-cn/globals_for_function.json +++ b/tests/translator/output/aws-cn/globals_for_function.json @@ -3,7 +3,7 @@ "FunctionWithOverrides": { "Properties": { "Architectures": [ - "x86_64" + "arm64" ], "Code": { "S3Bucket": "sam-demo-bucket", diff --git a/tests/translator/output/aws-us-gov/globals_for_function.json b/tests/translator/output/aws-us-gov/globals_for_function.json index bf9046d922..2c28631419 100644 --- a/tests/translator/output/aws-us-gov/globals_for_function.json +++ b/tests/translator/output/aws-us-gov/globals_for_function.json @@ -3,7 +3,7 @@ "FunctionWithOverrides": { "Properties": { "Architectures": [ - "x86_64" + "arm64" ], "Code": { "S3Bucket": "sam-demo-bucket", diff --git a/tests/translator/output/globals_for_function.json b/tests/translator/output/globals_for_function.json index 2e4def2424..85d8632f4a 100644 --- a/tests/translator/output/globals_for_function.json +++ b/tests/translator/output/globals_for_function.json @@ -3,7 +3,7 @@ "FunctionWithOverrides": { "Properties": { "Architectures": [ - "x86_64" + "arm64" ], "Code": { "S3Bucket": "sam-demo-bucket",