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

Releasing v0.12.0 #1031

Merged
merged 16 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
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
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=compat.py
ignore=compat.py, __main__.py

# Pickle collected data for later comparisons.
persistent=yes
Expand Down
17 changes: 11 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
# Enable container based builds
sudo: required
language: python
dist: xenial

services:
- docker

python:
- "2.7"
- "3.6"
- "3.7"

# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs
matrix:
include:
- python: 3.7
dist: xenial
sudo: true
addons:
apt:
packages:
# Xenial images don't have jdk8 installed by default.
- openjdk-8-jdk

before_install:
# Use the JDK8 that we installed
- JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64
- PATH=$JAVA_HOME/bin:$PATH

- nvm install 8.10
- npm --version
- node --version
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ information to effectively respond to your bug report or contribution.

## Development Guide

Refer to the [Development Guide](DEVELOPMENT_GUIDE.rst) for help with environment setup, running tests, submitting a PR, or anything that will make you more productive.
Refer to the [Development Guide](DEVELOPMENT_GUIDE.md) for help with environment setup, running tests, submitting a PR, or anything that will make you more productive.


## Reporting Bugs/Feature Requests
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ Design Document

A design document is a written description of the feature/capability you
are building. We have a [design document
template](./designs/_template.rst) to help you quickly fill in the
template](./designs/_template.md) to help you quickly fill in the
blanks and get you working quickly. We encourage you to write a design
document for any feature you write, but for some types of features we
definitely require a design document to proceed with implementation.
Expand Down
4 changes: 2 additions & 2 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Flask~=1.0.2
boto3~=1.9, >=1.9.56
PyYAML~=3.12
cookiecutter~=1.6.0
aws-sam-translator==1.9.0
aws-sam-translator==1.9.1
docker>=3.3.0
dateparser~=0.7
python-dateutil~=2.6
pathlib2~=2.3.2; python_version<"3.4"
requests==2.20.1
aws_lambda_builders==0.0.5
aws_lambda_builders==0.1.0
serverlessrepo==0.1.5
2 changes: 1 addition & 1 deletion samcli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
SAM CLI version
"""

__version__ = '0.11.0'
__version__ = '0.12.0'
12 changes: 12 additions & 0 deletions samcli/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Invokable Module for CLI

python -m samcli
"""

from samcli.cli.main import cli

if __name__ == "__main__":
# NOTE(TheSriram): prog_name is always set to "sam". This way when the CLI is invoked as a module,
# the help text that is generated still says "sam" instead of "__main__".
cli(prog_name="sam")
11 changes: 6 additions & 5 deletions samcli/commands/build/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from samcli.commands._utils.options import template_option_without_build, docker_common_options, \
parameter_override_option
from samcli.commands.build.build_context import BuildContext
from samcli.lib.build.app_builder import ApplicationBuilder, UnsupportedRuntimeException, \
BuildError, UnsupportedBuilderLibraryVersionError
from samcli.lib.build.app_builder import ApplicationBuilder, BuildError, UnsupportedBuilderLibraryVersionError
from samcli.lib.build.workflow_config import UnsupportedRuntimeException
from samcli.commands._utils.template import move_template

LOG = logging.getLogger(__name__)
Expand All @@ -30,9 +30,10 @@
\b
Supported Runtimes
------------------
1. Python2.7\n
2. Python3.6\n
3. Python3.7\n
1. Python 2.7, 3.6, 3.7 using PIP\n
4. Nodejs 8.10, 6.10 using NPM
4. Ruby 2.5 using Bundler
5. Java 8 using Gradle
\b
Examples
--------
Expand Down
61 changes: 26 additions & 35 deletions samcli/lib/build/app_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import io
import json
import logging
from collections import namedtuple

try:
import pathlib
Expand All @@ -20,15 +19,12 @@
from aws_lambda_builders.builder import LambdaBuilder
from aws_lambda_builders.exceptions import LambdaBuilderError
from aws_lambda_builders import RPC_PROTOCOL_VERSION as lambda_builders_protocol_version
from .workflow_config import get_workflow_config


LOG = logging.getLogger(__name__)


class UnsupportedRuntimeException(Exception):
pass


class UnsupportedBuilderLibraryVersionError(Exception):

def __init__(self, container_name, error_msg):
Expand All @@ -41,32 +37,6 @@ class BuildError(Exception):
pass


def _get_workflow_config(runtime):

config = namedtuple('Capability', ["language", "dependency_manager", "application_framework", "manifest_name"])

if runtime.startswith("python"):
return config(
language="python",
dependency_manager="pip",
application_framework=None,
manifest_name="requirements.txt")
elif runtime.startswith("nodejs"):
return config(
language="nodejs",
dependency_manager="npm",
application_framework=None,
manifest_name="package.json")
elif runtime.startswith("ruby"):
return config(
language="ruby",
dependency_manager="bundler",
application_framework=None,
manifest_name="Gemfile")
else:
raise UnsupportedRuntimeException("'{}' runtime is not supported".format(runtime))


class ApplicationBuilder(object):
"""
Class to build an entire application. Currently, this class builds Lambda functions only, but there is nothing that
Expand Down Expand Up @@ -174,14 +144,33 @@ def update_template(self, template_dict, original_template_path, built_artifacts
return template_dict

def _build_function(self, function_name, codeuri, runtime):
"""
Given the function information, this method will build the Lambda function. Depending on the configuration
it will either build the function in process or by spinning up a Docker container.

Parameters
----------
function_name : str
Name or LogicalId of the function

