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
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ def on_before_start(self):

@staticmethod
def _resolve_parameters(
template: dict | None, parameters: dict | None, account_id: str, region_name: str
template: dict | None,
parameters: dict | None,
account_id: str,
region_name: str,
before_parameters: dict | None,
) -> dict[str, EngineParameter]:
template_parameters = template.get("Parameters", {})
resolved_parameters = {}
Expand Down Expand Up @@ -278,10 +282,25 @@ def _resolve_parameters(
resolved_parameter["resolved_value"] = resolve_ssm_parameter(
account_id, region_name, given_value or default_value
)
except Exception:
raise ValidationError(
f"Parameter {name} should either have input value or default value"
)
except Exception as e:
# we could not find the parameter however CDK provides the resolved value rather than the
# parameter name again so try to look up the value in the previous parameters
if (
before_parameters
and (before_param := before_parameters.get(name))
and isinstance(before_param, dict)
and (resolved_value := before_param.get("resolved_value"))
):
LOG.debug(
"Parameter %s could not be resolved, using previous value of %s",
name,
resolved_value,
)
resolved_parameter["resolved_value"] = resolved_value
else:
raise ValidationError(
f"Parameter {name} should either have input value or default value"
) from e
elif given_value is None and default_value is None:
invalid_parameters.append(name)
continue
Expand Down Expand Up @@ -320,6 +339,7 @@ def _setup_change_set_model(
after_parameters,
change_set.stack.account_id,
change_set.stack.region_name,
before_parameters,
)

change_set.resolved_parameters = resolved_parameters
Expand Down
109 changes: 109 additions & 0 deletions tests/aws/services/cloudformation/api/test_reference_resolving.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os

import pytest
from tests.aws.services.cloudformation.conftest import skip_if_legacy_engine

from localstack.services.cloudformation.engine.template_deployer import MOCK_REFERENCE
from localstack.testing.pytest import markers
from localstack.utils import testutil
from localstack.utils.strings import short_uid


Expand Down Expand Up @@ -103,3 +105,110 @@ def test_reference_unsupported_resource(deploy_cfn_template, aws_client):
value_of_unsupported = deployment.outputs["parameter"]
assert ref_of_unsupported == MOCK_REFERENCE
assert value_of_unsupported == f"The value of the attribute is: {MOCK_REFERENCE}"


@markers.aws.validated
@skip_if_legacy_engine()
def test_redeploy_cdk_with_reference(
aws_client, account_id, create_lambda_function, deploy_cfn_template, snapshot, cleanups
):
"""
Test a user scenario with a lambda function that fails to redeploy

"""
# perform cdk bootstrap
template_file = os.path.join(
os.path.dirname(__file__), "../../../templates/cdk_bootstrap_v28.yaml"
)
qualifier = short_uid()
bootstrap_stack = deploy_cfn_template(
template_path=template_file,
parameters={
"CloudFormationExecutionPolicies": "",
"FileAssetsBucketKmsKeyId": "AWS_MANAGED_KEY",
"PublicAccessBlockConfiguration": "true",
"TrustedAccounts": "",
"TrustedAccountsForLookup": "",
"Qualifier": qualifier,
},
)

lambda_bucket = bootstrap_stack.outputs["BucketName"]

# upload the lambda function
lambda_src_1 = """
def handler(event, context):
return {"status": "ok"}
"""
lambda_src_2 = """
def handler(event, context):
return {"status": "foo"}
"""

function_name = f"function-{short_uid()}"
cleanups.append(lambda: aws_client.lambda_.delete_function(FunctionName=function_name))

def deploy_or_update_lambda(content: str, lambda_key: str):
archive = testutil.create_lambda_archive(content)
with open(archive, "rb") as infile:
aws_client.s3.put_object(Bucket=lambda_bucket, Key=lambda_key, Body=infile)

lambda_exists = False
try:
aws_client.lambda_.get_function(FunctionName=function_name)
lambda_exists = True
except Exception:
# TODO: work out the proper exception
pass

if lambda_exists:
aws_client.lambda_.update_function_code(
FunctionName=function_name,
S3Bucket=lambda_bucket,
S3Key=lambda_key,
)
else:
aws_client.lambda_.create_function(
FunctionName=function_name,
Runtime="python3.12",
Handler="handler",
Code={
"S3Bucket": lambda_bucket,
"S3Key": lambda_key,
},
# The role does not matter
Role=f"arn:aws:iam::{account_id}:role/LambdaExecutionRole",
)
aws_client.lambda_.get_waiter("function_active_v2").wait(FunctionName=function_name)

lambda_key_1 = f"{short_uid()}.zip"
deploy_or_update_lambda(lambda_src_1, lambda_key_1)

# deploy the template the first time
stack = deploy_cfn_template(
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/cdk-lambda-redeploy.json"
),
parameters={
"DeployBucket": lambda_bucket,
"DeployKey": lambda_key_1,
"BootstrapVersion": f"/cdk-bootstrap/{qualifier}/version",
},
)

