Skip to content

Commit dd837e0

Browse files
committed
Address PR feedback
1 parent b767311 commit dd837e0

File tree

16 files changed

+220
-61
lines changed

16 files changed

+220
-61
lines changed

aws_lambda_builders/actions.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,10 @@ class CopySourceAction(BaseAction):
9292

9393
PURPOSE = Purpose.COPY_SOURCE
9494

95-
def __init__(self, source_dir, dest_dir, only=None, excludes=None):
95+
def __init__(self, source_dir, dest_dir, excludes=None):
9696
self.source_dir = source_dir
9797
self.dest_dir = dest_dir
98-
self.only = only
9998
self.excludes = excludes or []
10099

101100
def execute(self):
102-
if self.only:
103-
def ignore(source, names):
104-
return [name for name in names if name not in self.only]
105-
else:
106-
ignore = shutil.ignore_patterns(*self.excludes)
107-
copytree(self.source_dir, self.dest_dir, ignore=ignore)
101+
copytree(self.source_dir, self.dest_dir, ignore=shutil.ignore_patterns(*self.excludes))

aws_lambda_builders/workflows/go_modules/DESIGN.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,13 @@ builds a static binary using standard go tools.
1717

1818
```python
1919
def build(self, source_dir_path, artifacts_dir_path, executable_name):
20-
"""Builds a go project into an artifact directory.
20+
"""Builds a go project onto an output path.
2121
2222
:type source_dir_path: str
2323
:param source_dir_path: Directory with the source files.
2424
25-
:type artifacts_dir_path: str
26-
:param artifacts_dir_path: Directory to write dependencies into.
27-
28-
:type executable_name: str
29-
:param executable_name: Name of the executable to create from the build.
25+
:type output_path: str
26+
:param output_path: Filename to write the executable output to.
3027
```
3128
3229
### Implementation

aws_lambda_builders/workflows/go_modules/actions.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,16 @@ class GoModulesBuildAction(BaseAction):
1212
DESCRIPTION = "Building Go package with Go Modules"
1313
PURPOSE = Purpose.RESOLVE_DEPENDENCIES
1414

15-
def __init__(self, source_dir, artifacts_dir, executable_name, builder):
15+
def __init__(self, source_dir, output_path, builder):
1616
self.source_dir = source_dir
17-
self.artifacts_dir = artifacts_dir
18-
self.executable_name = executable_name
17+
self.output_path = output_path
1918
self.builder = builder
2019

2120
def execute(self):
2221
try:
2322
self.builder.build(
2423
self.source_dir,
25-
self.artifacts_dir,
26-
self.executable_name
24+
self.output_path,
2725
)
2826
except BuilderError as ex:
2927
raise ActionFailedError(str(ex))

aws_lambda_builders/workflows/go_modules/builder.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,32 @@ def __init__(self, **kwargs):
1515

1616

1717
class GoModulesBuilder(object):
18-
def __init__(self, osutils):
18+
def __init__(self, osutils, runtime_path):
1919
"""Initialize a GoModulesBuilder.
2020
2121
:type osutils: :class:`lambda_builders.utils.OSUtils`
2222
:param osutils: A class used for all interactions with the
2323
outside OS.
24+
25+
:type runtime_path: str
26+
:param runtime_path: The path to the go runtime.
2427
"""
2528
self.osutils = osutils
29+
self.runtime_path = runtime_path
2630

