Skip to content

Commit

Permalink
Merge pull request #2370 from aws/release/v1.45.0
Browse files Browse the repository at this point in the history
Release/v1.45.0
  • Loading branch information
hawflau authored Apr 6, 2022
2 parents ce9a905 + 920760e commit e38e0dc
Show file tree
Hide file tree
Showing 40 changed files with 2,123 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionUrl",
"ResourceType": "AWS::Lambda::Url"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "MyLambdaFunctionUrlPublicPermissions",
"ResourceType": "AWS::Lambda::Permission"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionUrl",
"ResourceType": "AWS::Lambda::Url"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "MyLambdaFunctionAliaslive",
"ResourceType": "AWS::Lambda::Alias"
},
{
"LogicalResourceId": "MyLambdaFunctionVersion",
"ResourceType": "AWS::Lambda::Version"
},
{
"LogicalResourceId": "MyLambdaFunctionUrlPublicPermissions",
"ResourceType": "AWS::Lambda::Permission"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
CodeUri: ${codeuri}
MemorySize: 128
FunctionUrlConfig:
AuthType: NONE
Cors:
AllowOrigins:
- "https://foo.com"
AllowMethods:
- "POST"
AllowCredentials: true
AllowHeaders:
- "x-Custom-Header"
ExposeHeaders:
- "x-amzn-header"
MaxAge: 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
CodeUri: ${codeuri}
MemorySize: 128
AutoPublishAlias: live
FunctionUrlConfig:
AuthType: NONE
Cors:
AllowOrigins:
- "https://foo.com"
AllowMethods:
- "POST"
AllowCredentials: true
AllowHeaders:
- "x-Custom-Header"
ExposeHeaders:
- "x-amzn-header"
MaxAge: 10
33 changes: 33 additions & 0 deletions integration/single/test_basic_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,39 @@ def test_basic_function_with_architecture(self, file_name, architecture):

self.assertEqual(function_architecture, architecture)

@parameterized.expand(
[
("single/basic_function_with_function_url_config", None),
("single/basic_function_with_function_url_with_autopuplishalias", "live"),
]
)
@skipIf(current_region_does_not_support(["Url"]), "Url is not supported in this testing region")
def test_basic_function_with_url_config(self, file_name, qualifier):
"""
Creates a basic lambda function with Function Url enabled
"""
self.create_and_verify_stack(file_name)

lambda_client = self.client_provider.lambda_client

function_name = self.get_physical_id_by_type("AWS::Lambda::Function")
function_url_config = (
lambda_client.get_function_url_config(FunctionName=function_name, Qualifier=qualifier)
if qualifier
else lambda_client.get_function_url_config(FunctionName=function_name)
)
cors_config = {
"AllowOrigins": ["https://foo.com"],
"AllowMethods": ["POST"],
"AllowCredentials": True,
"AllowHeaders": ["x-custom-header"],
"ExposeHeaders": ["x-amzn-header"],
"MaxAge": 10,
}

self.assertEqual(function_url_config["AuthType"], "NONE")
self.assertEqual(function_url_config["Cors"], cors_config)

def test_function_with_deployment_preference_alarms_intrinsic_if(self):
self.create_and_verify_stack("single/function_with_deployment_preference_alarms_intrinsic_if")

Expand Down
2 changes: 1 addition & 1 deletion samtranslator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.44.0"
__version__ = "1.45.0"
10 changes: 10 additions & 0 deletions samtranslator/model/lambda_.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class LambdaPermission(Resource):
"SourceAccount": PropertyType(False, is_str()),
"SourceArn": PropertyType(False, is_str()),
"EventSourceToken": PropertyType(False, is_str()),
"FunctionUrlAuthType": PropertyType(False, is_str()),
}


Expand Down Expand Up @@ -123,3 +124,12 @@ class LambdaLayerVersion(Resource):
}

runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")}


