Skip to content

Commit e3384dd

Browse files
committed
implement global include
1 parent ca22130 commit e3384dd

File tree

2 files changed

+55
-26
lines changed

2 files changed

+55
-26
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
from typing import Any, Final, Optional, TypedDict
55

66
import boto3
7+
from botocore.exceptions import ClientError
78
from samtranslator.translator.transform import transform as transform_sam
89

10+
from localstack.aws.connect import connect_to
911
from localstack.services.cloudformation.engine.policy_loader import create_policy_loader
12+
from localstack.services.cloudformation.engine.template_preparer import parse_template
1013
from localstack.services.cloudformation.engine.transformers import (
1114
FailedTransformationException,
1215
execute_macro,
@@ -26,12 +29,14 @@
2629
)
2730
from localstack.services.cloudformation.stores import get_cloudformation_store
2831
from localstack.services.cloudformation.v2.entities import ChangeSet
32+
from localstack.utils import testutil
2933

3034
LOG = logging.getLogger(__name__)
3135

3236
SERVERLESS_TRANSFORM = "AWS::Serverless-2016-10-31"
3337
EXTENSIONS_TRANSFORM = "AWS::LanguageExtensions"
3438
SECRETSMANAGER_TRANSFORM = "AWS::SecretsManager-2020-07-23"
39+
INCLUDE_TRANSFORM = "AWS::Include"
3540

3641

3742
# TODO: evaluate the use of subtypes to represent and validate types of transforms
@@ -135,6 +140,32 @@ def _apply_global_serverless_transformation(
135140
if region_before is not None:
136141
os.environ["AWS_DEFAULT_REGION"] = region_before
137142

143+
@staticmethod
144+
def _apply_global_include(
145+
global_transform: GlobalTransform, template: dict, parameters: dict, account_id, region_name
146+
) -> dict:
147+
location = global_transform.parameters.get("Location")
148+
if not location or not location.startswith("s3://"):
149+
raise FailedTransformationException(
150+
transformation=INCLUDE_TRANSFORM,
151+
message="Unexpected Location parameter for AWS::Include transformer: %s" % location,
152+
)
153+
154+
s3_client = connect_to(aws_access_key_id=account_id, region_name=region_name).s3
155+
bucket, _, path = location.removeprefix("s3://").partition("/")
156+
try:
157+
content = testutil.download_s3_object(s3_client, bucket, path)
158+
except ClientError:
159+
raise FailedTransformationException(
160+
transformation=INCLUDE_TRANSFORM,
161+
message="Error downloading S3 object '%s/%s'" % (bucket, path),
162+
)
163+
try:
164+
template_to_include = parse_template(content)
165+
except Exception as e:
166+
raise FailedTransformationException(transformation=INCLUDE_TRANSFORM, message=str(e))
167+
return {**template, **template_to_include}
168+
138169
@staticmethod
139170
def _apply_global_macro_transformation(
140171
account_id: str,
@@ -179,6 +210,14 @@ def _apply_global_transform(
179210
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-secretsmanager.html
180211
LOG.warning("%s is not yet supported. Ignoring.", SECRETSMANAGER_TRANSFORM)
181212
transformed_template = template
213+
elif transform_name == INCLUDE_TRANSFORM:
214+
transformed_template = self._apply_global_include(
215+
global_transform=global_transform,
216+
region_name=self._change_set.region_name,
217+
account_id=self._change_set.account_id,
218+
template=template,
219+
parameters=parameters,
220+
)
182221
else:
183222
transformed_template = self._apply_global_macro_transformation(
184223
account_id=self._change_set.account_id,

tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,11 @@ def test_multiple_fn_transform_order(
371371
@markers.aws.validated
372372
@pytest.mark.parametrize("transform", ["true", "false"])
373373
def test_conditional_transform(
374-
self,
375-
transform,
376-
snapshot,
377-
capture_update_process,
378-
create_macro,
374+
self,
375+
transform,
376+
snapshot,
377+
capture_update_process,
378+
create_macro,
379379
):
380380
name1 = f"parameter-{short_uid()}"
381381
snapshot.add_transformer(RegexTransformer(name1, "parameter-name"))
@@ -397,21 +397,8 @@ def test_conditional_transform(
397397
}
398398

399399
template_2 = {
400-
"Parameters": {
401-
"Transform": {
402-
"Type": "String"
403-
}
404-
},
405-
"Conditions": {
406-
"Deploy": {
407-
"Fn::Equals": [
408-
{
409-
"Ref": "Transform"
410-
},
411-
"true"
412-
]
413-
}
414-
},
400+
"Parameters": {"Transform": {"Type": "String"}},
401+
"Conditions": {"Deploy": {"Fn::Equals": [{"Ref": "Transform"}, "true"]}},
415402
"Resources": {
416403
"Parameter": {
417404
"Type": "AWS::SSM::Parameter",
@@ -425,17 +412,17 @@ def test_conditional_transform(
425412
],
426413
},
427414
}
428-
}
415+
},
429416
}
430417

431418
capture_update_process(snapshot, template_1, template_2, p2={"Transform": transform})
432419

433420
@markers.aws.validated
434421
def test_macro_with_function(
435-
self,
436-
snapshot,
437-
capture_update_process,
438-
create_macro,
422+
self,
423+
snapshot,
424+
capture_update_process,
425+
create_macro,
439426
):
440427
name1 = f"parameter-{short_uid()}"
441428
snapshot.add_transformer(RegexTransformer(name1, "parameter-name"))
@@ -465,7 +452,10 @@ def test_macro_with_function(
465452
"Value": "<replace-this>",
466453
"Type": "String",
467454
"Fn::Transform": [
468-
{"Name": macro_name, "Parameters": {"Input":{"Fn::Join": ["-", ["test", "string"]]}}},
455+
{
456+
"Name": macro_name,
457+
"Parameters": {"Input": {"Fn::Join": ["-", ["test", "string"]]}},
458+
},
469459
],
470460
},
471461
}

0 commit comments

Comments
 (0)