27-
def build(self, source_dir_path, artifacts_dir_path, executable_name):
28-
"""Builds a go project into an artifact directory.
31+
def build(self, source_dir_path, output_path):
32+
"""Builds a go project onto an output path.
2933
3034
:type source_dir_path: str
3135
:param source_dir_path: Directory with the source files.
3236
33-
:type artifacts_dir_path: str
34-
:param artifacts_dir_path: Directory to write dependencies into.
35-
36-
:type executable_name: str
37-
:param executable_name: Name of the executable to create from the build.
37+
:type output_path: str
38+
:param output_path: Filename to write the executable output to.
3839
"""
3940
env = {}
4041
env.update(self.osutils.environ)
4142
env.update({"GOOS": "linux", "GOARCH": "amd64"})
42-
cmd = ["go", "build", "-o", self.osutils.joinpath(artifacts_dir_path, executable_name), source_dir_path]
43+
cmd = [self.runtime_path, "build", "-o", output_path, source_dir_path]
4344

4445
p = self.osutils.popen(
4546
cmd,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
Go Path Resolver that looks for the executable by runtime first, before proceeding to 'go' in PATH.
3+
"""
4+
import whichcraft
5+
6+
7+
class GoPathResolver(object):
8+
9+
def __init__(self, runtime, which=None):
10+
self.language = "go"
11+
self.runtime = runtime
12+
self.executables = [self.language]
13+
self.which = which or whichcraft.which
14+
15+
def _which(self):
16+
for executable in self.executables:
17+
path = self.which(executable)
18+
if path:
19+
return path
20+
raise ValueError("Path resolution for runtime: {} of language: "
21+
"{} was not successful".format(self.runtime, self.language))
22+
23+
@property
24+
def exec_path(self):
25+
return self._which()

