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

Commit b6af98b

Browse files
committed
CFn: validate Type is provided
# Motivation While discusing #13496 I asserted that we can safely access `resource["Type"]` in the visitors since we validate that the `Type` key is always present. However this _was not true_! This PR resolves this # Changes * Add test deploying resource without a `Type` key * Validate that the type is present during the modelling phase
1 parent 75aeab7 commit b6af98b

4 files changed

Lines changed: 50 additions & 0 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing_extensions import TypeVar
1010

1111
from localstack.aws.api.cloudformation import ChangeAction
12+
from localstack.services.cloudformation.engine.validations import ValidationError
1213
from localstack.services.cloudformation.resource_provider import ResourceProviderExecutor
1314
from localstack.services.cloudformation.v2.types import (
1415
EngineParameter,
@@ -1079,6 +1080,15 @@ def _visit_resource(
10791080
scope_type, (before_type, after_type) = self._safe_access_in(
10801081
scope, TypeKey, before_resource, after_resource
10811082
)
1083+
1084+
# If both before and after types are Nothing then the user did
1085+
# not specify a value for the type of this resource, which is
1086+
# a hard error in cloudFormation.
1087+
if is_nothing(before_type) and is_nothing(after_type):
1088+
raise ValidationError(
1089+
f"Template format error: [{scope}] Every Resources object must contain a Type member."
1090+
)
1091+
10821092
terminal_value_type = self._visit_type(
10831093
scope=scope_type, before_type=before_type, after_type=after_type
10841094
)

tests/aws/services/cloudformation/test_template_engine.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,3 +1317,28 @@ def test_stack_id(self, deploy_cfn_template, snapshot):
13171317
snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "<stack-id>"))
13181318

13191319
snapshot.match("StackId", stack.outputs["StackId"])
1320+
1321+
1322+
@markers.aws.validated
1323+
def test_no_type(aws_client, snapshot):
1324+
template = {
1325+
"Resources": {
1326+
"Foo": {
1327+
"Properties": {
1328+
"Name": "foo",
1329+
},
1330+
},
1331+
},
1332+
}
1333+
1334+
stack_name = f"stack-{short_uid()}"
1335+
change_set_name = f"cs-{short_uid()}"
1336+
with pytest.raises(ClientError) as e:
1337+
aws_client.cloudformation.create_change_set(
1338+
ChangeSetName=change_set_name,
1339+
StackName=stack_name,
1340+
TemplateBody=json.dumps(template),
1341+
ChangeSetType="CREATE",
1342+
)
1343+
1344+
snapshot.match("error", e.value)

tests/aws/services/cloudformation/test_template_engine.snapshot.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,5 +700,11 @@
700700
"TopicName": "<parameter-value>"
701701
}
702702
}
703+
},
704+
"tests/aws/services/cloudformation/test_template_engine.py::test_no_type": {
705+
"recorded-date": "15-12-2025, 10:55:28",
706+
"recorded-content": {
707+
"error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template format error: [/Resources/Foo] Every Resources object must contain a Type member."
708+
}
703709
}
704710
}

tests/aws/services/cloudformation/test_template_engine.validation.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,14 @@
151151
"teardown": 50.06,
152152
"total": 90.44
153153
}
154+
},
155+
"tests/aws/services/cloudformation/test_template_engine.py::test_no_type": {
156+
"last_validated_date": "2025-12-15T10:55:28+00:00",
157+
"durations_in_seconds": {
158+
"setup": 0.75,
159+
"call": 0.27,
160+
"teardown": 0.0,
161+
"total": 1.02
162+
}
154163
}
155164
}

0 commit comments

Comments
 (0)