class LambdaUrl(Resource):
resource_type = "AWS::Lambda::Url"
property_types = {
"TargetFunctionArn": PropertyType(True, one_of(is_str(), is_type(dict))),
"AuthType": PropertyType(True, is_str()),
"Cors": PropertyType(False, is_type(dict)),
}
120 changes: 120 additions & 0 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
LambdaAlias,
LambdaLayerVersion,
LambdaEventInvokeConfig,
LambdaUrl,
LambdaPermission,
)
from samtranslator.model.types import dict_of, is_str, is_type, list_of, one_of, any_type
from samtranslator.translator import logical_id_generator
Expand Down Expand Up @@ -93,6 +95,7 @@ class SamFunction(SamResourceMacro):
"ImageConfig": PropertyType(False, is_type(dict)),
"CodeSigningConfigArn": PropertyType(False, is_str()),
"Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
"FunctionUrlConfig": PropertyType(False, is_type(dict)),
}
event_resolver = ResourceTypeResolver(
samtranslator.model.eventsources,
Expand Down Expand Up @@ -169,6 +172,13 @@ def to_cloudformation(self, **kwargs):
resources.append(lambda_version)
resources.append(lambda_alias)

if self.FunctionUrlConfig:
lambda_url = self._construct_function_url(lambda_function, lambda_alias)
resources.append(lambda_url)
url_permission = self._construct_url_permission(lambda_function)
if url_permission:
resources.append(url_permission)

if self.DeploymentPreference:
self._validate_deployment_preference_and_add_update_policy(
kwargs.get("deployment_preference_collection", None),
Expand Down Expand Up @@ -850,6 +860,116 @@ def _validate_deployment_preference_and_add_update_policy(
"UpdatePolicy", deployment_preference_collection.update_policy(self.logical_id).to_dict()
)

def _construct_function_url(self, lambda_function, lambda_alias):
"""
This method is used to construct a lambda url resource
Parameters
----------
lambda_function : LambdaFunction
Lambda Function resource
lambda_alias : LambdaAlias
Lambda Alias resource
Returns
-------
LambdaUrl
Lambda Url resource
"""
self._validate_function_url_params(lambda_function)

logical_id = f"{lambda_function.logical_id}Url"
lambda_url = LambdaUrl(logical_id=logical_id)

cors = self.FunctionUrlConfig.get("Cors")
if cors:
lambda_url.Cors = cors
lambda_url.AuthType = self.FunctionUrlConfig.get("AuthType")
lambda_url.TargetFunctionArn = (
lambda_alias.get_runtime_attr("arn") if lambda_alias else lambda_function.get_runtime_attr("name")
)
return lambda_url

def _validate_function_url_params(self, lambda_function):
"""
Validate parameters provided to configure Lambda Urls
"""
self._validate_url_auth_type(lambda_function)
self._validate_cors_config_parameter(lambda_function)

def _validate_url_auth_type(self, lambda_function):
if is_intrinsic(self.FunctionUrlConfig):
return

auth_type = self.FunctionUrlConfig.get("AuthType")
if auth_type and is_intrinsic(auth_type):
return

if not auth_type or auth_type not in ["AWS_IAM", "NONE"]:
raise InvalidResourceException(
lambda_function.logical_id,
"AuthType is required to configure function property `FunctionUrlConfig`. Please provide either AWS_IAM or NONE.",
)

def _validate_cors_config_parameter(self, lambda_function):
if is_intrinsic(self.FunctionUrlConfig):
return

cors_property_data_type = {
"AllowOrigins": list,
"AllowMethods": list,
"AllowCredentials": bool,
"AllowHeaders": list,
"ExposeHeaders": list,
"MaxAge": int,
}

cors = self.FunctionUrlConfig.get("Cors")

if not cors or is_intrinsic(cors):
return

for prop_name, prop_value in cors.items():
if prop_name not in cors_property_data_type:
raise InvalidResourceException(
lambda_function.logical_id,
"{} is not a valid property for configuring Cors.".format(prop_name),
)
prop_type = cors_property_data_type.get(prop_name)
if not is_intrinsic(prop_value) and not isinstance(prop_value, prop_type):
raise InvalidResourceException(
lambda_function.logical_id,
"{} must be of type {}.".format(prop_name, str(prop_type).split("'")[1]),
)

def _construct_url_permission(self, lambda_function):
"""
Construct the lambda permission associated with the function url resource in a case
for public access when AuthType is NONE
Parameters
----------
lambda_function : LambdaUrl
Lambda Function resource
Returns
-------
LambdaPermission
The lambda permission appended to a function url resource with public access
"""
auth_type = self.FunctionUrlConfig.get("AuthType")

if auth_type not in ["NONE"] or is_intrinsic(self.FunctionUrlConfig):
return None

logical_id = f"{lambda_function.logical_id}UrlPublicPermissions"
lambda_permission = LambdaPermission(logical_id=logical_id)
lambda_permission.Action = "lambda:InvokeFunctionUrl"
lambda_permission.FunctionName = lambda_function.get_runtime_attr("name")
lambda_permission.Principal = "*"
lambda_permission.FunctionUrlAuthType = auth_type
return lambda_permission


class SamApi(SamResourceMacro):
"""SAM rest API macro."""
Expand Down
10 changes: 8 additions & 2 deletions samtranslator/plugins/globals/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Globals(object):
"CodeSigningConfigArn",
"Architectures",
"EphemeralStorage",
"FunctionUrlConfig",
],
# Everything except
# DefinitionBody: because its hard to reason about merge of Swagger dictionaries
Expand Down Expand Up @@ -80,6 +81,8 @@ class Globals(object):
],
SamResourceType.SimpleTable.value: ["SSESpecification"],
}
# unreleased_properties *must be* part of supported_properties too
unreleased_properties = {}

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

supported = self.supported_properties[resource_type]
supported_displayed = [
prop for prop in supported if prop not in self.unreleased_properties.get(resource_type, [])
]
for key, value in properties.items():
supported = self.supported_properties[resource_type]
if key not in supported:
raise InvalidGlobalsSectionException(
self._KEYWORD,
"'{key}' is not a supported property of '{section}'. "
"Must be one of the following values - {supported}".format(
key=key, section=section_name, supported=supported
key=key, section=section_name, supported=supported_displayed
),
)

Expand Down
1 change: 0 additions & 1 deletion samtranslator/swagger/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ def add_path(self, path, method=None):
:param string path: Path name
:param string method: HTTP method
:raises ValueError: If the value of `path` in Swagger is not a dictionary
"""
method = self._normalize_method_name(method)

Expand Down
Loading

0 comments on commit e38e0dc

Please sign in to comment.