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(build): Build a single function #1146

Merged
merged 2 commits into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion designs/sam_build_cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Success criteria for the change
- Python with PIP
- Golang with Go CLI
- Dotnetcore with DotNet CLI
2. Each Lambda function in SAM template gets built
2. Each Lambda function in SAM template gets built by default unless a `function_identifier` (LogicalID) is passed
to the build command
3. Produce stable builds (best effort): If the source files did not
change, built artifacts should not change.
4. Built artifacts should \"just work\" with `sam local` and
Expand Down
22 changes: 22 additions & 0 deletions samcli/commands/build/build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Context object used by build command
"""

import logging
import os
import shutil

Expand All @@ -14,6 +15,9 @@
from samcli.commands.local.lib.sam_function_provider import SamFunctionProvider
from samcli.commands._utils.template import get_template_data
from samcli.commands.exceptions import UserException
from samcli.local.lambdafn.exceptions import FunctionNotFound

LOG = logging.getLogger(__name__)


class BuildContext(object):
Expand All @@ -23,6 +27,7 @@ class BuildContext(object):
_BUILD_DIR_PERMISSIONS = 0o755

def __init__(self,
function_identifier,
template_file,
base_dir,
build_dir,
Expand All @@ -34,6 +39,7 @@ def __init__(self,
docker_network=None,
skip_pull_image=False):

self._function_identifier = function_identifier
self._template_file = template_file
self._base_dir = base_dir
self._build_dir = build_dir
Expand Down Expand Up @@ -128,3 +134,19 @@ def manifest_path_override(self):
@property
def mode(self):
return self._mode

@property
def functions_to_build(self):
if self._function_identifier:
function = self._function_provider.get(self._function_identifier)

if not function:
all_functions = [f.name for f in self._function_provider.get_all()]
available_function_message = "{} not found. Possible options in your template: {}" \
.format(self._function_identifier, all_functions)
LOG.info(available_function_message)
raise FunctionNotFound("Unable to find a Function with name '%s'", self._function_identifier)

return [function]

return self._function_provider.get_all()
27 changes: 17 additions & 10 deletions samcli/commands/build/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from samcli.lib.build.app_builder import ApplicationBuilder, BuildError, UnsupportedBuilderLibraryVersionError, \
ContainerBuildNotSupported
from samcli.lib.build.workflow_config import UnsupportedRuntimeException
from samcli.local.lambdafn.exceptions import FunctionNotFound
from samcli.commands._utils.template import move_template

LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -81,8 +82,10 @@
@docker_common_options
@cli_framework_options
@aws_creds_options
@click.argument('function_identifier', required=False)
@pass_context
def cli(ctx,
function_identifier,
template,
base_dir,
build_dir,
Expand All @@ -96,11 +99,12 @@ def cli(ctx,

mode = _get_mode_value_from_envvar("SAM_BUILD_MODE", choices=["debug"])

do_cli(template, base_dir, build_dir, True, use_container, manifest, docker_network,
do_cli(function_identifier, template, base_dir, build_dir, True, use_container, manifest, docker_network,
skip_pull_image, parameter_overrides, mode) # pragma: no cover


def do_cli(template, # pylint: disable=too-many-locals
def do_cli(function_identifier, # pylint: disable=too-many-locals
template,
base_dir,
build_dir,
clean,
Expand All @@ -119,7 +123,8 @@ def do_cli(template, # pylint: disable=too-many-locals
if use_container:
LOG.info("Starting Build inside a container")

with BuildContext(template,
with BuildContext(function_identifier,
template,
base_dir,
build_dir,
clean=clean,
Expand All @@ -129,14 +134,16 @@ def do_cli(template, # pylint: disable=too-many-locals
docker_network=docker_network,
skip_pull_image=skip_pull_image,
mode=mode) as ctx:
try:
builder = ApplicationBuilder(ctx.functions_to_build,
ctx.build_dir,
ctx.base_dir,
manifest_path_override=ctx.manifest_path_override,
container_manager=ctx.container_manager,
mode=ctx.mode)
except FunctionNotFound as ex:
raise UserException(str(ex))

builder = ApplicationBuilder(ctx.function_provider,
ctx.build_dir,
ctx.base_dir,
manifest_path_override=ctx.manifest_path_override,
container_manager=ctx.container_manager,
mode=ctx.mode
)
try:
artifacts = builder.build()
modified_template = builder.update_template(ctx.template_dict,
Expand Down
10 changes: 5 additions & 5 deletions samcli/lib/build/app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ApplicationBuilder(object):
"""

