Skip to content

Fix for bug: CloudFormation tags on AWS::Serverless::Function do not deploy #11459 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
25 changes: 25 additions & 0 deletions localstack-core/localstack/services/lambda_/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@
LAMBDA_DEFAULT_MEMORY_SIZE = 128

LAMBDA_TAG_LIMIT_PER_RESOURCE = 50
LAMBDA_TAG_KEY_LEN_LIMIT = 128
LAMBDA_TAG_VALUE_LEN_LIMIT = 256
RESERVED_TAG_PREFIX = "aws:"
LAMBDA_DEFAULT_TAG_PREFIX = "aws:cloudformation:"
LAMBDA_DEFAULT_TAGS = frozenset(
[LAMBDA_DEFAULT_TAG_PREFIX + tag for tag in ["logical-id", "stack-id", "stack-name"]]
)
LAMBDA_TAG_ALLOWED_CHARS = re.compile(r"^[a-zA-Z0-9\s+\-=._:/@]+$")
LAMBDA_LAYERS_LIMIT_PER_FUNCTION = 5

TAG_KEY_CUSTOM_URL = "_custom_id_"
Expand Down Expand Up @@ -4022,6 +4030,23 @@ def _store_tags(self, function: Function, tags: dict[str, str]):
raise InvalidParameterValueException(
"Number of tags exceeds resource tag limit.", Type="User"
)
# following validation conditions are performed based on the following AWS guidelines
# https://docs.aws.amazon.com/lambda/latest/dg/configuration-tags.html
for tag_key, tag_value in tags.items():
if len(tag_key) > 128 or len(tag_value) > 256:
raise InvalidParameterValueException(
"Length of tag's key or value exceed limit.", Type="User"
)
if not bool(LAMBDA_TAG_ALLOWED_CHARS.match(tag_key)) or not bool(
LAMBDA_TAG_ALLOWED_CHARS.match(tag_value)
):
raise InvalidParameterValueException(
"Tag key or value contains non allowed characters.", Type="User"
)
if tag_key.startswith(RESERVED_TAG_PREFIX) and tag_key not in LAMBDA_DEFAULT_TAGS:
raise InvalidParameterValueException(
"Tag key cannot contain reserved prefix (aws:)", Type="User"
)
with function.lock:
function.tags = tags
# dirty hack for changed revision id, should reevaluate model to prevent this:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,20 @@ def create(
kwargs["Environment"] = {
"Variables": {k: str(v) for k, v in environment_variables.items()}
}

# kwargs["Tags"] = request.desired_state.get('Tags',[])
tags = dict()
for tag_dict in request.desired_state.get("Tags", []):
tag_dict_key = tag_dict.get("Key")
tag_dict_value = tag_dict.get("Value")
tags[tag_dict_key] = tag_dict_value

# the following mmust be added by default as specified here:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html
Copy link

Choose a reason for hiding this comment

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

syntax: 'mmust' is misspelled, should be 'must'

tags["aws:cloudformation:logical-id"] = request.logical_resource_id
tags["aws:cloudformation:stack-id"] = request.stack_id
tags["aws:cloudformation:stack-name"] = request.stack_name

kwargs["Tags"] = tags
kwargs["Code"] = _get_lambda_code_param(model)
create_response = lambda_client.create_function(**kwargs)
model["Arn"] = create_response["FunctionArn"]
Expand Down
102 changes: 102 additions & 0 deletions tests/aws/services/lambda_/test_lambda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5353,6 +5353,108 @@ def test_tag_limits(self, create_lambda_function, snapshot, aws_client):
aws_client.lambda_.tag_resource(Resource=function_arn, Tags={"a_key": "a_value"})
snapshot.match("tag_lambda_too_many_tags_additional", e.value.response)

@markers.aws.validated
def test_tag_invalid(self, create_lambda_function, snapshot, aws_client):
""""""
function_name = f"fn-tag-{short_uid()}"
create_lambda_function(
handler_file=TEST_LAMBDA_PYTHON_ECHO,
func_name=function_name,
runtime=Runtime.python3_12,
)
function_arn = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"][
"FunctionArn"
]

# invalid
long_tag_key = 'k' * 129
tags = {long_tag_key: 'value', 'normal_key': 'normal_value'}