lambda_key_2 = f"{short_uid()}.zip"
deploy_or_update_lambda(lambda_src_2, lambda_key_2)

# deploy the template the second time
deploy_cfn_template(
stack_name=stack.stack_id,
template_path=os.path.join(
os.path.dirname(__file__), "../../../templates/cdk-lambda-redeploy.json"
),
is_update=True,
parameters={
"DeployBucket": lambda_bucket,
"DeployKey": lambda_key_2,
"BootstrapVersion": "28",
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,9 @@
"MyTopicSubWithMap": "<topic-name>|arn:<partition>:sns:<region>:111111111111:<topic-name>|something"
}
}
},
"tests/aws/services/cloudformation/api/test_reference_resolving.py::test_redeploy_cdk_with_reference": {
"recorded-date": "24-09-2025, 19:40:59",
"recorded-content": {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
"tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": {
"last_validated_date": "2023-05-11T11:43:51+00:00"
},
"tests/aws/services/cloudformation/api/test_reference_resolving.py::test_redeploy_cdk_with_reference": {
"last_validated_date": "2025-09-24T19:41:46+00:00",
"durations_in_seconds": {
"setup": 12.48,
"call": 102.32,
"teardown": 47.28,
"total": 162.08
}
},
"tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": {
"last_validated_date": "2023-05-12T05:51:06+00:00"
}
Expand Down
124 changes: 124 additions & 0 deletions tests/aws/templates/cdk-lambda-redeploy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"Resources": {
"ReproPaymentsHandlerServiceRole7453EE1B": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
},
"Metadata": {
"aws:cdk:path": "CdkStack/Repro/PaymentsHandler/ServiceRole/Resource"
}
},
"ReproPaymentsHandlerFA388BE4": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "DeployBucket"
},
"S3Key": {
"Ref": "DeployKey"
}
},
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": [
"ReproPaymentsHandlerServiceRole7453EE1B",
"Arn"
]
},
"Runtime": "nodejs22.x"
},
"DependsOn": [
"ReproPaymentsHandlerServiceRole7453EE1B"
],
"Metadata": {
"aws:cdk:path": "CdkStack/Repro/PaymentsHandler/Resource",
"aws:asset:path": "asset.83bd6ee15f56848d793729be08e79f53eed8a43e4d0857f3ad9a83280cb4784b",
"aws:asset:is-bundled": true,
"aws:asset:property": "Code"
}
},
"CDKMetadata": {
"Type": "AWS::CDK::Metadata",
"Properties": {
"Analytics": "v2:deflate64:H4sIAAAAAAAA/zXNywqDMBCF4WdxP05thEKXVeiyBX0AGeMo8RKLk9SF+O4lSlff6vxHoUrueI1olVg3QzyaGrfSkR6AVqm2kaa6ocrODfeCr4Ont9qZ2YKhCbdiHhny1gZ3kLQiEXaCjwBIipnXA7uMhOGsYd7af2OHgmX2i2Y4BqWjztguBN/efbzbIXxjL5evUni9YRL1Yky8eOvMxFic/gBWkHMZyAAAAA=="
},
"Metadata": {
"aws:cdk:path": "CdkStack/CDKMetadata/Default"
}
}
},
"Outputs": {
"ReproLambdaName070199FC": {
"Value": {
"Ref": "ReproPaymentsHandlerFA388BE4"
}
}
},
"Parameters": {
"DeployBucket": {
"Type": "String"
},
"DeployKey": {
"Type": "String"
},
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Loading