Skip to content

Commit

Permalink
Release: v0.2.0 (#95)
Browse files Browse the repository at this point in the history
* Fix local package installation (#72)

When packaging the dependencies only the first local package would be
included in the final bundle. This change adds support for any number
of local directory links to be treated as buildable dependencies. Tests
were also added for the case of 1 or 2 local directory links specified
in the requirements.txt.

* fix: Remove tests from .whl file (bdist_wheel) (#90)

* Add .NET Core builder support (#91)

* feat(maven): Support for Java Maven builder (#87)

* chore: version bump to 0.2.0 (#94)
  • Loading branch information
sriram-mv authored and jfuss committed Mar 7, 2019
1 parent 9bc19ca commit b1e6624
Show file tree
Hide file tree
Showing 50 changed files with 1,805 additions and 5 deletions.
5 changes: 4 additions & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ install:

# setup go
- rmdir c:\go /s /q
- "choco install golang"
- "choco install golang --version 1.11.5"
- "choco install bzr"
- "choco install dep"
- setx PATH "C:\go\bin;C:\gopath\bin;C:\Program Files (x86)\Bazaar\;C:\Program Files\Mercurial;%PATH%;"
Expand All @@ -40,6 +40,9 @@ install:
# setup Gradle
- "choco install gradle"

# Echo final Path
- "echo %PATH%"

test_script:
- "%PYTHON%\\python.exe -m pytest --cov aws_lambda_builders --cov-report term-missing tests/unit tests/functional"
- "%PYTHON%\\python.exe -m pytest tests/integration"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ GitHub.sublime-settings
!.vscode/extensions.json
.history

### .NET Build Folders ###
**/bin/
**/obj/

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Expand Down
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ install:

- go get -u github.com/golang/dep/cmd/dep

# Install .NET Core 2.1
- export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 DOTNET_CLI_TELEMETRY_OPTOUT=1
- if [ "$LINUX" ]; then sudo apt install libunwind8; fi
- wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh
- chmod +x /tmp/dotnet-install.sh
- /tmp/dotnet-install.sh -v 2.1.504
- export DOTNET_ROOT=/home/travis/.dotnet
- export PATH=/home/travis/.dotnet:/home/travis/.dotnet/tools:$PATH
- dotnet --info

# Install the code requirements
- make init
script:
Expand Down
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__ = '0.1.0'
__version__ = '0.2.0'
RPC_PROTOCOL_VERSION = "0.2"
2 changes: 2 additions & 0 deletions aws_lambda_builders/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
import aws_lambda_builders.workflows.go_dep
import aws_lambda_builders.workflows.go_modules
import aws_lambda_builders.workflows.java_gradle
import aws_lambda_builders.workflows.java_maven
import aws_lambda_builders.workflows.dotnet_clipackage
86 changes: 86 additions & 0 deletions aws_lambda_builders/workflows/dotnet_clipackage/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# .NET Core - Lambda Builder

### Scope

To build .NET Core Lambda functions this builder will use the AWS .NET Core Global Tool [Amazon.Lambda.Tools](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools).
This tool has several commands for building and publishing .NET Core Lambda functions. For this integration
the `dotnet lambda package` command will be used to create a zip file that can be deployed to Lambda.

The builder will install the Amazon.Lambda.Tools Global Tool or update to the latest version before executing
the package command.

This builder assumes the [.NET Core command-line interface (CLI)](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x)
is already installed and added to the path environment variable. This is a reasonable requirement as the
.NET Core CLI is a required tool for .NET Core developers to build any .NET Core project.

The .NET Core CLI handles the validation that the correct version of .NET Core is installed and errors out when there is
not a correct version.

### Challenges

#### Output

The output of `dotnet lambda package` command is a zip archive that consumers can then deploy to Lambda. For SAM build
the expected output is a directory of all of the output files. To make the package command compatible with the SAM build
this builder will direct the package command to output the zip file in the artifacts folder. Once the package command is complete
it expands the zip file and then deletes the zip file.

#### Parameters

The package command takes in serveral parameters. Here is the help for the package command.
```bash
> dotnet lambda package --help
Amazon Lambda Tools for .NET Core applications (3.1.2)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

package:
Command to package a Lambda project into a zip file ready for deployment

dotnet lambda package [arguments] [options]
Arguments:
<ZIP-FILE> The name of the zip file to package the project into
Options:
-c | --configuration Configuration to build with, for example Release or Debug.
-f | --framework Target framework to compile, for example netcoreapp2.1.
--msbuild-parameters Additional msbuild parameters passed to the 'dotnet publish' command. Add quotes around the value if the value contains spaces.
-pl | --project-location The location of the project, if not set the current directory will be assumed.
-cfg | --config-file Configuration file storing default values for command line arguments.
-pcfg | --persist-config-file If true the arguments used for a successful deployment are persisted to a config file.
-o | --output-package The output zip file name
-dvc | --disable-version-check Disable the .NET Core version check. Only for advanced usage.
```
Currently **--framework** is the only required parameter which tells the underlying build process what version of .NET Core to build for.
Parameters can be passed into the package command either by a config file called **aws-lambda-tools-defaults.json** or on
the command line. All .NET Core project templates provided by AWS contain the **aws-lambda-tools-defaults.json** file which has
configuration and framework set.
If a parameter is set on the command line it will override any values set in the **aws-lambda-tools-defaults.json**.
An alternative config file can be specified with the **--config-file** parameter.
This builder will forward any options that were provided to it starting with a '-' into the Lambda package command. Forwarding
all parameters to the Lambda package command keeps the builder future compatible with changes to the package command. The package
command does not error out for unknown parameters.
### Implementation
The implementation is broken up into 2 steps. The first action is to make sure the Amazon.Lambda.Tools Global Tool
is installed. The second action is to execute the `dotnet lambda package` command.
#### Step 1: Install Amazon.Lambda.Tools
The tool is installed by executing the command `dotnet tool install -g Amazon.Lambda.Tools` This will install the
tool from [NuGet](https://www.nuget.org/packages/Amazon.Lambda.Tools/) the .NET package management system.
To keep the tool updated the command `dotnet tool update -g Amazon.Lambda.Tools` will be executed if the install
command fail because the tool was already installed.
It is a requirement for Amazon.Lambda.Tools to maintain backwards compatiblity for the package command. This is an
existing requirement for compatiblity with PowerShell Lambda support and the AWS Tools for Visual Studio Team Services.
#### Step 2: Build the Lambda Deployment bundle
To create the Lambda deployment bundle the `dotnet lambda package` command is execute in the project directory. This will
create zip file in the artifacts directory. The builder will then expand the zip file into the zip artifacts folder and
delete the zip file.
5 changes: 5 additions & 0 deletions aws_lambda_builders/workflows/dotnet_clipackage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Builds .NET Core Lambda functions using Amazon.Lambda.Tools Global Tool https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools
"""

from .workflow import DotnetCliPackageWorkflow
85 changes: 85 additions & 0 deletions aws_lambda_builders/workflows/dotnet_clipackage/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""
Actions for Ruby dependency resolution with Bundler
"""

import os
import logging

from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError
from .utils import OSUtils
from .dotnetcli import DotnetCLIExecutionError

LOG = logging.getLogger(__name__)

class GlobalToolInstallAction(BaseAction):

"""
A Lambda Builder Action which installs the Amazon.Lambda.Tools .NET Core Global Tool
"""

NAME = 'GlobalToolInstall'
DESCRIPTION = "Install or update the Amazon.Lambda.Tools .NET Core Global Tool."
PURPOSE = Purpose.COMPILE_SOURCE

def __init__(self, subprocess_dotnet):
super(GlobalToolInstallAction, self).__init__()
self.subprocess_dotnet = subprocess_dotnet

def execute(self):
try:
LOG.debug("Installing Amazon.Lambda.Tools Global Tool")
self.subprocess_dotnet.run(
['tool', 'install', '-g', 'Amazon.Lambda.Tools'],
)
except DotnetCLIExecutionError as ex:
LOG.debug("Error installing probably due to already installed. Attempt to update to latest version.")
try:
self.subprocess_dotnet.run(
['tool', 'update', '-g', 'Amazon.Lambda.Tools'],
)
except DotnetCLIExecutionError as ex:
raise ActionFailedError("Error configuring the Amazon.Lambda.Tools .NET Core Global Tool: " + str(ex))

class RunPackageAction(BaseAction):
"""
A Lambda Builder Action which builds the .NET Core project using the Amazon.Lambda.Tools .NET Core Global Tool
"""

NAME = 'RunPackageAction'
DESCRIPTION = "Execute the `dotnet lambda package` command."
PURPOSE = Purpose.COMPILE_SOURCE

def __init__(self, source_dir, subprocess_dotnet, artifacts_dir, options, os_utils=None):
super(RunPackageAction, self).__init__()
self.source_dir = source_dir
self.subprocess_dotnet = subprocess_dotnet
self.artifacts_dir = artifacts_dir
self.options = options
self.os_utils = os_utils if os_utils else OSUtils()

def execute(self):
try:
LOG.debug("Running `dotnet lambda package` in %s", self.source_dir)

zipfilename = os.path.basename(os.path.normpath(self.source_dir)) + ".zip"
zipfullpath = os.path.join(self.artifacts_dir, zipfilename)

arguments = ['lambda', 'package', '--output-package', zipfullpath]

if self.options is not None:
for key in self.options:
if str.startswith(key, "-"):
arguments.append(key)
arguments.append(self.options[key])

self.subprocess_dotnet.run(
arguments,
cwd=self.source_dir
)

# The dotnet lambda package command outputs a zip file for the package. To make this compatible
# with the workflow, unzip the zip file into the artifacts directory and then delete the zip archive.
self.os_utils.expand_zip(zipfullpath, self.artifacts_dir)

except DotnetCLIExecutionError as ex:
raise ActionFailedError(str(ex))
63 changes: 63 additions & 0 deletions aws_lambda_builders/workflows/dotnet_clipackage/dotnetcli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Wrapper around calls to dotent CLI through a subprocess.
"""

import sys
import logging

from .utils import OSUtils

LOG = logging.getLogger(__name__)

class DotnetCLIExecutionError(Exception):
"""
Exception raised when dotnet CLI fails.
Will encapsulate error output from the command.
"""

MESSAGE = "Dotnet CLI Failed: {message}"

def __init__(self, **kwargs):
Exception.__init__(self, self.MESSAGE.format(**kwargs))

class SubprocessDotnetCLI(object):
"""
Wrapper around the Dotnet CLI, encapsulating
execution results.
"""

def __init__(self, dotnet_exe=None, os_utils=None):
self.os_utils = os_utils if os_utils else OSUtils()
if dotnet_exe is None:
if self.os_utils.is_windows():
dotnet_exe = 'dotnet.exe'
else:
dotnet_exe = 'dotnet'

self.dotnet_exe = dotnet_exe

def run(self, args, cwd=None):
if not isinstance(args, list):
raise ValueError('args must be a list')

if not args:
raise ValueError('requires at least one arg')

invoke_dotnet = [self.dotnet_exe] + args

LOG.debug("executing dotnet: %s", invoke_dotnet)

p = self.os_utils.popen(invoke_dotnet,
stdout=self.os_utils.pipe,
stderr=self.os_utils.pipe,
cwd=cwd)

out, err = p.communicate()

# The package command contains lots of useful information on how the package was created and
# information when the package command was not successful. For that reason the output is
# always written to the output to help developers diagnose issues.
LOG.info(out.decode('utf8').strip())

if p.returncode != 0:
raise DotnetCLIExecutionError(message=err.decode('utf8').strip())
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Dotnet executable resolution
"""

from .utils import OSUtils

class DotnetCliResolver(object):

def __init__(self, executable_search_paths=None, os_utils=None):
self.binary = 'dotnet'
self.executable_search_paths = executable_search_paths
self.os_utils = os_utils if os_utils else OSUtils()

@property
def exec_paths(self):

# look for the windows executable
paths = self.os_utils.which('dotnet.exe', executable_search_paths=self.executable_search_paths)
if not paths:
# fallback to the non windows name without the .exe suffix
paths = self.os_utils.which('dotnet', executable_search_paths=self.executable_search_paths)

if not paths:
raise ValueError("No dotnet cli executable found!")

return paths
36 changes: 36 additions & 0 deletions aws_lambda_builders/workflows/dotnet_clipackage/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Commonly used utilities
"""

import os
import platform
import shutil
import subprocess
import zipfile
from aws_lambda_builders.utils import which


class OSUtils(object):
"""
Convenience wrapper around common system functions
"""

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

def is_windows(self):
return platform.system().lower() == 'windows'

def which(self, executable, executable_search_paths=None):
return which(executable, executable_search_paths=executable_search_paths)

def expand_zip(self, zipfullpath,destination_dir):
ziparchive = zipfile.ZipFile(zipfullpath, 'r')
ziparchive.extractall(destination_dir)
ziparchive.close()
os.remove(zipfullpath)

@property
def pipe(self):
return subprocess.PIPE
Loading

0 comments on commit b1e6624

Please sign in to comment.