Skip to content

Commit e38e0dc

Browse files
authored
Merge pull request #2370 from aws/release/v1.45.0
Release/v1.45.0
2 parents ce9a905 + 920760e commit e38e0dc

File tree

40 files changed

+2123
-7
lines changed

40 files changed

+2123
-7
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"LogicalResourceId": "MyLambdaFunction",
4+
"ResourceType": "AWS::Lambda::Function"
5+
},
6+
{
7+
"LogicalResourceId": "MyLambdaFunctionUrl",
8+
"ResourceType": "AWS::Lambda::Url"
9+
},
10+
{
11+
"LogicalResourceId": "MyLambdaFunctionRole",
12+
"ResourceType": "AWS::IAM::Role"
13+
},
14+
{
15+
"LogicalResourceId": "MyLambdaFunctionUrlPublicPermissions",
16+
"ResourceType": "AWS::Lambda::Permission"
17+
}
18+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"LogicalResourceId": "MyLambdaFunction",
4+
"ResourceType": "AWS::Lambda::Function"
5+
},
6+
{
7+
"LogicalResourceId": "MyLambdaFunctionUrl",
8+
"ResourceType": "AWS::Lambda::Url"
9+
},
10+
{
11+
"LogicalResourceId": "MyLambdaFunctionRole",
12+
"ResourceType": "AWS::IAM::Role"
13+
},
14+
{
15+
"LogicalResourceId": "MyLambdaFunctionAliaslive",
16+
"ResourceType": "AWS::Lambda::Alias"
17+
},
18+
{
19+
"LogicalResourceId": "MyLambdaFunctionVersion",
20+
"ResourceType": "AWS::Lambda::Version"
21+
},
22+
{
23+
"LogicalResourceId": "MyLambdaFunctionUrlPublicPermissions",
24+
"ResourceType": "AWS::Lambda::Permission"
25+
}
26+
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Resources:
2+
MyLambdaFunction:
3+
Type: AWS::Serverless::Function
4+
Properties:
5+
Handler: index.handler
6+
Runtime: nodejs12.x
7+
CodeUri: ${codeuri}
8+
MemorySize: 128
9+
FunctionUrlConfig:
10+
AuthType: NONE
11+
Cors:
12+
AllowOrigins:
13+
- "https://foo.com"
14+
AllowMethods:
15+
- "POST"
16+
AllowCredentials: true
17+
AllowHeaders:
18+
- "x-Custom-Header"
19+
ExposeHeaders:
20+
- "x-amzn-header"
21+
MaxAge: 10
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Resources:
2+
MyLambdaFunction:
3+
Type: AWS::Serverless::Function
4+
Properties:
5+
Handler: index.handler
6+
Runtime: nodejs12.x
7+
CodeUri: ${codeuri}
8+
MemorySize: 128
9+
AutoPublishAlias: live
10+
FunctionUrlConfig:
11+
AuthType: NONE
12+
Cors:
13+
AllowOrigins:
14+
- "https://foo.com"
15+
AllowMethods:
16+
- "POST"
17+
AllowCredentials: true
18+
AllowHeaders:
19+
- "x-Custom-Header"
20+
ExposeHeaders:
21+
- "x-amzn-header"
22+
MaxAge: 10

integration/single/test_basic_function.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,39 @@ def test_basic_function_with_architecture(self, file_name, architecture):
6666

6767
self.assertEqual(function_architecture, architecture)
6868

69+
@parameterized.expand(
70+
[
71+
("single/basic_function_with_function_url_config", None),
72+
("single/basic_function_with_function_url_with_autopuplishalias", "live"),
73+
]
74+
)
75+
@skipIf(current_region_does_not_support(["Url"]), "Url is not supported in this testing region")
76+
def test_basic_function_with_url_config(self, file_name, qualifier):
77+
"""
78+
Creates a basic lambda function with Function Url enabled
79+
"""
80+
self.create_and_verify_stack(file_name)
81+
82+
lambda_client = self.client_provider.lambda_client
83+
84+
function_name = self.get_physical_id_by_type("AWS::Lambda::Function")
85+
function_url_config = (
86+
lambda_client.get_function_url_config(FunctionName=function_name, Qualifier=qualifier)
87+
if qualifier
88+
else lambda_client.get_function_url_config(FunctionName=function_name)
89+
)
90+
cors_config = {
91+
"AllowOrigins": ["https://foo.com"],
92+
"AllowMethods": ["POST"],
93+
"AllowCredentials": True,
94+
"AllowHeaders": ["x-custom-header"],
95+
"ExposeHeaders": ["x-amzn-header"],
96+
"MaxAge": 10,
97+
}
98+
99+
self.assertEqual(function_url_config["AuthType"], "NONE")
100+
self.assertEqual(function_url_config["Cors"], cors_config)
101+
69102
def test_function_with_deployment_preference_alarms_intrinsic_if(self):
70103
self.create_and_verify_stack("single/function_with_deployment_preference_alarms_intrinsic_if")
71104

