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

Merge from develop to master #289

Merged
merged 10 commits into from
Sep 29, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -389,5 +389,6 @@ $RECYCLE.BIN/
/Dockerfile

tests/integration/workflows/go_dep/data/src/*/vendor/*
tests/integration/workflows/go_dep/data/pkg/*

# End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode
2 changes: 1 addition & 1 deletion aws_lambda_builders/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
AWS Lambda Builder Library
"""
__version__ = "1.7.0"
__version__ = "1.8.0"
RPC_PROTOCOL_VERSION = "0.3"
2 changes: 2 additions & 0 deletions aws_lambda_builders/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import logging
import re

from aws_lambda_builders.architecture import X86_64
from aws_lambda_builders.builder import LambdaBuilder
from aws_lambda_builders.exceptions import WorkflowNotFoundError, WorkflowUnknownError, WorkflowFailedError
from aws_lambda_builders import RPC_PROTOCOL_VERSION as lambda_builders_protocol_version
Expand Down Expand Up @@ -124,6 +125,7 @@ def main(): # pylint: disable=too-many-statements
optimizations=params["optimizations"],
options=params["options"],
mode=params.get("mode", None),
architecture=params.get("architecture", X86_64),
)

# Return a success response
Expand Down
5 changes: 5 additions & 0 deletions aws_lambda_builders/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.
"""
X86_64 = "x86_64"
ARM64 = "arm64"
7 changes: 7 additions & 0 deletions aws_lambda_builders/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import logging

from aws_lambda_builders.architecture import X86_64, ARM64
from aws_lambda_builders.registry import get_workflow, DEFAULT_REGISTRY
from aws_lambda_builders.workflow import Capability

Expand Down Expand Up @@ -64,6 +65,7 @@ def build(
options=None,
executable_search_paths=None,
mode=None,
architecture=X86_64,
):
"""
Actually build the code by running workflows
Expand Down Expand Up @@ -105,6 +107,10 @@ def build(
:type mode: str
:param mode:
Optional, Mode the build should produce

:type architecture: str
:param architecture:
Type of architecture x86_64 and arm64 for Lambda Function
"""

if not os.path.exists(scratch_dir):
Expand All @@ -120,6 +126,7 @@ def build(
options=options,
executable_search_paths=executable_search_paths,
mode=mode,
architecture=architecture,
)

return workflow.run()
Expand Down
24 changes: 24 additions & 0 deletions aws_lambda_builders/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ class MisMatchRuntimeError(LambdaBuilderError):
)


class RuntimeValidatorError(LambdaBuilderError):
"""
Raise when runtime is not supported or when runtime is not compatible with architecture
"""

MESSAGE = "Runtime validation error for {runtime}"


class UnsupportedRuntimeError(RuntimeValidatorError):
"""
Raise when runtime is not supported
"""

MESSAGE = "Runtime {runtime} is not suppported"


class UnsupportedArchitectureError(RuntimeValidatorError):
"""
Raise when runtime does not support architecture
"""

MESSAGE = "Architecture {architecture} is not supported for runtime {runtime}"


class WorkflowNotFoundError(LambdaBuilderError):
"""
Raised when a workflow matching the given capabilities was not found
Expand Down
16 changes: 16 additions & 0 deletions aws_lambda_builders/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import logging

from aws_lambda_builders.architecture import X86_64, ARM64

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -148,3 +149,18 @@ def _access_check(fn, mode):
if _access_check(name, mode):
paths.append(name)
return paths


def get_goarch(architecture):
"""
Parameters
----------
architecture : str
name of the type of architecture

Returns
-------
str
returns a valid GO Architecture value
"""
return "arm64" if architecture == ARM64 else "amd64"
62 changes: 61 additions & 1 deletion aws_lambda_builders/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,74 @@

import logging

from aws_lambda_builders.architecture import ARM64, X86_64
from aws_lambda_builders.exceptions import UnsupportedRuntimeError, UnsupportedArchitectureError


LOG = logging.getLogger(__name__)

SUPPORTED_RUNTIMES = {
"nodejs10.x": [X86_64],
"nodejs12.x": [ARM64, X86_64],
"nodejs14.x": [ARM64, X86_64],
"python2.7": [X86_64],
"python3.6": [X86_64],
"python3.7": [X86_64],
"python3.8": [ARM64, X86_64],
"python3.9": [ARM64, X86_64],
"ruby2.5": [X86_64],
"ruby2.7": [ARM64, X86_64],
"java8": [ARM64, X86_64],
"java11": [ARM64, X86_64],
"go1.x": [ARM64, X86_64],
"dotnetcore2.1": [X86_64],
"dotnetcore3.1": [ARM64, X86_64],
"provided": [ARM64, X86_64],
}


class RuntimeValidator(object):
def __init__(self, runtime):
def __init__(self, runtime, architecture):
"""

Parameters
----------
runtime : str
name of the AWS Lambda runtime that you are building for. This is sent to the builder for
informational purposes.
architecture : str
Architecture for which the build will be based on in AWS lambda
"""
self.runtime = runtime
self._runtime_path = None
self.architecture = architecture

def validate(self, runtime_path):
"""
Parameters
----------
runtime_path : str
runtime to check eg: /usr/bin/runtime

Returns
-------
str
runtime to check eg: /usr/bin/runtime

Raises
------
UnsupportedRuntimeError
Raised when runtime provided is not support.

UnsupportedArchitectureError
Raised when runtime is not compatible with architecture
"""
runtime_architectures = SUPPORTED_RUNTIMES.get(self.runtime, None)

if not runtime_architectures:
raise UnsupportedRuntimeError(runtime=self.runtime)
if self.architecture not in runtime_architectures:
raise UnsupportedArchitectureError(runtime=self.runtime, architecture=self.architecture)

self._runtime_path = runtime_path
return runtime_path
87 changes: 48 additions & 39 deletions aws_lambda_builders/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,15 @@
from aws_lambda_builders.path_resolver import PathResolver
from aws_lambda_builders.validator import RuntimeValidator
from aws_lambda_builders.registry import DEFAULT_REGISTRY
from aws_lambda_builders.exceptions import WorkflowFailedError, WorkflowUnknownError, MisMatchRuntimeError
from aws_lambda_builders.exceptions import (
WorkflowFailedError,
WorkflowUnknownError,
MisMatchRuntimeError,
RuntimeValidatorError,
)
from aws_lambda_builders.actions import ActionFailedError
from aws_lambda_builders.architecture import X86_64


LOG = logging.getLogger(__name__)

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


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

@functools.wraps(func)
def wrapper(self, *args, **kwargs):
def wrapper(self, *args, **kwargs): # pylint: disable=too-many-statements
valid_paths = {}
invalid_paths = {}
validation_errors = []
# NOTE: we need to access binaries to get paths and resolvers, before validating.
for binary, binary_checker in self.binaries.items():
invalid_paths[binary] = []
Expand All @@ -61,18 +69,30 @@ def wrapper(self, *args, **kwargs):
except MisMatchRuntimeError as ex:
LOG.debug("Invalid executable for %s at %s", binary, executable_path, exc_info=str(ex))
invalid_paths[binary].append(executable_path)

except RuntimeValidatorError as ex:
LOG.debug("Runtime validation error for %s", binary, exc_info=str(ex))
if str(ex) not in validation_errors:
validation_errors.append(str(ex))

if valid_paths.get(binary, None):
binary_checker.binary_path = valid_paths[binary]
break
if validation_errors:
raise WorkflowFailedError(
workflow_name=self.NAME, action_name="Validation", reason="\n".join(validation_errors)
)

if len(self.binaries) != len(valid_paths):
validation_failed_binaries = set(self.binaries.keys()).difference(valid_paths.keys())
messages = []
for validation_failed_binary in validation_failed_binaries:
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(
validation_failed_binary, invalid_paths[validation_failed_binary], self.runtime
)
messages.append(message)
raise WorkflowFailedError(workflow_name=self.NAME, action_name="Validation", reason="\n".join(messages))
validation_errors.append(message)
raise WorkflowFailedError(
workflow_name=self.NAME, action_name="Validation", reason="\n".join(validation_errors)
)
func(self, *args, **kwargs)

return wrapper
Expand Down Expand Up @@ -140,48 +160,36 @@ def __init__(
optimizations=None,
options=None,
mode=BuildMode.RELEASE,
architecture=X86_64,
):
"""
Initialize the builder with given arguments. These arguments together form the "public API" that each
build action must support at the minimum.

:type source_dir: str
:param source_dir:
Parameters
----------
source_dir : str
Path to a folder containing the source code

:type artifacts_dir: str
:param artifacts_dir:
artifacts_dir : str
Path to a folder where the built artifacts should be placed

:type scratch_dir: str
:param scratch_dir:
scratch_dir : str
Path to a directory that the workflow can use as scratch space. Workflows are expected to use this directory
to write temporary files instead of ``/tmp`` or other OS-specific temp directories.

:type manifest_path: str
:param manifest_path:
manifest_path : str
Path to the dependency manifest

:type runtime: str
:param runtime:
Optional, name of the AWS Lambda runtime that you are building for. This is sent to the builder for
informational purposes.

:type optimizations: dict
:param optimizations:
Optional dictionary of optimization flags to pass to the build action. **Not supported**.

:type options: dict
:param options:
Optional dictionary of options ot pass to build action. **Not supported**.

:type executable_search_paths: list
:param executable_search_paths:
Optional, Additional list of paths to search for executables required by the workflow.

:type mode: str
:param mode:
Optional, Mode the build should produce
runtime : str, optional
name of the AWS Lambda runtime that you are building for. This is sent to the builder for
informational purposes, by default None
executable_search_paths : list, optional
Additional list of paths to search for executables required by the workflow, by default None
optimizations : dict, optional
dictionary of optimization flags to pass to the build action. **Not supported**, by default None
options : dict, optional
dictionary of options ot pass to build action. **Not supported**., by default None
mode : str, optional
Mode the build should produce, by default BuildMode.RELEASE
architecture : str, optional
Architecture type either arm64 or x86_64 for which the build will be based on in AWS lambda, by default X86_64
"""

self.source_dir = source_dir
Expand All @@ -193,6 +201,7 @@ def __init__(
self.options = options
self.executable_search_paths = executable_search_paths
self.mode = mode
self.architecture = architecture

# Actions are registered by the subclasses as they seem fit
self.actions = []
Expand Down Expand Up @@ -225,7 +234,7 @@ def get_validators(self):
"""
No-op validator that does not validate the runtime_path.
"""
return [RuntimeValidator(runtime=self.runtime)]
return [RuntimeValidator(runtime=self.runtime, architecture=self.architecture)]

@property
def binaries(self):
Expand Down
Loading