Skip to content

Commit 69ed784

Browse files
authored
CFN: add more validations to intrinsic FnEquals (#13217)
1 parent e0a4a18 commit 69ed784

File tree

5 files changed

+63
-0
lines changed

5 files changed

+63
-0
lines changed

localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ def _save_runtime_cache(self) -> None:
227227
def process(self) -> None:
228228
self._setup_runtime_cache()
229229
node_template = self._change_set.update_model.node_template
230+
node_conditions = self._change_set.update_model.node_template.conditions
231+
self.visit(node_conditions)
230232
self.visit(node_template)
231233
self._save_runtime_cache()
232234

@@ -643,6 +645,12 @@ def _compute_fn_equals(args: list[Any]) -> bool:
643645
return args[0] == args[1]
644646

645647
arguments_delta = self.visit(node_intrinsic_function.arguments)
648+
649+
if isinstance(arguments_delta.after, list) and len(arguments_delta.after) != 2:
650+
raise ValidationError(
651+
"Template error: every Fn::Equals object requires a list of 2 string parameters."
652+
)
653+
646654
delta = self._cached_apply(
647655
scope=node_intrinsic_function.scope,
648656
arguments_delta=arguments_delta,
@@ -919,6 +927,7 @@ def _compute_fn_split(args: list[Any]) -> Any:
919927
return split_string
920928

921929
arguments_delta = self.visit(node_intrinsic_function.arguments)
930+
922931
delta = self._cached_apply(
923932
scope=node_intrinsic_function.scope,
924933
arguments_delta=arguments_delta,

localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def visit_node_template(self, node_template: NodeTemplate):
5454
# entities (parameters, mappings, conditions, etc.). Then compute the output fields; computing
5555
# only the output fields would only result in the deployment logic of the referenced outputs
5656
# being evaluated, hence enforce the visiting of all the resources first.
57+
self.visit(node_template.conditions)
5758
self.visit(node_template.resources)
5859
self.visit(node_template.outputs)
5960

tests/aws/services/cloudformation/engine/test_conditions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import json
12
import os.path
23

34
import pytest
5+
from botocore.exceptions import ClientError
46
from localstack_snapshot.snapshots.transformer import SortingTransformer
57
from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine
68

@@ -550,3 +552,29 @@ def test_references_to_disabled_resources(
550552
StackName=stack.stack_id
551553
)
552554
snapshot.match("resources-description", describe_resources)
555+
556+
557+
class TestValidateConditions:
558+
@markers.aws.validated
559+
@skip_if_legacy_engine
560+
def test_validate_equals_args_len(self, aws_client, snapshot):
561+
template = {
562+
"Conditions": {"ShouldDeploy": {"Fn::Equals": ["a"]}},
563+
"Resources": {
564+
"Topic1": {
565+
"Type": "AWS::SNS::Topic",
566+
},
567+
},
568+
}
569+
570+
stack_name = f"stack-{short_uid()}"
571+
change_set_name = f"ch-{short_uid()}"
572+
with pytest.raises(ClientError) as ex:
573+
aws_client.cloudformation.create_change_set(
574+
StackName=stack_name,
575+
ChangeSetName=change_set_name,
576+
ChangeSetType="CREATE",
577+
TemplateBody=json.dumps(template),
578+
)
579+
580+
snapshot.match("error", ex.value.response)

tests/aws/services/cloudformation/engine/test_conditions.snapshot.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,5 +933,21 @@
933933
}
934934
}
935935
}
936+
},
937+
"tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": {
938+
"recorded-date": "08-10-2025, 18:18:14",
939+
"recorded-content": {
940+
"error": {
941+
"Error": {
942+
"Code": "ValidationError",
943+
"Message": "Template error: every Fn::Equals object requires a list of 2 string parameters.",
944+
"Type": "Sender"
945+
},
946+
"ResponseMetadata": {
947+
"HTTPHeaders": {},
948+
"HTTPStatusCode": 400
949+
}
950+
}
951+
}
936952
}
937953
}

tests/aws/services/cloudformation/engine/test_conditions.validation.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,14 @@
5252
},
5353
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": {
5454
"last_validated_date": "2024-06-18T19:43:43+00:00"
55+
},
56+
"tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": {
57+
"last_validated_date": "2025-10-08T18:18:14+00:00",
58+
"durations_in_seconds": {
59+
"setup": 0.25,
60+
"call": 0.36,
61+
"teardown": 0.0,
62+
"total": 0.61
63+
}
5564
}
5665
}

0 commit comments

Comments
 (0)