Skip to content

Commit

Permalink
feat: add property to update lambda version when lambda layer is upda…
Browse files Browse the repository at this point in the history
…ted (#3661)
  • Loading branch information
aaythapa authored Nov 6, 2024
1 parent 51b6994 commit fa7549f
Show file tree
Hide file tree
Showing 24 changed files with 855 additions and 21 deletions.
5 changes: 5 additions & 0 deletions docs/globals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Currently, the following resources and properties are being supported:
EphemeralStorage:
RuntimeManagementConfig:
LoggingConfig:
FileSystemConfigs:
Api:
# Properties of AWS::Serverless::Api
Expand Down Expand Up @@ -113,6 +114,10 @@ Currently, the following resources and properties are being supported:
# Properties of AWS::Serverless::SimpleTable
SSESpecification:
LayerVersion:
# Properties of AWS::Serverless::LayerVersion
PublishLambdaVersion:
Implicit APIs
~~~~~~~~~~~~~

Expand Down
36 changes: 36 additions & 0 deletions integration/combination/test_function_with_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,42 @@ def test_alias_with_event_sources_get_correct_permissions(self):
function_policy = json.loads(function_policy_str)
self.assertEqual(len(function_policy["Statement"]), len(permission_resources))

def test_function_with_alias_and_layer_version(self):
self.create_and_verify_stack("combination/function_with_alias_all_properties_and_layer_version")
alias_name = "Live"
function_name = self.get_physical_id_by_type("AWS::Lambda::Function")
version_ids = self.get_function_version_by_name(function_name)
self.assertEqual(["1"], version_ids)

alias = self.get_alias(function_name, alias_name)
self.assertEqual("1", alias["FunctionVersion"])

# Changing Description in the LayerVersion should create a new version, and leave the existing version intact
self.set_template_resource_property("MyLayer", "Description", "test123")
self.update_stack()

version_ids = self.get_function_version_by_name(function_name)
self.assertEqual(["1", "2"], version_ids)

alias = self.get_alias(function_name, alias_name)
self.assertEqual("2", alias["FunctionVersion"])

# Changing ContentUri in LayerVersion should create a new version, and leave the existing version intact
self.set_template_resource_property("MyLayer", "ContentUri", self.file_to_s3_uri_map["layer2.zip"]["uri"])
self.update_stack()

version_ids = self.get_function_version_by_name(function_name)
self.assertEqual(["1", "2", "3"], version_ids)

alias = self.get_alias(function_name, alias_name)
self.assertEqual("3", alias["FunctionVersion"])

# Make sure the stack has only One Version & One Alias resource
alias = self.get_stack_resources("AWS::Lambda::Alias")
versions = self.get_stack_resources("AWS::Lambda::Version")
self.assertEqual(len(alias), 1)
self.assertEqual(len(versions), 1)

def get_function_version_by_name(self, function_name):
lambda_client = self.client_provider.lambda_client
versions = lambda_client.list_versions_by_function(FunctionName=function_name)["Versions"]
Expand Down
4 changes: 4 additions & 0 deletions integration/config/file_to_s3_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"type": "s3",
"uri": ""
},
"layer2.zip": {
"type": "s3",
"uri": ""
},
"swagger1.json": {
"type": "s3",
"uri": ""
Expand Down
1 change: 1 addition & 0 deletions integration/helpers/file_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"code.zip": {"type": "s3", "uri": ""},
"code2.zip": {"type": "s3", "uri": ""},
"layer1.zip": {"type": "s3", "uri": ""},
"layer2.zip": {"type": "s3", "uri": ""},
"swagger1.json": {"type": "s3", "uri": ""},
"swagger2.json": {"type": "s3", "uri": ""},
"binary-media.zip": {"type": "s3", "uri": ""},
Expand Down
Binary file added integration/resources/code/layer2.zip
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "MyLambdaFunctionAliasLive",
"ResourceType": "AWS::Lambda::Alias"
},
{
"LogicalResourceId": "MyLambdaFunctionVersion",
"ResourceType": "AWS::Lambda::Version"
},
{
"LogicalResourceId": "MyLayer",
"ResourceType": "AWS::Lambda::LayerVersion"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ${codeuri}
Handler: index.handler
Runtime: nodejs20.x
AutoPublishAlias: Live
AutoPublishAliasAllProperties: true
Layers:
- !Ref MyLayer

MyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ${contenturi}
RetentionPolicy: Delete
PublishLambdaVersion: true
Description: test
Metadata:
SamTransformTest: true
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Properties(BaseModel):
"CompatibleRuntimes",
["AWS::Lambda::LayerVersion", "Properties", "CompatibleRuntimes"],
)
PublishLambdaVersion: Optional[bool] # TODO: add docs
ContentUri: Union[str, ContentUri] = properties("ContentUri")
Description: Optional[PassThroughProp] = passthrough_prop(
PROPERTIES_STEM,
Expand All @@ -65,3 +66,7 @@ class Properties(BaseModel):
class Resource(ResourceAttributes):
Type: Literal["AWS::Serverless::LayerVersion"]
Properties: Properties


class Globals(BaseModel):
PublishLambdaVersion: Optional[bool] # TODO: add docs
1 change: 1 addition & 0 deletions samtranslator/internal/schema_source/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Globals(BaseModel):
HttpApi: Optional[aws_serverless_httpapi.Globals]
SimpleTable: Optional[aws_serverless_simpletable.Globals]
StateMachine: Optional[aws_serverless_statemachine.Globals]
LayerVersion: Optional[aws_serverless_layerversion.Globals]


Resources = Union[
Expand Down
37 changes: 34 additions & 3 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
from samtranslator.model.intrinsics import (
fnGetAtt,
fnSub,
get_logical_id_from_intrinsic,
is_intrinsic,
is_intrinsic_if,
is_intrinsic_no_value,
Expand Down Expand Up @@ -265,6 +266,7 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
"""
resources: List[Any] = []
intrinsics_resolver: IntrinsicsResolver = kwargs["intrinsics_resolver"]
resource_resolver: ResourceResolver = kwargs["resource_resolver"]
mappings_resolver: Optional[IntrinsicsResolver] = kwargs.get("mappings_resolver")
conditions = kwargs.get("conditions", {})
feature_toggle = kwargs.get("feature_toggle")
Expand Down Expand Up @@ -303,7 +305,10 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
else:
lambda_function.Description = {"Fn::Join": [" ", [description, code_sha256]]}
lambda_version = self._construct_version(
lambda_function, intrinsics_resolver=intrinsics_resolver, code_sha256=code_sha256
lambda_function,
intrinsics_resolver=intrinsics_resolver,
resource_resolver=resource_resolver,
code_sha256=code_sha256,
)
lambda_alias = self._construct_alias(alias_name, lambda_function, lambda_version)
resources.append(lambda_version)
Expand Down Expand Up @@ -882,8 +887,12 @@ def _construct_inline_code(*args: Any, **kwargs: Dict[str, Any]) -> Dict[str, An
dispatch_function: Callable[..., Dict[str, Any]] = artifact_dispatch[filtered_key]
return dispatch_function(artifacts[filtered_key], self.logical_id, filtered_key)

def _construct_version(
self, function: LambdaFunction, intrinsics_resolver: IntrinsicsResolver, code_sha256: Optional[str] = None
def _construct_version( # noqa: PLR0912
self,
function: LambdaFunction,
intrinsics_resolver: IntrinsicsResolver,
resource_resolver: ResourceResolver,
code_sha256: Optional[str] = None,
) -> LambdaVersion:
"""Constructs a Lambda Version resource that will be auto-published when CodeUri of the function changes.
Old versions will not be deleted without a direct reference from the CloudFormation template.
Expand Down Expand Up @@ -929,6 +938,26 @@ def _construct_version(
# property that when set to true would change the lambda version whenever a property in the lambda function changes
if self.AutoPublishAliasAllProperties:
properties = function._generate_resource_dict().get("Properties", {})

# When a Lambda LayerVersion resource is updated, a new Lambda layer is created.
# However, we need the Lambda function to automatically create a new version
# and use the new layer. By setting the `PublishLambdaVersion` property to true,
# a new Lambda function version will be created when the layer version is updated.
if function.Layers:
for layer in function.Layers:
layer_logical_id = get_logical_id_from_intrinsic(layer)
if not layer_logical_id:
continue

layer_resource = resource_resolver.get_resource_by_logical_id(layer_logical_id)
if not layer_resource:
continue

layer_properties = layer_resource.get("Properties", {})
publish_lambda_version = layer_properties.get("PublishLambdaVersion", False)
if publish_lambda_version:
properties.update({layer_logical_id: layer_properties})

logical_dict = properties
else:
with suppress(AttributeError, UnboundLocalError):
Expand Down Expand Up @@ -1596,6 +1625,7 @@ class SamLayerVersion(SamResourceMacro):
property_types = {
"LayerName": PropertyType(False, one_of(IS_STR, IS_DICT)),
"Description": PropertyType(False, IS_STR),
"PublishLambdaVersion": PropertyType(False, IS_BOOL),
"ContentUri": PropertyType(True, one_of(IS_STR, IS_DICT)),
"CompatibleArchitectures": PropertyType(False, list_of(one_of(IS_STR, IS_DICT))),
"CompatibleRuntimes": PropertyType(False, list_of(one_of(IS_STR, IS_DICT))),
Expand All @@ -1605,6 +1635,7 @@ class SamLayerVersion(SamResourceMacro):

LayerName: Optional[Intrinsicable[str]]
Description: Optional[Intrinsicable[str]]
PublishLambdaVersion: Optional[bool]
ContentUri: Dict[str, Any]
CompatibleArchitectures: Optional[List[Any]]
CompatibleRuntimes: Optional[List[Any]]
Expand Down
1 change: 1 addition & 0 deletions samtranslator/plugins/globals/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Globals:
],
SamResourceType.SimpleTable.value: ["SSESpecification"],
SamResourceType.StateMachine.value: ["PropagateTags"],
SamResourceType.LambdaLayerVersion.value: ["PublishLambdaVersion"],
}
# unreleased_properties *must be* part of supported_properties too
unreleased_properties: Dict[str, List[str]] = {
Expand Down
18 changes: 18 additions & 0 deletions samtranslator/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -277235,6 +277235,9 @@
"HttpApi": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_httpapi__Globals"
},
"LayerVersion": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_layerversion__Globals"
},
"SimpleTable": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_simpletable__Globals"
},
Expand Down Expand Up @@ -280291,6 +280294,17 @@
"title": "Route53",
"type": "object"
},
"samtranslator__internal__schema_source__aws_serverless_layerversion__Globals": {
"additionalProperties": false,
"properties": {
"PublishLambdaVersion": {
"title": "Publishlambdaversion",
"type": "boolean"
}
},
"title": "Globals",
"type": "object"
},
"samtranslator__internal__schema_source__aws_serverless_layerversion__Properties": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -280341,6 +280355,10 @@
"title": "LicenseInfo",
"type": "string"
},
"PublishLambdaVersion": {
"title": "Publishlambdaversion",
"type": "boolean"
},
"RetentionPolicy": {
"anyOf": [
{
Expand Down
18 changes: 18 additions & 0 deletions schema_source/sam.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3413,6 +3413,9 @@
"HttpApi": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_httpapi__Globals"
},
"LayerVersion": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_layerversion__Globals"
},
"SimpleTable": {
"$ref": "#/definitions/samtranslator__internal__schema_source__aws_serverless_simpletable__Globals"
},
Expand Down Expand Up @@ -7221,6 +7224,17 @@
"title": "Route53",
"type": "object"
},
"samtranslator__internal__schema_source__aws_serverless_layerversion__Globals": {
"additionalProperties": false,
"properties": {
"PublishLambdaVersion": {
"title": "Publishlambdaversion",
"type": "boolean"
}
},
"title": "Globals",
"type": "object"
},
"samtranslator__internal__schema_source__aws_serverless_layerversion__Properties": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -7321,6 +7335,10 @@
],
"title": "LicenseInfo"
},
"PublishLambdaVersion": {
"title": "Publishlambdaversion",
"type": "boolean"
},
"RetentionPolicy": {
"anyOf": [
{
Expand Down
5 changes: 5 additions & 0 deletions tests/model/test_sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class TestArchitecture(TestCase):
"intrinsics_resolver": IntrinsicsResolver({}),
"event_resources": [],
"managed_policy_map": {"foo": "bar"},
"resource_resolver": ResourceResolver({}),
}

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down Expand Up @@ -60,6 +61,7 @@ class TestCodeUriandImageUri(TestCase):
"intrinsics_resolver": IntrinsicsResolver({}),
"event_resources": [],
"managed_policy_map": {"foo": "bar"},
"resource_resolver": ResourceResolver({}),
}

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down Expand Up @@ -143,6 +145,7 @@ class TestAssumeRolePolicyDocument(TestCase):
"intrinsics_resolver": IntrinsicsResolver({}),
"event_resources": [],
"managed_policy_map": {"foo": "bar"},
"resource_resolver": ResourceResolver({}),
}

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down Expand Up @@ -193,6 +196,7 @@ class TestVersionDescription(TestCase):
"intrinsics_resolver": IntrinsicsResolver({}),
"event_resources": [],
"managed_policy_map": {"foo": "bar"},
"resource_resolver": ResourceResolver({}),
}

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down Expand Up @@ -441,6 +445,7 @@ class TestFunctionUrlConfig(TestCase):
"intrinsics_resolver": IntrinsicsResolver({}),
"event_resources": [],
"managed_policy_map": {"foo": "bar"},
"resource_resolver": ResourceResolver({}),
}

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Resources:
MinimalFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/hello.zip
Handler: hello.handler
Runtime: python3.10
AutoPublishAlias: live
AutoPublishAliasAllProperties: true
VersionDescription: sam-testing
Layers:
- !Ref TestEnvLayer

TestEnvLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: test-env-dependencies
Description: Dependencies for test env implementation
ContentUri: s3://bucket/key
PublishLambdaVersion: false
Loading

0 comments on commit fa7549f

Please sign in to comment.