samtranslator/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.44.0"
1+
__version__ = "1.45.0"

samtranslator/model/lambda_.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class LambdaPermission(Resource):
9595
"SourceAccount": PropertyType(False, is_str()),
9696
"SourceArn": PropertyType(False, is_str()),
9797
"EventSourceToken": PropertyType(False, is_str()),
98+
"FunctionUrlAuthType": PropertyType(False, is_str()),
9899
}
99100

100101

@@ -123,3 +124,12 @@ class LambdaLayerVersion(Resource):
123124
}
124125

125126
runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
127+
128+
129+
class LambdaUrl(Resource):
130+
resource_type = "AWS::Lambda::Url"
131+
property_types = {
132+
"TargetFunctionArn": PropertyType(True, one_of(is_str(), is_type(dict))),
133+
"AuthType": PropertyType(True, is_str()),
134+
"Cors": PropertyType(False, is_type(dict)),
135+
}

samtranslator/model/sam_resources.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
LambdaAlias,
3434
LambdaLayerVersion,
3535
LambdaEventInvokeConfig,
36+
LambdaUrl,
37+
LambdaPermission,
3638
)
3739
from samtranslator.model.types import dict_of, is_str, is_type, list_of, one_of, any_type
3840
from samtranslator.translator import logical_id_generator
@@ -93,6 +95,7 @@ class SamFunction(SamResourceMacro):
9395
"ImageConfig": PropertyType(False, is_type(dict)),
9496
"CodeSigningConfigArn": PropertyType(False, is_str()),
9597
"Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
98+
"FunctionUrlConfig": PropertyType(False, is_type(dict)),
9699
}
97100
event_resolver = ResourceTypeResolver(
98101
samtranslator.model.eventsources,
@@ -169,6 +172,13 @@ def to_cloudformation(self, **kwargs):
169172
resources.append(lambda_version)
170173
resources.append(lambda_alias)
171174

175+
if self.FunctionUrlConfig:
176+
lambda_url = self._construct_function_url(lambda_function, lambda_alias)
177+
resources.append(lambda_url)
178+
url_permission = self._construct_url_permission(lambda_function)
179+
if url_permission:
180+
resources.append(url_permission)
181+
172182
if self.DeploymentPreference:
173183
self._validate_deployment_preference_and_add_update_policy(
174184
kwargs.get("deployment_preference_collection", None),
@@ -850,6 +860,116 @@ def _validate_deployment_preference_and_add_update_policy(
850860
"UpdatePolicy", deployment_preference_collection.update_policy(self.logical_id).to_dict()
851861
)
852862

863+
def _construct_function_url(self, lambda_function, lambda_alias):
864+
"""
865+
This method is used to construct a lambda url resource
866+
867+
Parameters
868+
----------
869+
lambda_function : LambdaFunction
870+
Lambda Function resource
871+
lambda_alias : LambdaAlias
872+
Lambda Alias resource
873+
874+
Returns
875+
-------
876+
LambdaUrl
877+
Lambda Url resource
878+
"""
879+
self._validate_function_url_params(lambda_function)
880+
881+
logical_id = f"{lambda_function.logical_id}Url"
882+
lambda_url = LambdaUrl(logical_id=logical_id)
883+
884+
cors = self.FunctionUrlConfig.get("Cors")
885+
if cors:
886+
lambda_url.Cors = cors
887+
lambda_url.AuthType = self.FunctionUrlConfig.get("AuthType")
888+
lambda_url.TargetFunctionArn = (
889+
lambda_alias.get_runtime_attr("arn") if lambda_alias else lambda_function.get_runtime_attr("name")
890+
)
891+
return lambda_url
892+
893+
def _validate_function_url_params(self, lambda_function):
894+
"""
895+
Validate parameters provided to configure Lambda Urls
896+
"""
897+
self._validate_url_auth_type(lambda_function)
898+
self._validate_cors_config_parameter(lambda_function)
899+
900+
def _validate_url_auth_type(self, lambda_function):
901+
if is_intrinsic(self.FunctionUrlConfig):
902+
return
903+
904+
auth_type = self.FunctionUrlConfig.get("AuthType")
905+
if auth_type and is_intrinsic(auth_type):
906+
return
907+
908+
if not auth_type or auth_type not in ["AWS_IAM", "NONE"]:
909+
raise InvalidResourceException(
910+
lambda_function.logical_id,
911+
"AuthType is required to configure function property `FunctionUrlConfig`. Please provide either AWS_IAM or NONE.",
912+
)
913+
914+
def _validate_cors_config_parameter(self, lambda_function):
915+
if is_intrinsic(self.FunctionUrlConfig):
916+
return
917+
918+
cors_property_data_type = {
919+
"AllowOrigins": list,
920+
"AllowMethods": list,
921+
"AllowCredentials": bool,
922+
"AllowHeaders": list,
923+
"ExposeHeaders": list,
924+
"MaxAge": int,
925+
}
926+
927+
cors = self.FunctionUrlConfig.get("Cors")
928+
929+
if not cors or is_intrinsic(cors):
930+
return
931+
932+
for prop_name, prop_value in cors.items():
933+
if prop_name not in cors_property_data_type:
934+
raise InvalidResourceException(
935+
lambda_function.logical_id,
936+
"{} is not a valid property for configuring Cors.".format(prop_name),
937+
)
938+
prop_type = cors_property_data_type.get(prop_name)
939+
if not is_intrinsic(prop_value) and not isinstance(prop_value, prop_type):
940+
raise InvalidResourceException(
941+
lambda_function.logical_id,
942+
"{} must be of type {}.".format(prop_name, str(prop_type).split("'")[1]),
943+
)
944+
945+
def _construct_url_permission(self, lambda_function):
946+
"""
947+
Construct the lambda permission associated with the function url resource in a case
948+
for public access when AuthType is NONE
949+
950+
Parameters
951+
----------
952+
lambda_function : LambdaUrl
953+
Lambda Function resource
954+
955+
Returns
956+
-------
957+
LambdaPermission
958+
The lambda permission appended to a function url resource with public access
959+
"""
960+
auth_type = self.FunctionUrlConfig.get("AuthType")
961+
962+
if auth_type not in ["NONE"] or is_intrinsic(self.FunctionUrlConfig):
963+
return None
964+
965+
logical_id = f"{lambda_function.logical_id}UrlPublicPermissions"
966+
lambda_permission = LambdaPermission(logical_id=logical_id)
967+
lambda_permission.Action = "lambda:InvokeFunctionUrl"
968+
lambda_permission.FunctionName = lambda_function.get_runtime_attr("name")
969+
lambda_permission.Principal = "*"
970+
lambda_permission.FunctionUrlAuthType = auth_type
971+
return lambda_permission
972+
853973

854974
class SamApi(SamResourceMacro):
855975
"""SAM rest API macro."""

samtranslator/plugins/globals/globals.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Globals(object):
4343
"CodeSigningConfigArn",
4444
"Architectures",
4545
"EphemeralStorage",
46+
"FunctionUrlConfig",
4647
],
4748
# Everything except
4849
# DefinitionBody: because its hard to reason about merge of Swagger dictionaries
@@ -80,6 +81,8 @@ class Globals(object):
8081
],
8182
SamResourceType.SimpleTable.value: ["SSESpecification"],
8283
}
84+
# unreleased_properties *must be* part of supported_properties too
85+
unreleased_properties = {}
8386

8487
def __init__(self, template):
8588
"""
@@ -195,14 +198,17 @@ def _parse(self, globals_dict):
195198
if not isinstance(properties, dict):
196199
raise InvalidGlobalsSectionException(self._KEYWORD, "Value of ${section} must be a dictionary")
197200

201+
supported = self.supported_properties[resource_type]
202+
supported_displayed = [
203+
prop for prop in supported if prop not in self.unreleased_properties.get(resource_type, [])
204+
]
198205
for key, value in properties.items():
199-
supported = self.supported_properties[resource_type]
200206
if key not in supported:
201207
raise InvalidGlobalsSectionException(
202208
self._KEYWORD,
203209
"'{key}' is not a supported property of '{section}'. "
204210
"Must be one of the following values - {supported}".format(
205-
key=key, section=section_name, supported=supported
211+
key=key, section=section_name, supported=supported_displayed
206212
),
207213
)
208214

samtranslator/swagger/swagger.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ def add_path(self, path, method=None):
166166
167167
:param string path: Path name
168168
:param string method: HTTP method
169-
:raises ValueError: If the value of `path` in Swagger is not a dictionary
170169
"""
171170
method = self._normalize_method_name(method)
172171

0 commit comments

Comments
 (0)