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 all 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Praise: Glad the new engine is easily modifiable.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
NodeProperties,
NodeProperty,
NodeResource,
NodeResources,
NodeTemplate,
Nothing,
NothingType,
Expand Down Expand Up @@ -1170,6 +1171,20 @@ def _resolve_resource_condition_reference(self, reference: TerminalValue) -> Pre
after = after_delta.after
return PreprocEntityDelta(before=before, after=after)

def visit_node_resources(self, node_resources: NodeResources):
"""
Skip resources where they conditionally evaluate to False
"""
for node_resource in node_resources.resources:
if not is_nothing(node_resource.condition_reference):
condition_delta = self._resolve_resource_condition_reference(
node_resource.condition_reference
)
condition_after = condition_delta.after
if condition_after is False:
continue
self.visit(node_resource)

def visit_node_resource(
self, node_resource: NodeResource
) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
Expand Down Expand Up @@ -1280,6 +1295,14 @@ def visit_node_outputs(
before: list[PreprocOutput] = []
after: list[PreprocOutput] = []
for node_output in node_outputs.outputs:
if not is_nothing(node_output.condition_reference):
condition_delta = self._resolve_resource_condition_reference(
node_output.condition_reference
)
condition_after = condition_delta.after
if condition_after is False:
continue

output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output)
output_before = output_delta.before
output_after = output_delta.after
Expand Down
37 changes: 36 additions & 1 deletion tests/aws/services/cloudformation/engine/test_conditions.py
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Consider adding a validation test about a situation where an output references a conditionally skip resource.

Copy link
Copy Markdown
Contributor Author

@simonrw simonrw Oct 8, 2025

Choose a reason for hiding this comment

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

Good idea, thanks! I'll tackle this in a follow up

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os.path

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

from localstack.services.cloudformation.v2.utils import is_v2_engine
Expand Down Expand Up @@ -153,7 +154,7 @@ def test_dependent_get_att(self, aws_client, snapshot):
snapshot.match("dependent_ref_exc", e.value.response)

@markers.aws.validated
@pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet")
@skip_if_legacy_engine()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🥳

