Skip to content

Commit 0bc7d65

Browse files
mndevecimgrandis
andauthored
feat: ARM support (#25) (#287)
Co-authored-by: Mathieu Grandis <[email protected]>
1 parent bc95771 commit 0bc7d65

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1252
-264
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,5 +389,6 @@ $RECYCLE.BIN/
389389
/Dockerfile
390390

391391
tests/integration/workflows/go_dep/data/src/*/vendor/*
392+
tests/integration/workflows/go_dep/data/pkg/*
392393

393394
# End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode

aws_lambda_builders/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import logging
1313
import re
1414

15+
from aws_lambda_builders.architecture import X86_64
1516
from aws_lambda_builders.builder import LambdaBuilder
1617
from aws_lambda_builders.exceptions import WorkflowNotFoundError, WorkflowUnknownError, WorkflowFailedError
1718
from aws_lambda_builders import RPC_PROTOCOL_VERSION as lambda_builders_protocol_version
@@ -124,6 +125,7 @@ def main(): # pylint: disable=too-many-statements
124125
optimizations=params["optimizations"],
125126
options=params["options"],
126127
mode=params.get("mode", None),
128+
architecture=params.get("architecture", X86_64),
127129
)
128130

129131
# Return a success response

aws_lambda_builders/architecture.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Enum for determining type of architectures for Lambda Function.
3+
"""
4+
X86_64 = "x86_64"
5+
ARM64 = "arm64"

aws_lambda_builders/builder.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import logging
88

9+
from aws_lambda_builders.architecture import X86_64, ARM64
910
from aws_lambda_builders.registry import get_workflow, DEFAULT_REGISTRY
1011
from aws_lambda_builders.workflow import Capability
1112

@@ -64,6 +65,7 @@ def build(
6465
options=None,
6566
executable_search_paths=None,
6667
mode=None,
68+
architecture=X86_64,
6769
):
6870
"""
6971
Actually build the code by running workflows
@@ -105,6 +107,10 @@ def build(
105107
:type mode: str
106108
:param mode:
107109
Optional, Mode the build should produce
110+
111+
:type architecture: str
112+
:param architecture:
113+
Type of architecture x86_64 and arm64 for Lambda Function
108114
"""
109115

110116
if not os.path.exists(scratch_dir):
@@ -120,6 +126,7 @@ def build(
120126
options=options,
121127
executable_search_paths=executable_search_paths,
122128
mode=mode,
129+
architecture=architecture,
123130
)
124131

125132
return workflow.run()

aws_lambda_builders/exceptions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ class MisMatchRuntimeError(LambdaBuilderError):
2424
)
2525

2626

27+
class RuntimeValidatorError(LambdaBuilderError):
28+
"""
29+
Raise when runtime is not supported or when runtime is not compatible with architecture
30+
"""
31+
32+
MESSAGE = "Runtime validation error for {runtime}"
33+
34+
35+
class UnsupportedRuntimeError(RuntimeValidatorError):
36+
"""
37+
Raise when runtime is not supported
38+
"""
39+
40+
MESSAGE = "Runtime {runtime} is not suppported"
41+
42+
43+
class UnsupportedArchitectureError(RuntimeValidatorError):
44+
"""
45+
Raise when runtime does not support architecture
46+
"""
47+
48+
MESSAGE = "Architecture {architecture} is not supported for runtime {runtime}"
49+
50+
2751
class WorkflowNotFoundError(LambdaBuilderError):
2852
"""
2953
Raised when a workflow matching the given capabilities was not found

aws_lambda_builders/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import os
88
import logging
99

10+
from aws_lambda_builders.architecture import X86_64, ARM64
1011

1112
LOG = logging.getLogger(__name__)
1213

@@ -148,3 +149,18 @@ def _access_check(fn, mode):
148149
if _access_check(name, mode):
149150
paths.append(name)
150151
return paths
152+
153+
154+
def get_goarch(architecture):
155+
"""
156+
Parameters
157+
----------
158+
architecture : str
159+
name of the type of architecture
160+
161+
Returns
162+
-------
163+
str
164+
returns a valid GO Architecture value
165+
"""
166+
return "arm64" if architecture == ARM64 else "amd64"

aws_lambda_builders/validator.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,74 @@
44

55
import logging
66

7+
from aws_lambda_builders.architecture import ARM64, X86_64
8+
from aws_lambda_builders.exceptions import UnsupportedRuntimeError, UnsupportedArchitectureError
9+
10+
711
LOG = logging.getLogger(__name__)
812

13+
SUPPORTED_RUNTIMES = {
14+
"nodejs10.x": [X86_64],
15+
"nodejs12.x": [ARM64, X86_64],
16+
"nodejs14.x": [ARM64, X86_64],
17+
"python2.7": [X86_64],
18+
"python3.6": [X86_64],
19+
"python3.7": [X86_64],
20+
"python3.8": [ARM64, X86_64],
21+
"python3.9": [ARM64, X86_64],
22+
"ruby2.5": [X86_64],
23+
"ruby2.7": [ARM64, X86_64],
24+
"java8": [ARM64, X86_64],
25+
"java11": [ARM64, X86_64],
26+
"go1.x": [ARM64, X86_64],
27+
"dotnetcore2.1": [X86_64],
28+
"dotnetcore3.1": [ARM64, X86_64],
29+
"provided": [ARM64, X86_64],
30+
}
31+
932

1033
class RuntimeValidator(object):
11-
def __init__(self, runtime):
34+
def __init__(self, runtime, architecture):
35+
"""
36+
37+
Parameters
38+
----------
39+
runtime : str
40+
name of the AWS Lambda runtime that you are building for. This is sent to the builder for
41+
informational purposes.
42+
architecture : str
43+
Architecture for which the build will be based on in AWS lambda
44+
"""
1245
self.runtime = runtime
1346
self._runtime_path = None
47+
self.architecture = architecture
1448

1549
def validate(self, runtime_path):
50+
"""
51+
Parameters
52+
----------
53+
runtime_path : str
54+
runtime to check eg: /usr/bin/runtime
55+
56+
Returns
57+
-------
58+
str
59+
runtime to check eg: /usr/bin/runtime
60+
61+
Raises
62+
------
63+
UnsupportedRuntimeError
64+
Raised when runtime provided is not support.
65+
66+
UnsupportedArchitectureError
67+
Raised when runtime is not compatible with architecture
68+
"""
69+
runtime_architectures = SUPPORTED_RUNTIMES.get(self.runtime, None)
70+
71+
if not runtime_architectures:
72+
raise UnsupportedRuntimeError(runtime=self.runtime)
73+
if self.architecture not in runtime_architectures:
74+
raise UnsupportedArchitectureError(runtime=self.runtime, architecture=self.architecture)
75+
1676
self._runtime_path = runtime_path
1777
return runtime_path

aws_lambda_builders/workflow.py

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,15 @@
1212
from aws_lambda_builders.path_resolver import PathResolver
1313
from aws_lambda_builders.validator import RuntimeValidator
1414
from aws_lambda_builders.registry import DEFAULT_REGISTRY
15-
from aws_lambda_builders.exceptions import WorkflowFailedError, WorkflowUnknownError, MisMatchRuntimeError
15+
from aws_lambda_builders.exceptions import (
16+
WorkflowFailedError,
17+
WorkflowUnknownError,
18+
MisMatchRuntimeError,
19+
RuntimeValidatorError,
20+
)
1621
from aws_lambda_builders.actions import ActionFailedError
22+
from aws_lambda_builders.architecture import X86_64
23+
1724

1825
LOG = logging.getLogger(__name__)
1926

@@ -32,16 +39,17 @@ class BuildMode(object):
3239

3340

3441
# TODO: Move sanitize out to its own class.
35-
def sanitize(func):
42+
def sanitize(func): # pylint: disable=too-many-statements
3643
"""
3744
sanitize the executable path of the runtime specified by validating it.
3845
:param func: Workflow's run method is sanitized
3946
"""
4047

4148
@functools.wraps(func)
42-
def wrapper(self, *args, **kwargs):
49+
def wrapper(self, *args, **kwargs): # pylint: disable=too-many-statements
4350
valid_paths = {}
4451
invalid_paths = {}
52+
validation_errors = []
4553
# NOTE: we need to access binaries to get paths and resolvers, before validating.
4654
for binary, binary_checker in self.binaries.items():
4755
invalid_paths[binary] = []
@@ -61,18 +69,30 @@ def wrapper(self, *args, **kwargs):
6169
except MisMatchRuntimeError as ex:
6270
LOG.debug("Invalid executable for %s at %s", binary, executable_path, exc_info=str(ex))
6371
invalid_paths[binary].append(executable_path)
72+
73+
except RuntimeValidatorError as ex:
74+
LOG.debug("Runtime validation error for %s", binary, exc_info=str(ex))
75+
if str(ex) not in validation_errors:
76+
validation_errors.append(str(ex))
77+
6478
if valid_paths.get(binary, None):
6579
binary_checker.binary_path = valid_paths[binary]
6680
break
81+
if validation_errors:
82+
raise WorkflowFailedError(
83+
workflow_name=self.NAME, action_name="Validation", reason="\n".join(validation_errors)
84+
)
85+
6786
if len(self.binaries) != len(valid_paths):
6887
validation_failed_binaries = set(self.binaries.keys()).difference(valid_paths.keys())
69-
messages = []
7088
for validation_failed_binary in validation_failed_binaries:
7189
message = "Binary validation failed for {0}, searched for {0} in following locations : {1} which did not satisfy constraints for runtime: {2}. Do you have {0} for runtime: {2} on your PATH?".format(
7290
validation_failed_binary, invalid_paths[validation_failed_binary], self.runtime
7391
)
74-
messages.append(message)
75-
raise WorkflowFailedError(workflow_name=self.NAME, action_name="Validation", reason="\n".join(messages))
92+
validation_errors.append(message)
93+
raise WorkflowFailedError(
94+
workflow_name=self.NAME, action_name="Validation", reason="\n".join(validation_errors)
95+
)
7696
func(self, *args, **kwargs)
7797

7898
return wrapper
@@ -140,48 +160,36 @@ def __init__(
140160
optimizations=None,
141161
options=None,
142162
mode=BuildMode.RELEASE,
163+
architecture=X86_64,
143164
):
144165
"""
145166
Initialize the builder with given arguments. These arguments together form the "public API" that each
146167
build action must support at the minimum.
147168
148-
:type source_dir: str
149-
:param source_dir:
169+
Parameters
170+
----------
171+
source_dir : str
150172
Path to a folder containing the source code
151-
152-
:type artifacts_dir: str
153-
:param artifacts_dir:
173+
artifacts_dir : str
154174
Path to a folder where the built artifacts should be placed
155-
156-
:type scratch_dir: str
157-
:param scratch_dir:
175+
scratch_dir : str
158176
Path to a directory that the workflow can use as scratch space. Workflows are expected to use this directory
159177
to write temporary files instead of ``/tmp`` or other OS-specific temp directories.
160-
161-
:type manifest_path: str
162-
:param manifest_path:
178+
manifest_path : str
163179
Path to the dependency manifest
164-
165-
:type runtime: str
166-
:param runtime:
167-
Optional, name of the AWS Lambda runtime that you are building for. This is sent to the builder for
168-
informational purposes.
169-
170-
:type optimizations: dict
171-
:param optimizations:
172-
Optional dictionary of optimization flags to pass to the build action. **Not supported**.
173-
174-
:type options: dict
175-
:param options:
176-
Optional dictionary of options ot pass to build action. **Not supported**.
177-
178-
:type executable_search_paths: list
179-
:param executable_search_paths:
180-
Optional, Additional list of paths to search for executables required by the workflow.
181-
182-
:type mode: str
183-
:param mode:
184-
Optional, Mode the build should produce
180+
runtime : str, optional
181+
name of the AWS Lambda runtime that you are building for. This is sent to the builder for
182+
informational purposes, by default None
183+
executable_search_paths : list, optional
184+
Additional list of paths to search for executables required by the workflow, by default None
185+
optimizations : dict, optional
186+
dictionary of optimization flags to pass to the build action. **Not supported**, by default None
187+
options : dict, optional
188+
dictionary of options ot pass to build action. **Not supported**., by default None
189+
mode : str, optional
190+
Mode the build should produce, by default BuildMode.RELEASE
191+
architecture : str, optional
192+
Architecture type either arm64 or x86_64 for which the build will be based on in AWS lambda, by default X86_64
185193
"""
186194

187195
self.source_dir = source_dir
@@ -193,6 +201,7 @@ def __init__(
193201
self.options = options
194202
self.executable_search_paths = executable_search_paths
195203
self.mode = mode
204+
self.architecture = architecture
196205

197206
# Actions are registered by the subclasses as they seem fit
198207
self.actions = []
@@ -225,7 +234,7 @@ def get_validators(self):
225234
"""
226235
No-op validator that does not validate the runtime_path.
227236
"""
228-
return [RuntimeValidator(runtime=self.runtime)]
237+
return [RuntimeValidator(runtime=self.runtime, architecture=self.architecture)]
229238

230239
@property
231240
def binaries(self):

0 commit comments

Comments
 (0)