config = _get_workflow_config(runtime)
codeuri : str
Path to where the code lives

# Create the arguments to pass to the builder
runtime : str
AWS Lambda function runtime

Returns
-------
str
Path to the location where built artifacts are available
"""

# Create the arguments to pass to the builder
# Code is always relative to the given base directory.
code_dir = str(pathlib.Path(self._base_dir, codeuri).resolve())

config = get_workflow_config(runtime, code_dir, self._base_dir)

# artifacts directory will be created by the builder
artifacts_dir = str(pathlib.Path(self._build_dir, function_name))

Expand Down Expand Up @@ -217,7 +206,8 @@ def _build_function_in_process(self,
artifacts_dir,
scratch_dir,
manifest_path,
runtime=runtime)
runtime=runtime,
executable_search_paths=config.executable_search_paths)
except LambdaBuilderError as ex:
raise BuildError(str(ex))

Expand All @@ -243,7 +233,8 @@ def _build_function_on_container(self, # pylint: disable=too-many-locals
runtime,
log_level=log_level,
optimizations=None,
options=None)
options=None,
executable_search_paths=config.executable_search_paths)

try:
try:
Expand Down
159 changes: 159 additions & 0 deletions samcli/lib/build/workflow_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
Contains Builder Workflow Configs for different Runtimes
"""

import os
import logging
from collections import namedtuple


LOG = logging.getLogger(__name__)


CONFIG = namedtuple('Capability', ["language", "dependency_manager", "application_framework", "manifest_name",
"executable_search_paths"])

PYTHON_PIP_CONFIG = CONFIG(
language="python",
dependency_manager="pip",
application_framework=None,
manifest_name="requirements.txt",
executable_search_paths=None)

NODEJS_NPM_CONFIG = CONFIG(
language="nodejs",
dependency_manager="npm",
application_framework=None,
manifest_name="package.json",
executable_search_paths=None)

RUBY_BUNDLER_CONFIG = CONFIG(
language="ruby",
dependency_manager="bundler",
application_framework=None,
manifest_name="Gemfile",
executable_search_paths=None)

JAVA_GRADLE_CONFIG = CONFIG(
language="java",
dependency_manager="gradle",
application_framework=None,
manifest_name="build.gradle",
executable_search_paths=None)


class UnsupportedRuntimeException(Exception):
pass


def get_workflow_config(runtime, code_dir, project_dir):
"""
Get a workflow config that corresponds to the runtime provided. This method examines contents of the project
and code directories to determine the most appropriate workflow for the given runtime. Currently the decision is
based on the presence of a supported manifest file. For runtimes that have more than one workflow, we choose a
workflow by examining ``code_dir`` followed by ``project_dir`` for presence of a supported manifest.

Parameters
----------
runtime str
The runtime of the config

code_dir str
Directory where Lambda function code is present

project_dir str
Root of the Serverless application project.

Returns
-------
namedtuple(Capability)
namedtuple that represents the Builder Workflow Config
"""

selectors_by_runtime = {
"python2.7": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
"python3.6": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
"python3.7": BasicWorkflowSelector(PYTHON_PIP_CONFIG),
"nodejs4.3": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
"nodejs6.10": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
"nodejs8.10": BasicWorkflowSelector(NODEJS_NPM_CONFIG),
"ruby2.5": BasicWorkflowSelector(RUBY_BUNDLER_CONFIG),

# When Maven builder exists, add to this list so we can automatically choose a builder based on the supported
# manifest
"java8": ManifestWorkflowSelector([
# Gradle builder needs custom executable paths to find `gradlew` binary
JAVA_GRADLE_CONFIG._replace(executable_search_paths=[code_dir, project_dir])
]),
}

if runtime not in selectors_by_runtime:
raise UnsupportedRuntimeException("'{}' runtime is not supported".format(runtime))

selector = selectors_by_runtime[runtime]

try:
config = selector.get_config(code_dir, project_dir)
return config
except ValueError as ex:
raise UnsupportedRuntimeException("Unable to find a supported build workflow for runtime '{}'. Reason: {}"
.format(runtime, str(ex)))


class BasicWorkflowSelector(object):
"""
Basic workflow selector that returns the first available configuration in the given list of configurations
"""

def __init__(self, configs):

if not isinstance(configs, list):
configs = [configs]

self.configs = configs

def get_config(self, code_dir, project_dir):
"""
Returns the first available configuration
"""
return self.configs[0]


class ManifestWorkflowSelector(BasicWorkflowSelector):
"""
Selects a workflow by examining the directories for presence of a supported manifest
"""

def get_config(self, code_dir, project_dir):
"""
Finds a configuration by looking for a manifest in the given directories.

Returns
-------
samcli.lib.build.workflow_config.CONFIG
A supported configuration if one is found

Raises
------
ValueError
If none of the supported manifests files are found
"""

# Search for manifest first in code directory and then in the project directory.
# Search order is important here because we want to prefer the manifest present within the code directory over
# a manifest present in project directory.
search_dirs = [code_dir, project_dir]
LOG.debug("Looking for a supported build workflow in following directories: %s", search_dirs)

for config in self.configs:

if any([self._has_manifest(config, directory) for directory in search_dirs]):
return config

raise ValueError("None of the supported manifests '{}' were found in the following paths '{}'".format(
[config.manifest_name for config in self.configs],
search_dirs))

@staticmethod
def _has_manifest(config, directory):
return os.path.exists(os.path.join(directory, config.manifest_name))
Loading