with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
aws_client.lambda_.tag_resource(
Resource=function_arn, Tags=tags
)

snapshot.match("tag_lambda_invalid_key_length", e.value.response)

# invalid
long_tag_value = 'v' * 257
tags = {'key': long_tag_value, 'normal_key': 'normal_value'}

with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
aws_client.lambda_.tag_resource(
Resource=function_arn, Tags=tags
)

snapshot.match("tag_lambda_invalid_value_length", e.value.response)

# Test invalid characters in tag key
invalid_tag_key = 'invalid*key'
tags = {invalid_tag_key: 'value', 'normal_key': 'normal_value'}

with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
aws_client.lambda_.tag_resource(
Resource=function_arn, Tags=tags
)

snapshot.match("tag_lambda_invalid_chars_in_key", e.value.response)

# Test invalid character in tag value
invalid_tag_value = 'invalid*value'
tags = {'key': invalid_tag_value, 'normal_key': 'normal_value'}

with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
aws_client.lambda_.tag_resource(
Resource=function_arn, Tags=tags
)

snapshot.match("tag_lambda_invalid_chars_in_value", e.value.response)

# Test reserved tag prefix (aws:) not allowed
reserved_tag_key = 'aws:some_key'
tags = {reserved_tag_key: 'value'}

with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
aws_client.lambda_.tag_resource(
Resource=function_arn, Tags=tags
)

snapshot.match("tag_lambda_invalid_prefix_in_key", e.value.response)

# Test valid tags are accepted
valid_tags = {'validKey': 'validValue', 'anotherKey': 'anotherValue'}
aws_client.lambda_.tag_resource(Resource=function_arn, Tags=valid_tags)
# Confirm that the valid tags are applied

response = aws_client.lambda_.list_tags(Resource=function_arn)

snapshot.match("tag_lambda_valid_tags", response)


# @markers.aws.validated
# def test_tag_value_length_invalid(self, create_lambda_function, snapshot, aws_client):
# """"""
# function_name = f"fn-tag-{short_uid()}"
# create_lambda_function(
# handler_file=TEST_LAMBDA_PYTHON_ECHO,
# func_name=function_name,
# runtime=Runtime.python3_12,
# )
# function_arn = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"][
# "FunctionArn"
# ]
#
# # invalid
# long_tag_value = 'v' * 257
# tags = {'key': long_tag_value}
#
# with pytest.raises(aws_client.lambda_.exceptions.InvalidParameterValueException) as e:
# aws_client.lambda_.tag_resource(
# Resource=function_arn, Tags=tags
# )
#
# snapshot.match("tag_lambda_invalid_value_length", e.value.response)

@markers.aws.validated
def test_tag_versions(self, create_lambda_function, snapshot, aws_client):
function_name = f"fn-tag-{short_uid()}"
Expand Down
75 changes: 75 additions & 0 deletions tests/aws/services/lambda_/test_lambda_api.snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -17462,5 +17462,80 @@
}
}
}
},
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_invalid": {
"recorded-date": "18-09-2024, 08:13:12",
"recorded-content": {
"tag_lambda_invalid_key_length": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Length of tag's key or value exceed limit."
},
"Type": "User",
"message": "Length of tag's key or value exceed limit.",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"tag_lambda_invalid_value_length": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Length of tag's key or value exceed limit."
},
"Type": "User",
"message": "Length of tag's key or value exceed limit.",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"tag_lambda_invalid_chars_in_key": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Tag key or value contains non allowed characters."
},
"Type": "User",
"message": "Tag key or value contains non allowed characters.",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"tag_lambda_invalid_chars_in_value": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Tag key or value contains non allowed characters."
},
"Type": "User",
"message": "Tag key or value contains non allowed characters.",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"tag_lambda_invalid_prefix_in_key": {
"Error": {
"Code": "InvalidParameterValueException",
"Message": "Tag key cannot contain reserved prefix (aws:)"
},
"Type": "User",
"message": "Tag key cannot contain reserved prefix (aws:)",
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 400
}
},
"tag_lambda_valid_tags": {
"Tags": {
"anotherKey": "anotherValue",
"validKey": "validValue"
},
"ResponseMetadata": {
"HTTPHeaders": {},
"HTTPStatusCode": 200
}
}
}
}
}