def __init__(self,
function_provider,
functions_to_build,
build_dir,
base_dir,
manifest_path_override=None,
Expand All @@ -61,8 +61,8 @@ def __init__(self,

Parameters
----------
function_provider : samcli.commands.local.lib.sam_function_provider.SamFunctionProvider
Provider that can vend out functions available in the SAM template
functions_to_build: Iterator
Iterator that can vend out functions available in the SAM template

build_dir : str
Path to the directory where we will be storing built artifacts
Expand All @@ -79,7 +79,7 @@ def __init__(self,
mode : str
Optional, name of the build mode to use ex: 'debug'
"""
self._function_provider = function_provider
self._functions_to_build = functions_to_build
self._build_dir = build_dir
self._base_dir = base_dir
self._manifest_path_override = manifest_path_override
Expand All @@ -100,7 +100,7 @@ def build(self):

result = {}

for lambda_function in self._function_provider.get_all():
for lambda_function in self._functions_to_build:

LOG.info("Building resource '%s'", lambda_function.name)
result[lambda_function.name] = self._build_function(lambda_function.name,
Expand Down
12 changes: 9 additions & 3 deletions tests/integration/buildcmd/build_integ_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@


class BuildIntegBase(TestCase):
template = "template.yaml"

@classmethod
def setUpClass(cls):
Expand All @@ -34,7 +35,7 @@ def setUpClass(cls):
cls.scratch_dir = str(Path(__file__).resolve().parent.joinpath("scratch"))

cls.test_data_path = str(Path(integration_dir, "testdata", "buildcmd"))
cls.template_path = str(Path(cls.test_data_path, "template.yaml"))
cls.template_path = str(Path(cls.test_data_path, cls.template))

def setUp(self):

Expand All @@ -61,9 +62,14 @@ def base_command(cls):
return command

def get_command_list(self, build_dir=None, base_dir=None, manifest_path=None, use_container=None,
parameter_overrides=None, mode=None):
parameter_overrides=None, mode=None, function_identifier=None):

command_list = [self.cmd, "build", "-t", self.template_path]
command_list = [self.cmd, "build"]

if function_identifier:
command_list += [function_identifier]

command_list += ["-t", self.template_path]

if parameter_overrides:
command_list += ["--parameter-overrides", self._make_parameter_override_arg(parameter_overrides)]
Expand Down
80 changes: 80 additions & 0 deletions tests/integration/buildcmd/test_build_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,83 @@ def _verify_built_artifact(self, build_dir, function_logical_id, expected_files)
all_artifacts = set(os.listdir(str(resource_artifact_dir)))
actual_files = all_artifacts.intersection(expected_files)
self.assertEquals(actual_files, expected_files)


class TestBuildCommand_SingleFunctionBuilds(BuildIntegBase):
template = "many-functions-template.yaml"

EXPECTED_FILES_GLOBAL_MANIFEST = set()
EXPECTED_FILES_PROJECT_MANIFEST = {'__init__.py', 'main.py', 'numpy',
# 'cryptography',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is cryptography commented out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied from the above python tests.

This is the specific reason: https://github.com/awslabs/aws-sam-cli/blob/develop/tests/integration/testdata/buildcmd/Python/requirements.txt#L4

"jinja2",
'requirements.txt'}

def test_fucntion_not_found(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

overrides = {"Runtime": 'python3.7', "CodeUri": "Python", "Handler": "main.handler"}
cmdlist = self.get_command_list(parameter_overrides=overrides,
function_identifier="FunctionNotInTemplate")

process = subprocess.Popen(cmdlist, cwd=self.working_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()

self.assertEquals(process.returncode, 1)
self.assertIn('FunctionNotInTemplate not found', str(stderr.decode('utf8')))

@parameterized.expand([
("python3.7", False, "FunctionOne"),
("python3.7", "use_container", "FunctionOne"),
("python3.7", False, "FunctionTwo"),
("python3.7", "use_container", "FunctionTwo")
])
def test_build_single_function(self, runtime, use_container, function_identifier):
# Don't run test on wrong Python versions
py_version = self._get_python_version()
if py_version != runtime:
self.skipTest("Current Python version '{}' does not match Lambda runtime version '{}'".format(py_version,
runtime))

overrides = {"Runtime": runtime, "CodeUri": "Python", "Handler": "main.handler"}
sriram-mv marked this conversation as resolved.
Show resolved Hide resolved
cmdlist = self.get_command_list(use_container=use_container,
parameter_overrides=overrides,
function_identifier=function_identifier)

LOG.info("Running Command: {}", cmdlist)
process = subprocess.Popen(cmdlist, cwd=self.working_dir)
process.wait()

self._verify_built_artifact(self.default_build_dir, function_identifier,
self.EXPECTED_FILES_PROJECT_MANIFEST)

expected = {
"pi": "3.14",
"jinja": "Hello World"
}
self._verify_invoke_built_function(self.built_template,
function_identifier,
self._make_parameter_override_arg(overrides),
expected)
self.verify_docker_container_cleanedup(runtime)

def _verify_built_artifact(self, build_dir, function_logical_id, expected_files):
self.assertTrue(build_dir.exists(), "Build directory should be created")

build_dir_files = os.listdir(str(build_dir))
self.assertIn("template.yaml", build_dir_files)
self.assertIn(function_logical_id, build_dir_files)

template_path = build_dir.joinpath("template.yaml")
resource_artifact_dir = build_dir.joinpath(function_logical_id)

# Make sure the template has correct CodeUri for resource
self._verify_resource_property(str(template_path),
function_logical_id,
"CodeUri",
function_logical_id)

all_artifacts = set(os.listdir(str(resource_artifact_dir)))
print(all_artifacts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove prints

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

actual_files = all_artifacts.intersection(expected_files)
self.assertEquals(actual_files, expected_files)

def _get_python_version(self):
return "python{}.{}".format(sys.version_info.major, sys.version_info.minor)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameteres:
Runtime:
Type: String
CodeUri:
Type: String
Handler:
Type: String

Resources:

FunctionOne:
Type: AWS::Serverless::Function
Properties:
Handler: !Ref Handler
Runtime: !Ref Runtime
CodeUri: !Ref CodeUri
Timeout: 600

FunctionTwo:
Type: AWS::Serverless::Function
Properties:
Handler: !Ref Handler
Runtime: !Ref Runtime
CodeUri: !Ref CodeUri
Timeout: 600
Loading