Skip to content
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

feat: ARM support #2164

Merged
merged 2 commits into from
Sep 29, 2021
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
1 change: 1 addition & 0 deletions docs/globals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Currently, the following resources and properties are being supported:
PermissionsBoundary:
ReservedConcurrentExecutions:
EventInvokeConfig:
Architectures:

Api:
# Properties of AWS::Serverless::Api
Expand Down
13 changes: 13 additions & 0 deletions integration/config/region_service_exclusion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ regions:
- IoT
- GatewayResponses
- HttpApi
- ARM
ap-east-1:
- Cognito
- IoT
- ServerlessRepo
- HttpApi
- ARM
ap-northeast-2:
- HttpApi
- ARM
ap-northeast-3:
- Cognito
- IoT
- ServerlessRepo
- XRay
- CodeDeploy
- HttpApi
- ARM
ap-south-1:
- HttpApi
ap-southeast-1:
Expand All @@ -30,6 +34,7 @@ regions:
- Cognito
- IoT
- HttpApi
- ARM
cn-north-1:
- ServerlessRepo
- Cognito
Expand All @@ -39,6 +44,7 @@ regions:
- IoT
- GatewayResponses
- HttpApi
- ARM
cn-northwest-1:
- ServerlessRepo
- Cognito
Expand All @@ -48,12 +54,14 @@ regions:
- IoT
- GatewayResponses
- HttpApi
- ARM
eu-north-1:
- ServerlessRepo
- Cognito
- IoT
- HttpApi
- Layers
- ARM
eu-south-1:
- ServerlessRepo
- Cognito
Expand All @@ -63,24 +71,29 @@ regions:
- IoT
- GatewayResponses
- HttpApi
- ARM
eu-west-2:
- HttpApi
eu-west-3:
- Cognito
- IoT
- XRay
- HttpApi
- ARM
me-south-1:
- ServerlessRepo
- Cognito
- IoT
- HttpApi
- ARM
sa-east-1:
- IoT
- Cognito
- HttpApi
- ARM
us-east-2:
- HttpApi
us-west-1:
- Cognito
- IoT
- ARM
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{ "LogicalResourceId":"MyLayerVersion", "ResourceType":"AWS::Lambda::LayerVersion"}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
CodeUri: ${codeuri}
MemorySize: 128
Architectures: ["arm64"]

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Resources:
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
CodeUri: ${codeuri}
MemorySize: 128
Architectures:
- x86_64

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Parameters:
Runtimes:
Type: CommaDelimitedList
Default: nodejs12.x,nodejs10.x
LayerName:
Type: String
Default: MyNamedLayerVersion


Resources:
MyLayerVersion:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ${contenturi}
LayerName:
Ref: LayerName
CompatibleRuntimes:
Ref: Runtimes
CompatibleArchitectures: [x86_64, arm64]

Outputs:
MyLayerArn:
Value:
Ref: MyLayerVersion
LayerName:
Value:
Ref: LayerName

22 changes: 22 additions & 0 deletions integration/single/test_basic_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ def test_function_with_http_api_events(self, file_name):

self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT)

@parameterized.expand(
[
("single/basic_function", ["x86_64"]),
("single/basic_function_no_envvar", ["x86_64"]),
("single/basic_function_openapi", ["x86_64"]),
("single/basic_function_with_arm_architecture", ["arm64"]),
("single/basic_function_with_x86_architecture", ["x86_64"]),
]
)
@skipIf(current_region_does_not_support(["ARM"]), "ARM is not supported in this testing region")
def test_basic_function_with_architecture(self, file_name, architecture):
"""
Creates a basic lambda function
"""
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_architecture = lambda_client.get_function_configuration(FunctionName=function_name)["Architectures"]

self.assertEqual(function_architecture, architecture)

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
20 changes: 19 additions & 1 deletion integration/single/test_basic_layer_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TestBasicLayerVersion(BaseTest):
"""

@skipIf(current_region_does_not_support(["Layers"]), "Layers is not supported in this testing region")
def test_basic_layer_version(self):
def test_basic_layer_version(self, filename):
"""
Creates a basic lambda layer version
"""
Expand Down Expand Up @@ -46,3 +46,21 @@ def test_basic_layer_with_parameters(self):

self.assertEqual(layer_version_result["LicenseInfo"], license)
self.assertEqual(layer_version_result["Description"], description)

@skipIf(current_region_does_not_support(["Layers"]), "Layers is not supported in this testing region")
@skipIf(current_region_does_not_support(["ARM"]), "ARM is not supported in this testing region")
def test_basic_layer_with_architecture(self):
"""
Creates a basic lambda layer version specifying compatible architecture
"""
self.create_and_verify_stack("single/basic_layer_with_compatible_architecture")

outputs = self.get_stack_outputs()
layer_arn = outputs["MyLayerArn"]
layer_name = outputs["LayerName"]

layer_version_result = self.client_provider.lambda_client.get_layer_version_by_arn(Arn=layer_arn)
self.client_provider.lambda_client.delete_layer_version(
LayerName=layer_name, VersionNumber=layer_version_result["Version"]
)
self.assertEqual(layer_version_result["CompatibleArchitectures"], ["x86_64", "arm64"])
5 changes: 5 additions & 0 deletions samtranslator/model/architecture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Enum for determining type of architectures for Lambda Function.
"""
ARM64 = "arm64"
X86_64 = "x86_64"
2 changes: 2 additions & 0 deletions samtranslator/model/lambda_.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class LambdaFunction(Resource):
"FileSystemConfigs": PropertyType(False, list_of(is_type(dict))),
"CodeSigningConfigArn": PropertyType(False, is_str()),
"ImageConfig": PropertyType(False, is_type(dict)),
"Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
}

runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
Expand Down Expand Up @@ -113,6 +114,7 @@ class LambdaLayerVersion(Resource):
"Content": PropertyType(True, is_type(dict)),
"Description": PropertyType(False, is_str()),
"LayerName": PropertyType(False, is_str()),
"CompatibleArchitectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
"CompatibleRuntimes": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
"LicenseInfo": PropertyType(False, is_str()),
}
Expand Down
63 changes: 63 additions & 0 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ApiGatewayApiKey,
)
from samtranslator.model.apigatewayv2 import ApiGatewayV2Stage, ApiGatewayV2DomainName
from samtranslator.model.architecture import ARM64, X86_64
from samtranslator.model.cloudformation import NestedStack
from samtranslator.model.dynamodb import DynamoDBTable
from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException
Expand Down Expand Up @@ -91,6 +92,7 @@ class SamFunction(SamResourceMacro):
"FileSystemConfigs": PropertyType(False, list_of(is_type(dict))),
"ImageConfig": PropertyType(False, is_type(dict)),
"CodeSigningConfigArn": PropertyType(False, is_str()),
"Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
}
event_resolver = ResourceTypeResolver(
samtranslator.model.eventsources,
Expand Down Expand Up @@ -419,6 +421,7 @@ def _construct_lambda_function(self):
lambda_function.FileSystemConfigs = self.FileSystemConfigs
lambda_function.ImageConfig = self.ImageConfig
lambda_function.PackageType = self.PackageType
lambda_function.Architectures = self.Architectures

if self.Tracing:
lambda_function.TracingConfig = {"Mode": self.Tracing}
Expand All @@ -429,6 +432,7 @@ def _construct_lambda_function(self):
lambda_function.CodeSigningConfigArn = self.CodeSigningConfigArn

self._validate_package_type(lambda_function)
self._validate_architectures(lambda_function)
return lambda_function

def _add_event_invoke_managed_policy(self, dest_config, logical_id, condition, dest_arn):
Expand Down Expand Up @@ -543,6 +547,36 @@ def _validate_package_type_image():
# Call appropriate validation function based on the package type.
return _validate_per_package_type[packagetype]()

def _validate_architectures(self, lambda_function):
"""
Validates Function based on the existence of architecture type

parameters
----------
lambda_function: LambdaFunction
Object of function properties supported on AWS Lambda

Raises
------
InvalidResourceException
Raised when the Architectures property is invalid
"""

architectures = [X86_64] if lambda_function.Architectures is None else lambda_function.Architectures

if is_intrinsic(architectures):
return

if (
not isinstance(architectures, list)
or len(architectures) != 1
or (not is_intrinsic(architectures[0]) and (architectures[0] not in [X86_64, ARM64]))
):
raise InvalidResourceException(
lambda_function.logical_id,
"Architectures needs to be a list with one string, either `{}` or `{}`.".format(X86_64, ARM64),
)

def _validate_dlq(self):
"""Validates whether the DeadLetterQueue LogicalId is validation
:raise: InvalidResourceException
Expand Down Expand Up @@ -1138,6 +1172,7 @@ class SamLayerVersion(SamResourceMacro):
"LayerName": PropertyType(False, one_of(is_str(), is_type(dict))),
"Description": PropertyType(False, is_str()),
"ContentUri": PropertyType(True, one_of(is_str(), is_type(dict))),
"CompatibleArchitectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
"CompatibleRuntimes": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))),
"LicenseInfo": PropertyType(False, is_str()),
"RetentionPolicy": PropertyType(False, is_str()),
Expand Down Expand Up @@ -1220,6 +1255,9 @@ def _construct_lambda_layer(self, intrinsics_resolver):
lambda_layer.LayerName = self.LayerName
lambda_layer.Description = self.Description
lambda_layer.Content = construct_s3_location_object(self.ContentUri, self.logical_id, "ContentUri")

lambda_layer.CompatibleArchitectures = self.CompatibleArchitectures
self._validate_architectures(lambda_layer)
lambda_layer.CompatibleRuntimes = self.CompatibleRuntimes
lambda_layer.LicenseInfo = self.LicenseInfo

Expand Down Expand Up @@ -1253,6 +1291,31 @@ def _get_retention_policy_value(self):
"'RetentionPolicy' must be one of the following options: {}.".format([self.RETAIN, self.DELETE]),
)

def _validate_architectures(self, lambda_layer):
"""Validate the values inside the CompatibleArchitectures field of a layer

Parameters
----------
lambda_layer: SamLayerVersion
The AWS Lambda layer version to validate

Raises
------
InvalidResourceException
If any of the architectures is not valid
"""
architectures = lambda_layer.CompatibleArchitectures or [X86_64]
# Intrinsics are not validated
if is_intrinsic(architectures):
return
for arq in architectures:
# We validate the values only if we they're not intrinsics
if not is_intrinsic(arq) and not arq in [ARM64, X86_64]:
raise InvalidResourceException(
lambda_layer.logical_id,
"CompatibleArchitectures needs to be a list of '{}' or '{}'".format(X86_64, ARM64),
)


class SamStateMachine(SamResourceMacro):
"""SAM state machine macro."""
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 @@ -42,6 +42,7 @@ class Globals(object):
"EventInvokeConfig",
"FileSystemConfigs",
"CodeSigningConfigArn",
"Architectures",
],
# Everything except
# DefinitionBody: because its hard to reason about merge of Swagger dictionaries
Expand Down
Loading