aws_lambda_builders/workflows/go_modules/utils.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ class OSUtils(object):
1313
"""
1414
@property
1515
def environ(self):
16-
return os.environ
16+
return os.environ.copy()
1717

1818
def joinpath(self, *args):
1919
return os.path.join(*args)
2020

21-
def basename(self, *args):
22-
return os.path.basename(*args)
23-
2421
def popen(self, command, stdout=None, stderr=None, env=None, cwd=None):
2522
p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd)
2623
return p
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
Go Runtime Validation
3+
"""
4+
5+
import logging
6+
import os
7+
import subprocess
8+
9+
from aws_lambda_builders.exceptions import MisMatchRuntimeError
10+
11+
LOG = logging.getLogger(__name__)
12+
13+
14+
class GoRuntimeValidator(object):
15+
SUPPORTED_RUNTIMES = {
16+
"go1.x"
17+
}
18+
19+
def __init__(self, runtime, runtime_path):
20+
self.language = "go"
21+
self.runtime = runtime
22+
self.runtime_path = runtime_path
23+
24+
def has_runtime(self):
25+
"""
26+
Checks if the runtime is supported.
27+
:param string runtime: Runtime to check
28+
:return bool: True, if the runtime is supported.
29+
"""
30+
return self.runtime in self.SUPPORTED_RUNTIMES
31+
32+
def validate_runtime(self):
33+
"""
34+
Checks if the language supplied matches the required lambda runtime
35+
:param string runtime_path: runtime to check eg: /usr/bin/go
36+
:raises MisMatchRuntimeError: Version mismatch of the language vs the required runtime
37+
"""
38+
if not self.has_runtime():
39+
LOG.warning("'%s' runtime is not "
40+
"a supported runtime", self.runtime_path)
41+
return
42+
43+
expected_major_version = self.runtime.replace(self.language, "").split('.')[0]
44+
45+
p = subprocess.Popen([self.runtime_path, "version"],
46+
cwd=os.getcwd(),
47+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
48+
out, _ = p.communicate()
49+
50+
mismatched = p.returncode != 0 \
51+
or len(out.split()) < 3 \
52+
or out.split()[2].replace(self.language, "").split('.')[0] != expected_major_version
53+
if mismatched:
54+
raise MisMatchRuntimeError(language=self.language,
55+
found_runtime=self.runtime_path,
56+
required_runtime=self.runtime,
57+
runtime_path=self.runtime_path)

aws_lambda_builders/workflows/go_modules/workflow.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
Go Modules Workflow
33
"""
44
from aws_lambda_builders.workflow import BaseWorkflow, Capability
5-
from aws_lambda_builders.actions import CopySourceAction
65

76
from .actions import GoModulesBuildAction
87
from .builder import GoModulesBuilder
8+
from .path_resolver import GoPathResolver
9+
from .validator import GoRuntimeValidator
910
from .utils import OSUtils
1011

1112

@@ -37,9 +38,18 @@ def __init__(self,
3738
if osutils is None:
3839
osutils = OSUtils()
3940

40-
executable_name = osutils.basename(source_dir)
41-
builder = GoModulesBuilder(osutils)
41+
options = kwargs.get("options") or {}
42+
handler = options.get("handler", None)
43+
44+
output_path = osutils.joinpath(artifacts_dir, handler)
45+
46+
builder = GoModulesBuilder(osutils, runtime_path=self.get_executable())
4247
self.actions = [
43-
GoModulesBuildAction(source_dir, artifacts_dir, executable_name, builder),
44-
CopySourceAction(source_dir, artifacts_dir, only=[executable_name]),
48+
GoModulesBuildAction(source_dir, output_path, builder),
4549
]
50+
51+
def get_executable(self):
52+
return GoPathResolver(runtime=self.runtime).exec_path
53+
54+
def get_validator(self):
55+
return GoRuntimeValidator(runtime=self.runtime, runtime_path=self.get_executable())

requirements/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
six~=1.11
2+
whichcraft~=0.5.2

tests/functional/workflows/go_modules/test_go_utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ def test_joinpath_joins_path_components(self):
1919
result = self.osutils.joinpath('a', 'b', 'c')
2020
self.assertEqual(result, os.path.join('a', 'b', 'c'))
2121

22-
def test_basename_returns_path_basename(self):
23-
result = self.osutils.basename(os.path.dirname(__file__))
24-
self.assertEqual(result, 'go_modules')
25-
2622
def test_popen_runs_a_process_and_returns_outcome(self):
2723
cwd_py = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', 'cwd.py')
2824
p = self.osutils.popen([sys.executable, cwd_py],

tests/integration/workflows/go_modules/test_go.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def setUp(self):
2121
self.builder = LambdaBuilder(language="go",
2222
dependency_manager="modules",
2323
application_framework=None)
24+
self.runtime = "go1.x"
2425

2526
def tearDown(self):
2627
shutil.rmtree(self.artifacts_dir)
@@ -29,24 +30,30 @@ def tearDown(self):
2930
def test_builds_project_without_dependencies(self):
3031
source_dir = os.path.join(self.TEST_DATA_FOLDER, "no-deps")
3132
self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir,
32-
os.path.join(source_dir, "go.mod"))
33-
expected_files = {"no-deps"}
33+
os.path.join(source_dir, "go.mod"),
34+
runtime=self.runtime,
35+
options={"handler": "no-deps-main"})
36+
expected_files = {"no-deps-main"}
3437
output_files = set(os.listdir(self.artifacts_dir))
3538
print(output_files)
3639
self.assertEquals(expected_files, output_files)
3740

3841
def test_builds_project_with_dependencies(self):
3942
source_dir = os.path.join(self.TEST_DATA_FOLDER, "with-deps")
4043
self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir,
41-
os.path.join(source_dir, "go.mod"))
42-
expected_files = {"with-deps"}
44+
os.path.join(source_dir, "go.mod"),
45+
runtime=self.runtime,
46+
options={"handler": "with-deps-main"})
47+
expected_files = {"with-deps-main"}
4348
output_files = set(os.listdir(self.artifacts_dir))
4449
self.assertEquals(expected_files, output_files)
4550

4651
def test_fails_if_modules_cannot_resolve_dependencies(self):
4752
source_dir = os.path.join(self.TEST_DATA_FOLDER, "broken-deps")
4853
with self.assertRaises(WorkflowFailedError) as ctx:
4954
self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir,
50-
os.path.join(source_dir, "go.mod"))
55+
os.path.join(source_dir, "go.mod"),
56+
runtime=self.runtime,
57+
options={"handler": "failed"})
5158
self.assertIn("GoModulesBuilder:Build - Builder Failed: ",
5259
str(ctx.exception))

tests/unit/workflows/go_modules/test_actions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ class TestGoModulesBuildAction(TestCase):
1010
@patch("aws_lambda_builders.workflows.go_modules.builder.GoModulesBuilder")
1111
def test_runs_bundle_install(self, BuilderMock):
1212
builder = BuilderMock.return_value
13-
action = GoModulesBuildAction("source_dir", "artifacts_dir", "executable_name", builder)
13+
action = GoModulesBuildAction("source_dir", "output_path", builder)
1414
action.execute()
15-
builder.build.assert_called_with("source_dir", "artifacts_dir", "executable_name")
15+
builder.build.assert_called_with("source_dir", "output_path")
1616

1717
@patch("aws_lambda_builders.workflows.go_modules.builder.GoModulesBuilder")
1818
def test_raises_action_failed_on_failure(self, BuilderMock):
1919
builder = BuilderMock.return_value
2020
builder.build.side_effect = BuilderError(message="Fail")
21-
action = GoModulesBuildAction("source_dir", "artifacts_dir", "executable_name", builder)
21+
action = GoModulesBuildAction("source_dir", "output_path", builder)
2222
with self.assertRaises(ActionFailedError) as raised:
2323
action.execute()
2424
self.assertEqual(raised.exception.args[0], "Builder Failed: Fail")

tests/unit/workflows/go_modules/test_builder.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,22 @@ def communicate(self):
1414
return self.out, self.err
1515

1616

17-
class TestSubprocessBundler(TestCase):
17+
class TestGoBuilder(TestCase):
1818

1919
@patch("aws_lambda_builders.workflows.go_modules.utils.OSUtils")
2020
def setUp(self, OSUtilMock):
2121
self.osutils = OSUtilMock.return_value
2222
self.osutils.pipe = 'PIPE'
2323
self.popen = FakePopen()
2424
self.osutils.popen.side_effect = [self.popen]
25-
self.under_test = GoModulesBuilder(self.osutils)
25+
self.under_test = GoModulesBuilder(self.osutils, "go")
2626

2727
def test_run_executes_bundler_on_nixes(self):
2828
self.osutils.is_windows.side_effect = [False]
29-
self.under_test = GoModulesBuilder(self.osutils)
30-
self.under_test.build("source_dir", "artifacts_dir", "executable_name")
29+
self.under_test = GoModulesBuilder(self.osutils, "go")
30+
self.under_test.build("source_dir", "output_path")
3131
self.osutils.popen.assert_called_with(
32-
["go", "build", "-o",
33-
self.osutils.joinpath("artifacts_dir", "executable_name"), "source_dir"],
32+
["go", "build", "-o", "output_path", "source_dir"],
3433
cwd="source_dir",
3534
env={'GOOS': 'linux', 'GOARCH': 'amd64'},
3635
stderr='PIPE',
@@ -39,12 +38,12 @@ def test_run_executes_bundler_on_nixes(self):
3938

4039
def test_returns_popen_out_decoded_if_retcode_is_0(self):
4140
self.popen.out = b'some encoded text\n\n'
42-
result = self.under_test.build("source_dir", "artifacts_dir", "executable_name")
41+
result = self.under_test.build("source_dir", "output_path")
4342
self.assertEqual(result, 'some encoded text')
4443

4544
def test_raises_BuilderError_with_err_text_if_retcode_is_not_0(self):
4645
self.popen.returncode = 1
4746
self.popen.err = b'some error text\n\n'
4847
with self.assertRaises(BuilderError) as raised:
49-
self.under_test.build("source_dir", "artifacts_dir", "executable_name")
48+
self.under_test.build("source_dir", "output_path")
5049
self.assertEqual(raised.exception.args[0], "Builder Failed: some error text")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
from unittest import TestCase
3+
4+
import mock
5+
6+
from aws_lambda_builders.workflows.go_modules.path_resolver import GoPathResolver
7+
8+
9+
class TestPythonPathResolver(TestCase):
10+
11+
def setUp(self):
12+
self.path_resolver = GoPathResolver(runtime="go1.x")
13+
14+
def test_inits(self):
15+
self.assertEquals(self.path_resolver.language, "go")
16+
self.assertEquals(self.path_resolver.runtime, "go1.x")
17+
self.assertEquals(self.path_resolver.executables,
18+
[self.path_resolver.language])
19+
20+
def test_which_fails(self):
21+
path_resolver = GoPathResolver(runtime="go1.x", which=lambda x: None)
22+
with self.assertRaises(ValueError):
23+
path_resolver._which()
24+
25+
def test_which_success_immediate(self):
26+
with mock.patch.object(self.path_resolver, '_which') as which_mock:
27+
which_mock.return_value = os.getcwd()
28+
self.assertEquals(self.path_resolver.exec_path, os.getcwd())

0 commit comments

Comments
 (0)