def test_dependent_ref_intrinsic_fn_condition(self, aws_client, deploy_cfn_template):
"""
Checks behavior of un-refable resources
Expand Down Expand Up @@ -515,3 +516,37 @@ def test_update_conditions(self, deploy_cfn_template, aws_client):
assert aws_client.s3.head_bucket(Bucket=bucket_1)
with pytest.raises(aws_client.s3.exceptions.ClientError):
aws_client.s3.head_bucket(Bucket=bucket_2)

@markers.aws.validated
@pytest.mark.parametrize("should_deploy", ["yes", "no"])
@skip_if_legacy_engine()
def test_references_to_disabled_resources(
self, deploy_cfn_template, aws_client, should_deploy, snapshot
):
snapshot.add_transformer(
SortingTransformer("StackResources", lambda e: e["LogicalResourceId"])
)
snapshot.add_transformer(SortingTransformer("Parameters", lambda e: e["ParameterKey"]))
snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId"))
snapshot.add_transformer(snapshot.transform.cloudformation_api())

parameter_value = short_uid()
snapshot.add_transformer(snapshot.transform.regex(parameter_value, "<parameter-value>"))

stack = deploy_cfn_template(
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/references_to_conditions.yml"
),
parameters={
"Toggle": should_deploy,
"ParameterValue": parameter_value,
},
)

describe_stack_res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_id)
snapshot.match("stack-description", describe_stack_res)

describe_resources = aws_client.cloudformation.describe_stack_resources(
StackName=stack.stack_id
)
snapshot.match("resources-description", describe_resources)
Original file line number Diff line number Diff line change
Expand Up @@ -775,5 +775,163 @@
}
}
}
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[yes]": {
"recorded-date": "07-10-2025, 21:21:23",
"recorded-content": {
"stack-description": {
"Stacks": [
{
"Capabilities": [
"CAPABILITY_AUTO_EXPAND",
"CAPABILITY_IAM",
"CAPABILITY_NAMED_IAM"
],
"ChangeSetId": "arn:<partition>:cloudformation:<region>:111111111111:changeSet/<resource:1>",
"CreationTime": "datetime",
"DisableRollback": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
},
"EnableTerminationProtection": false,
"LastUpdatedTime": "datetime",
"NotificationARNs": [],
"Outputs": [
{
"OutputKey": "ConditionalParameterValue",
"OutputValue": "<parameter-value>"
}
],
"Parameters": [
{
"ParameterKey": "ParameterValue",
"ParameterValue": "<parameter-value>"
},
{
"ParameterKey": "Toggle",
"ParameterValue": "yes"
}
],
"RollbackConfiguration": {},
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"StackStatus": "CREATE_COMPLETE",
"Tags": []
}
],
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"resources-description": {
"StackResources": [
{
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"LogicalResourceId": "AnotherResource",
"PhysicalResourceId": "<physical-resource-id:1>",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::SSM::Parameter",
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"Timestamp": "timestamp"
},
{
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"LogicalResourceId": "BaseParameter",
"PhysicalResourceId": "<physical-resource-id:2>",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::SSM::Parameter",
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"Timestamp": "timestamp"
},
{
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"LogicalResourceId": "ConditionalParameter",
"PhysicalResourceId": "<physical-resource-id:3>",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::SSM::Parameter",
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"Timestamp": "timestamp"
}
],
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[no]": {
"recorded-date": "07-10-2025, 21:21:37",
"recorded-content": {
"stack-description": {
"Stacks": [
{
"Capabilities": [
"CAPABILITY_AUTO_EXPAND",
"CAPABILITY_IAM",
"CAPABILITY_NAMED_IAM"
],
"ChangeSetId": "arn:<partition>:cloudformation:<region>:111111111111:changeSet/<resource:1>",
"CreationTime": "datetime",
"DisableRollback": false,
"DriftInformation": {
"StackDriftStatus": "NOT_CHECKED"
},
"EnableTerminationProtection": false,
"LastUpdatedTime": "datetime",
"NotificationARNs": [],
"Parameters": [
{
"ParameterKey": "ParameterValue",
"ParameterValue": "<parameter-value>"
},
{
"ParameterKey": "Toggle",
"ParameterValue": "no"
}
],
"RollbackConfiguration": {},
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"StackStatus": "CREATE_COMPLETE",
"Tags": []
}
],
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
},
"resources-description": {
"StackResources": [
{
"DriftInformation": {
"StackResourceDriftStatus": "NOT_CHECKED"
},
"LogicalResourceId": "BaseParameter",
"PhysicalResourceId": "<physical-resource-id:1>",
"ResourceStatus": "CREATE_COMPLETE",
"ResourceType": "AWS::SSM::Parameter",
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:2>",
"StackName": "<stack-name:1>",
"Timestamp": "timestamp"
}
],
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": {
"last_validated_date": "2023-06-26T22:43:18+00:00"
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[no]": {
"last_validated_date": "2025-10-07T21:21:41+00:00",
"durations_in_seconds": {
"setup": 0.0,
"call": 7.01,
"teardown": 4.39,
"total": 11.4
}
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_references_to_disabled_resources[yes]": {
"last_validated_date": "2025-10-07T21:21:30+00:00",
"durations_in_seconds": {
"setup": 0.89,
"call": 10.41,
"teardown": 6.43,
"total": 17.73
}
},
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": {
"last_validated_date": "2024-06-18T19:43:43+00:00"
}
Expand Down
39 changes: 39 additions & 0 deletions tests/aws/templates/references_to_conditions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Parameters:
Toggle:
Type: String

ParameterValue:
Type: String

Conditions:
ShouldDeploy:
Fn::Equals:
- !Ref Toggle
- "yes"

Resources:
BaseParameter:
# We need a single resource to deploy
Type: AWS::SSM::Parameter
Properties:
Type: String
Value: !Ref ParameterValue

ConditionalParameter:
Type: AWS::SSM::Parameter
Condition: ShouldDeploy
Properties:
Type: String
Value: !Ref ParameterValue

AnotherResource:
Type: AWS::SSM::Parameter
Condition: ShouldDeploy
Properties:
Type: String
Value: !GetAtt ConditionalParameter.Value

Outputs:
ConditionalParameterValue:
Condition: ShouldDeploy
Value: !GetAtt ConditionalParameter.Value
Loading