Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ def _save_runtime_cache(self) -> None:
def process(self) -> None:
self._setup_runtime_cache()
node_template = self._change_set.update_model.node_template
node_conditions = self._change_set.update_model.node_template.conditions
self.visit(node_conditions)
self.visit(node_template)
self._save_runtime_cache()

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

arguments_delta = self.visit(node_intrinsic_function.arguments)

if isinstance(arguments_delta.after, list) and len(arguments_delta.after) != 2:
raise ValidationError(
"Template error: every Fn::Equals object requires a list of 2 string parameters."
)

delta = self._cached_apply(
scope=node_intrinsic_function.scope,
arguments_delta=arguments_delta,
Expand Down Expand Up @@ -919,6 +927,7 @@ def _compute_fn_split(args: list[Any]) -> Any:
return split_string

arguments_delta = self.visit(node_intrinsic_function.arguments)

delta = self._cached_apply(
scope=node_intrinsic_function.scope,
arguments_delta=arguments_delta,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def visit_node_template(self, node_template: NodeTemplate):
# entities (parameters, mappings, conditions, etc.). Then compute the output fields; computing
# only the output fields would only result in the deployment logic of the referenced outputs
# being evaluated, hence enforce the visiting of all the resources first.
self.visit(node_template.conditions)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is only needed because in your test you have an unused condition, but in general this should not be required since resources would refer to the conditions which would cause the visiting process to visit the conditions, right?

I'm glad we have this extra parity with explicitly visiting the conditions (perhaps we should explicitly visit all nodes?) but it seems like a more contrived change than it needs to be.

Copy link
Copy Markdown
Member Author

@pinzon pinzon Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this here for now. But I have an idea to discuss later.

self.visit(node_template.resources)
self.visit(node_template.outputs)

Expand Down
28 changes: 28 additions & 0 deletions tests/aws/services/cloudformation/engine/test_conditions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
import os.path

import pytest
from botocore.exceptions import ClientError
from localstack_snapshot.snapshots.transformer import SortingTransformer
from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine

Expand Down Expand Up @@ -550,3 +552,29 @@ def test_references_to_disabled_resources(
StackName=stack.stack_id
)
snapshot.match("resources-description", describe_resources)


class TestValidateConditions:
@markers.aws.validated
@skip_if_legacy_engine
def test_validate_equals_args_len(self, aws_client, snapshot):
template = {
"Conditions": {"ShouldDeploy": {"Fn::Equals": ["a"]}},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL you can have a condition that's unused!

"Resources": {
"Topic1": {
"Type": "AWS::SNS::Topic",
},
},
}

stack_name = f"stack-{short_uid()}"
change_set_name = f"ch-{short_uid()}"
with pytest.raises(ClientError) as ex:
aws_client.cloudformation.create_change_set(
StackName=stack_name,
ChangeSetName=change_set_name,
ChangeSetType="CREATE",
TemplateBody=json.dumps(template),
)

snapshot.match("error", ex.value.response)
Original file line number Diff line number Diff line change
Expand Up @@ -933,5 +933,21 @@
}
}
}
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": {
"recorded-date": "08-10-2025, 18:18:14",
"recorded-content": {
"error": {
"Error": {
"Code": "ValidationError",
"Message": "Template error: every Fn::Equals object requires a list of 2 string parameters.",
"Type": "Sender"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,14 @@
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": {
"last_validated_date": "2024-06-18T19:43:43+00:00"
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestValidateConditions::test_validate_equals_args_len": {
"last_validated_date": "2025-10-08T18:18:14+00:00",
"durations_in_seconds": {
"setup": 0.25,
"call": 0.36,
"teardown": 0.0,
"total": 0.61
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2451,5 +2451,21 @@
"Tags": []
}
}
},
"tests/aws/services/cloudformation/test_change_set_fn_split.py::TestValidations::test_validate_args_len": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: where did these snapshots come from? I guess they are left over from your split PR (#13244), can we remove them before merging?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Nice catch 👍

"recorded-date": "02-10-2025, 20:16:50",
"recorded-content": {
"error": {
"Error": {
"Code": "ValidationError",
"Message": "Template error: every Fn::Split object requires two parameters, (1) a string delimiter and (2) a string to be split or a function that returns a string to be split.",
"Type": "Sender"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,14 @@
},
"tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": {
"last_validated_date": "2025-06-02T11:23:28+00:00"
},
"tests/aws/services/cloudformation/test_change_set_fn_split.py::TestValidations::test_validate_args_len": {
"last_validated_date": "2025-10-02T20:16:50+00:00",
"durations_in_seconds": {
"setup": 0.25,
"call": 0.35,
"teardown": 0.0,
"total": 0.6
}
}
}
Loading