diff --git a/aws_lambda_builders/workflows/__init__.py b/aws_lambda_builders/workflows/__init__.py index 8e8425446..24b82710a 100644 --- a/aws_lambda_builders/workflows/__init__.py +++ b/aws_lambda_builders/workflows/__init__.py @@ -6,3 +6,4 @@ import aws_lambda_builders.workflows.nodejs_npm import aws_lambda_builders.workflows.ruby_bundler import aws_lambda_builders.workflows.go_dep +import aws_lambda_builders.workflows.go_modules diff --git a/aws_lambda_builders/workflows/go_modules/DESIGN.md b/aws_lambda_builders/workflows/go_modules/DESIGN.md new file mode 100644 index 000000000..22b0bf00c --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/DESIGN.md @@ -0,0 +1,38 @@ +## Go - Go Modules Lambda Builder + +### Scope + +This package leverages standard Go tooling available as of Go1.11 to build Go +applications to be deployed in an AWS Lambda environment. The scope of this +builder is to take an existing directory containing customer code, and a +top-level `go.mod` file specifying third party dependencies. The builder will +run `go build` on the project and put the resulting binary in the given +artifacts folder. + +### Interface + +The top level interface is presented by the `GoModulesBuilder` class. There +will be one public method `build`, which takes the provided arguments and +builds a static binary using standard go tools. + +```python +def build(self, source_dir_path, artifacts_dir_path, executable_name): + """Builds a go project onto an output path. + + :type source_dir_path: str + :param source_dir_path: Directory with the source files. + + :type output_path: str + :param output_path: Filename to write the executable output to. +``` + +### Implementation + +The general algorithm for preparing a Go package for use on AWS Lambda +is very simple. It's as follows: + +Pass in GOOS=linux and GOARCH=amd64 to the `go build` command to target the +OS and architecture used on AWS Lambda. Let go tooling handle the +cross-compilation, regardless of the build environment. Move the resulting +static binary to the artifacts folder to be shipped as a single-file zip +archive. diff --git a/aws_lambda_builders/workflows/go_modules/__init__.py b/aws_lambda_builders/workflows/go_modules/__init__.py new file mode 100644 index 000000000..a46dddfab --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/__init__.py @@ -0,0 +1,5 @@ +""" +Builds Go Lambda functions using standard Go tooling +""" + +from .workflow import GoModulesWorkflow diff --git a/aws_lambda_builders/workflows/go_modules/actions.py b/aws_lambda_builders/workflows/go_modules/actions.py new file mode 100644 index 000000000..39ddedc6c --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/actions.py @@ -0,0 +1,27 @@ +""" +Action to build a Go project using standard Go tooling +""" + +from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError +from .builder import BuilderError + + +class GoModulesBuildAction(BaseAction): + + NAME = "Build" + DESCRIPTION = "Building Go package with Go Modules" + PURPOSE = Purpose.COMPILE_SOURCE + + def __init__(self, source_dir, output_path, builder): + self.source_dir = source_dir + self.output_path = output_path + self.builder = builder + + def execute(self): + try: + self.builder.build( + self.source_dir, + self.output_path, + ) + except BuilderError as ex: + raise ActionFailedError(str(ex)) diff --git a/aws_lambda_builders/workflows/go_modules/builder.py b/aws_lambda_builders/workflows/go_modules/builder.py new file mode 100644 index 000000000..35f649b26 --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/builder.py @@ -0,0 +1,61 @@ +""" +Build a Go project using standard Go tooling +""" +import logging + + +LOG = logging.getLogger(__name__) + + +class BuilderError(Exception): + MESSAGE = "Builder Failed: {message}" + + def __init__(self, **kwargs): + Exception.__init__(self, self.MESSAGE.format(**kwargs)) + + +class GoModulesBuilder(object): + + LANGUAGE = "go" + + def __init__(self, osutils, binaries): + """Initialize a GoModulesBuilder. + + :type osutils: :class:`lambda_builders.utils.OSUtils` + :param osutils: A class used for all interactions with the + outside OS. + + :type binaries: dict + :param binaries: A dict of language binaries + """ + self.osutils = osutils + self.binaries = binaries + + def build(self, source_dir_path, output_path): + """Builds a go project onto an output path. + + :type source_dir_path: str + :param source_dir_path: Directory with the source files. + + :type output_path: str + :param output_path: Filename to write the executable output to. + """ + env = {} + env.update(self.osutils.environ) + env.update({"GOOS": "linux", "GOARCH": "amd64"}) + runtime_path = self.binaries[self.LANGUAGE].binary_path + cmd = [runtime_path, "build", "-o", output_path, source_dir_path] + + p = self.osutils.popen( + cmd, + cwd=source_dir_path, + env=env, + stdout=self.osutils.pipe, + stderr=self.osutils.pipe, + ) + out, err = p.communicate() + + if p.returncode != 0: + raise BuilderError(message=err.decode("utf8").strip()) + + return out.decode("utf8").strip() diff --git a/aws_lambda_builders/workflows/go_modules/utils.py b/aws_lambda_builders/workflows/go_modules/utils.py new file mode 100644 index 000000000..2efc79aca --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/utils.py @@ -0,0 +1,27 @@ +""" +Commonly used utilities +""" + +import os +import subprocess + + +class OSUtils(object): + """ + Wrapper around file system functions, to make it easy to + unit test actions in memory + """ + @property + def environ(self): + return os.environ.copy() + + def joinpath(self, *args): + return os.path.join(*args) + + def popen(self, command, stdout=None, stderr=None, env=None, cwd=None): + p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd) + return p + + @property + def pipe(self): + return subprocess.PIPE diff --git a/aws_lambda_builders/workflows/go_modules/validator.py b/aws_lambda_builders/workflows/go_modules/validator.py new file mode 100644 index 000000000..e44779efc --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/validator.py @@ -0,0 +1,68 @@ +""" +Go Runtime Validation +""" + +import logging +import os +import subprocess + +from aws_lambda_builders.exceptions import MisMatchRuntimeError + +LOG = logging.getLogger(__name__) + + +class GoRuntimeValidator(object): + + LANGUAGE = "go" + SUPPORTED_RUNTIMES = { + "go1.x" + } + + def __init__(self, runtime): + self.runtime = runtime + self._valid_runtime_path = None + + def has_runtime(self): + """ + Checks if the runtime is supported. + :param string runtime: Runtime to check + :return bool: True, if the runtime is supported. + """ + return self.runtime in self.SUPPORTED_RUNTIMES + + def validate(self, runtime_path): + """ + Checks if the language supplied matches the required lambda runtime + :param string runtime_path: runtime to check eg: /usr/bin/go + :raises MisMatchRuntimeError: Version mismatch of the language vs the required runtime + """ + if not self.has_runtime(): + LOG.warning("'%s' runtime is not " + "a supported runtime", self.runtime) + return None + + expected_major_version = int(self.runtime.replace(self.LANGUAGE, "").split('.')[0]) + min_expected_minor_version = 11 if expected_major_version == 1 else 0 + + p = subprocess.Popen([runtime_path, "version"], + cwd=os.getcwd(), + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, _ = p.communicate() + + if p.returncode == 0: + out_parts = out.decode().split() + if len(out_parts) >= 3: + version_parts = [int(x) for x in out_parts[2].replace(self.LANGUAGE, "").split('.')] + if len(version_parts) == 3: + if version_parts[0] == expected_major_version and version_parts[1] >= min_expected_minor_version: + self._valid_runtime_path = runtime_path + return self._valid_runtime_path + + # otherwise, raise mismatch exception + raise MisMatchRuntimeError(language=self.LANGUAGE, + required_runtime=self.runtime, + runtime_path=runtime_path) + + @property + def validated_runtime_path(self): + return self._valid_runtime_path diff --git a/aws_lambda_builders/workflows/go_modules/workflow.py b/aws_lambda_builders/workflows/go_modules/workflow.py new file mode 100644 index 000000000..b19666292 --- /dev/null +++ b/aws_lambda_builders/workflows/go_modules/workflow.py @@ -0,0 +1,51 @@ +""" +Go Modules Workflow +""" +from aws_lambda_builders.workflow import BaseWorkflow, Capability + +from .actions import GoModulesBuildAction +from .builder import GoModulesBuilder +from .validator import GoRuntimeValidator +from .utils import OSUtils + + +class GoModulesWorkflow(BaseWorkflow): + + NAME = "GoModulesBuilder" + + CAPABILITY = Capability(language="go", + dependency_manager="modules", + application_framework=None) + + def __init__(self, + source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + runtime=None, + osutils=None, + **kwargs): + + super(GoModulesWorkflow, self).__init__( + source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + runtime=runtime, + **kwargs) + + if osutils is None: + osutils = OSUtils() + + options = kwargs.get("options") or {} + handler = options.get("handler", None) + + output_path = osutils.joinpath(artifacts_dir, handler) + + builder = GoModulesBuilder(osutils, binaries=self.binaries) + self.actions = [ + GoModulesBuildAction(source_dir, output_path, builder), + ] + + def get_validators(self): + return [GoRuntimeValidator(runtime=self.runtime)] diff --git a/aws_lambda_builders/workflows/python_pip/validator.py b/aws_lambda_builders/workflows/python_pip/validator.py index e5d44f691..22e50a2d3 100644 --- a/aws_lambda_builders/workflows/python_pip/validator.py +++ b/aws_lambda_builders/workflows/python_pip/validator.py @@ -70,4 +70,4 @@ def _validate_python_cmd(self, runtime_path): @property def validated_runtime_path(self): - return self._valid_runtime_path if self._valid_runtime_path is not None else None + return self._valid_runtime_path diff --git a/tests/functional/workflows/go_modules/test_go_utils.py b/tests/functional/workflows/go_modules/test_go_utils.py new file mode 100644 index 000000000..1264af818 --- /dev/null +++ b/tests/functional/workflows/go_modules/test_go_utils.py @@ -0,0 +1,39 @@ +import os +import sys + +from unittest import TestCase + +from aws_lambda_builders.workflows.go_modules import utils + + +class TestOSUtils(TestCase): + + def setUp(self): + self.osutils = utils.OSUtils() + + def test_environ_returns_environment(self): + result = self.osutils.environ + self.assertEqual(result, os.environ) + + def test_joinpath_joins_path_components(self): + result = self.osutils.joinpath('a', 'b', 'c') + self.assertEqual(result, os.path.join('a', 'b', 'c')) + + def test_popen_runs_a_process_and_returns_outcome(self): + cwd_py = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', 'cwd.py') + p = self.osutils.popen([sys.executable, cwd_py], + stdout=self.osutils.pipe, + stderr=self.osutils.pipe) + out, err = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertEqual(out.decode('utf8').strip(), os.getcwd()) + + def test_popen_can_accept_cwd(self): + testdata_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata') + p = self.osutils.popen([sys.executable, 'cwd.py'], + stdout=self.osutils.pipe, + stderr=self.osutils.pipe, + cwd=testdata_dir) + out, err = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertEqual(out.decode('utf8').strip(), os.path.abspath(testdata_dir)) diff --git a/tests/integration/workflows/go_modules/test_go.py b/tests/integration/workflows/go_modules/test_go.py new file mode 100644 index 000000000..1e138d4a6 --- /dev/null +++ b/tests/integration/workflows/go_modules/test_go.py @@ -0,0 +1,59 @@ +import os +import shutil +import tempfile + +from unittest import TestCase + +from aws_lambda_builders.builder import LambdaBuilder +from aws_lambda_builders.exceptions import WorkflowFailedError + + +class TestGoWorkflow(TestCase): + """ + Verifies that `go` workflow works by building a Lambda using Go Modules + """ + + TEST_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "testdata") + + def setUp(self): + self.artifacts_dir = tempfile.mkdtemp() + self.scratch_dir = tempfile.mkdtemp() + self.builder = LambdaBuilder(language="go", + dependency_manager="modules", + application_framework=None) + self.runtime = "go1.x" + + def tearDown(self): + shutil.rmtree(self.artifacts_dir) + shutil.rmtree(self.scratch_dir) + + def test_builds_project_without_dependencies(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-deps") + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "go.mod"), + runtime=self.runtime, + options={"handler": "no-deps-main"}) + expected_files = {"no-deps-main"} + output_files = set(os.listdir(self.artifacts_dir)) + print(output_files) + self.assertEquals(expected_files, output_files) + + def test_builds_project_with_dependencies(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps") + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "go.mod"), + runtime=self.runtime, + options={"handler": "with-deps-main"}) + expected_files = {"with-deps-main"} + output_files = set(os.listdir(self.artifacts_dir)) + self.assertEquals(expected_files, output_files) + + def test_fails_if_modules_cannot_resolve_dependencies(self): + source_dir = os.path.join(self.TEST_DATA_FOLDER, "broken-deps") + with self.assertRaises(WorkflowFailedError) as ctx: + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, + os.path.join(source_dir, "go.mod"), + runtime=self.runtime, + options={"handler": "failed"}) + self.assertIn("GoModulesBuilder:Build - Builder Failed: ", + str(ctx.exception)) diff --git a/tests/integration/workflows/go_modules/testdata/broken-deps/go.mod b/tests/integration/workflows/go_modules/testdata/broken-deps/go.mod new file mode 100644 index 000000000..864baf723 --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/broken-deps/go.mod @@ -0,0 +1,12 @@ +module github.com/awslabs/aws-lambda-builders + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/aws/aws-lambda-go v0.9999.0 // doesn't exist, broken dependency + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.1 // indirect + github.com/stretchr/testify v1.2.2 // indirect + gopkg.in/urfave/cli.v1 v1.20.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/tests/integration/workflows/go_modules/testdata/broken-deps/main.go b/tests/integration/workflows/go_modules/testdata/broken-deps/main.go new file mode 100644 index 000000000..da29a2cad --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/broken-deps/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/tests/integration/workflows/go_modules/testdata/no-deps/go.mod b/tests/integration/workflows/go_modules/testdata/no-deps/go.mod new file mode 100644 index 000000000..846a0e06b --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/no-deps/go.mod @@ -0,0 +1 @@ +module github.com/awslabs/aws-lambda-builders diff --git a/tests/integration/workflows/go_modules/testdata/no-deps/main.go b/tests/integration/workflows/go_modules/testdata/no-deps/main.go new file mode 100644 index 000000000..da29a2cad --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/no-deps/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/tests/integration/workflows/go_modules/testdata/with-deps/go.mod b/tests/integration/workflows/go_modules/testdata/with-deps/go.mod new file mode 100644 index 000000000..18f6e0f05 --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/with-deps/go.mod @@ -0,0 +1,12 @@ +module github.com/awslabs/aws-lambda-builders + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/aws/aws-lambda-go v1.8.0 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.1 // indirect + github.com/stretchr/testify v1.2.2 // indirect + gopkg.in/urfave/cli.v1 v1.20.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect +) diff --git a/tests/integration/workflows/go_modules/testdata/with-deps/go.sum b/tests/integration/workflows/go_modules/testdata/with-deps/go.sum new file mode 100644 index 000000000..2a45ed3a9 --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/with-deps/go.sum @@ -0,0 +1,10 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aws/aws-lambda-go v1.8.0 h1:YMCzi9FP7MNVVj9AkGpYyaqh/mvFOjhqiDtnNlWtKTg= +github.com/aws/aws-lambda-go v1.8.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tests/integration/workflows/go_modules/testdata/with-deps/main.go b/tests/integration/workflows/go_modules/testdata/with-deps/main.go new file mode 100644 index 000000000..a119afd60 --- /dev/null +++ b/tests/integration/workflows/go_modules/testdata/with-deps/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/aws/aws-lambda-go/events" + "github.com/aws/aws-lambda-go/lambda" +) + +func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + return events.APIGatewayProxyResponse{ + Body: "Hello World", + StatusCode: 200, + }, nil +} + +func main() { + lambda.Start(handler) +} diff --git a/tests/unit/workflows/go_modules/__init__.py b/tests/unit/workflows/go_modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/workflows/go_modules/test_actions.py b/tests/unit/workflows/go_modules/test_actions.py new file mode 100644 index 000000000..b126442c6 --- /dev/null +++ b/tests/unit/workflows/go_modules/test_actions.py @@ -0,0 +1,24 @@ +from unittest import TestCase +from mock import patch + +from aws_lambda_builders.actions import ActionFailedError +from aws_lambda_builders.workflows.go_modules.actions import GoModulesBuildAction +from aws_lambda_builders.workflows.go_modules.builder import BuilderError + + +class TestGoModulesBuildAction(TestCase): + @patch("aws_lambda_builders.workflows.go_modules.builder.GoModulesBuilder") + def test_runs_bundle_install(self, BuilderMock): + builder = BuilderMock.return_value + action = GoModulesBuildAction("source_dir", "output_path", builder) + action.execute() + builder.build.assert_called_with("source_dir", "output_path") + + @patch("aws_lambda_builders.workflows.go_modules.builder.GoModulesBuilder") + def test_raises_action_failed_on_failure(self, BuilderMock): + builder = BuilderMock.return_value + builder.build.side_effect = BuilderError(message="Fail") + action = GoModulesBuildAction("source_dir", "output_path", builder) + with self.assertRaises(ActionFailedError) as raised: + action.execute() + self.assertEqual(raised.exception.args[0], "Builder Failed: Fail") diff --git a/tests/unit/workflows/go_modules/test_builder.py b/tests/unit/workflows/go_modules/test_builder.py new file mode 100644 index 000000000..9e69db981 --- /dev/null +++ b/tests/unit/workflows/go_modules/test_builder.py @@ -0,0 +1,55 @@ +from unittest import TestCase + +from mock import patch, Mock + +from aws_lambda_builders.binary_path import BinaryPath +from aws_lambda_builders.workflows.go_modules.builder import GoModulesBuilder, BuilderError + + +class FakePopen: + def __init__(self, out=b'out', err=b'err', retcode=0): + self.out = out + self.err = err + self.returncode = retcode + + def communicate(self): + return self.out, self.err + + +class TestGoBuilder(TestCase): + + @patch("aws_lambda_builders.workflows.go_modules.utils.OSUtils") + def setUp(self, OSUtilMock): + self.osutils = OSUtilMock.return_value + self.osutils.pipe = 'PIPE' + self.popen = FakePopen() + self.osutils.popen.side_effect = [self.popen] + self.binaries = { + "go": BinaryPath(resolver=Mock(), validator=Mock(), + binary="go", binary_path="/path/to/go") + } + self.under_test = GoModulesBuilder(self.osutils, self.binaries) + + def test_run_executes_bundler_on_nixes(self): + self.osutils.is_windows.side_effect = [False] + self.under_test = GoModulesBuilder(self.osutils, self.binaries) + self.under_test.build("source_dir", "output_path") + self.osutils.popen.assert_called_with( + ["/path/to/go", "build", "-o", "output_path", "source_dir"], + cwd="source_dir", + env={'GOOS': 'linux', 'GOARCH': 'amd64'}, + stderr='PIPE', + stdout='PIPE', + ) + + def test_returns_popen_out_decoded_if_retcode_is_0(self): + self.popen.out = b'some encoded text\n\n' + result = self.under_test.build("source_dir", "output_path") + self.assertEqual(result, 'some encoded text') + + def test_raises_BuilderError_with_err_text_if_retcode_is_not_0(self): + self.popen.returncode = 1 + self.popen.err = b'some error text\n\n' + with self.assertRaises(BuilderError) as raised: + self.under_test.build("source_dir", "output_path") + self.assertEqual(raised.exception.args[0], "Builder Failed: some error text") diff --git a/tests/unit/workflows/go_modules/test_validator.py b/tests/unit/workflows/go_modules/test_validator.py new file mode 100644 index 000000000..9e3931de7 --- /dev/null +++ b/tests/unit/workflows/go_modules/test_validator.py @@ -0,0 +1,62 @@ +from unittest import TestCase + +import mock +from parameterized import parameterized + +from aws_lambda_builders.exceptions import MisMatchRuntimeError +from aws_lambda_builders.workflows.go_modules.validator import GoRuntimeValidator + + +class MockSubProcess(object): + + def __init__(self, returncode, out=b"", err=b""): + self.returncode = returncode + self.out = out + self.err = err + + def communicate(self): + return (self.out, self.err) + + +class TestGoRuntimeValidator(TestCase): + + def setUp(self): + self.validator = GoRuntimeValidator(runtime="go1.x") + + @parameterized.expand([ + "go1.x", + ]) + def test_supported_runtimes(self, runtime): + validator = GoRuntimeValidator(runtime=runtime) + self.assertTrue(validator.has_runtime()) + + def test_runtime_validate_unsupported_language_fail_open(self): + validator = GoRuntimeValidator(runtime="go2.x") + validator.validate(runtime_path="/usr/bin/go2") + + def test_runtime_validate_supported_version_runtime(self): + with mock.patch("subprocess.Popen") as mock_subprocess: + mock_subprocess.return_value = MockSubProcess(0, out=b"go version go1.11.2 test") + self.validator.validate(runtime_path="/usr/bin/go") + self.assertTrue(mock_subprocess.call_count, 1) + + def test_runtime_validate_mismatch_nonzero_exit(self): + with mock.patch("subprocess.Popen") as mock_subprocess: + mock_subprocess.return_value = MockSubProcess(1) + with self.assertRaises(MisMatchRuntimeError): + self.validator.validate(runtime_path="/usr/bin/go") + self.assertTrue(mock_subprocess.call_count, 1) + + def test_runtime_validate_mismatch_invalid_version(self): + with mock.patch("subprocess.Popen") as mock_subprocess: + mock_subprocess.return_value = MockSubProcess(0, out=b"go version") + with self.assertRaises(MisMatchRuntimeError): + self.validator.validate(runtime_path="/usr/bin/go") + self.assertTrue(mock_subprocess.call_count, 1) + + def test_runtime_validate_mismatch_minor_version(self): + with mock.patch("subprocess.Popen") as mock_subprocess: + mock_subprocess.return_value = MockSubProcess(0, out=b"go version go1.10.2 test") + with self.assertRaises(MisMatchRuntimeError): + self.validator.validate(runtime_path="/usr/bin/go") + self.assertTrue(mock_subprocess.call_count, 1) diff --git a/tests/unit/workflows/go_modules/test_workflow.py b/tests/unit/workflows/go_modules/test_workflow.py new file mode 100644 index 000000000..f27fafc05 --- /dev/null +++ b/tests/unit/workflows/go_modules/test_workflow.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +from aws_lambda_builders.workflows.go_modules.workflow import GoModulesWorkflow +from aws_lambda_builders.workflows.go_modules.actions import GoModulesBuildAction + + +class TestGoModulesWorkflow(TestCase): + """ + the workflow requires an external utility (builder) to run, so it is extensively tested in integration tests. + this is just a quick wiring test to provide fast feedback if things are badly broken + """ + + def test_workflow_sets_up_builder_actions(self): + workflow = GoModulesWorkflow( + "source", "artifacts", "scratch_dir", "manifest", + runtime="go1.x", + options={"handler": "main"}) + self.assertEqual(len(workflow.actions), 1) + self.assertIsInstance(workflow.actions[0], GoModulesBuildAction)