Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.

Commit cf9a6fc

Browse files
authored
CFn: skip properties of conditional resources with false conditions (#13664)
1 parent 8422daf commit cf9a6fc

File tree

5 files changed

+119
-4
lines changed

5 files changed

+119
-4
lines changed

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,13 +1260,25 @@ def visit_node_resource(
12601260
depends_on_after = depends_on_delta.after
12611261

12621262
type_delta = self.visit(node_resource.type_)
1263-
properties_delta: PreprocEntityDelta[PreprocProperties, PreprocProperties] = self.visit(
1264-
node_resource.properties
1263+
1264+
# Check conditions before visiting properties to avoid resolving references
1265+
# (e.g. GetAtt) to conditional resources that were never created.
1266+
should_process_before = change_type != ChangeType.CREATED and (
1267+
is_nothing(condition_before) or condition_before
1268+
)
1269+
should_process_after = change_type != ChangeType.REMOVED and (
1270+
is_nothing(condition_after) or condition_after
12651271
)
12661272

1273+
properties_delta: PreprocEntityDelta[PreprocProperties, PreprocProperties]
1274+
if should_process_before or should_process_after:
1275+
properties_delta = self.visit(node_resource.properties)
1276+
else:
1277+
properties_delta = PreprocEntityDelta(before=Nothing, after=Nothing)
1278+
12671279
before = Nothing
12681280
after = Nothing
1269-
if change_type != ChangeType.CREATED and is_nothing(condition_before) or condition_before:
1281+
if should_process_before:
12701282
logical_resource_id = node_resource.name
12711283
before_physical_resource_id = self._before_resource_physical_id(
12721284
resource_logical_id=logical_resource_id
@@ -1280,7 +1292,7 @@ def visit_node_resource(
12801292
depends_on=depends_on_before,
12811293
requires_replacement=False,
12821294
)
1283-
if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after:
1295+
if should_process_after:
12841296
logical_resource_id = node_resource.name
12851297
try:
12861298
after_physical_resource_id = self._after_resource_physical_id(

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,32 @@ def test_references_to_disabled_resources(
553553
)
554554
snapshot.match("resources-description", describe_resources)
555555

556+
@markers.aws.validated
557+
@skip_if_legacy_engine()
558+
def test_conditional_resource_referencing_conditional(
559+
self, deploy_cfn_template, aws_client, snapshot
560+
):
561+
"""
562+
Test that a template with a conditional resource referencing another conditional
563+
resource via !GetAtt is accepted and deploys correctly when conditions are false.
564+
"""
565+
snapshot.add_transformer(
566+
SortingTransformer("StackResources", lambda e: e["LogicalResourceId"])
567+
)
568+
snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId"))
569+
snapshot.add_transformer(snapshot.transform.cloudformation_api())
570+
571+
stack = deploy_cfn_template(
572+
template_path=os.path.join(
573+
os.path.dirname(__file__),
574+
"../../../templates/conditions/conditional-resource-getatt-delete.yaml",
575+
),
576+
parameters={"EnableQueue": "false"},
577+
)
578+
579+
resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_id)
580+
snapshot.match("resources-description", resources)
581+
556582

557583
class TestValidateConditions:
558584
@markers.aws.validated

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,5 +949,30 @@
949949
}
950950
}
951951
}
952+
},
953+
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_resource_referencing_conditional": {
954+
"recorded-date": "04-02-2026, 01:53:15",
955+
"recorded-content": {
956+
"resources-description": {
957+
"StackResources": [
958+
{
959+
"DriftInformation": {
960+
"StackResourceDriftStatus": "NOT_CHECKED"
961+
},
962+
"LogicalResourceId": "MyTable",
963+
"PhysicalResourceId": "<physical-resource-id:1>",
964+
"ResourceStatus": "CREATE_COMPLETE",
965+
"ResourceType": "AWS::DynamoDB::Table",
966+
"StackId": "arn:<partition>:cloudformation:<region>:111111111111:stack/<stack-name:1>/<resource:1>",
967+
"StackName": "<stack-name:1>",
968+
"Timestamp": "timestamp"
969+
}
970+
],
971+
"ResponseMetadata": {
972+
"HTTPHeaders": {},
973+
"HTTPStatusCode": 200
974+
}
975+
}
976+
}
952977
}
953978
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
{
2+
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_resource_referencing_conditional": {
3+
"last_validated_date": "2026-02-04T01:53:29+00:00",
4+
"durations_in_seconds": {
5+
"setup": 0.7,
6+
"call": 23.45,
7+
"teardown": 14.46,
8+
"total": 38.61
9+
}
10+
},
211
"tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_get_att": {
312
"last_validated_date": "2025-09-30T19:25:10+00:00",
413
"durations_in_seconds": {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
3+
Parameters:
4+
EnableQueue:
5+
Type: String
6+
Default: 'false'
7+
AllowedValues: ['true', 'false']
8+
9+
Conditions:
10+
CreateQueue: !Equals [!Ref EnableQueue, 'true']
11+
12+
Resources:
13+
MyTable:
14+
Type: AWS::DynamoDB::Table
15+
Properties:
16+
TableName: !Sub '${AWS::StackName}-table'
17+
BillingMode: PAY_PER_REQUEST
18+
AttributeDefinitions:
19+
- AttributeName: PK
20+
AttributeType: S
21+
KeySchema:
22+
- AttributeName: PK
23+
KeyType: HASH
24+
25+
MyQueue:
26+
Type: AWS::SQS::Queue
27+
Condition: CreateQueue
28+
29+
MyAlarm:
30+
Type: AWS::CloudWatch::Alarm
31+
Condition: CreateQueue
32+
Properties:
33+
AlarmName: !Sub '${AWS::StackName}-alarm'
34+
MetricName: ApproximateNumberOfMessagesVisible
35+
Namespace: AWS/SQS
36+
Statistic: Sum
37+
Period: 300
38+
EvaluationPeriods: 1
39+
Threshold: 100
40+
ComparisonOperator: GreaterThanThreshold
41+
Dimensions:
42+
- Name: QueueName
43+
Value: !GetAtt MyQueue.QueueName

0 commit comments

Comments
 (0)