diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000000..4355c23d91 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,67 @@ +DESIGN DOCUMENT +=============== + +> \"..with proper design, the features come cheaply \" - Dennis Ritchie + +This document is intended to capture key decisions in the design of this +CLI. This is especially useful for new contributors to understand the +codebase and keep the changes aligned with design decisions. We will +update this document when new components are added or the CLI design +changes significantly. + +Tenets +====== + +These are some key guiding principles for the design: + +- Extensibility is by design. It is not an after thought. +- Each component must be self-contained and testable in isolation. +- Command line interface must be one of many supported input + mechanisms. +- SAM is one of many supported input formats. + +CLI Framework +============= + +This component implements generic CLI functionality that makes it easy +to write individual CLI commands. It performs: + +- CLI argument parsing +- Generating help text and man pages from RST docs of the command. +- Fetching configuration information from environment +- Consistent exit code generation +- [Future] HTTP Mode: Ability to call the CLI commands with same + parameters through a HTTP Endpoint. This is useful for IDEs and + other tools to integrate with this CLI. + +Each command, along with any subcommands, is implemented using Click +annotations. They are not directly wired with the core of the CLI. +Instead, commands are dynamically loaded into the CLI at run time by +importing the Python package implementing the command. For example, +assuming two commands are implemented at Python packages +``foo.cli.cmd1`` and ``foo.cli.cmd2``, then the CLI framework will +dynamically import these two packages and connect them to parent Click +instance. The CLI framework expects the command's Click object to be +exposed through an attribute called `cli`. + +For example: if ``foo.bar.hello`` is the package where ``hello`` command +is implemented, then ``/foo/bar/hello/__init__.py`` file is expected +to contain a Click object called `cli`. + +By convention, the name of last module in the package's name is the +command's name. ie. A package of ``foo.bar.baz`` will produce a command +name ``baz``. + +Commands that make up of the core functionality (like local, validate, +generate-event etc) are also implemented this way. They are baked into +the CLI, but in the future, we will provide options to completely remove +a command. + +By convention, each command is implemented as a separate Python package +where the `__init__.py` file exposes the `cli` attribute. This allows +new commands to be built and distributed as Python packages through PIP, +opening the architecture to support plugins in future. This structure +also forces commands implementations to be modular, reusable, and highly +customizable. When RC files are implemented, new commands can be added +or existing commands can be removed, with simple a configuration in the +RC file. diff --git a/DESIGN.rst b/DESIGN.rst deleted file mode 100644 index 1b4f3ef56d..0000000000 --- a/DESIGN.rst +++ /dev/null @@ -1,54 +0,0 @@ -DESIGN DOCUMENT -=============== - - "..with proper design, the features come cheaply " - Dennis Ritchie - - -This document is intended to capture key decisions in the design of this CLI. This is especially useful for new -contributors to understand the codebase and keep the changes aligned with design decisions. We will update -this document when new components are added or the CLI design changes significantly. - -Tenets -====== -These are some key guiding principles for the design: - -- Extensibility is by design. It is not an after thought. -- Each component must be self-contained and testable in isolation. -- Command line interface must be one of many supported input mechanisms. -- SAM is one of many supported input formats. - - -CLI Framework -============= -This component implements generic CLI functionality that makes it easy to write individual CLI commands. It performs: - -- CLI argument parsing -- Generating help text and man pages from RST docs of the command. -- Fetching configuration information from environment -- Consistent exit code generation -- [Future] HTTP Mode: Ability to call the CLI commands with same parameters through a HTTP Endpoint. This is useful for IDEs and other tools to integrate with this CLI. - -Each command, along with any subcommands, is implemented using Click annotations. They are not directly wired with -the core of the CLI. Instead, commands are dynamically loaded into the CLI at run time by importing the Python -package implementing the command. For example, assuming two commands are implemented at Python packages -"foo.cli.cmd1" and "foo.cli.cmd2", then the CLI framework will dynamically import these two packages and connect them -to parent Click instance. The CLI framework expects the command's Click object to be exposed through an attribute -called ``cli``. - -For example: if "foo.bar.hello" is the package where "hello" command is implemented, then -"/foo/bar/hello/__init__.py" file is expected to contain a Click object called ``cli``. - -By convention, the name of last module in the package's name is the command's name. ie. A package of "foo.bar.baz" -will produce a command name "baz". - -Commands that make up of the core functionality (like local, validate, generate-event etc) are also implemented -this way. They are baked into the CLI, but in the future, we will provide options to completely remove a command. - -By convention, each command is implemented as a separate Python package where the ``__init__.py`` file -exposes the ``cli`` attribute. This allows new commands to be built and distributed as Python packages -through PIP, opening the architecture to support plugins in future. This structure also forces commands implementations -to be modular, reusable, and highly customizable. When RC files are implemented, new commands can be added or existing -commands can be removed, with simple a configuration in the RC file. - - -.. _`click`: https://github.com/pallets/click diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000000..7b9117b25f --- /dev/null +++ b/DEVELOPMENT_GUIDE.md @@ -0,0 +1,169 @@ +DEVELOPMENT GUIDE +================= + +**Welcome hacker!** + +This document will make your life easier by helping you setup a +development environment, IDEs, tests, coding practices, or anything that +will help you be more productive. If you found something is missing or +inaccurate, update this guide and send a Pull Request. + +**Note**: `pyenv` currently only supports macOS and Linux. If you are a +Windows users, consider using [pipenv](https://docs.pipenv.org/). + +Environment Setup +----------------- + +### 1. Install Python Versions + +We support Python 2.7, 3.6 and 3.7 versions. Follow the idioms from this +[excellent cheatsheet](http://python-future.org/compatible_idioms.html) +to make sure your code is compatible with both Python versions. Our +CI/CD pipeline is setup to run unit tests against both Python versions. +So make sure you test it with both versions before sending a Pull +Request. [pyenv](https://github.com/pyenv/pyenv) is a great tool to +easily setup multiple Python versions. + +> Note: For Windows, type +> `export PATH="/c/Users//.pyenv/libexec:$PATH"` to add pyenv to +> your path. + +1. Install PyEnv - + `curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash` +2. `pyenv install 2.7.14` +3. `pyenv install 3.6.8` +4. `pyenv install 3.7.2` +5. Make Python versions available in the project: + `pyenv local 3.6.8 2.7.14 3.7.2` + +### 2. Activate Virtualenv + +Virtualenv allows you to install required libraries outside of the +Python installation. A good practice is to setup a different virtualenv +for each project. [pyenv](https://github.com/pyenv/pyenv) comes with a +handy plugin that can create virtualenv. + +Depending on the python version, the following commands would change to +be the appropriate python version. + +1. `pyenv virtualenv 3.7.2 samcli37` +2. `pyenv activate samcli37` for Python3.7 + +### 3. Install dev version of SAM CLI + +We will install a development version of SAM CLI from source into the +virtualenv for you to try out the CLI as you make changes. We will +install in a command called `samdev` to keep it separate from a global +SAM CLI installation, if any. + +1. Activate Virtualenv: `pyenv activate samcli37` +2. Install dev CLI: `make init` +3. Make sure installation succeeded: `which samdev` + +### 4. (Optional) Install development version of SAM Transformer + +If you want to run the latest version of [SAM +Transformer](https://github.com/awslabs/serverless-application-model/), +you can clone it locally and install it in your pyenv. This is useful if +you want to validate your templates against any new, unreleased SAM +features ahead of time. + +This step is optional and will use the specified version of +aws-sam-transformer from PyPi by default. + + +``cd ~/projects (cd into the directory where you usually place projects)`` + +``git clone https://github.com/awslabs/serverless-application-model/`` + +``git checkout develop `` + +Install the SAM Transformer in editable mode so that all changes you make to the SAM Transformer locally are immediately picked up for SAM CLI. + +``pip install -e . `` + +Move back to your SAM CLI directory and re-run init, If necessary: open requirements/base.txt and replace the version number of aws-sam-translator with the ``version number`` specified in your local version of `serverless-application-model/samtranslator/__init__.py` + +``cd ../aws-sam-cli`` + +``make init`` + +Running Tests +------------- + +### Unit testing with multiple Python versions + +[tox](http://tox.readthedocs.io/en/latest/) is used to run tests against +all supported Python versions. Follow these instructions to setup +multiple Python versions using [pyenv](https://github.com/pyenv/pyenv) +before running tox: + +1. Deactivate virtualenvs, if any: `pyenv deactivate` +2. `pip install tox` +3. Run tests against all supported Python versions: `tox` + +### Integration Test + +`make integ-test` - To run integration test against global SAM CLI +installation. It looks for a command named `sam` in your shell. + +`SAM_CLI_DEV=1 make integ-test` - To run integration tests against +development version of SAM CLI. This is useful if you are making changes +to the CLI and want to verify that it works. It is a good practice to +run integration tests before submitting a pull request. + +Code Conventions +---------------- + +Please follow these code conventions when making your changes. This will +align your code to the same conventions used in rest of the package and +make it easier for others to read/understand your code. Some of these +conventions are best practices that we have learnt over time. + +- Use [numpy + docstring](https://numpydoc.readthedocs.io/en/latest/format.html) + format for docstrings. Some parts of the code still use an older, + unsupported format. If you happened to be modifying these methods, + please change the docstring format as well. +- Don\'t write any code in `__init__.py` file +- Module-level logger variable must be named as `LOG` +- If your method wants to report a failure, it *must* raise a custom + exception. Built-in Python exceptions like `TypeError`, `KeyError` + are raised by Python interpreter and usually signify a bug in your + code. Your method must not explicitly raise these exceptions because + the caller has no way of knowing whether it came from a bug or not. + Custom exceptions convey are must better at conveying the intent and + can be handled appropriately by the caller. In HTTP lingo, custom + exceptions are equivalent to 4xx (user\'s fault) and built-in + exceptions are equivalent to 5xx (Service Fault) +- CLI commands must always raise a subclass of `click.ClickException` + to signify an error. Error code and message must be set as a part of + this exception and not explicitly returned by the CLI command. +- Don't use `*args` or `**kwargs` unless there is a really strong + reason to do so. You must explain the reason in great detail in + docstrings if you were to use them. +- Library classes, ie. the ones under `lib` folder, must **not** use + Click. Usage of Click must be restricted to the `commands` package. + In the library package, your classes must expose interfaces that are + independent of the user interface, be it a CLI thru Click, or CLI + thru argparse, or HTTP API, or a GUI. +- Do not catch the broader `Exception`, unless you have a really + strong reason to do. You must explain the reason in great detail in + comments. + +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 +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. + +**When do you need a design document?** + +- Adding a new command +- Making a breaking change to CLI interface +- Refactoring code that alters the design of certain components +- Experimental features diff --git a/DEVELOPMENT_GUIDE.rst b/DEVELOPMENT_GUIDE.rst deleted file mode 100644 index b1bc8e21f5..0000000000 --- a/DEVELOPMENT_GUIDE.rst +++ /dev/null @@ -1,157 +0,0 @@ -DEVELOPMENT GUIDE -================= - -**Welcome hacker!** - -This document will make your life easier by helping you setup a development environment, IDEs, tests, coding practices, -or anything that will help you be more productive. If you found something is missing or inaccurate, update this guide -and send a Pull Request. - -**Note**: ``pyenv`` currently only supports macOS and Linux. If you are a Windows users, consider using `pipenv`_. - -Environment Setup ------------------ - -1. Install Python Versions -~~~~~~~~~~~~~~~~~~~~~~~~~~ -We support Python 2.7, 3.6 and 3.7 versions. -Follow the idioms from this `excellent cheatsheet`_ to make sure your code is compatible with both Python versions. -Our CI/CD pipeline is setup to run unit tests against both Python versions. So make sure you test it with both -versions before sending a Pull Request. `pyenv`_ is a great tool to easily setup multiple Python versions. - - Note: For Windows, type ``export PATH="/c/Users//.pyenv/libexec:$PATH"`` to add pyenv to your path. - -#. Install PyEnv - ``curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash`` -#. ``pyenv install 2.7.14`` -#. ``pyenv install 3.6.4`` -#. ``pyenv install 3.7.0`` -#. Make Python versions available in the project: ``pyenv local 3.6.4 2.7.14 3.7.0`` - - -2. Activate Virtualenv -~~~~~~~~~~~~~~~~~~~~~~ -Virtualenv allows you to install required libraries outside of the Python installation. A good practice is to setup -a different virtualenv for each project. `pyenv`_ comes with a handy plugin that can create virtualenv. - -Depending on the python version, the following commands would change to be the appropriate python version. - -#. ``pyenv virtualenv 3.7.0 samcli37`` -#. ``pyenv activate samcli37`` for Python3.7 - - -3. Install dev version of SAM CLI -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We will install a development version of SAM CLI from source into the virtualenv for you to try out the CLI as you -make changes. We will install in a command called ``samdev`` to keep it separate from a global SAM CLI installation, -if any. - -#. Activate Virtualenv: ``pyenv activate samcli37`` -#. Install dev CLI: ``make init`` -#. Make sure installation succeeded: ``which samdev`` - -4. (Optional) Install development version of SAM Transformer -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you want to run the latest version of [SAM Transformer](https://github.com/awslabs/serverless-application-model/), you can clone it locally and install it in your pyenv. This is useful if you want to validate your templates against any new, unreleased SAM features ahead of time. - -This step is optional and will use the specified version of aws-sam-transformer from PyPi by default. - -```bash -# cd into the directory where you usually place projects and clone the latest SAM Transformer -cd ~/projects -git clone https://github.com/awslabs/serverless-application-model/ - -# cd into the new directory and checkout the relevant branch -cd serverless-application-model -git checkout develop - -# Install the SAM Transformer in editable mode so that all changes you make to -# the SAM Transformer locally are immediately picked up for SAM CLI. -pip install -e . - -# Move back to your SAM CLI directory and re-run init -# If necessary: open requirements/base.txt and replace the version number of aws-sam-translator with the -# version number specified in your local version of serverless-application-model/samtranslator/__init__.py -cd ../aws-sam-cli -make init -``` - -Running Tests -------------- - -Unit testing with multiple Python versions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -`tox`_ is used to run tests against all supported Python versions. Follow these instructions to setup multiple Python -versions using `pyenv`_ before running tox: - -#. Deactivate virtualenvs, if any: ``pyenv deactivate`` -#. ``pip install tox`` -#. Run tests against all supported Python versions: ``tox`` - -Integration Test -~~~~~~~~~~~~~~~~ - -``make integ-test`` - To run integration test against global SAM CLI installation. It looks for a command named ``sam`` -in your shell. - -``SAM_CLI_DEV=1 make integ-test`` - To run integration tests against development version of SAM CLI. This is useful if -you are making changes to the CLI and want to verify that it works. It is a good practice to run integration tests -before submitting a pull request. - -Code Conventions ----------------- - -Please follow these code conventions when making your changes. This will align your code to the same conventions used -in rest of the package and make it easier for others to read/understand your code. Some of these conventions are -best practices that we have learnt over time. - -- Use `numpy docstring`_ format for docstrings. Some parts of the code still use an older, unsupported format. If you - happened to be modifying these methods, please change the docstring format as well. - -- Don't write any code in ``__init__.py`` file - -- Module-level logger variable must be named as ``LOG`` - -- If your method wants to report a failure, it *must* raise a custom exception. Built-in Python exceptions like - ``TypeError``, ``KeyError`` are raised by Python interpreter and usually signify a bug in your code. Your method must - not explicitly raise these exceptions because the caller has no way of knowing whether it came from a bug or not. - Custom exceptions convey are must better at conveying the intent and can be handled appropriately by the caller. - In HTTP lingo, custom exceptions are equivalent to 4xx (user's fault) and built-in exceptions are equivalent - to 5xx (Service Fault) - -- CLI commands must always raise a subclass of ``click.ClickException`` to signify an error. Error code and message - must be set as a part of this exception and not explicitly returned by the CLI command. - -- Don't use ``*args`` or ``**kwargs`` unless there is a really strong reason to do so. You must explain the reason - in great detail in docstrings if you were to use them. - -- Library classes, ie. the ones under ``lib`` folder, must **not** use Click. Usage of Click must be restricted to - the ``commands`` package. In the library package, your classes must expose interfaces that are independent - of the user interface, be it a CLI thru Click, or CLI thru argparse, or HTTP API, or a GUI. - -- Do not catch the broader ``Exception``, unless you have a really strong reason to do. You must explain the reason - in great detail in comments. - -Design Document ---------------- - -A design document is a written description of the feature/capability you are building. We have a -`design document template`_ 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. - -**When do you need a design document?** - -- Adding a new command -- Making a breaking change to CLI interface -- Refactoring code that alters the design of certain components -- Experimental features - - -.. _excellent cheatsheet: http://python-future.org/compatible_idioms.html -.. _pyenv: https://github.com/pyenv/pyenv -.. _tox: http://tox.readthedocs.io/en/latest/ -.. _numpy docstring: https://numpydoc.readthedocs.io/en/latest/format.html -.. _pipenv: https://docs.pipenv.org/ -.. _design document template: ./designs/_template.rst diff --git a/README.md b/README.md new file mode 100644 index 0000000000..c2ad58f588 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +

+

+ +SAM CLI (Beta) +============== + +![Build +Status](https://travis-ci.org/awslabs/aws-sam-cli.svg?branch=develop) +![Apache-2.0](https://img.shields.io/npm/l/aws-sam-local.svg) +![Contributers](https://img.shields.io/github/contributors/awslabs/aws-sam-cli.svg) +![GitHub-release](https://img.shields.io/github/release/awslabs/aws-sam-cli.svg) +![PyPI version](https://badge.fury.io/py/aws-sam-cli.svg) + +[Join the SAM developers channel (\#samdev) on +Slack](https://join.slack.com/t/awsdevelopers/shared_invite/enQtMzg3NTc5OTM2MzcxLTdjYTdhYWE3OTQyYTU4Njk1ZWY4Y2ZjYjBhMTUxNGYzNDg5MWQ1ZTc5MTRlOGY0OTI4NTdlZTMwNmI5YTgwOGM/) +to collaborate with fellow community members and the AWS SAM team. + +`sam` is the AWS CLI tool for managing Serverless applications written +with [AWS Serverless Application Model +(SAM)](https://github.com/awslabs/serverless-application-model). SAM CLI +can be used to test functions locally, start a local API Gateway from a +SAM template, validate a SAM template, fetch logs, generate sample +payloads for various event sources, and generate a SAM project in your +favorite Lambda Runtime. + +Main features +------------- + +- Develop and test your Lambda functions locally with `sam local` and + Docker +- Invoke functions from known event sources such as Amazon S3, Amazon + DynamoDB, Amazon Kinesis Streams, etc. +- Start local API Gateway from a SAM template, and quickly iterate + over your functions with hot-reloading +- Validate SAM templates +- Get started with boilerplate Serverless Service in your chosen + Lambda Runtime `sam init` + +Get Started +----------- + +Learn how to get started using the SAM CLI with these guides: + +- [Installation](https://aws.amazon.com/serverless/sam/): Set up your macOS, Linux or + Windows Machine to run serverless projects with SAM CLI. +- [Introduction to SAM and SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) What is + SAM and SAM CLI, and how can you use it to make a simple hello-world + app. +- [Running and debugging serverless applications + locally](docs/usage.md): Describes how to use SAM CLI for invoking + Lambda functions locally, running automated tests, fetching logs, + and debugging applications +- [Packaging and deploying your + application](docs/deploying_serverless_applications.md): Deploy + your local application using an S3 bucket, and AWS CloudFormation. +- [Advanced](docs/advanced_usage.md): Learn how to work with compiled + languages (such as Java and .NET), configure IAM credentials, + provide environment variables, and more. +- [Examples](https://github.com/awslabs/serverless-application-model/tree/master/examples/apps) + +Project Status +-------------- + +- \[x\] Python Versions support + - \[x\] Python 2.7 + - \[x\] Python 3.6 + - \[x\] Python 3.7 +- \[ \] Supported AWS Lambda Runtimes + - \[x\] `nodejs` + - \[x\] `nodejs4.3` + - \[x\] `nodejs6.10` + - \[x\] `nodejs8.10` + - \[x\] `java8` + - \[x\] `python2.7` + - \[x\] `python3.6` + - \[x\] `python3.7` + - \[x\] `go1.x` + - \[ \] `dotnetcore1.0` + - \[x\] `dotnetcore2.0` + - \[x\] `dotnetcore2.1` + - \[x\] `ruby2.5` + - \[x\] `Provided` +- \[x\] AWS credential support +- \[x\] Debugging support +- \[x\] Inline Swagger support within SAM templates +- \[x\] Validating SAM templates locally +- \[x\] Generating boilerplate templates + - \[x\] `nodejs` + - \[x\] `nodejs4.3` + - \[x\] `nodejs6.10` + - \[x\] `nodejs8.10` + - \[x\] `java8` + - \[x\] `python2.7` + - \[x\] `python3.6` + - \[x\] `python3.7` + - \[x\] `go1.x` + - \[x\] `dotnetcore1.0` + - \[x\] `dotnetcore2.0` + - \[x\] `ruby2.5` + - \[ \] `Provided` + +Contributing +------------ + +Contributions and feedback are welcome! Proposals and pull requests will +be considered and responded to. For more information, see the +[CONTRIBUTING](CONTRIBUTING.md) file. + +A special thank you +------------------- + +SAM CLI uses the open source +[docker-lambda](https://github.com/lambci/docker-lambda) Docker images +created by [@mhart](https://github.com/mhart). + diff --git a/README.rst b/README.rst deleted file mode 100644 index 75d90d9569..0000000000 --- a/README.rst +++ /dev/null @@ -1,126 +0,0 @@ -.. raw:: html - -

- -.. raw:: html - -

- -============== -SAM CLI (Beta) -============== - -|Build Status| |Apache-2.0| |Contributers| |GitHub-release| |PyPI version| - -`Join the SAM developers channel (#samdev) on -Slack `__ to collaborate with -fellow community members and the AWS SAM team. - -``sam`` is the AWS CLI tool for managing Serverless applications -written with `AWS Serverless Application Model -(SAM) `__. SAM -CLI can be used to test functions locally, start a local API Gateway -from a SAM template, validate a SAM template, fetch logs, generate sample payloads -for various event sources, and generate a SAM project in your favorite -Lambda Runtime. - - - -Main features -------------- - -- Develop and test your Lambda functions locally with ``sam local`` and - Docker -- Invoke functions from known event sources such as Amazon S3, Amazon - DynamoDB, Amazon Kinesis Streams, etc. -- Start local API Gateway from a SAM template, and quickly iterate over - your functions with hot-reloading -- Validate SAM templates -- Get started with boilerplate Serverless Service in your chosen Lambda - Runtime ``sam init`` - - -Get Started ------------ - -Learn how to get started using the SAM CLI with these guides: - -- `Installation `__: Set up your macOS, Linux or Windows Machine to run serverless projects with SAM CLI. -- `Introduction to SAM and SAM CLI `__ What is SAM and SAM CLI, and how can you use it to make a simple hello-world app. -- `Running and debugging serverless applications locally `__: Describes how to use SAM CLI for invoking Lambda functions locally, running automated tests, fetching logs, and debugging applications -- `Packaging and deploying your application `__: Deploy your local application using an S3 bucket, and AWS CloudFormation. -- `Advanced `__: Learn how to work with compiled languages (such as Java and .NET), configure IAM credentials, provide environment variables, and more. -- `Examples `__ - - -Project Status --------------- - -- [ ] Python Versions support - - - [x] Python 2.7 - - [x] Python 3.6 - -- [ ] Supported AWS Lambda Runtimes - - - [x] ``nodejs`` - - [x] ``nodejs4.3`` - - [x] ``nodejs6.10`` - - [x] ``nodejs8.10`` - - [x] ``java8`` - - [x] ``python2.7`` - - [x] ``python3.6`` - - [x] ``python3.7`` - - [x] ``go1.x`` - - [ ] ``dotnetcore1.0`` - - [x] ``dotnetcore2.0`` - - [x] ``dotnetcore2.1`` - - [x] ``ruby2.5`` - - [x] ``Provided`` - -- [x] AWS credential support -- [x] Debugging support -- [x] Inline Swagger support within SAM templates -- [x] Validating SAM templates locally -- [x] Generating boilerplate templates - - - [x] ``nodejs`` - - [x] ``nodejs4.3`` - - [x] ``nodejs6.10`` - - [x] ``nodejs8.10`` - - [x] ``java8`` - - [x] ``python2.7`` - - [x] ``python3.6`` - - [x] ``python3.7`` - - [x] ``go1.x`` - - [x] ``dotnetcore1.0`` - - [x] ``dotnetcore2.0`` - - [x] ``ruby2.5`` - - [ ] ``Provided`` - -Contributing ------------- - -Contributions and feedback are welcome! Proposals and pull requests will -be considered and responded to. For more information, see the -`CONTRIBUTING `__ file. - -A special thank you -------------------- - -SAM CLI uses the open source -`docker-lambda `__ Docker -images created by `@mhart `__. - - -.. raw:: html - - - -.. |Build Status| image:: https://travis-ci.org/awslabs/aws-sam-cli.svg?branch=develop -.. |Apache-2.0| image:: https://img.shields.io/npm/l/aws-sam-local.svg?maxAge=2592000 -.. |Contributers| image:: https://img.shields.io/github/contributors/awslabs/aws-sam-cli.svg?maxAge=2592000 -.. |GitHub-release| image:: https://img.shields.io/github/release/awslabs/aws-sam-cli.svg?maxAge=2592000 -.. |PyPI version| image:: https://badge.fury.io/py/aws-sam-cli.svg - -======= diff --git a/designs/_template.md b/designs/_template.md new file mode 100644 index 0000000000..31d5a00b8f --- /dev/null +++ b/designs/_template.md @@ -0,0 +1,99 @@ +Title: Template for design documents +==================================== + +Use this as a template to write a design document when adding new +commands or major features to SAM CLI. It helps other developers +understand the scope of the project, validate technical complexity and +feasibility. It also serves as a public documentation of how the feature +actually works. + +**Process:** + +1. Copy this template to another file in the `designs` folder. +2. Fill out the sections in the template. +3. Send a "Work In Progress" Pull Request with your design document. We can discuss the +designs in more detail and iterate on the requirements. Feel free to +start implementing a prototype if you think it will help flush out +design. +4. Once the PR is approved, create Github Issues for each task +listed in the document and start implementing them. + +What is the problem? +-------------------- + +What will be changed? +--------------------- + +Success criteria for the change +------------------------------- + +Out-of-Scope +------------ + +User Experience Walkthrough +--------------------------- + +Implementation +============== + +CLI Changes +----------- + +*Explain the changes to command line interface, including adding new +commands, modifying arguments etc* + +### Breaking Change + +*Are there any breaking changes to CLI interface? Explain* + +Design +------ + +*Explain how this feature will be implemented. Highlight the components +of your implementation, relationships* *between components, constraints, +etc.* + +`.samrc` Changes +---------------- + +*Explain the new configuration entries, if any, you want to add to +.samrc* + +Security +-------- + +*Tip: How does this change impact security? Answer the following +questions to help answer this question better:* + +**What new dependencies (libraries/cli) does this change require?** + +**What other Docker container images are you using?** + +**Are you creating a new HTTP endpoint? If so explain how it will be +created & used** + +**Are you connecting to a remote API? If so explain how is this +connection secured** + +**Are you reading/writing to a temporary folder? If so, what is this +used for and when do you clean up?** + +**How do you validate new .samrc configuration?** + +Documentation Changes +--------------------- + +Open Issues +----------- + +Task Breakdown +-------------- + +- \[x\] Send a Pull Request with this design document +- \[ \] Build the command line interface +- \[ \] Build the underlying library +- \[ \] Unit tests +- \[ \] Functional Tests +- \[ \] Integration tests +- \[ \] Run all tests on Windows +- \[ \] Update documentation diff --git a/designs/_template.rst b/designs/_template.rst deleted file mode 100644 index 1c7c1b0f7b..0000000000 --- a/designs/_template.rst +++ /dev/null @@ -1,86 +0,0 @@ -Title: Template for design documents -==================================== - -Use this as a template to write a design document when adding new commands or major features to SAM CLI. It helps -other developers understand the scope of the project, validate technical complexity and feasibility. It also -serves as a public documentation of how the feature actually works. - -**Process:** -1. Copy this template to another file in the ``designs`` folder -1. Fill out the sections in the template -1. Send a "Work In Progress" Pull Request with your design document. We can discuss the designs in more detail and -iterate on the requirements. Feel free to start implementing a prototype if you think it will help flush out design. -1. Once the PR is approved, create Github Issues for each task listed in the document and start implementing them. - -What is the problem? --------------------- - -What will be changed? ---------------------- - -Success criteria for the change -------------------------------- - -Out-of-Scope ------------- - -User Experience Walkthrough ---------------------------- - - -Implementation -============== - -CLI Changes ------------ -*Explain the changes to command line interface, including adding new commands, modifying arguments etc* - -Breaking Change -~~~~~~~~~~~~~~~ -*Are there any breaking changes to CLI interface? Explain* - -Design ------- -*Explain how this feature will be implemented. Highlight the components of your implementation, relationships* -*between components, constraints, etc.* - - -``.samrc`` Changes ------------------- -*Explain the new configuration entries, if any, you want to add to .samrc* - - -Security --------- - -*Tip: How does this change impact security? Answer the following questions to help answer this question better:* - -**What new dependencies (libraries/cli) does this change require?** - -**What other Docker container images are you using?** - -**Are you creating a new HTTP endpoint? If so explain how it will be created & used** - -**Are you connecting to a remote API? If so explain how is this connection secured** - -**Are you reading/writing to a temporary folder? If so, what is this used for and when do you clean up?** - -**How do you validate new .samrc configuration?** - - -Documentation Changes ---------------------- - -Open Issues ------------ - -Task Breakdown --------------- -- [x] Send a Pull Request with this design document -- [ ] Build the command line interface -- [ ] Build the underlying library -- [ ] Unit tests -- [ ] Functional Tests -- [ ] Integration tests -- [ ] Run all tests on Windows -- [ ] Update documentation diff --git a/designs/dotnetcore-debugging.md b/designs/dotnetcore-debugging.md new file mode 100644 index 0000000000..a62164dffa --- /dev/null +++ b/designs/dotnetcore-debugging.md @@ -0,0 +1,211 @@ +# .NET Core 2.0 / 2.1 Debugging + +This is a design for the .NET Core 2.0 and 2.1 missing debugging feature. + +### What is the problem + +Currently SAM CLI does not provide debugging support for .NET Core 2.0 and 2.1 because `dotnet` command does **not** support _start and wait for the debugger_ configuration out of the box and neither does current AWS Lambda runner ([2.0](https://github.com/lambci/docker-lambda/tree/master/dotnetcore2.0) / [2.1](https://github.com/lambci/docker-lambda/tree/master/dotnetcore2.1)) has debugging support flag or anything. + +Apart from that, .NET Core remote debugger - [vsdbg](https://aka.ms/getvsdbgsh) does not support http based communication, but instead only piping `stdin/stdout` through some transport program from host to the **target** machine (Docker container) could enable debugging. For that C# VS Code *extension* [has support](https://github.com/OmniSharp/omnisharp-vscode/wiki/Attaching-to-remote-processes) for configuring `pipeTransport`. Customers could use this mechanism to remotely attach to the running Lambda container with their function. Our goal is to provide `launch.json` configuration capable of doing so to the users, and enable debugging support on the **runner side**. + +And let's break it down to the checklist + +* Get and build `vsdbg` for the Lambda container. +* Mount `vsdbg` to the running Docker container. +* Add _"wait for the debugger"_ flag to the runner program and implement corresponding functionality. +* Implement corresponding container entry point override in SAM CLI to support invoking the runner with this flag **on**. +* Provide instructions for the customer of how to configure VS Code and VS to attach. + +### What will be changed + +Runner program for .NET core [2.0](https://github.com/lambci/docker-lambda/blob/master/dotnetcore2.0/run/MockBootstraps/Program.cs) and [2.1](https://github.com/lambci/docker-lambda/blob/master/dotnetcore2.1/run/MockBootstraps/Program.cs) in another [repo](https://github.com/lambci/docker-lambda) to support *waiting for the debugger* to attach. I've filed [PR](https://github.com/lambci/docker-lambda/pull/130) with only required changes already (✅ merged); + +The good part is that no new commands or parameters on SAM CLI side are required. + +### Success Criteria + +1. Provide handy script to get the `vsdbg` via downloading it to the .NET Lambda runtime container, installing it and getting through the mounting (or manual instructions to achieve the above results). After that user would be able to provide it to SAM CLI via `--debugger-path`. +2. Provide ready-to-go `launch.json` configuration for attaching to the Lambda function via VS Code. +3. Customer should be able to easily debug .NET Core 2.0 and 2.1 apps on VS Code (minimal requirement). + +### Out-of-Scope + +SAM CLI users can perform debugging via Visual Studio 2017 also, but it has some other very different Docker container debugging support, and to keep things consistent it requires over complicated setup from the user. More about approach [here](https://github.com/Microsoft/MIEngine/wiki/Offroad-Debugging-of-.NET-Core-on-Linux---OSX-from-Visual-Studio). So we've decided that it would be a better long term solution to hand this are over to the [new AWS Lambda Test Tool](https://github.com/aws/aws-lambda-dotnet/tree/master/Tools/LambdaTestTool) of VS 2017 AWS Toolkit. + +Rider support needs separate investigation, as it does not support `vsdbg` by any means (licensing [issue](https://github.com/dotnet/core/issues/505)) and therefore they have [their own](https://blog.jetbrains.com/dotnet/2017/02/23/rider-eap-18-coreclr-debugging-back-windows/) debugger implementation and possibly UX around .NET Core remote debugging. If support is required - I think we should open another issue. + +### User Experience Walkthrough + +##### 1. Getting the debugger locally + +At first, user should be able to get and install `vsdbg` on their machine easily. For this we should provide instruction (like it was done for `golang` debugger installation) for them to follow, which will effectively spin up .NET Lambda runtime Docker container with mounted via `bind` path to get the debugger on the **host** machine. + +In this container following: `curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg` should be run to get and install required debugger (specifically built for this container). Later SAM will mount this same folder with the debugger to the target running container. + +Commands below (compatible with `powershell` and `bash`) are taken from my prepared [POC](https://github.com/ndobryanskyy/dotnetcore-aws-local-debugging-poc) and meet all of the above requirements: + +```sh +# Create directory to store debugger locally +mkdir $HOME/vsdbg + +# Mount this to get built vsdbg for AWS Lambda runtime container on host machine +docker run --rm --mount type=bind,src=$HOME/vsdbg,dst=/vsdbg --entrypoint bash lambci/lambda:dotnetcore2.0 -c "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg" +``` + +*Note: we are building on* `:dotnetcore2.0` *just to ensure the support of minimal version. As for now, it makes no difference which image to choose.* + +##### 2. Publishing the code + +Customer should be informed, that in order to have the best debugging experience app must be published in `Debug` configuration by supplying `-c Debug` flag for `dotnet publish` command. + +_For example:_ + +```sh +dotnet publish -c Debug -o out + +# Or via container +docker run --rm --mount src=$(PWD),dst=/var/task,type=bind lambci/lambda:build-dotnetcore2.1 dotnet publish -c Debug -o out +``` + +##### 3. Attaching + +Customer will use ready-to-go `launch.json` (example could be found [here](https://github.com/ndobryanskyy/dotnetcore-aws-local-debugging-poc/blob/master/Lambda/.vscode/launch.json)) debug configuration for VS Code, fill in `debugger_port` for the `docker ps` and supply this port to SAM CLI while invoking. + +_For example:_ + +```sh +sam local invoke --event event.json -d 6000 --debugger-path ~/vsdbg +``` + +After invoking the function in command line, customer will see: + +``` +Waiting for the debugger to attach... +``` + +After that user should just conveniently click the _start debugging_ button with our **".NET Core Docker Attach"** configuration. + +## Implementation + +### CLI Changes + +None required. We would happily use `debugger_port` and `debugger_path` like other runtimes. + +### Design + +##### Supplying debugger + +We will have pretty similar approach to what GO is now doing. Customer will get the `vsdbg` on their machine and supply it to SAM via `--debugger-path` . So everything for the first step is already implemented and we will reuse that mechanism. + +##### Runner with debugging support + +As for the debugging support for the runner, unfortunately `dotnet` command does not support _"start and wait"_ configuration out of the box. Neither does C# provide any event which notifies, that debugger was attached. Therefore we will implement custom mechanism for that. + +During the discussion in this related [issue](https://github.com/awslabs/aws-sam-cli/issues/568) it was decided to go with infinite loop approach. It means that program will query [Debugger.IsAttached](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.debugger.isattached?view=netcore-2.0) property with some interval (for now it is 50ms - which seems instantaneous for the user), also we have timeout for this loop which is 10 minutes for now (thanks for shaping that out, @mikemorain, @sanathkr). Interval and timeout are **open** for suggestions and edits. + +_Examine the code from [PR](https://github.com/lambci/docker-lambda/pull/130/files):_ + +```c# +public static bool TryWaitForAttaching(TimeSpan queryInterval, TimeSpan timeout) +{ + var stopwatch = Stopwatch.StartNew(); + + while (!Debugger.IsAttached) + { + if (stopwatch.Elapsed > timeout) + { + return false; + } + + Task.Delay(queryInterval).Wait(); + } + + return true; +} +``` + +Also for this program `-d` flag was added which, when specified, will make the runner wait for the debugger to attach. + +##### Attaching + +Now, as our runner supports waiting for the debugger to attach, the only thing left to do is actually attach to the `dotnet` process with our Lambda **inside** the Docker container. For that I suggest using remote `dotnet` [debugging support](https://github.com/OmniSharp/omnisharp-vscode/wiki/Attaching-to-remote-processes) from VS Code C# extension. We will take advantage of its `pipeTransport` feature. + +`docker exec` is used to step into the running container and serve as a `pipeTransport` to perform .NET Core remote debugging and one very neat trick with `docker ps` [command](https://docs.docker.com/engine/reference/commandline/ps/) to avoid introducing any changes to SAM CLI and provide user unified UX across runtimes. + +SAM CLI internally uses *published port* to provide debugging support for all of its runtimes. But .NET Core debugger is not capable of running in http mode. That is why VS Code C# extension provides `pipeTransport` configuration section to enable remote .NET debugging. User must provide `pipeProgram` which will let VS Code to talk to `vsdbg` located under `debuggerPath` on **target** machine (Docker container in our case). + +I've chosen `docker` to serve as `pipeProgram` via its `exec` command. By supplying `-i` flag we keep the `stdin` open to let VS Code perform its communication via `stdin/stdout`. The only unsolved part in this equation is how do we know `container name` or `container id` to perform `exec` on, because SAM CLI does not specifically set those. And the answer is - use `docker ps` with filter! 🎉 + +``` +docker ps -q -f publish= +``` + +`-q` will make this command print only the id of the container, and `-f publish=` will filter containers based on the published port, pretty neat, right? This exact trick was used in [launch.json](https://github.com/ndobryanskyy/dotnetcore-aws-local-debugging-poc/blob/master/Lambda/.vscode/launch.json) from POC to get container id for the `docker exec` command. I've used `powershell` on windows to get this nested command working (but the sample includes configuration for OSX and Linux also). + +_Examine sample launch.json configuration_ + +``` +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Docker Attach", + "type": "coreclr", + "request": "attach", + "processId": "1", + + "pipeTransport": { + "pipeProgram": "sh", + "pipeArgs": [ + "-c", + "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" + ], + "debuggerPath": "/vsdbg/vsdbg", + "pipeCwd": "${workspaceFolder}", + }, + + "windows": { + "pipeTransport": { + "pipeProgram": "powershell", + "pipeArgs": [ + "-c", + "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" + ], + "debuggerPath": "/vsdbg/vsdbg", + "pipeCwd": "${workspaceFolder}", + } + }, + + "sourceFileMap": { + "/var/task": "${workspaceFolder}" + } + } + ] +} +``` + +As for the `processId` - luckily entry point program always gets PID of 1 in a running container, so no remote picker required! + +### Open questions + +1. A bit off-topic, but still, has someone else encountered the problem with python 3.7.1 ? As it now does not flush the `stdout` and `stderr` from the running container immediately but rather after SAM ends the invocation, and user does not see _"waiting for the debugger to attach..."_ message. Which leads to bad debugging experience. this behavior is also reproduced on any python (except 2.7). And I see some correlation with this [issue](https://github.com/awslabs/aws-sam-cli/pull/729). I've tested and the problem is certainly on Python side, as auto flushing is enabled in .NET by default; + + **UPD**: Filed [PR](https://github.com/awslabs/aws-sam-cli/pull/843) that fixes that. + +2. Jet Brains Rider support remains under question too; + +3. VS Code .NET debugger adapter from C# extension `vsdbg-ui` reports _"The pipe program 'docker' exited unexpectedly with code 137."_ after debugger session ends. It seems, that 137 (*128+9*) is **killed** exit code, which seems a bit strange. I could not track the issue to the core because `vsdbg` is not open source actually. + + I've investigated this issue and it turned out, that this behavior is observed (on my Windows machine) for any remote .NET debugging inside Docker container. I will reach out to `csharp` extension team to get their thoughts on that. + + **UPD**: Just the same behavior is observed on Mac machine. + + + +### Tasks breakdown + +- [x] Submit PR with the design doc; +- [x] Submit PR with runner program improvements; +- [x] Submit PR with changes and user documentation required to take advantage of .NET Core debugging to SAM CLI repo. +- [x] Merge [PR](https://github.com/lambci/docker-lambda/pull/130) to `lambci/docker-lambda` [repo](https://github.com/lambci/docker-lambda); +- [x] Investigate debugging support for VS 2017 \ No newline at end of file diff --git a/designs/resource_metadata_overriding.md b/designs/resource_metadata_overriding.md new file mode 100644 index 0000000000..b814a851b1 --- /dev/null +++ b/designs/resource_metadata_overriding.md @@ -0,0 +1,170 @@ +Understand Resource Level Metadata +================================== + +This is a design to capture how SAM CLI can support templates that are generated +from different frameworks, e.g. AWS Cloud Development Kit. + +Initially, the support will only be for processing Resource Metadata within the template, which enables support for +customers using AWS Cloud Development Kit (CDK). + +What is the problem? +-------------------- + +Customers have different ways to define their AWS Resources. As of writing (Jan. 2109), +SAM CLI supports the use case of defining an application in CloudFormation/SAM (a super +set of CloudFormation). These CloudFormation/SAM applications are written in `json` or `yaml` +and deployed through AWS CloudFormation. Frameworks like CDK offer customers an alternative +in how they define their applications. SAM CLI should support the ability to invoke functions +defined through these other frameworks to enable them to locally debug or manage their +applications. + +What will be changed? +--------------------- + +To start, we will add support for processing Resource Metadata that is embedded into the template: +SAM CLI will add a processing step on the templates it reads. This will consist of reading +the template and for each resource reading the Metadata and replacing values as specified. + +In the future, we can support creating these templates from the different frameworks in a command directly within +SAM CLI but is out of scope in the initial implementation of support. + +Success criteria for the change +------------------------------- + +* Ability to invoke functions locally that contain Metadata on a Resource +* Process a template with CDK Metadata on a resource. + +Out-of-Scope +------------ + +* A command that will generate the template from the framework. + +User Experience Walkthrough +--------------------------- + +CDK is a framework that appends this Metadata to Resources within a template and will use this as an example. + +### Customer using CDK + +A customer will use CDK to generate the template. This can be done by generating a template and saving it to a file: +`cdk synth > template.yaml`. Then will then be able to `sam local [invoke|start-api|start-lambda]` any +function they have defined [1]. + + +[1] Note: The cdk version must be greater than v0.21.0 as the metadata needed to parse is not appended on older versions. + + +Implementation +============== + +CLI Changes +----------- + +For the features currently in scope, there are no changes to the CLI interface. + +### Breaking Change + +No breaking changes + +Design +------ + +All the providers, which are used to get resources out of the template provided to the command, call +`SamBaseProvider.get_template(template_dict, parameter_overrides)` to get a normalized template. This function call is +responsible for taking a SAM template dictionary and returning a cleaned copy of the template where SAM plugins have +been run and parameter values have been substituted. Given the current scope of this call, expanding it to also normalize +metadata, seems reasonable. We will expand `SamBaseProvider.get_tempalte()` to call a `ResourceMetadataNormalizer` class +that will be responsible for understanding the metadata and normalizing the template with respect to the metadata. + +Template snippet that contains the metadata SAM CLI will parse and understand. + +```yaml + +Resources: + MyFunction: + Type: AWS::Lambda::Function + Properties: + Code: + S3Bucket: mybucket + S3Key: myKey + ... + Metadata: + aws:asset:path: '/path/to/function/code' + aws:asset:property: 'Code' +``` + +The two keys we will recognize are `aws:asset:path` and `aws:asset:property`. `aws:asset:path`'s value will be the path +to the code, files, etc that are on the machine, while `aws:asset:property` is the Property of the Resource that +needs to be replaced. So in the example above, the `Code` Property will be replaced with `/path/to/function/code`. + +Below algorithm to do this Metadata Normalization on the template. + +```python +class ResourceMetadataNormalizer(object): + + @staticmethod + def normalize(template_dict): + for resource in template_dict.get('Resources'): + if 'Metadata' in resource: + asset_property = resource.get('Metadata').get('aws:asset:property') + asset_path = resource.get('Metadata').get('aws:asset:path') + ResourceMetadataNormalizer.replace_property(asset_property, asset_path) +``` + +`.samrc` Changes +---------------- + +N/A + +Security +-------- + +*Tip: How does this change impact security? Answer the following +questions to help answer this question better:* + +**What new dependencies (libraries/cli) does this change require?** + +None + +**What other Docker container images are you using?** + +None + +**Are you creating a new HTTP endpoint? If so explain how it will be +created & used** + +No + +**Are you connecting to a remote API? If so explain how is this +connection secured** + +No + +**Are you reading/writing to a temporary folder? If so, what is this +used for and when do you clean up?** + +No + +**How do you validate new .samrc configuration?** + +N/A + +Documentation Changes +--------------------- + +* Blog or Documentation that explains how you can define an application in CDK and use SAM CLI to test/invoke + +Open Issues +----------- + +Task Breakdown +-------------- + +- \[x\] Send a Pull Request with this design document +- \[ \] Build the command line interface +- \[ \] Build the underlying library +- \[ \] Unit tests +- \[ \] Functional Tests +- \[ \] Integration tests +- \[ \] Run all tests on Windows +- \[ \] Update documentation diff --git a/designs/sam_build_cmd.md b/designs/sam_build_cmd.md new file mode 100644 index 0000000000..1f52752cb8 --- /dev/null +++ b/designs/sam_build_cmd.md @@ -0,0 +1,557 @@ +`sam build` command +=================== + +This is the design for a command to **build** a Lambda function. +**Build** is the operation of converting the function\'s source code to +an artifact that can be executed on AWS Lambda. + +What is the problem? +-------------------- + +To run a function on AWS Lambda, customers need to provide a zip file +containing an executable form of the code. The process of creating the +executable usually involves downloading dependent libraries, optionally +compiling the code on Amazon Linux, copying static assets, and arranging +the files in a directory structure that AWS Lambda accepts. In some +programming languages like Javascript, this process is fairly +straightforward (just zip a folder), and in others like Python, it is +much involved. + +Customers using SAM CLI can easily create Lambda function code (using +`sam init`) and package their artifact as a zip file (using +`sam package`), but they have to handle the build process themselves. +Customers usually implement their own build scripts or adopt other\'s +scripts from the internet. This is where many customers fall off the +cliff. + +What will be changed? +--------------------- + +In this proposal, we will be providing a new command, `sam build`, to +build Lambda functions for all programming languages that AWS Lambda +supports. The cardinality of the problem is number of programming +languages (N) times the number of package managers (M) per language. +Hence is it is nearly impossible to natively support each combination. +Instead, `sam build` will support an opinionated set of package managers +for all programming languages. In the future, we will provide an option +for customers to bring their own build commands or override the default +for any programming language. SAM CLI will still take care of the grunt +work of iterating through every function in the SAM template, figuring +out the source code location, creating temporary folders to store built +artifacts, running the build command and move artifacts to right +location. + +Success criteria for the change +------------------------------- + +1. Support all programming languages supported by AWS Lambda + - Nodejs with NPM + - Java with Maven + - Python with PIP + - Golang with Go CLI + - Dotnetcore with DotNet CLI +2. Each Lambda function in SAM template gets built +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 + `sam package` suite of commands +5. Opt-in to building native dependencies that can run on AWS Lambda + using Docker. +6. Support one dependency manifest (ex: package.json) per each Lambda + function. +7. Support out-of-source builds: ie. source code of Lambda function is + outside the directory containing SAM template. +8. Integrate build action with `sam local/package/deploy` commands so + the Lambda functions will be automatically built as part of the + command without explicitly running the build command. + +Out-of-Scope +------------ + +1. Ability to provide arbitrary build commands for each Lambda Function + runtime. This will either override the built-in default or add build + support for languages/tools that SAM CLI does not support (ex: + java+gradle). +2. Supports adding data files ie. files that are not referenced by the + package manager (ex: images, css etc) +3. Support to exclude certain files from the built artifact (ex: using + .gitignore or using regex) +4. Support for building the app for debugging locally with debug + symbols (ex: Golang) +5. Support caching dependencies & re-installing them only when the + dependency manifest changes (ex: by maintaining hash of + package.json) +6. If the app contains a `buildspec.yaml`, automatically run it using + CodeBuild Local. +7. Watch for file changes and build automatically (ex: + `sam build --watch`) +8. Support other build systems by default Webpack, Yarn or Gradle. +9. Support in AWS CLI, `aws cloudformation`, suite of commands +10. Support for fine-grained hooks (ex: hooks that run pre-build, + post-build, etc) +11. Support one dependency manifest per app, shared by all the Lambda + functions (this is usually against best practices) + +User Experience Walkthrough +--------------------------- + +Let\'s assume customers has the following SAM template: + +> **NOTE**: Currently we advice customers to set *CodeUri* to a folder +> containing built artifacts that can be readily packaged by +> `sam package` command. But to use with *build* command, customers need +> to set *CodeUri* to the folder containing source code and not built +> artifacts. + +``` {.sourceCode .yaml} +MyFunction1: + Type: AWS::Lambda::Function + Properties: + ... + Code: ./source-code1 + ... + +MyFunction2: + Type: AWS::Serverless::Function + Properties: + ... + Code: ./source-code2 + ... +``` + +To build, package and deploy this app, customers would do the following: + +**1. Build:** Run the following command to build all functions in the +template and output a SAM template that can be run through the package +command: + +``` {.sourceCode .bash} +# Build the code and write artifacts to ./build folder +# NOTE: All arguments will have sensible defaults so users can just use `sam build` +$ sam build -t template.yaml -b ./build -o built-template.yaml +``` + +Output of the *sam build* command is a SAM template where CodeUri is +pointing to the built artifacts. Note the values of Code properties in +following output: + +``` {.sourceCode .bash} +$ cat built-template.yaml +MyFunction1: + Type: AWS::Lambda::Function + Properties: + ... + Code: ./build/MyFunction1 + ... + +MyFunction2: + Type: AWS::Serverless::Function + Properties: + ... + CodeUri: ./build/MyFunction2 + ... +``` + +**2. Package and Deploy:** Package the built artifacts by running the +*package* command on the template output by *build* command + +``` {.sourceCode .bash} +# Package the code +$ sam package --template-file built-template.yaml --s3-bucket mybucket --output-template-file packaged-template.yaml + +# Deploy the app +$ sam deploy --template-file packaged-template.yaml --stack-name mystack +``` + +### Other Usecases + +1. **Build Native Dependencies**: Pass the `--native` flag to the + *build* command. This will run the build inside a Docker container. +2. **Out-of-Source Builds**: In this scenario, Lambda function code is + present in a folder outside the folder containing the SAM template. + Absolute path to these folders are determined at runtime in a build + machine. Set the `--root=/my/folder` flag to absolute path to the + folder relative to which we will resolve relative *CodeUri* paths. +3. **Inherited dependency manifest**: By default, we will look for a + dependency manifest (ex: package.json) at same folder containing SAM + template. If a `--root` flag is set, we will look for manifest at + this folder. If neither locations have a manifest, we will look for + a manifest within the folder containing function code. Manifest + present within the code folder always overrides manifest at the + root. +4. **Arbitrary build commands**: Override build commands per-runtime by + specifying full path to the command in `.samrc`. +5. **Build & Run Locally**: Use the `--template` property of + `sam local` suite of commands to specify the template produced by + *build* command (ex: `build-template.yaml`) + +Implementation +============== + +CLI Changes +----------- + +*Explain the changes to command line interface, including adding new +commands, modifying arguments etc* + +1. Adding a new top-level command called `sam build`. +2. Add `built-template.yaml` to list of default template names searched + by `sam local` commands + +### Breaking Change + +*Are there any breaking changes to CLI interface? Explain* + +No Breaking Change to CLI interface + +Design +------ + +*Explain how this feature will be implemented. Highlight the components +of your implementation, relationships* *between components, constraints, +etc.* + +Build library provides the ability to execute build actions on each +registered resource. A build action is either a built-in functionality +or a custom build command provided by user. At a high level, the +algorithm looks like this: + +``` {.sourceCode .python} +for resource in sam_template: + # Find the appropriate builder + builder = get_builder(resource.Type) + + # Do the build + output_folder = make_build_folder(resource) + builder.build(resource.Code, resource.runtime, output_folder) +``` + +We will keep the implementation of build agnostic of the resource type. +This opens up the future possibility of adding build actions to any +resource types, not just Lambda functions. Initially we will start by +supporting only the resource types `AWS::Serverless::Function` and +`AWS::Lambda::Function`. + +### Build Folder + +Default Location: `$PKG_ROOT/build/` + +By default, we will create a folder called `build` right next to where +the SAM template is located. This will contain the built artifacts for +each resource. Customers can always override this folder location. + +Built artifacts for each resource will be stored within a folder named +with the LogicalID of the resource. This allows us to build separate zip +files for each Lambda, so users can update one Lambda without triggering +an update on another. The same model will work for building other +non-Lambda resources. + +*Advantages:* + +- Extensible to other resource types +- Supports parallel builds for each resource +- Aligned with a CloudFormation stack + +*Disadvantages:* + +- Too many build folders, and hence zip files, to manage. +- Difficult to share code between all Lambdas. + + + + $PKG_ROOT/build/ + artifacts.json (not for MVP) + MyFunction1/ + .... + + MyFunction2/ + ... + + MyApiGw/ + ... + + MyECRContainer/ + ... + +#### Future Extensions + +In the future, we will change the limitation around each folder being +named after the resource\'s LogicalID. Instead, we will support an +artifacts.json file that will map Lambda function resource's LogicalId +to the path to a folder that contains built artifacts for this function. +This allows us to support custom build systems that use different folder +layout. + +A well-known folder structure also helps "sam local" and "sam package" +commands to automatically discover the built artifacts for each Lambda +function and package it. + +### Stable Builds + +A build is defined to be stable if the built artifacts changes if and +only if the contents or metadata (ex: timestamp, ownership) on source +files changes. This is an important attribute of a build system. Since +SAM CLI relies on 3rd party package managers like NPM to do the heavy +lifting, we can only provide a "best effort" service here. By running +`sam build` on a build system that creates a new environment from +scratch (ex: Travis/CircleCI/CodeBuild/Jenkins etc), you can achieve +truly stable builds. For more information on why this is important, +refer to Debian\'s guide on [reproducible +builds](https://reproducible-builds.org/). + +SAM CLI does the following to produce stable builds: + +1. Clean build folder on every run +2. Include metadata when coping files and folders +3. Run build actions with minimal information passed from the + environment + +### Built-in Build Actions + +Build actions natively supported by SAM CLI follow a standard workflow: + +1. Search for a supported dependency manifest file. If a known manifest + is not present, we will abort the build. +2. Setup: Create build folder +3. Resolve: Install dependencies +4. Compile: Optionally, compile the code if necessary +5. Copy Source: Optionally, Copy Lambda function code to the build + folder + +Setup step is shared among all runtimes. Other steps in the workflow are +implemented differently for each runtime. + +#### Javascript using NPM + +Install dependencies specified by `package.json` and copy source files + +**Manifest Name**: `package.json` + +**Files Excluded From Copy Source**: `node_modules/*` + + ---------------------------------------------------- + Action Command + ------------- -------------------------------------- + Resolve `npm install` + + Compile No Op + + Copy Source Copy files and exclude node\_modules + ---------------------------------------------------- + +#### Java using Maven + +Let Maven take care of everything + +**Manifest Name**: `pom.xml` + +**Files Excluded From Copy Source**: N/A + + +| Action | Command | +|---------------|----------------| +| Resolve | No Op | +| Compile | `mvn package` | +| Copy Source | No Op | + + +#### Golang using Go CLI + +Go\'s CLI will build the binary. + +**Manifest Name**: `Gopkg.toml` + +**Files Excluded From Copy Source**: N/A + +| Action | Command | +|---------------|--------------------------------------------------| +| Resolve | `dep ensure -v` | +| Compile | `GOOS=linux go build -ldflags="-s -w" main.go` | +| Copy Source | No Op | + + +#### Dotnet using Dotnet CLI + +**Manifest Name**: `*.csproj` + +**Files Excluded From Copy Source**: N/A + + +| Action | Command | +|---------------|--------------------------------------------------------------------------------------| +| Resolve | No Op | +| Compile | `dotnet lambda package --configuration release --output-package $BUILD/package.zip` | +| Copy Source | No Op | + + +#### Python using PIP + +**Manifest Name**: `requirements.txt` + +**Files Excluded From Copy Source**: `*.pyc, __pycache__` + +| Action | Command | +|---------------|--------------------------------------------------------------------------------------| +| Resolve | `pip install --isolated --disable-pip-version-check -r requirements.txt -t $BUILD` | +| Compile | No Op | +| Copy Source | Copy all files | + + +### Implementation of Build Actions + +Some of the built-in build actions are implemented in the programming +language that the actions supports. For example, the Nodejs build action +will be implemented in Javascript to take advantage of language-specific +libraries. These modules are called **builders**. This is a reasonable +implementation choice because customers building Nodejs apps are +expected to have Node installed on their system. For languages like +Golang, we will delegate entire functionality to `go` tool by invoking +it as a subprocess. The SAM CLI distribution will now bundle Javascript +code within a Python package, which even though seems odd, carries +value. + +**Pros:** + +- Easy to lift & shift +- Easy to use language specific libraries that can support deeper + integrations in future like webpack build or running gulp scripts +- Sets precedence for other runtimes like Java which might need + reflexion to create the package +- Easier to get help from JS community who is more familiar with + building JS packages. + +**Cons:** + +- Vending JS files in Python package +- Might take dependency on certain version of Node. We can\'t enforce + that customers have this version of Node on their system. +- Might have to webpack all dependencies, minify and vend one file + that we just run using node pack.js. +- Could become a tech debt if this approach doesn\'t scale. + +#### Builder Interface + +In this implementation model, some steps in the build action are +implemented natively in Python and some in a separate programming +language. To complete a build operation, SAM CLI reads SAM template, +prepares necessary folder structure, and invokes the appropriate builder +process/command by passing necessary information through stdin as +JSON-RPC. SAM CLI waits for a JSON-RPC response back through stdout of +the process and depending on the status, either fails the build or +proceeds to next step. + +**Input:** + +``` {.sourceCode .json} +{ + "jsonrpc": "2.0", + + "id": "42", + + // Only supported method is `resolve-dependencies` + "method": "resolve-dependencies", + "params": { + "source_dir": "/folder/where/source/files/located", + "build_dir": "/directory/for/builder/artifacts", + "runtime": "aws lambda function runtime ex. node8.10", + "template_path": "/path/to/sam/template" + } +} +``` + +**Output:** + +``` {.sourceCode .json} +{ + "jsonrpc": "2.0", + + "id": "42", + + "result": { + // No result expected for successful execution + } +} +``` + +### Building Native Binaries + +To build native binaries, we need to run on an architecture and +operating system that is similar to AWS Lambda. We use the Docker +containers provided by [Docker +Lambda](https://github.com/lambci/docker-lambda) project to run the same +set of commands described above on this container. We will mount source +code folder and build folder into the container so the commands have +access to necessary files. + +`.samrc` Changes (Out-of-Scope) +------------------------------- + +*Explain the new configuration entries, if any, you want to add to +.samrc* + +We will add a new section to `.samrc` where customers can provide custom +build actions. This section will look like: + +``` {.sourceCode .json} +{ + "build": { + "actions": { + "java8": "gradle build", + "dotnetcore2.1": "./build.sh" + } + } +} +``` + +Security +-------- + +*Tip: How does this change impact security? Answer the following +questions to help answer this question better:* + +**What new dependencies (libraries/cli) does this change require?** + +**What other Docker container images are you using?** + +**Are you creating a new HTTP endpoint? If so explain how it will be +created & used** + +**Are you connecting to a remote API? If so explain how is this +connection secured** + +**Are you reading/writing to a temporary folder? If so, what is this +used for and when do you clean up?** + +**How do you validate new .samrc configuration?** + +Documentation Changes +--------------------- + +TBD + +Open Questions +-------------- + +1. Should we support `artifacts.json` now to be future-proof? **Answer: + NO** +2. Should we create the default `build` folder within a `.sam` folder + inside the project to provide a home for other scratch files if + necessary? **Answer: Out of Scope for current implementation** + +Task Breakdown +-------------- + +- \[x\] Send a Pull Request with this design document +- \[ \] Build the command line interface +- \[ \] Wire up SAM provider to discover function to build +- \[ \] Library to build Python functions for MVP (others languages + will follow next) +- \[ \] Add `built-template.yaml` to list of default template names + searched by `sam local` commands +- \[ \] Update `sam init` templates to include `sam build` in the + README +- \[ \] Unit tests +- \[ \] Functional Tests +- \[ \] Integration tests +- \[ \] Run all tests on Windows +- \[ \] Update documentation diff --git a/designs/sam_build_cmd.rst b/designs/sam_build_cmd.rst deleted file mode 100644 index 05aedd62e0..0000000000 --- a/designs/sam_build_cmd.rst +++ /dev/null @@ -1,515 +0,0 @@ -.. contents:: **Table of Contents** - :depth: 2 - :local: - -``sam build`` command -===================== -This is the design for a command to **build** a Lambda function. **Build** is the operation of converting the function's -source code to an artifact that can be executed on AWS Lambda. - - -What is the problem? --------------------- -To run a function on AWS Lambda, customers need to provide a zip file containing an executable form of the code. The -process of creating the executable usually involves downloading dependent libraries, optionally compiling the code -on Amazon Linux, copying static assets, and arranging the files in a directory structure that AWS Lambda accepts. -In some programming languages like Javascript, this process is fairly straightforward (just zip a folder), and in -others like Python, it is much involved. - -Customers using SAM CLI can easily create Lambda function code (using ``sam init``) and package their artifact as a -zip file (using ``sam package``), but they have to handle the build process themselves. Customers usually implement -their own build scripts or adopt other's scripts from the internet. This is where many customers fall off the cliff. - - -What will be changed? ---------------------- -In this proposal, we will be providing a new command, ``sam build``, to build Lambda functions for all programming -languages that AWS Lambda supports. The cardinality of the problem is number of programming languages (N) times the -number of package managers (M) per language. Hence is it is nearly impossible to natively support each combination. -Instead, ``sam build`` will support an opinionated set of package managers for all programming languages. In the future, -we will provide an option for customers to bring their own build commands or override the default for any programming -language. SAM CLI will still take care of the grunt work of iterating through every function in the SAM template, -figuring out the source code location, creating temporary folders to store built artifacts, running the build command -and move artifacts to right location. - - -Success criteria for the change -------------------------------- -#. Support all programming languages supported by AWS Lambda - - * Nodejs with NPM - * Java with Maven - * Python with PIP - * Golang with Go CLI - * Dotnetcore with DotNet CLI - - -#. Each Lambda function in SAM template gets built - -#. Produce stable builds (best effort): If the source files did not change, built artifacts should not change. - -#. Built artifacts should "just work" with ``sam local`` and ``sam package`` suite of commands - -#. Opt-in to building native dependencies that can run on AWS Lambda using Docker. - -#. Support one dependency manifest (ex: package.json) per each Lambda function. - -#. Support out-of-source builds: ie. source code of Lambda function is outside the directory containing SAM template. - -#. Integrate build action with ``sam local/package/deploy`` commands so the Lambda functions will be automatically - built as part of the command without explicitly running the build command. - - -Out-of-Scope ------------- -#. Ability to provide arbitrary build commands for each Lambda Function runtime. This will either override the built-in - default or add build support for languages/tools that SAM CLI does not support (ex: java+gradle). -#. Supports adding data files ie. files that are not referenced by the package manager (ex: images, css etc) -#. Support to exclude certain files from the built artifact (ex: using .gitignore or using regex) -#. Support for building the app for debugging locally with debug symbols (ex: Golang) -#. Support caching dependencies & re-installing them only when the dependency manifest changes - (ex: by maintaining hash of package.json) -#. If the app contains a ``buildspec.yaml``, automatically run it using CodeBuild Local. -#. Watch for file changes and build automatically (ex: ``sam build --watch``) -#. Support other build systems by default Webpack, Yarn or Gradle. -#. Support in AWS CLI, ``aws cloudformation``, suite of commands -#. Support for fine-grained hooks (ex: hooks that run pre-build, post-build, etc) -#. Support one dependency manifest per app, shared by all the Lambda functions (this is usually against best practices) - - -User Experience Walkthrough ---------------------------- -Let's assume customers has the following SAM template: - - **NOTE**: Currently we advice customers to set *CodeUri* to a folder containing built artifacts that can be readily - packaged by ``sam package`` command. But to use with *build* command, customers need to set *CodeUri* to the folder - containing source code and not built artifacts. - -.. code-block:: yaml - - MyFunction1: - Type: AWS::Lambda::Function - Properties: - ... - Code: ./source-code1 - ... - - MyFunction2: - Type: AWS::Serverless::Function - Properties: - ... - Code: ./source-code2 - ... - - -To build, package and deploy this app, customers would do the following: - -**1. Build:** Run the following command to build all functions in the template and output a SAM template that can be run through -the package command: - -.. code-block:: bash - - # Build the code and write artifacts to ./build folder - # NOTE: All arguments will have sensible defaults so users can just use `sam build` - $ sam build -t template.yaml -b ./build -o built-template.yaml - - -Output of the *sam build* command is a SAM template where CodeUri is pointing to the built artifacts. Note the values of -Code properties in following output: - -.. code-block:: bash - - $ cat built-template.yaml - MyFunction1: - Type: AWS::Lambda::Function - Properties: - ... - Code: ./build/MyFunction1 - ... - - MyFunction2: - Type: AWS::Serverless::Function - Properties: - ... - CodeUri: ./build/MyFunction2 - ... - -**2. Package and Deploy:** Package the built artifacts by running the *package* command on the template output -by *build* command - -.. code-block:: bash - - # Package the code - $ sam package --template-file built-template.yaml --s3-bucket mybucket --output-template-file packaged-template.yaml - - # Deploy the app - $ sam deploy --template-file packaged-template.yaml --stack-name mystack - -Other Usecases -~~~~~~~~~~~~~~~ - -#. **Build Native Dependencies**: Pass the ``--native`` flag to the *build* command. This will run the build inside - a Docker container. -#. **Out-of-Source Builds**: In this scenario, Lambda function code is present in a folder outside the folder containing - the SAM template. Absolute path to these folders are determined at runtime in a build machine. Set the - ``--root=/my/folder`` flag to absolute path to the folder relative to which we will resolve relative *CodeUri* paths. -#. **Inherited dependency manifest**: By default, we will look for a dependency manifest (ex: package.json) at same - folder containing SAM template. If a ``--root`` flag is set, we will look for manifest at this folder. If neither - locations have a manifest, we will look for a manifest within the folder containing function code. Manifest present - within the code folder always overrides manifest at the root. -#. **Arbitrary build commands**: Override build commands per-runtime by specifying full path to the command in - ``.samrc``. -#. **Build & Run Locally**: Use the ``--template`` property of ``sam local`` suite of commands to specify the - template produced by *build* command (ex: ``build-template.yaml``) - -Implementation -============== - -CLI Changes ------------ -*Explain the changes to command line interface, including adding new commands, modifying arguments etc* - -#. Adding a new top-level command called ``sam build``. -#. Add ``built-template.yaml`` to list of default template names searched by ``sam local`` commands - - -Breaking Change -~~~~~~~~~~~~~~~ -*Are there any breaking changes to CLI interface? Explain* - -No Breaking Change to CLI interface - -Design ------- -*Explain how this feature will be implemented. Highlight the components of your implementation, relationships* -*between components, constraints, etc.* - -Build library provides the ability to execute build actions on each registered resource. A build action is either -a built-in functionality or a custom build command provided by user. At a high level, the algorithm looks like this: - -.. code-block:: python - - for resource in sam_template: - # Find the appropriate builder - builder = get_builder(resource.Type) - - # Do the build - output_folder = make_build_folder(resource) - builder.build(resource.Code, resource.runtime, output_folder) - - -We will keep the implementation of build agnostic of the resource type. This opens up the future possibility of adding -build actions to any resource types, not just Lambda functions. Initially we will start by supporting only the -resource types ``AWS::Serverless::Function`` and ``AWS::Lambda::Function``. - -Build Folder -~~~~~~~~~~~~ -Default Location: ``$PKG_ROOT/build/`` - -By default, we will create a folder called ``build`` right next to where the SAM template is located. -This will contain the built artifacts for each resource. Customers can always override this folder location. - -Built artifacts for each resource will be stored within a folder named with the LogicalID of the resource. -This allows us to build separate zip files for each Lambda, so users can update one Lambda without triggering an update -on another. The same model will work for building other non-Lambda resources. - -*Advantages:* - -* Extensible to other resource types -* Supports parallel builds for each resource -* Aligned with a CloudFormation stack - -*Disadvantages:* - -* Too many build folders, and hence zip files, to manage. -* Difficult to share code between all Lambdas. - -:: - - $PKG_ROOT/build/ - artifacts.json (not for MVP) - MyFunction1/ - .... - - MyFunction2/ - ... - - MyApiGw/ - ... - - MyECRContainer/ - ... - - -Future Extensions -^^^^^^^^^^^^^^^^^ -In the future, we will change the limitation around each folder being named after the resource's LogicalID. -Instead, we will support an artifacts.json file that will map Lambda function resource's LogicalId to the path to a -folder that contains built artifacts for this function. This allows us to support custom build systems that use -different folder layout. - -A well-known folder structure also helps “sam local” and “sam package” commands to automatically discover the -built artifacts for each Lambda function and package it. - -Stable Builds -~~~~~~~~~~~~~ -A build is defined to be stable if the built artifacts changes if and only if the contents or metadata -(ex: timestamp, ownership) on source files changes. This is an important attribute of a build system. Since SAM CLI -relies on 3rd party package managers like NPM to do the heavy lifting, we can only provide a "best effort" service here. -By running ``sam build`` on a build system that creates a new environment from scratch -(ex: Travis/CircleCI/CodeBuild/Jenkins etc), you can achieve truly stable builds. For more information on why this -is important, refer to Debian's guide on `reproducible builds `_. - - -SAM CLI does the following to produce stable builds: - -#. Clean build folder on every run -#. Include metadata when coping files and folders -#. Run build actions with minimal information passed from the environment - - -Built-in Build Actions -~~~~~~~~~~~~~~~~~~~~~~ -Build actions natively supported by SAM CLI follow a standard workflow: - -#. Search for a supported dependency manifest file. If a known manifest is not present, we will abort the build. -#. Setup: Create build folder -#. Resolve: Install dependencies -#. Compile: Optionally, compile the code if necessary -#. Copy Source: Optionally, Copy Lambda function code to the build folder - -Setup step is shared among all runtimes. Other steps in the workflow are implemented differently for each runtime. - -Javascript using NPM -^^^^^^^^^^^^^^^^^^^^ - -Install dependencies specified by ``package.json`` and copy source files - -**Manifest Name**: ``package.json`` - -**Files Excluded From Copy Source**: ``node_modules/*`` - -+-------------+--------------------------------------+ -| Action | Command | -+=============+======================================+ -| Resolve | ``npm install`` | -+-------------+--------------------------------------+ -| Compile | No Op | -+-------------+--------------------------------------+ -| Copy Source | Copy files and exclude node_modules | -+-------------+--------------------------------------+ - - -Java using Maven -^^^^^^^^^^^^^^^^ - -Let Maven take care of everything - -**Manifest Name**: ``pom.xml`` - -**Files Excluded From Copy Source**: N/A - -+-------------+-----------------+ -| Action | Command | -+=============+=================+ -| Resolve | No Op | -+-------------+-----------------+ -| Compile | ``mvn package`` | -+-------------+-----------------+ -| Copy Source | No Op | -+-------------+-----------------+ - -Golang using Go CLI -^^^^^^^^^^^^^^^^^^^ - -Go's CLI will build the binary. - -**Manifest Name**: ``Gopkg.toml`` - -**Files Excluded From Copy Source**: N/A - -+-------------+---------------------------------------------------+ -| Action | Command | -+=============+===================================================+ -| Resolve | ``dep ensure -v`` | -+-------------+---------------------------------------------------+ -| Compile | ``GOOS=linux go build -ldflags="-s -w" main.go`` | -+-------------+---------------------------------------------------+ -| Copy Source | No Op | -+-------------+---------------------------------------------------+ - - -Dotnet using Dotnet CLI -^^^^^^^^^^^^^^^^^^^^^^^ - -**Manifest Name**: ``*.csproj`` - -**Files Excluded From Copy Source**: N/A - -+-------------+----------------------------------------------------------------------------------------+ -| Action | Command | -+=============+========================================================================================+ -| Resolve | No Op | -+-------------+----------------------------------------------------------------------------------------+ -| Compile | ``dotnet lambda package --configuration release --output-package $BUILD/package.zip`` | -+-------------+----------------------------------------------------------------------------------------+ -| Copy Source | No Op | -+-------------+----------------------------------------------------------------------------------------+ - - -Python using PIP -^^^^^^^^^^^^^^^^ - -**Manifest Name**: ``requirements.txt`` - -**Files Excluded From Copy Source**: ``*.pyc, __pycache__`` - -+-------------+---------------------------------------------------------------------------------------+ -| Action | Command | -+=============+=======================================================================================+ -| Resolve | ``pip install --isolated --disable-pip-version-check -r requirements.txt -t $BUILD`` | -+-------------+---------------------------------------------------------------------------------------+ -| Compile | No Op | -+-------------+---------------------------------------------------------------------------------------+ -| Copy Source | Copy all files | -+-------------+---------------------------------------------------------------------------------------+ - -Implementation of Build Actions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some of the built-in build actions are implemented in the programming language that the actions supports. For example, -the Nodejs build action will be implemented in Javascript to take advantage of language-specific libraries. These -modules are called **builders**. This is a reasonable implementation choice because customers building Nodejs apps are -expected to have Node installed on their system. For languages like Golang, we will delegate entire functionality -to ``go`` tool by invoking it as a subprocess. The SAM CLI distribution will now bundle Javascript code within a Python -package, which even though seems odd, carries value. - - -**Pros:** - -- Easy to lift & shift -- Easy to use language specific libraries that can support deeper integrations in future like webpack build or - running gulp scripts -- Sets precedence for other runtimes like Java which might need reflexion to create the package -- Easier to get help from JS community who is more familiar with building JS packages. - -**Cons:** - -- Vending JS files in Python package -- Might take dependency on certain version of Node. We can't enforce that customers have this version of Node on their system. -- Might have to webpack all dependencies, minify and vend one file that we just run using node pack.js. -- Could become a tech debt if this approach doesn't scale. - -Builder Interface -^^^^^^^^^^^^^^^^^ - -In this implementation model, some steps in the build action are implemented natively in Python and some in a separate -programming language. To complete a build operation, SAM CLI reads SAM template, prepares necessary folder structure, -and invokes the appropriate builder process/command by passing necessary information through stdin as JSON-RPC. SAM CLI -waits for a JSON-RPC response back through stdout of the process and depending on the status, either fails the build -or proceeds to next step. - -**Input:** - -.. code-block:: json - - { - "jsonrpc": "2.0", - - "id": "42", - - // Only supported method is `resolve-dependencies` - "method": "resolve-dependencies", - "params": { - "source_dir": "/folder/where/source/files/located", - "build_dir": "/directory/for/builder/artifacts", - "runtime": "aws lambda function runtime ex. node8.10", - "template_path": "/path/to/sam/template" - } - } - - -**Output:** - -.. code-block:: json - - { - "jsonrpc": "2.0", - - "id": "42", - - "result": { - // No result expected for successful execution - } - } - - -Building Native Binaries -~~~~~~~~~~~~~~~~~~~~~~~~ -To build native binaries, we need to run on an architecture and operating system that is similar to AWS Lambda. We use -the Docker containers provided by `Docker Lambda `_ project to run the same -set of commands described above on this container. We will mount source code folder and build folder into the container -so the commands have access to necessary files. - -``.samrc`` Changes (Out-of-Scope) ---------------------------------- -*Explain the new configuration entries, if any, you want to add to .samrc* - -We will add a new section to ``.samrc`` where customers can provide custom build actions. This section will look like: - -.. code-block:: json - - { - "build": { - "actions": { - "java8": "gradle build", - "dotnetcore2.1": "./build.sh" - } - } - } - -Security --------- - -*Tip: How does this change impact security? Answer the following questions to help answer this question better:* - -**What new dependencies (libraries/cli) does this change require?** - -**What other Docker container images are you using?** - -**Are you creating a new HTTP endpoint? If so explain how it will be created & used** - -**Are you connecting to a remote API? If so explain how is this connection secured** - -**Are you reading/writing to a temporary folder? If so, what is this used for and when do you clean up?** - -**How do you validate new .samrc configuration?** - - -Documentation Changes ---------------------- -TBD - -Open Questions --------------- - -#. Should we support ``artifacts.json`` now to be future-proof? **Answer: NO** -#. Should we create the default ``build`` folder within a ``.sam`` folder inside the project to provide a home for - other scratch files if necessary? **Answer: Out of Scope for current implementation** - - -Task Breakdown --------------- -- [x] Send a Pull Request with this design document -- [ ] Build the command line interface -- [ ] Wire up SAM provider to discover function to build -- [ ] Library to build Python functions for MVP (others languages will follow next) -- [ ] Add ``built-template.yaml`` to list of default template names searched by ``sam local`` commands -- [ ] Update ``sam init`` templates to include ``sam build`` in the README -- [ ] Unit tests -- [ ] Functional Tests -- [ ] Integration tests -- [ ] Run all tests on Windows -- [ ] Update documentation - - diff --git a/designs/sam_publish_cmd.rst b/designs/sam_publish_cmd.rst new file mode 100644 index 0000000000..45b2de3dd2 --- /dev/null +++ b/designs/sam_publish_cmd.rst @@ -0,0 +1,294 @@ +.. contents:: **Table of Contents** + :depth: 2 + :local: + +``sam publish`` command +==================================== + +This is the design for a command to publish an application to `AWS Serverless Application Repository (SAR)`_ with a SAM +template. It can be used to create a new application w/ its first version, update existing application's metadata, and +create new versions of the application. + +.. _AWS Serverless Application Repository (SAR): https://aws.amazon.com/serverless/serverlessrepo/ + + +What is the problem? +-------------------- +To publish an app to AWS Serverless Application Repository, customers need to go through the following steps: first upload +the application code and SAM template to an Amazon S3 bucket, correctly set S3 bucket policy that grants the service read +permissions for artifacts uploaded to S3, then open the AWS Serverless Application Repository console and provide information +in a bunch of input boxes. If they use the AWS CLI, they need to pass all the information as parameters, and it's easy to make +a mistake while typing in the command line. + + +What will be changed? +--------------------- +In this proposal, we will be providing a new command, ``sam publish``, which takes a SAM template as input and publishes +an application to AWS Serverless Application Repository using applicaiton metadata specified in the template. Customers +need to provide application metadata information in the template, then ``sam package`` will handle uploading local files to S3, +and ``sam publish`` will create the app in Serverless Application Repository. + + +Success criteria for the change +------------------------------- +#. Support all the following use cases: + + * Create new application w/ its first version in SAR using ``sam publish`` + * Create new version of existing SAR application using ``sam publish`` + * Update application metadata of existing SAR application using ``sam publish`` + +#. ``sam package`` command can upload local readme/license files to S3. + + +Out-of-Scope +------------ +#. Manage application permissions while publishing the app. + +#. Recursively publish nested apps in the template (SAR CreateApplication API doesn't support yet). + +#. Run through CI/CD pipeline for the application before publishing. + +#. Publish to other repositories besides SAR. + +#. Recognize template changes and suggest version number. + +#. Publish appication if ``AWS::ServerlessRepo::Application`` section is not found in the template's ``Metadata`` section. + + +User Experience Walkthrough +--------------------------- + +Assuming that customers have the following SAM template: + +.. code-block:: yaml + + Metadata: + AWS::ServerlessRepo::Application: + Name: my-app + Description: hello world + Author: user1 + SpdxLicenseId: Apache-2.0 + LicenseUrl: ./LICENSE.txt + ReadmeUrl: ./README.md + Labels: ['tests'] + HomePageUrl: https://github.com/user1/my-app-project + SemanticVersion: 0.0.1 + SourceCodeUrl: https://github.com/user1/my-app-project + + Resources: + HelloWorldFunction: + Type: AWS::Lambda::Function + Properties: + ... + CodeUri: ./source-code1 + ... + +Build Lambda source code + Run ``sam build -t template.yaml -b ./build -o built-template.yaml`` to build all functions in the template and output + a SAM template that can be run through the package command. + +Package built artifacts and local file references + Run ``sam package --template-file built-template.yaml --output-template-file packaged.yaml --s3-bucket my-bucket`` + to upload code artifacts, readme and license files to S3 and generate the packaged template. + +Create new application in SAR + Run ``sam publish -t ./packaged.yaml`` to publish a new application named my-app in SAR with the first version + created as 0.0.1. The app will be created as private by default. SAM CLI prints application created message, metadata + used to create application and link to the console details page. + + >>> sam publish -t ./packaged.yaml + Publish Succeeded + Created new application with the following metadata: + { + "Name": "my-app", + "Description": "hello world", + "Author": "user1", + "SpdxLicenseId": "Apache-2.0", + "LicenseUrl": "s3://test/LICENSE.txt", + "ReadmeUrl": "s3://test/README.md", + "Labels": ['tests'], + "HomePageUrl": "https://github.com/user1/my-app-project", + "SemanticVersion": "0.0.1", + "SourceCodeUrl": "https://github.com/user1/my-app-project" + } + Click the link below to view your application in AWS console: + https://console.aws.amazon.com/serverlessrepo/home?region=#/published-applications/ + +Create new version of an existing SAR application + Modify the existing template, change SemanticVersion to 0.0.2, and run ``sam publish -t ./packaged.yaml`` again. + SAM CLI prints application metadata updated message, values of updated metadata and link to the console details page. + + >>> sam publish -t ./packaged.yaml + Publish Succeeded + The following metadata of application has been updated: + { + "Author": "user1", + "Description": "description", + "ReadmeUrl": "s3://test/README.md", + ... + "SemanticVersion": "0.0.2", + "SourceCodeUrl": "https://github.com/hello" + } + Click the link below to view your application in AWS console: + https://console.aws.amazon.com/serverlessrepo/home?region=#/published-applications/ + +Update the metadata of an existing application without creating new version + Keep SemanticVersion unchanged, then modify metadata fields like Description or ReadmeUrl, and run + ``sam publish -t ./packaged.yaml``. SAM CLI prints application metadata updated message, values of updated + metadata and link to the console details page. + + >>> sam publish -t ./packaged.yaml + Publish Succeeded + The following metadata of application has been updated: + { + "Author": "qwang", + "Description": "description", + "ReadmeUrl": "s3://test/README.md" + ... + } + Click the link below to view your application in AWS console: + https://console.aws.amazon.com/serverlessrepo/home?region=#/published-applications/ + +Once the application is published, other developers in your team or your organization will be able to deploy it with a few +clicks. If the application is shared publicly, the whole community will be able to find it by visiting the AWS Serverless +Application Repository `public site`_. + +.. _public site: https://serverlessrepo.aws.amazon.com/applications + + +Implementation +============== + +CLI Changes +----------- +*Explain the changes to command line interface, including adding new commands, modifying arguments etc* + +1. Add a new top-level command called ``sam publish`` with the following help message. + +.. code-block:: text + + Usage: sam publish [OPTIONS] + + Use this command to publish a packaged AWS SAM template to the AWS + Serverless Application Repository to share within your team, across your + organization, or with the community at large. + + This command expects the template's Metadata section to contain an + AWS::ServerlessRepo::Application section with application metadata + for publishing. For more details on this metadata section, see + https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html + + Examples + -------- + To publish an application + $ sam publish -t packaged.yaml --region + + Options: + -t, --template PATH AWS SAM template file [default: template.[yaml|yml]] + --profile TEXT Select a specific profile from your credential file to + get AWS credentials. + --region TEXT Set the AWS Region of the service (e.g. us-east-1). + --debug Turn on debug logging to print debug message generated + by SAM CLI. + --help Show this message and exit. + +2. Update ``sam package`` (``aws cloudformation package``) command to support uploading locally referenced readme and +license files to S3. + +Breaking Change +~~~~~~~~~~~~~~~ +*Are there any breaking changes to CLI interface? Explain* + +N/A + +Design +------ +*Explain how this feature will be implemented. Highlight the components of your implementation, relationships* +*between components, constraints, etc.* + +SAM CLI will read the packaged SAM template and pass it as string to `aws-serverlessrepo-python `_ +library. The algorithm for ``sam publish -t ./packaged.yaml`` looks like this: + +.. code-block:: python + + from serverlessrepo import publish_application + + with open('./packaged.yaml', 'r') as f: + template = f.read() + result = publish_application(template) + + +``.samrc`` Changes +------------------ +*Explain the new configuration entries, if any, you want to add to .samrc* + +N/A + +Security +-------- + +*Tip: How does this change impact security? Answer the following questions to help answer this question better:* + +**What new dependencies (libraries/cli) does this change require?** + +A new dependency `aws-serverlessrepo-python `_ will be added to interact with SAR. + +**What other Docker container images are you using?** + +N/A + +**Are you creating a new HTTP endpoint? If so explain how it will be created & used** + +N/A + +**Are you connecting to a remote API? If so explain how is this connection secured** + +Will be connecting to boto3 serverlessrepo `create_application`_, `update_application`_, `create_application_version`_ APIs through +the `aws-serverlessrepo-python `_ library. The connection is secured by requiring +AWS credentials and permissions for the target application. + +.. _create_application : https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/serverlessrepo.html#ServerlessApplicationRepository.Client.create_application +.. _update_application : https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/serverlessrepo.html#ServerlessApplicationRepository.Client.update_application +.. _create_application_version: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/serverlessrepo.html#ServerlessApplicationRepository.Client.create_application_version + + +**Are you reading/writing to a temporary folder? If so, what is this used for and when do you clean up?** + +N/A + +**How do you validate new .samrc configuration?** + +N/A + +Documentation Changes +--------------------- + +1. Add "AWS::ServerlessRepo::Application" spec in `Publishing Applications`_ guide. + + - Can be added in `SAM specification`_ in the future. + +2. Add ``ReadmeUrl`` and ``LicenseUrl`` in `aws cloudformation package`_ documentation. + +3. Add ``sam publish`` in `AWS SAM CLI Command Reference`_, and explain the command, usage, examples, options. + +4. Add a quick start guide "Publishing your application to AWS Serverless Application Repository" explaining how to use ``sam publish``. + +.. _SAM specification: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md +.. _Publishing Applications: https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverless-app-publishing-applications.html +.. _aws cloudformation package: https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html +.. _AWS SAM CLI Command Reference: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-command-reference.html + +Open Issues +----------- + +N/A + +Task Breakdown +-------------- +- [x] Send a Pull Request with this design document +- [ ] Build the command line interface +- [ ] Build the underlying library +- [ ] Unit tests +- [ ] Integration tests +- [ ] Run all tests on Windows +- [ ] Update documentation diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 0d692e8b5c..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = SAMCLI -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md new file mode 100644 index 0000000000..4fa33fd076 --- /dev/null +++ b/docs/advanced_usage.md @@ -0,0 +1,265 @@ +Advanced Usage +============== + +- [Compiled Languages](#compiled-languages) +- [IAM Credentials](#iam-credentials) +- [Lambda Environment Variables](#lambda-environment-variables) + - [Environment Variable file](#environment-variable-file) + - [Shell environment](#shell-environment) + - [Combination of Shell and Environment Variable + file](#combination-of-shell-and-environment-variable-file) +- [Identifying local execution from Lambda function + code](#identifying-local-execution-from-lambda-function-code) +- [Static Assets](#static-assets) +- [Local Logging](#local-logging) +- [Remote Docker](#remote-docker) + +Compiled Languages +------------------ + +**Java** + +To use SAM CLI with compiled languages, such as Java that require a +packaged artifact (e.g. a JAR, or ZIP), you can specify the location of +the artifact with the `AWS::Serverless::Function` `CodeUri` property in +your SAM template. + +For example: + +``` {.sourceCode .yaml} +AWSTemplateFormatVersion: 2010-09-09 +Transform: AWS::Serverless-2016-10-31 + +Resources: + ExampleJavaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.example.HelloWorldHandler + CodeUri: ./target/HelloWorld-1.0.jar + Runtime: java8 +``` + +You should then build your JAR file using your normal build process. +Please note that JAR files used with AWS Lambda should be a shaded JAR +file (or uber jar) containing all of the function dependencies. + +``` {.sourceCode .bash} +// Build the JAR file +$ mvn package shade:shade + +// Invoke with SAM Local +$ echo '{ "some": "input" }' | sam local invoke + +// Or start local API Gateway simulator +$ sam local start-api +``` + +**.NET Core** + +To use SAM Local with compiled languages, such as .NET Core that require +a packaged artifact (e.g. a ZIP), you can specify the location of the +artifact with the `AWS::Serverless::Function` `CodeUri` property in your +SAM template. + +For example: + +``` {.sourceCode .yaml} +AWSTemplateFormatVersion: 2010-09-09 +Transform: AWS::Serverless-2016-10-31 + +Resources: + ExampleDotNetFunction: + Type: AWS::Serverless::Function + Properties: + Handler: HelloWorld::HelloWorld.Function::Handler + CodeUri: ./artifacts/HelloWorld.zip + Runtime: dotnetcore2.0 +``` + +You should then build your ZIP file using your normal build process. + +You can generate a .NET Core example by using the +`sam init --runtime dotnetcore` command. + +IAM Credentials +--------------- + +SAM CLI will invoke functions with your locally configured IAM +credentials. + +As with the AWS CLI and SDKs, SAM CLI will look for credentials in the +following order: + +1. Environment Variables (`AWS_ACCESS_KEY_ID`, + `AWS_SECRET_ACCESS_KEY`). +2. The AWS credentials file (located at `~/.aws/credentials` on Linux, + macOS, or Unix, or at `C:\Users\USERNAME \.aws\credentials` on + Windows). +3. Instance profile credentials (if running on Amazon EC2 with an + assigned instance role). + +In order to test API Gateway with a non-default profile from your AWS +credentials file append `--profile ` to the `start-api` +command: + +``` {.sourceCode .bash} +// Test API Gateway locally with a credential profile. +$ sam local start-api --profile some_profile +``` + +See this [Configuring the AWS +CLI](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence) +for more details. + +Lambda Environment Variables +---------------------------- + +If your Lambda function uses environment variables, you can provide +values for them will passed to the Docker container. Here is how you +would do it: + +For example, consider the SAM template snippet: + +``` {.sourceCode .yaml} +Resources: + MyFunction1: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs4.3 + Environment: + Variables: + TABLE_NAME: prodtable + BUCKET_NAME: prodbucket + + MyFunction2: + Type: AWS::Serverless::Function + Properties: + Handler: app.handler + Runtime: nodejs4.3 + Environment: + Variables: + STAGE: prod + TABLE_NAME: prodtable +``` + +Environment Variable file +------------------------- + +Use the `--env-vars` argument of the `invoke` or `start-api` commands to +provide a JSON file that contains values to override the environment +variables already defined in your function template. The file should be +structured as follows: + +``` {.sourceCode .json} +{ + "MyFunction1": { + "TABLE_NAME": "localtable", + "BUCKET_NAME": "testBucket" + }, + "MyFunction2": { + "TABLE_NAME": "localtable", + "STAGE": "dev" + }, +} +``` + +``` {.sourceCode .bash} +$ sam local start-api --env-vars env.json +``` + +Shell environment +----------------- + +Variables defined in your Shell's environment will be passed to the +Docker container, if they map to a Variable in your Lambda function. +Shell variables are globally applicable to functions ie. If two +functions have a variable called `TABLE_NAME`, then the value for +`TABLE_NAME` provided through Shell's environment will be availabe to +both functions. + +Following command will make value of `mytable` available to both +`MyFunction1` and `MyFunction2` + +``` {.sourceCode .bash} +$ TABLE_NAME=mytable sam local start-api +``` + +Combination of Shell and Environment Variable file +-------------------------------------------------- + +For greater control, you can use a combination shell variables and +external environment variable file. If a variable is defined in both +places, the one from the file will override the shell. Here is the order +of priority, highest to lowest. Higher priority ones will override the +lower. + +1. Environment Variable file +2. Shell's environment +3. Hard-coded values from the template + +Identifying local execution from Lambda function code +----------------------------------------------------- + +When your Lambda function is invoked using SAM CLI, it sets an +environment variable `AWS_SAM_LOCAL=true` in the Docker container. Your +Lambda function can use this property to enable or disable functionality +that would not make sense in local development. For example: Disable +emitting metrics to CloudWatch (or) Enable verbose logging etc. + +Static Assets +------------- + +Often, it's useful to serve up static assets (e.g CSS/HTML/Javascript +etc) when developing a Serverless application. On AWS, this would +normally be done with CloudFront/S3. SAM CLI by default looks for a +`./public/` directory in your SAM project directory and will serve up +all files from it at the root of the HTTP server when using +`sam local start-api`. You can override the default static asset +directory by using the `-s` or `--static-dir` command line flag. You can +also disable this behaviour completely by setting `--static-dir ""`. + +Local Logging +------------- + +Both `invoke` and `start-api` command allow you to pipe logs from the +function's invocation into a file. This will be useful if you are +running automated tests against SAM CLI and want to capture logs for +analysis. + +Example: + +``` {.sourceCode .bash} +$ sam local invoke --log-file ./output.log +``` + +Remote Docker +------------- + +Sam CLI loads function code by mounting filesystem to a Docker Volume. +As a result, The project directory must be pre-mounted on the remote +host where the Docker is running. + +If mounted, you can use the remote docker normally using +`--docker-volume-basedir` or environment variable +`SAM_DOCKER_VOLUME_BASEDIR`. + +Example - Docker Toolbox (Windows): + +When you install and run Docker Toolbox, the Linux VM with Docker is +automatically installed in the virtual box. + +The /c/ path for this Linux VM is automatically shared with C: on the +host machine. + +``` {.sourceCode .powershell} +$ sam local invoke --docker-volume-basedir /c/Users/shlee322/projects/test "Ratings" +``` + +### Learn More + +- [Project Overview](../README.md) +- [Getting started with SAM and the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) +- [Usage](usage.md) +- [Packaging and deploying your + application](deploying_serverless_applications.md) diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst deleted file mode 100644 index eaeb72e819..0000000000 --- a/docs/advanced_usage.rst +++ /dev/null @@ -1,272 +0,0 @@ - -============== -Advanced Usage -============== -- `Compiled Languages <#compiled-languages>`__ -- `IAM Credentials <#iam-credentials>`__ -- `Lambda Environment Variables <#lambda-environment-variables>`__ - - - `Environment Variable file <#environment-variable-file>`__ - - `Shell environment <#shell-environment>`__ - - `Combination of Shell and Environment Variable file <#combination-of-shell-and-environment-variable-file>`__ - -- `Identifying local execution from Lambda function - code <#identifying-local-execution-from-lambda-function-code>`__ -- `Static Assets <#static-assets>`__ -- `Local Logging <#local-logging>`__ -- `Remote Docker <#remote-docker>`__ - -Compiled Languages ------------------- - -**Java** - -To use SAM CLI with compiled languages, such as Java that require a -packaged artifact (e.g. a JAR, or ZIP), you can specify the location of -the artifact with the ``AWS::Serverless::Function`` ``CodeUri`` property -in your SAM template. - -For example: - -.. code:: yaml - - AWSTemplateFormatVersion: 2010-09-09 - Transform: AWS::Serverless-2016-10-31 - - Resources: - ExampleJavaFunction: - Type: AWS::Serverless::Function - Properties: - Handler: com.example.HelloWorldHandler - CodeUri: ./target/HelloWorld-1.0.jar - Runtime: java8 - -You should then build your JAR file using your normal build process. -Please note that JAR files used with AWS Lambda should be a shaded JAR -file (or uber jar) containing all of the function dependencies. - -.. code:: bash - - // Build the JAR file - $ mvn package shade:shade - - // Invoke with SAM Local - $ echo '{ "some": "input" }' | sam local invoke - - // Or start local API Gateway simulator - $ sam local start-api - - -**.NET Core** - -To use SAM Local with compiled languages, such as .NET Core that require a packaged artifact (e.g. a ZIP), you can specify the location of the artifact with the ``AWS::Serverless::Function`` ``CodeUri`` property in your SAM template. - -For example: - -.. code:: yaml - - AWSTemplateFormatVersion: 2010-09-09 - Transform: AWS::Serverless-2016-10-31 - - Resources: - ExampleDotNetFunction: - Type: AWS::Serverless::Function - Properties: - Handler: HelloWorld::HelloWorld.Function::Handler - CodeUri: ./artifacts/HelloWorld.zip - Runtime: dotnetcore2.0 - -You should then build your ZIP file using your normal build process. - -You can generate a .NET Core example by using the ``sam init --runtime dotnetcore`` command. - -.. _IAMCreds - -IAM Credentials ---------------- - -SAM CLI will invoke functions with your locally configured IAM -credentials. - -As with the AWS CLI and SDKs, SAM CLI will look for credentials in the -following order: - -1. Environment Variables (``AWS_ACCESS_KEY_ID``, - ``AWS_SECRET_ACCESS_KEY``). -2. The AWS credentials file (located at ``~/.aws/credentials`` on Linux, - macOS, or Unix, or at ``C:\Users\USERNAME \.aws\credentials`` on - Windows). -3. Instance profile credentials (if running on Amazon EC2 with an - assigned instance role). - -In order to test API Gateway with a non-default profile from your AWS -credentials file append ``--profile `` to the -``start-api`` command: - -.. code:: bash - - // Test API Gateway locally with a credential profile. - $ sam local start-api --profile some_profile - -See this `Configuring the AWS -CLI `__ -for more details. - -Lambda Environment Variables ----------------------------- - -If your Lambda function uses environment variables, you can provide -values for them will passed to the Docker container. Here is how you -would do it: - -For example, consider the SAM template snippet: - -.. code:: yaml - - - Resources: - MyFunction1: - Type: AWS::Serverless::Function - Properties: - Handler: index.handler - Runtime: nodejs4.3 - Environment: - Variables: - TABLE_NAME: prodtable - BUCKET_NAME: prodbucket - - MyFunction2: - Type: AWS::Serverless::Function - Properties: - Handler: app.handler - Runtime: nodejs4.3 - Environment: - Variables: - STAGE: prod - TABLE_NAME: prodtable - - -Environment Variable file -------------------------- - -Use the ``--env-vars`` argument of the ``invoke`` or ``start-api`` commands -to provide a JSON file that contains values to override the environment -variables already defined in your function template. The file should be -structured as follows: - -.. code:: json - - { - "MyFunction1": { - "TABLE_NAME": "localtable", - "BUCKET_NAME": "testBucket" - }, - "MyFunction2": { - "TABLE_NAME": "localtable", - "STAGE": "dev" - }, - } - -.. code:: bash - - $ sam local start-api --env-vars env.json - - -Shell environment ------------------ - -Variables defined in your Shell’s environment will be passed to the -Docker container, if they map to a Variable in your Lambda function. -Shell variables are globally applicable to functions ie. If two -functions have a variable called ``TABLE_NAME``, then the value for -``TABLE_NAME`` provided through Shell’s environment will be availabe to -both functions. - -Following command will make value of ``mytable`` available to both -``MyFunction1`` and ``MyFunction2`` - -.. code:: bash - - $ TABLE_NAME=mytable sam local start-api - -Combination of Shell and Environment Variable file --------------------------------------------------- - -For greater control, you can use a combination shell variables and -external environment variable file. If a variable is defined in both -places, the one from the file will override the shell. Here is the order -of priority, highest to lowest. Higher priority ones will override the -lower. - -1. Environment Variable file -2. Shell’s environment -3. Hard-coded values from the template - -Identifying local execution from Lambda function code ------------------------------------------------------ - -When your Lambda function is invoked using SAM CLI, it sets an -environment variable ``AWS_SAM_LOCAL=true`` in the Docker container. -Your Lambda function can use this property to enable or disable -functionality that would not make sense in local development. For -example: Disable emitting metrics to CloudWatch (or) Enable verbose -logging etc. - -Static Assets -------------- - -Often, it’s useful to serve up static assets (e.g CSS/HTML/Javascript -etc) when developing a Serverless application. On AWS, this would -normally be done with CloudFront/S3. SAM CLI by default looks for a -``./public/`` directory in your SAM project directory and will serve up -all files from it at the root of the HTTP server when using -``sam local start-api``. You can override the default static asset -directory by using the ``-s`` or ``--static-dir`` command line flag. You -can also disable this behaviour completely by setting -``--static-dir ""``. - -Local Logging -------------- - -Both ``invoke`` and ``start-api`` command allow you to pipe logs from -the function’s invocation into a file. This will be useful if you are -running automated tests against SAM CLI and want to capture logs for -analysis. - -Example: - -.. code:: bash - - $ sam local invoke --log-file ./output.log - -Remote Docker -------------- - -Sam CLI loads function code by mounting filesystem to a Docker Volume. -As a result, The project directory must be pre-mounted on the remote -host where the Docker is running. - -If mounted, you can use the remote docker normally using -``--docker-volume-basedir`` or environment variable -``SAM_DOCKER_VOLUME_BASEDIR``. - -Example - Docker Toolbox (Windows): - -When you install and run Docker Toolbox, the Linux VM with Docker is -automatically installed in the virtual box. - -The /c/ path for this Linux VM is automatically shared with C: on the -host machine. - -.. code:: powershell - - $ sam local invoke --docker-volume-basedir /c/Users/shlee322/projects/test "Ratings" - -Learn More -========== - -- `Project Overview <../README.rst>`__ -- `Installation `__ -- `Getting started with SAM and the SAM CLI `__ -- `Usage `__ -- `Packaging and deploying your application `__ \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 74ad42a370..0000000000 --- a/docs/conf.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# SAM CLI documentation build configuration file, created by -# sphinx-quickstart on Fri Apr 27 12:48:05 2018. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'SAM CLI' -copyright = '2018, AWS SAM' -author = 'AWS SAM' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '' -# The full version, including alpha/beta/rc tags. -release = '' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'SAMCLIdoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'SAMCLI.tex', 'SAM CLI Documentation', - 'AWS SAM', 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'samcli', 'SAM CLI Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'SAMCLI', 'SAM CLI Documentation', - author, 'SAMCLI', 'One line description of project.', - 'Miscellaneous'), -] - - - diff --git a/docs/deploying_serverless_applications.md b/docs/deploying_serverless_applications.md new file mode 100644 index 0000000000..ca65d54a99 --- /dev/null +++ b/docs/deploying_serverless_applications.md @@ -0,0 +1,80 @@ +Once you have created a Lambda function and a template.yaml file, you +can use the AWS CLI to package and deploy your serverless application. + +Packaging and deploying your application +======================================== + +In order to complete the procedures below, you need to first complete +the following: + +> [Set up an AWS +> Account](https://docs.aws.amazon.com/lambda/latest/dg/setup.html). +> +> [Set up the AWS +> CLI](https://docs.aws.amazon.com/lambda/latest/dg/setup-awscli.html) . + +Packaging your application +========================== + +To package your application, create an Amazon S3 bucket that the package +command will use to upload your ZIP deployment package (if you haven\'t +specified one in your example.yaml file). You can use the following +command to create the Amazon S3 bucket: + +``` {.sourceCode .bash} +aws s3 mb s3://bucket-name --region +``` + +Next, open a command prompt and type the following: + +``` {.sourceCode .bash} +sam package \ + --template-file path/template.yaml \ + --output-template-file serverless-output.yaml \ + --s3-bucket s3-bucket-name +``` + +The package command returns an AWS SAM template named +serverless-output.yaml that contains the CodeUri that points to the +deployment zip in the Amazon S3 bucket that you specified. This template +represents your serverless application. You are now ready to deploy it. + +Deploying your application +========================== + +To deploy the application, run the following command: + +``` {.sourceCode .bash} +sam deploy \ + --template-file serverless-output.yaml \ + --stack-name new-stack-name \ + --capabilities CAPABILITY_IAM +``` + +Note that the value you specify for the \--template-file parameter is +the name of the SAM template that was returned by the package command. +In addition, the `--capabilities` parameter is optional. The +AWS::Serverless::Function resource will implicitly create a role to +execute the Lambda function if one is not specified in the template. You +use the `--capabilities` parameter to explicitly acknowledge that AWS +CloudFormation is allowed to create roles on your behalf. + +When you run the aws sam deploy command, it creates an AWS +CloudFormation ChangeSet, which is a list of changes to the AWS +CloudFormation stack, and then deploys it. Some stack templates might +include resources that can affect permissions in your AWS account, for +example, by creating new AWS Identity and Access Management (IAM) users. +For those stacks, you must explicitly acknowledge their capabilities by +specifying the `--capabilities` parameter. + +To verify your results, open the AWS CloudFormation console to view the +newly created AWS CloudFormation stack and the Lambda console to view +your function. + +Learn More +---------- + +- [Project Overview](../README.md) +- [Getting started with SAM and the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) +- [Usage](usage.md) +- [Advanced](advanced_usage.md) diff --git a/docs/deploying_serverless_applications.rst b/docs/deploying_serverless_applications.rst deleted file mode 100644 index 21388654f9..0000000000 --- a/docs/deploying_serverless_applications.rst +++ /dev/null @@ -1,56 +0,0 @@ -Once you have created a Lambda function and a template.yaml file, you can use the AWS CLI to package and deploy your serverless application. - -Packaging and deploying your application -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to complete the procedures below, you need to first complete the following: - - `Set up an AWS Account `__. - - `Set up the AWS CLI `__ . - -Packaging your application -~~~~~~~~~~~~~~~~~~~~~~~~~~ -To package your application, create an Amazon S3 bucket that the package command will use to upload your ZIP deployment package (if you haven't specified one in your example.yaml file). You can use the following command to create the Amazon S3 bucket: - -.. code:: bash - - aws s3 mb s3://bucket-name --region - -Next, open a command prompt and type the following: - -.. code:: bash - - sam package \ - --template-file path/template.yaml \ - --output-template-file serverless-output.yaml \ - --s3-bucket s3-bucket-name - -The package command returns an AWS SAM template named serverless-output.yaml that contains the CodeUri that points to the deployment zip in the Amazon S3 bucket that you specified. This template represents your serverless application. You are now ready to deploy it. - -Deploying your application -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To deploy the application, run the following command: - -.. code:: bash - - sam deploy \ - --template-file serverless-output.yaml \ - --stack-name new-stack-name \ - --capabilities CAPABILITY_IAM - -Note that the value you specify for the --template-file parameter is the name of the SAM template that was returned by the package command. In addition, the --capabilities parameter is optional. The AWS::Serverless::Function resource will implicitly create a role to execute the Lambda function if one is not specified in the template. You use the --capabilities parameter to explicitly acknowledge that AWS CloudFormation is allowed to create roles on your behalf. - -When you run the aws sam deploy command, it creates an AWS CloudFormation ChangeSet, which is a list of changes to the AWS CloudFormation stack, and then deploys it. Some stack templates might include resources that can affect permissions in your AWS account, for example, by creating new AWS Identity and Access Management (IAM) users. For those stacks, you must explicitly acknowledge their capabilities by specifying the --capabilities parameter. - -To verify your results, open the AWS CloudFormation console to view the newly created AWS CloudFormation stack and the Lambda console to view your function. - -Learn More -========== - -- `Project Overview <../README.rst>`__ -- `Installation `__ -- `Getting started with SAM and the SAM CLI `__ -- `Usage `__ -- `Advanced `__ diff --git a/docs/getting_started.rst b/docs/getting_started.rst deleted file mode 100644 index 1631e78a51..0000000000 --- a/docs/getting_started.rst +++ /dev/null @@ -1,66 +0,0 @@ -Getting started with SAM and the SAM CLI -======================================== -The following sections introduce the Serverless Application Model (SAM) and the the tools available to implement it (SAM CLI): - -What is SAM -=========== -The AWS Serverless Application Model (AWS SAM) is a model to define serverless applications. AWS SAM is natively supported by AWS CloudFormation and defines simplified syntax for expressing serverless resources. The specification currently covers APIs, Lambda functions and Amazon DynamoDB tables. SAM is available under Apache 2.0 for AWS partners and customers to adopt and extend within their own toolsets. - -What is SAM CLI -=============== -Based on AWS SAM, SAM CLI is an AWS CLI tool that provides an environment for you to develop, test, and analyze your serverless applications locally before uploading them to the Lambda runtime. Whether you're developing on Linux, Mac, or Microsoft Windows, you can use SAM CLI to create a local testing environment that simulates the AWS runtime environment. The SAM CLI also allows faster, iterative development of your Lambda function code because there is no need to redeploy your application package to the AWS Lambda runtime. - - -Installing Docker -~~~~~~~~~~~~~~~~~ - -To use SAM CLI, you first need to install Docker, an open-source software container platform that allows you to build, manage and test applications, whether you're running on Linux, Mac or Windows. For more information and download instructions, see `Docker `__. - -Once you have Docker installed, SAM CLI automatically provides a customized Docker image called docker-lambda. This image is designed specifically by an AWS partner to simulate the live AWS Lambda execution environment. This environment includes installed software, libraries, security permissions, environment variables, and other features outlined at Lambda Execution Environment and Available Libraries. - -Using docker-lambda, you can invoke your Lambda function locally. In this environment, your serverless applications execute and perform much as in the AWS Lambda runtime, without your having to redeploy the runtime. Their execution and performance in this environment reflect such considerations as timeouts and memory use. - - Important - - Because this is a simulated environment, there is no guarantee that your local testing results will exactly match those in the actual AWS runtime. -For more information, see `docker-lambda `__. - -Installing SAM CLI -~~~~~~~~~~~~~~~~~~ - -The easiest way to install SAM CLI is to use `pip `__. - -.. code:: bash - - pip install aws-sam-cli - -Then verify that the installation succeeded. - -.. code:: bash - - sam --version - -If pip doesn't work for you, you can download the latest binary and start using SAM CLI immediately. You can find the binaries under the Releases section in the `SAM CLI GitHub Repository `__. - -Creating a hello-world app (init) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To get started with a project in SAM, you can use the 'sam init' command provided by the SAM CLI to get a fully deployable boilerplate serverless application in any of the supported runtimes. SAM init provides a quick way for customers to get started with creating a Lambda-based application and allow them to grow their idea into a production application by using other commands in the SAM CLI. - -To use 'sam init', nagivate to a directory where where you want the serverless application to be created. Using the SAM CLI, run the following command (using the runtime of your choice. The following example uses Python for demonstration purposes.): - -.. code:: - - $ sam init --runtime python - [+] Initializing project structure... - [SUCCESS] - Read sam-app/README.md for further instructions on how to proceed - [*] Project initialization is now complete -This will create a folder in the current directory titled sam-app. This folder will contain an `AWS SAM template `__, along with your function code file and a README file that provides further guidance on how to proceed with your SAM application. The SAM template defines the AWS Resources that your application will need to run in the AWS Cloud. - -Learn More -========== - -- `Project Overview <../README.rst>`__ -- `Installation `__ -- `Usage `__ -- `Packaging and deploying your application `__ -- `Advanced `__ \ No newline at end of file diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index e22dcc6906..0000000000 --- a/docs/installation.rst +++ /dev/null @@ -1,15 +0,0 @@ -============== -Installation -============== - -Installation Instructions for AWS SAM CLI are linked below. - -- `Homebrew for Mac`_ -- `Linuxbrew for Linux`_ -- `MSI for Windows`_ -- `Pip`_ - -.. _Pip: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-using-pip.html -.. _MSI for Windows: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-windows.html -.. _Homebrew for Mac: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-mac.html -.. _Linuxbrew for Linux: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 95f33dc2ee..0000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=SAMCLI - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/running_and_debugging_serverless_applications_locally.md b/docs/running_and_debugging_serverless_applications_locally.md new file mode 100644 index 0000000000..099cb12ccc --- /dev/null +++ b/docs/running_and_debugging_serverless_applications_locally.md @@ -0,0 +1,142 @@ +Running and debugging serverless applications locally +===================================================== + +SAM CLI works with AWS SAM, allowing you to invoke functions defined in +SAM templates, whether directly or through API Gateway endpoints. By +using SAM CLI, you can analyze your SAM application\'s performance in +your own testing environment and update accordingly. + +Invoking Lambda functions locally +--------------------------------- + +``` {.sourceCode .bash} +# Invoking function with event file + +$ echo '{"message": "Hey, are you there?" }' | sam local invoke "Ratings" + +# For more options + +$ sam local invoke --help +``` + +Running API Gateway Locally +--------------------------- + +start-api: Creates a local HTTP server hosting all of your Lambda +functions. When accessed by using a browser or the CLI, this operation +launches a Docker container locally to invoke your function. It reads +the CodeUri property of the AWS::Serverless::Function resource to find +the path in your file system containing the Lambda function code. This +path can be the project's root directory for interpreted languages like +Node.js or Python, a build directory that stores your compiled +artifacts, or for Java, a .jar file. + +If you use an interpreted language, local changes are made available +without rebuilding. This approach means you can reinvoke your Lambda +function with no need to restart the CLI. + +invoke: Invokes a local Lambda function once and terminates after +invocation completes. + +``` {.sourceCode .} +$ sam local start-api + +2018-05-08 08:48:38 Mounting HelloWorld at http://127.0.0.1:3000/ [GET] +2018-05-08 08:48:38 Mounting HelloWorld at http://127.0.0.1:3000/thumbnail [GET] +2018-05-08 08:48:38 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template +2018-05-08 08:48:38 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) +``` + +Debugging With SAM CLI +---------------------- + +Both sam local invoke and sam local start-api support local debugging of +your functions. To run SAM CLI with debugging support enabled, specify +\--debug-port or -d on the command line. + +``` {.sourceCode .bash} +# Invoke a function locally in debug mode on port 5858 + +$ sam local invoke -d 5858 function logical id + +# Start local API Gateway in debug mode on port 5858 + +$ sam local start-api -d 5858 +``` + +If you use sam local start-api, the local API Gateway exposes all of +your Lambda functions. But because you can specify only one debug port, +you can only debug one function at a time. + +Connecting a Debugger to your IDE +--------------------------------- + +For compiled languages or projects requiring complex packing support, we +recommend that you run your own build solution and point AWS SAM to the +directory that contains the build dependency files needed. You can use +the following IDEs or one of your choosing. + +- Cloud9 +- Eclipse +- Visual Studio Code + +Integrating other services +-------------------------- + +You can use the AWS Serverless Application Model to integrate other +services as event sources to your application. For example, assume you +have an application that requires a Dynamo DB table. The following shows +an example: + +``` {.sourceCode .yaml} +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + ProcessDynamoDBStream: + Type: AWS::Serverless::Function + Properties: + Handler: handler + Runtime: runtime + Policies: AWSLambdaDynamoDBExecutionRole + Events: + Stream: + Type: DynamoDB + Properties: + Stream: !GetAtt DynamoDBTable.StreamArn + BatchSize: 100 + StartingPosition: TRIM_HORIZON + + DynamoDBTable: + Type: AWS::DynamoDB::Table + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 5 + WriteCapacityUnits: 5 + StreamSpecification: + StreamViewType: NEW_IMAGE +``` + +Validate your SAM template +-------------------------- + +You can use SAM CLI to validate your template against the official AWS +Serverless Application Model specification. The following is an example +if you specify either an unsupported runtime or deprecated runtime +version. + +``` {.sourceCode .} +$ sam validate + +Error: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [SkillFunction] is invalid. property Runtim not defined for resource of type AWS::Serverless::Function + +$ sed -i 's/Runtim/Runtime/g` template.yaml + +$ sam validate +template.yaml is a valid SAM Template +``` diff --git a/docs/running_and_debugging_serverless_applications_locally.rst b/docs/running_and_debugging_serverless_applications_locally.rst deleted file mode 100644 index 7fa47250c3..0000000000 --- a/docs/running_and_debugging_serverless_applications_locally.rst +++ /dev/null @@ -1,113 +0,0 @@ -Running and debugging serverless applications locally -===================================================== -SAM CLI works with AWS SAM, allowing you to invoke functions defined in SAM templates, whether directly or through API Gateway endpoints. By using SAM CLI, you can analyze your SAM application's performance in your own testing environment and update accordingly. - -Invoking Lambda functions locally -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: bash - - # Invoking function with event file - - $ echo '{"message": "Hey, are you there?" }' | sam local invoke "Ratings" - - # For more options - - $ sam local invoke --help - - -Running API Gateway Locally -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -start-api: Creates a local HTTP server hosting all of your Lambda functions. When accessed by using a browser or the CLI, this operation launches a Docker container locally to invoke your function. It reads the CodeUri property of the AWS::Serverless::Function resource to find the path in your file system containing the Lambda function code. This path can be the project's root directory for interpreted languages like Node.js or Python, a build directory that stores your compiled artifacts, or for Java, a .jar file. - -If you use an interpreted language, local changes are made available without rebuilding. This approach means you can reinvoke your Lambda function with no need to restart the CLI. - -invoke: Invokes a local Lambda function once and terminates after invocation completes. - -.. code:: - - $ sam local start-api - - 2018-05-08 08:48:38 Mounting HelloWorld at http://127.0.0.1:3000/ [GET] - 2018-05-08 08:48:38 Mounting HelloWorld at http://127.0.0.1:3000/thumbnail [GET] - 2018-05-08 08:48:38 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template - 2018-05-08 08:48:38 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) - -Debugging With SAM CLI -~~~~~~~~~~~~~~~~~~~~~~ - -Both sam local invoke and sam local start-api support local debugging of your functions. To run SAM CLI with debugging support enabled, specify --debug-port or -d on the command line. - -.. code:: bash - - # Invoke a function locally in debug mode on port 5858 - - $ sam local invoke -d 5858 function logical id - - # Start local API Gateway in debug mode on port 5858 - - $ sam local start-api -d 5858 - -If you use sam local start-api, the local API Gateway exposes all of your Lambda functions. But because you can specify only one debug port, you can only debug one function at a time. - -Connecting a Debugger to your IDE -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For compiled languages or projects requiring complex packing support, we recommend that you run your own build solution and point AWS SAM to the directory that contains the build dependency files needed. You can use the following IDEs or one of your choosing. - -- Cloud9 -- Eclipse -- Visual Studio Code - -Integrating other services -~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can use the AWS Serverless Application Model to integrate other services as event sources to your application. For example, assume you have an application that requires a Dynamo DB table. The following shows an example: - -.. code:: yaml - - AWSTemplateFormatVersion: '2010-09-09' - Transform: AWS::Serverless-2016-10-31 - Resources: - ProcessDynamoDBStream: - Type: AWS::Serverless::Function - Properties: - Handler: handler - Runtime: runtime - Policies: AWSLambdaDynamoDBExecutionRole - Events: - Stream: - Type: DynamoDB - Properties: - Stream: !GetAtt DynamoDBTable.StreamArn - BatchSize: 100 - StartingPosition: TRIM_HORIZON - - DynamoDBTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: id - AttributeType: S - KeySchema: - - AttributeName: id - KeyType: HASH - ProvisionedThroughput: - ReadCapacityUnits: 5 - WriteCapacityUnits: 5 - StreamSpecification: - StreamViewType: NEW_IMAGE - -Validate your SAM template -~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can use SAM CLI to validate your template against the official AWS Serverless Application Model specification. The following is an example if you specify either an unsupported runtime or deprecated runtime version. - -.. code:: - - $ sam validate - - Error: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [SkillFunction] is invalid. property Runtim not defined for resource of type AWS::Serverless::Function - - $ sed -i 's/Runtim/Runtime/g` template.yaml - - $ sam validate - template.yaml is a valid SAM Template - diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000000..8fb6b5fecd --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,747 @@ +Usage +===== + +**Create a sample app with sam init command**: `sam init` or +`sam init --runtime ` + +`sam` requires a SAM template in order to know how to invoke your +function locally, and it's also true for spawning API Gateway locally +-If no template is specified `template.yaml` will be used instead. + +Alternatively, you can find other sample SAM Templates by visiting +[SAM](https://github.com/awslabs/serverless-application-model) official +repository. + +- [Invoke functions locally](#invoke-functions-locally) +- [Run automated tests for your Lambda functions + locally](#run-automated-tests-for-your-lambda-functions-locally) +- [Generate sample event source + payloads](#generate-sample-event-payloads) +- [Run API Gateway locally](#run-api-gateway-locally) +- [Debugging Applications](#debugging-applications) + - [Debugging Python functions](#debugging-python-functions) +- [Fetch, tail, and filter Lambda function + logs](#fetch-tail-and-filter-lambda-function-logs) +- [Validate SAM templates](#validate-sam-templates) +- [Package and Deploy to Lambda](#package-and-deploy-to-lambda) + +Invoke functions locally +------------------------ + +![SAM CLI Invoke Sample](../media/sam-invoke.gif) + +You can invoke your function locally by passing its \--SAM logical +ID\--and an event file. Alternatively, `sam local invoke` accepts stdin +as an event too. + +``` {.sourceCode .yaml} +Resources: + Ratings: # <-- Logical ID + Type: 'AWS::Serverless::Function' + ... +``` + +**Syntax** + +``` {.sourceCode .bash} +# Invoking function with event file +$ sam local invoke "Ratings" -e event.json + +# Invoking function with event via stdin +$ echo '{"message": "Hey, are you there?" }' | sam local invoke "Ratings" + +# For more options +$ sam local invoke --help +``` + +Run automated tests for your Lambda functions locally +----------------------------------------------------- + +You can use the `sam local invoke` command to manually test your code by +running Lambda function locally. With SAM CLI, you can easily author +automated integration tests by first running tests against local Lambda +functions before deploying to the cloud. The `sam local start-lambda` +command starts a local endpoint that emulates the AWS Lambda service's +invoke endpoint, and you can invoke it from your automated tests. +Because this endpoint emulates the Lambda service\'s invoke endpoint, +you can write tests once and run them (without any modifications) +against the local Lambda function or against a deployed Lambda function. +You can also run the same tests against a deployed SAM stack in your +CI/CD pipeline. + +Here is how this works: + +**1. Start the Local Lambda Endpoint** + +Start the local Lambda endpoint by running the following command in the +directory that contains your AWS SAM template: + +``` {.sourceCode .bash} +sam local start-lambda +``` + +This command starts a local endpoint at that +emulates the AWS Lambda service, and you can run your automated tests +against this local Lambda endpoint. When you send an invoke to this +endpoint using the AWS CLI or SDK, it will locally execute the Lambda +function specified in the request and return a response. + +**2. Run integration test against local Lambda endpoint** + +In your integration test, you can use AWS SDK to invoke your Lambda +function with test data, wait for response, and assert that the response +what you expect. To run the integration test locally, you should +configure AWS SDK to send Lambda Invoke API call to local Lambda +endpoint started in previous step. + +Here is an Python example (AWS SDK for other languages have similar +configurations): + +``` {.sourceCode .python} +import boto3 +import botocore + +# Set "running_locally" flag if you are running the integration test locally +running_locally = True + +if running_locally: + + # Create Lambda SDK client to connect to appropriate Lambda endpoint + lambda_client = boto3.client('lambda', + region_name="us-west-2", + endpoint_url="http://127.0.0.1:3001", + use_ssl=False, + verify=False, + config=botocore.client.Config( + signature_version=botocore.UNSIGNED, + read_timeout=0, + retries={'max_attempts': 0}, + ) + ) +else: + lambda_client = boto3.client('lambda') + + +# Invoke your Lambda function as you normally usually do. The function will run +# locally if it is configured to do so +response = lambda_client.invoke(FunctionName="HelloWorldFunction") + +# Verify the response +assert response == "Hello World" +``` + +This code can run without modifications against a Lambda function which +is deployed. To do so, set the `running_locally` flag to `False` . This +will setup AWS SDK to connect to AWS Lambda service on the cloud. + +Connecting to docker network +---------------------------- + +Both `sam local invoke` and `sam local start-api` support connecting the +create lambda docker containers to an existing docker network. + +To connect the containers to an existing docker network, you can use the +`--docker-network` command-line argument or the `SAM_DOCKER_NETWORK` +environment variable along with the name or id of the docker network you +wish to connect to. + +``` {.sourceCode .bash} +# Invoke a function locally and connect to a docker network +$ sam local invoke --docker-network my-custom-network + +# Start local API Gateway and connect all containers to a docker network +$ sam local start-api --docker-network b91847306671 -d 5858 +``` + +Generate sample event payloads +------------------------------ + +To make local development and testing of Lambda functions easier, you +can generate and customize event payloads for the following services: + +- Amazon Alexa +- Amazon API Gateway +- AWS Batch +- AWS CloudFormation +- Amazon CloudFront +- AWS CodeCommit +- AWS CodePipeline +- Amazon Cognito +- AWS Config +- Amazon DynamoDB +- Amazon Kinesis +- Amazon Lex +- Amazon Rekognition +- Amazon S3 +- Amazon SES +- Amazon SNS +- Amazon SQS +- AWS Step Functions + +**Syntax** + +``` {.sourceCode .bash} +$ sam local generate-event +``` + +You can generate multiple types of events from each service. For +example, to generate the event from S3 when a new object is created, +use: + +``` {.sourceCode .bash} +$ sam local generate-event s3 put +``` + +To generate the event from S3 when an object is deleted, you can use: + +``` {.sourceCode .bash} +$ sam local generate-event s3 delete +``` + +For more options, see `sam local generate-event --help`. + +Run API Gateway locally +----------------------- + +`sam local start-api` spawns a local API Gateway to test HTTP +request/response functionality. Features hot-reloading to allow you to +quickly develop and iterate over your functions. + +![SAM CLI Start API](../media/sam-start-api.gif) + +**Syntax** + +``` {.sourceCode .bash} +$ sam local start-api +``` + +`sam` will automatically find any functions within your SAM template +that have `Api` event sources defined, and mount them at the defined +HTTP paths. + +In the example below, the `Ratings` function would mount +`ratings.py:handler()` at `/ratings` for `GET` requests. + +``` {.sourceCode .yaml} +Ratings: + Type: AWS::Serverless::Function + Properties: + Handler: ratings.handler + Runtime: python3.6 + Events: + Api: + Type: Api + Properties: + Path: /ratings + Method: get +``` + +By default, SAM uses [Proxy +Integration](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html) +and expects the response from your Lambda function to include one or +more of the following: `statusCode`, `headers` and/or `body`. + +For example: + +``` {.sourceCode .javascript} +// Example of a Proxy Integration response +exports.handler = (event, context, callback) => { + callback(null, { + statusCode: 200, + headers: { "x-custom-header" : "my custom header value" }, + body: "hello world" + }); +} +``` + +For examples in other AWS Lambda languages, see [this +page](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). + +If your function does not return a valid [Proxy +Integration](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html) +response then you will get a HTTP 500 (Internal Server Error) when +accessing your function. SAM CLI will also print the following error log +message to help you diagnose the problem: + + ERROR: Function ExampleFunction returned an invalid response (must include one of: body, headers or statusCode in the response object) + +Debugging Applications +---------------------- + +Both `sam local invoke` and `sam local start-api` support local +debugging of your functions. + +To run SAM Local with debugging support enabled, just specify +`--debug-port` or `-d` on the command line. SAM CLI debug port option +`--debug-port` or `-d` will map that port to the local Lambda container +execution your IDE needs to connect to. + +``` {.sourceCode .bash} +# Invoke a function locally in debug mode on port 5858 +$ sam local invoke -d 5858 + +# Start local API Gateway in debug mode on port 5858 +$ sam local start-api -d 5858 +``` + +Note: If using `sam local start-api`, the local API Gateway will expose +all of your Lambda functions but, since you can specify a single debug +port, you can only debug one function at a time. You will need to hit +your API before SAM CLI binds to the port allowing the debugger to +connect. + +Here is an example showing how to debug a NodeJS function with Microsoft +Visual Studio Code: + +![SAM Local debugging example](../media/sam-debug.gif) + +In order to setup Visual Studio Code for debugging with AWS SAM CLI, use +the following launch configuration after setting directory where the +template.yaml is present as workspace root in Visual Studio Code: + +``` {.sourceCode .json} +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to SAM CLI", + "type": "node", + "request": "attach", + "address": "localhost", + "port": 5858, + // From the sam init example, it would be "${workspaceRoot}/hello_world" + "localRoot": "${workspaceRoot}/{directory of node app}", + "remoteRoot": "/var/task", + "protocol": "inspector", + "stopOnEntry": false + } + ] + } +``` + +Note: localRoot is set based on what the CodeUri points at +template.yaml, if there are nested directories within the CodeUri, that +needs to be reflected in localRoot. + +Note: Node.js versions \--below\-- 7 (e.g. Node.js 4.3 and Node.js 6.10) +use the `legacy` protocol, while Node.js versions including and above 7 +(e.g. Node.js 8.10) use the `inspector` protocol. Be sure to specify the +corresponding protocol in the `protocol` entry of your launch +configuration. This was tested with VS code version 1.26, 1.27 and 1.28 +for `legacy` and `inspector` protocol. + +Debugging Python functions +-------------------------- + +Python debugging requires you to enable remote debugging in your Lambda +function code, therefore it\'s a 2-step process: + +1. Install [ptvsd](https://pypi.org/project/ptvsd/) library and enable + within your code +2. Configure your IDE to connect to the debugger you configured for + your function + +As this may be your first time using SAM CLI, let\'s start with a +boilerplate Python app and install both app\'s dependencies and ptvsd: + +``` {.sourceCode .bash} +sam init --runtime python3.6 --name python-debugging +cd python-debugging/ + +# Install dependencies of our boilerplate app +pip install -r requirements.txt -t hello_world/build/ + +# Install ptvsd library for step through debugging +pip install ptvsd -t hello_world/build/ + +cp hello_world/app.py hello_world/build/ +``` + +### Ptvsd configuration + +As we installed ptvsd library in the previous step, we need to enable +ptvsd within our code, therefore open up `hello_world/build/app.py` and +add the following ptvsd specifics. + +``` {.sourceCode .python} +import ptvsd + +# Enable ptvsd on 0.0.0.0 address and on port 5890 that we'll connect later with our IDE +ptvsd.enable_attach(address=('0.0.0.0', 5890), redirect_output=True) +ptvsd.wait_for_attach() +``` + +**0.0.0.0** instead of **localhost** for listening across all network +interfaces and **5890** is the debugging port of your preference. + +### Visual Studio Code + +Now that we have both dependencies and ptvsd enabled within our code we +configure Visual Studio Code (VS Code) Debugging - Assuming you\'re +still in the application folder and have code command in your path, +let\'s open up VS Code: + +``` {.sourceCode .bash} +code . +``` + +`NOTE`: If you don\'t have code in your Path, please open up a new +instance of VS Code from `python-debugging/` folder we created earlier. + +In order to setup VS Code for debugging with AWS SAM CLI, use the +following launch configuration: + +``` {.sourceCode .json} +{ + "version": "0.2.0", + "configurations": [ + { + "name": "SAM CLI Python Hello World", + "type": "python", + "request": "attach", + "port": 5890, + "host": "localhost", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}/hello_world/build", + "remoteRoot": "/var/task" + } + ] + } + ] + } +``` + +For VS Code, the property **localRoot** under **pathMappings** key is +really important and there are 2 aspects you should know as to why this +is setup this way: + +1. **localRoot**: This path will be mounted in the Docker Container and + needs to have both application and dependencies at the root level +2. **workspaceFolder**: This path is the absolute path where VS Code + instance was opened + +If you opened VS Code in a different location other than +`python-debugging/` you need to replace it with the absolute path where +`python-debugging/` is. + +Once complete with VS Code Debugger configuration, make sure to add a +breakpoint anywhere you like in `hello_world/build/app.py` and then +proceed as follows: + +1. Run SAM CLI to invoke your function +2. Hit the URL to invoke the function and initialize ptvsd code + execution +3. Start the debugger within VS Code + +``` {.sourceCode .bash} +# Remember to hit the URL before starting the debugger in VS Code + +sam local start-api -d 5890 + +# OR + +# Change HelloWorldFunction to reflect the logical name found in template.yaml + +sam local generate-event apigateway aws-proxy | sam local invoke HelloWorldFunction -d 5890 +``` + +Debugging Golang functions +-------------------------- + +Golang function debugging is slightly different when compared to +Node.JS, Java, and Python. We require +[delve](https://github.com/derekparker/delve) as the debugger, and wrap +your function with it at runtime. The debugger is run in headless mode, +listening on the debug port. + +When debugging, you must compile your function in debug mode: + +`GOARCH=amd64 GOOS=linux go build -gcflags='-N -l' -o ` + +You must compile [delve]{.title-ref} to run in the container and provide +its local path via the [\--debugger-path]{.title-ref} argument. Build +delve locally as follows: + +`GOARCH=amd64 GOOS=linux go build -o /dlv github.com/derekparker/delve/cmd/dlv` + +NOTE: The output path needs to end in [/dlv]{.title-ref}. The docker +container will expect the dlv binary to be in the \ +and will cause mounting issue otherwise. + +Then invoke [sam]{.title-ref} similar to the following: + +`sam local start-api -d 5986 --debugger-path ` + +NOTE: The `--debugger-path` is the path to the directory that contains +the [dlv]{.title-ref} binary compiled from the above. + +The following is an example launch configuration for Visual Studio Code +to attach to a debug session. + +``` {.sourceCode .json} +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Connect to Lambda container", + "type": "go", + "request": "launch", + "mode": "remote", + "remotePath": "", + "port": , + "host": "127.0.0.1", + "program": "${workspaceRoot}", + "env": {}, + "args": [], + }, + ] +} +``` + +Debugging .NET Core 2.1 / 2.0 Functions +--------------------------------------- + +.NET Core function debugging is similiar to golang function debugging +and requires you to have `vsdbg` available on your machine to later +provide it to SAM CLI. VS Code will launch debugger inside Lambda +container and talk to it using `pipeTransport` configuration. + +When debugging, you must compile your function in debug mode: + +Either locally using .NET SDK + +``` {.sourceCode .bash} +dotnet publish -c Debug -o +``` + +Or via Docker + +``` {.sourceCode .bash} +docker run --rm --mount type=bind,src=$PWD,dst=/var/task lambci/lambda:build-dotnetcore dotnet publish -c Debug -o +``` + +**NOTE: both of these commands should be run from the directory with +.csproj file** + +You must get `vsdbg` built for AWS Lambda runtime container on your host +machine and provide its local path via the `--debugger-path` argument. +Get compatible debugger version as follows: + +``` {.sourceCode .bash} +# Create directory to store vsdbg +mkdir + +# Get and install vsdbg on runtime container. Mounted folder will let you have it under on your machine too +docker run --rm --mount type=bind,src=,dst=/vsdbg --entrypoint bash lambci/lambda:dotnetcore2.0 -c "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg" +``` + +Then invoke `sam` similar to the following: + +`sam local start-api -d --debugger-path ` + +NOTE: The `--debugger-path` is the path to the directory that contains +the `vsdbg` binary installed from the above. + +The following is an example launch configuration for Visual Studio Code +to attach to a debug session. + +``` {.sourceCode .json} +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Docker Attach", + "type": "coreclr", + "request": "attach", + "processId": "1", + + "pipeTransport": { + "pipeProgram": "sh", + "pipeArgs": [ + "-c", + "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" + ], + "debuggerPath": "/tmp/lambci_debug_files/vsdbg", + "pipeCwd": "${workspaceFolder}", + }, + + "windows": { + "pipeTransport": { + "pipeProgram": "powershell", + "pipeArgs": [ + "-c", + "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" + ], + "debuggerPath": "/tmp/lambci_debug_files/vsdbg", + "pipeCwd": "${workspaceFolder}", + } + }, + + "sourceFileMap": { + "/var/task": "${workspaceFolder}" + } + } + ] +} +``` + +Passing Additional Runtime Debug Arguments +------------------------------------------ + +To pass additional runtime arguments when debugging your function, use +the environment variable `DEBUGGER_ARGS`. This will pass a string of +arguments directly into the run command SAM CLI uses to start your +function. + +For example, if you want to load a debugger like iKPdb at runtime of +your Python function, you could pass the following as `DEBUGGER_ARGS`: +`-m ikpdb --ikpdb-port=5858 --ikpdb-working-directory=/var/task/ --ikpdb-client-working-directory=/myApp --ikpdb-address=0.0.0.0`. +This would load iKPdb at runtime with the other arguments you've +specified. In this case, your full SAM CLI command would be: + +``` {.sourceCode .bash} +$ DEBUGGER_ARGS="-m ikpdb --ikpdb-port=5858 --ikpdb-working-directory=/var/task/ --ikpdb-client-working-directory=/myApp --ikpdb-address=0.0.0.0" echo {} | sam local invoke -d 5858 myFunction +``` + +You may pass debugger arguments to functions of all runtimes. + +To simplify troubleshooting, we added a new command called `sam logs` to +SAM CLI. `sam logs` lets you fetch logs generated by your Lambda +function from the command line. In addition to printing the logs on the +terminal, this command has several nifty features to help you quickly +find the bug. Note: This command works for all AWS Lambda functions; not +just the ones you deploy using SAM. + +Fetch, tail, and filter Lambda function logs +-------------------------------------------- + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. +`sam logs` lets you fetch logs generated by your Lambda function from +the command line. In addition to printing the logs on the terminal, this +command has several nifty features to help you quickly find the bug. + +Note: This command works for all AWS Lambda functions; not just the ones +you deploy using SAM. + +**Basic Usage: Using CloudFormation Stack** + +When your function is a part of a CloudFormation stack, you can fetch +logs using the function\'s LogicalID: + +``` {.sourceCode .bash} +sam logs -n HelloWorldFunction --stack-name mystack +``` + +**Basic Usage: Using Lambda Function name** + +Or, you can fetch logs using the function\'s name + +``` {.sourceCode .bash} +sam logs -n mystack-HelloWorldFunction-1FJ8PD +``` + +**Tail Logs** + +Add `--tail` option to wait for new logs and see them as they arrive. +This is very handy during deployment or when troubleshooting a +production issue. + +``` {.sourceCode .bash} +sam logs -n HelloWorldFunction --stack-name mystack --tail +``` + +**View logs for specific time range** You can view logs for specific +time range using the `-s` and `-e` options + +``` {.sourceCode .bash} +sam logs -n HelloWorldFunction --stack-name mystack -s '10min ago' -e '2min ago' +``` + +**Filter Logs** + +Use the `--filter` option to quickly find logs that match terms, phrases +or values in your log events + +``` {.sourceCode .bash} +sam logs -n HelloWorldFunction --stack-name mystack --filter "error" +``` + +In the output, SAM CLI will underline all occurrences of the word +"error" so you can easily locate the filter keyword within the log +output. + +**Error Highlighting** + +When your Lambda function crashes or times out, SAM CLI will highlight +the timeout message in red. This will help you easily locate specific +executions that are timing out within a giant stream of log output. + +![](https://user-images.githubusercontent.com/22755571/42301038-3363a366-7fc8-11e8-9d0e-308b209cb92b.png) + +**JSON pretty printing** + +If your log messages print JSON strings, SAM CLI will automatically +pretty print the JSON to help you visually parse and understand the +JSON. + +![](https://user-images.githubusercontent.com/22755571/42301064-50c6cffa-7fc8-11e8-8f31-04ef117a9c5a.png) + +Validate SAM templates +---------------------- + +Validate your templates with `$ sam validate`. Currently this command +will validate that the template provided is valid JSON / YAML. As with +most SAM CLI commands, it will look for a `template.[yaml|yml]` file in +your current working directory by default. You can specify a different +template file/location with the `-t` or `--template` option. + +**Syntax** + +``` {.sourceCode .bash} +$ sam validate +/template.yml is a valid SAM Template +``` + +Note: The validate command requires AWS credentials to be configured. +See [IAM Credentials](#iam-credentials). + +Package and Deploy to Lambda +---------------------------- + +Once you have developed and tested your Serverless application locally, +you can deploy to Lambda using `sam package` and `sam deploy` command. + +`sam package` command will zip your code artifacts, upload to S3 and +produce a SAM file that is ready to be deployed to Lambda using AWS +CloudFormation. + +`sam deploy` command will deploy the packaged SAM template to +CloudFormation. + +Both `sam package` and `sam deploy` are identical to their AWS CLI +equivalents commands [aws cloudformation +package](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html) +and [aws cloudformation +deploy](http://docs.aws.amazon.com/cli/latest/reference/cloudformation/deploy/index.html) +respectively - Please consult the AWS CLI command documentation for +usage. + +Example: + +``` {.sourceCode .bash} +# Package SAM template +$ sam package --template-file sam.yaml --s3-bucket mybucket --output-template-file packaged.yaml + +# Deploy packaged SAM template +$ sam deploy --template-file ./packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM +``` + +Learn More +---------- + +- [Project Overview](../README.md) +- [Getting started with SAM and the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) +- [Packaging and deploying your + application](deploying_serverless_applications.md) +- [Advanced](advanced_usage.md) diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index ee12e26f24..0000000000 --- a/docs/usage.rst +++ /dev/null @@ -1,737 +0,0 @@ -===== -Usage -===== - -**Create a sample app with sam init command**: ``sam init`` or ``sam init --runtime `` - -``sam`` requires a SAM template in order to know how to invoke your -function locally, and it’s also true for spawning API Gateway locally - -If no template is specified ``template.yaml`` will be used instead. - -Alternatively, you can find other sample SAM Templates by visiting `SAM `__ official repository. - - -- `Invoke functions locally <#invoke-functions-locally>`__ -- `Run automated tests for your Lambda functions locally <#run-automated-tests-for-your-lambda-functions-locally>`__ -- `Generate sample event source - payloads <#generate-sample-event-payloads>`__ -- `Run API Gateway locally <#run-api-gateway-locally>`__ -- `Debugging Applications <#debugging-applications>`__ - - - `Debugging Python functions <#debugging-python-functions>`__ -- `Fetch, tail, and filter Lambda function logs <#fetch-tail-and-filter-lambda-function-logs>`__ -- `Validate SAM templates <#validate-sam-templates>`__ -- `Package and Deploy to - Lambda <#package-and-deploy-to-lambda>`__ - -Invoke functions locally ------------------------- - -.. figure:: ../media/sam-invoke.gif - :alt: SAM CLI Invoke Sample - - SAM CLI Invoke Sample - -You can invoke your function locally by passing its --SAM logical ID-- -and an event file. Alternatively, ``sam local invoke`` accepts stdin as -an event too. - -.. code:: yaml - - Resources: - Ratings: # <-- Logical ID - Type: 'AWS::Serverless::Function' - ... - -**Syntax** - -.. code:: bash - - # Invoking function with event file - $ sam local invoke "Ratings" -e event.json - - # Invoking function with event via stdin - $ echo '{"message": "Hey, are you there?" }' | sam local invoke "Ratings" - - # For more options - $ sam local invoke --help - - -Run automated tests for your Lambda functions locally ------------------------------------------------------ -You can use the ``sam local invoke`` command to manually test your code -by running Lambda function locally. With SAM CLI, you can easily -author automated integration tests by -first running tests against local Lambda functions before deploying to the -cloud. The ``sam local start-lambda`` command starts a local -endpoint that emulates the AWS Lambda service’s invoke endpoint, and you -can invoke it from your automated tests. Because this endpoint emulates -the Lambda service's invoke endpoint, you can write tests once and run -them (without any modifications) against the local Lambda function or -against a deployed Lambda function. You can also run the same tests -against a deployed SAM stack in your CI/CD pipeline. - -Here is how this works: - -**1. Start the Local Lambda Endpoint** - -Start the local Lambda endpoint by running the following command in the directory that contains your AWS -SAM template: - -.. code:: bash - - sam local start-lambda - -This command starts a local endpoint at http://127.0.0.1:3001 that -emulates the AWS Lambda service, and you can run your automated tests -against this local Lambda endpoint. When you send an invoke to this -endpoint using the AWS CLI or SDK, it will locally execute the Lambda -function specified in the request and return a response. - -**2. Run integration test against local Lambda endpoint** - -In your integration test, you can use AWS SDK to invoke your Lambda function -with test data, wait for response, and assert that the response what you -expect. To run the integration test locally, you should configure AWS -SDK to send Lambda Invoke API call to local Lambda endpoint started in -previous step. - -Here is an Python example (AWS SDK for other languages have similar -configurations): - -.. code:: python - - import boto3 - import botocore - - # Set "running_locally" flag if you are running the integration test locally - running_locally = True - - if running_locally: - - # Create Lambda SDK client to connect to appropriate Lambda endpoint - lambda_client = boto3.client('lambda', - region_name="us-west-2", - endpoint_url="http://127.0.0.1:3001", - use_ssl=False, - verify=False, - config=botocore.client.Config( - signature_version=botocore.UNSIGNED, - read_timeout=0, - retries={'max_attempts': 0}, - ) - ) - else: - lambda_client = boto3.client('lambda') - - - # Invoke your Lambda function as you normally usually do. The function will run - # locally if it is configured to do so - response = lambda_client.invoke(FunctionName="HelloWorldFunction") - - # Verify the response - assert response == "Hello World" - -This code can run without modifications against a Lambda function which -is deployed. To do so, set the ``running_locally`` flag to ``False`` . -This will setup AWS SDK to connect to AWS Lambda service on the cloud. - -Connecting to docker network ----------------------------- - -Both ``sam local invoke`` and ``sam local start-api`` support connecting -the create lambda docker containers to an existing docker network. - -To connect the containers to an existing docker network, you can use the -``--docker-network`` command-line argument or the ``SAM_DOCKER_NETWORK`` -environment variable along with the name or id of the docker network you -wish to connect to. - -.. code:: bash - - # Invoke a function locally and connect to a docker network - $ sam local invoke --docker-network my-custom-network - - # Start local API Gateway and connect all containers to a docker network - $ sam local start-api --docker-network b91847306671 -d 5858 - - -Generate sample event payloads ------------------------------- - -To make local development and testing of Lambda functions easier, you -can generate and customize event payloads for the following services: - -- Amazon Alexa -- Amazon API Gateway -- AWS Batch -- AWS CloudFormation -- Amazon CloudFront -- AWS CodeCommit -- AWS CodePipeline -- Amazon Cognito -- AWS Config -- Amazon DynamoDB -- Amazon Kinesis -- Amazon Lex -- Amazon Rekognition -- Amazon S3 -- Amazon SES -- Amazon SNS -- Amazon SQS -- AWS Step Functions - -**Syntax** - -.. code:: bash - - $ sam local generate-event - -You can generate multiple types of events from each service. For example, -to generate the event from S3 when a new object is created, use: - -.. code:: bash - - $ sam local generate-event s3 put - -To generate the event from S3 when an object is deleted, you can use: - -.. code:: bash - - $ sam local generate-event s3 delete - -For more options, see ``sam local generate-event --help``. - -Run API Gateway locally ------------------------ - -``sam local start-api`` spawns a local API Gateway to test HTTP -request/response functionality. Features hot-reloading to allow you to -quickly develop and iterate over your functions. - -.. figure:: ../media/sam-start-api.gif - :alt: SAM CLI Start API - - SAM CLI Start API - -**Syntax** - -.. code:: bash - - $ sam local start-api - -``sam`` will automatically find any functions within your SAM -template that have ``Api`` event sources defined, and mount them at the -defined HTTP paths. - -In the example below, the ``Ratings`` function would mount -``ratings.py:handler()`` at ``/ratings`` for ``GET`` requests. - -.. code:: yaml - - Ratings: - Type: AWS::Serverless::Function - Properties: - Handler: ratings.handler - Runtime: python3.6 - Events: - Api: - Type: Api - Properties: - Path: /ratings - Method: get - -By default, SAM uses `Proxy -Integration `__ -and expects the response from your Lambda function to include one or -more of the following: ``statusCode``, ``headers`` and/or ``body``. - -For example: - -.. code:: javascript - - // Example of a Proxy Integration response - exports.handler = (event, context, callback) => { - callback(null, { - statusCode: 200, - headers: { "x-custom-header" : "my custom header value" }, - body: "hello world" - }); - } - -For examples in other AWS Lambda languages, see `this -page `__. - -If your function does not return a valid `Proxy -Integration `__ -response then you will get a HTTP 500 (Internal Server Error) when -accessing your function. SAM CLI will also print the following error log -message to help you diagnose the problem: - -:: - - ERROR: Function ExampleFunction returned an invalid response (must include one of: body, headers or statusCode in the response object) - -Debugging Applications ----------------------- - -Both ``sam local invoke`` and ``sam local start-api`` support local -debugging of your functions. - -To run SAM Local with debugging support enabled, just specify -``--debug-port`` or ``-d`` on the command line. SAM CLI debug port option ``--debug-port`` or ``-d`` will map that port to the local Lambda container execution your IDE needs to connect to. - -.. code:: bash - - # Invoke a function locally in debug mode on port 5858 - $ sam local invoke -d 5858 - - # Start local API Gateway in debug mode on port 5858 - $ sam local start-api -d 5858 - - -Note: If using ``sam local start-api``, the local API Gateway will -expose all of your Lambda functions but, since you can specify a single -debug port, you can only debug one function at a time. You will need to -hit your API before SAM CLI binds to the port allowing the debugger to -connect. - -Here is an example showing how to debug a NodeJS function with Microsoft -Visual Studio Code: - -.. figure:: ../media/sam-debug.gif - :alt: SAM Local debugging example - - SAM Local debugging example - -In order to setup Visual Studio Code for debugging with AWS SAM CLI, use -the following launch configuration after setting directory where the template.yaml is present -as workspace root in Visual Studio Code: - -.. code:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": "Attach to SAM CLI", - "type": "node", - "request": "attach", - "address": "localhost", - "port": 5858, - // From the sam init example, it would be "${workspaceRoot}/hello_world" - "localRoot": "${workspaceRoot}/{directory of node app}", - "remoteRoot": "/var/task", - "protocol": "inspector", - "stopOnEntry": false - } - ] - } - -Note: localRoot is set based on what the CodeUri points at template.yaml, -if there are nested directories within the CodeUri, that needs to be -reflected in localRoot. - -Note: Node.js versions --below-- 7 (e.g. Node.js 4.3 and Node.js 6.10) -use the ``legacy`` protocol, while Node.js versions including and above -7 (e.g. Node.js 8.10) use the ``inspector`` protocol. Be sure to specify -the corresponding protocol in the ``protocol`` entry of your launch -configuration. This was tested with VS code version 1.26, 1.27 and 1.28 -for ``legacy`` and ``inspector`` protocol. - -Debugging Python functions --------------------------- - -Python debugging requires you to enable remote debugging in your Lambda function code, therefore it's a 2-step process: - -1. Install `ptvsd `__ library and enable within your code -2. Configure your IDE to connect to the debugger you configured for your function - -As this may be your first time using SAM CLI, let's start with a boilerplate Python app and install both app's dependencies and ptvsd: - -.. code:: bash - - sam init --runtime python3.6 --name python-debugging - cd python-debugging/ - - # Install dependencies of our boilerplate app - pip install -r requirements.txt -t hello_world/build/ - - # Install ptvsd library for step through debugging - pip install ptvsd -t hello_world/build/ - - cp hello_world/app.py hello_world/build/ - -Ptvsd configuration -^^^^^^^^^^^^^^^^^^^ - -As we installed ptvsd library in the previous step, we need to enable ptvsd within our code, therefore open up ``hello_world/build/app.py`` and add the following ptvsd specifics. - -.. code:: python - - import ptvsd - - # Enable ptvsd on 0.0.0.0 address and on port 5890 that we'll connect later with our IDE - ptvsd.enable_attach(address=('0.0.0.0', 5890), redirect_output=True) - ptvsd.wait_for_attach() - -**0.0.0.0** instead of **localhost** for listening across all network interfaces and **5890** is the debugging port of your preference. - -Visual Studio Code -^^^^^^^^^^^^^^^^^^ - -Now that we have both dependencies and ptvsd enabled within our code we configure Visual Studio Code (VS Code) Debugging - Assuming you're still in the application folder and have code command in your path, let's open up VS Code: - -.. code:: bash - - code . - -``NOTE``: If you don't have code in your Path, please open up a new instance of VS Code from ``python-debugging/`` folder we created earlier. - -In order to setup VS Code for debugging with AWS SAM CLI, use -the following launch configuration: - -.. code:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": "SAM CLI Python Hello World", - "type": "python", - "request": "attach", - "port": 5890, - "host": "localhost", - "pathMappings": [ - { - "localRoot": "${workspaceFolder}/hello_world/build", - "remoteRoot": "/var/task" - } - ] - } - ] - } - -For VS Code, the property **localRoot** under **pathMappings** key is really important and there are 2 aspects you should know as to why this is setup this way: - -1. **localRoot**: This path will be mounted in the Docker Container and needs to have both application and dependencies at the root level -2. **workspaceFolder**: This path is the absolute path where VS Code instance was opened - -If you opened VS Code in a different location other than ``python-debugging/`` you need to replace it with the absolute path where ``python-debugging/`` is. - -Once complete with VS Code Debugger configuration, make sure to add a breakpoint anywhere you like in ``hello_world/build/app.py`` and then proceed as follows: - -1. Run SAM CLI to invoke your function -2. Hit the URL to invoke the function and initialize ptvsd code execution -3. Start the debugger within VS Code - -.. code:: bash - - # Remember to hit the URL before starting the debugger in VS Code - - sam local start-api -d 5890 - - # OR - - # Change HelloWorldFunction to reflect the logical name found in template.yaml - - sam local generate-event apigateway aws-proxy | sam local invoke HelloWorldFunction -d 5890 - - -Debugging Golang functions --------------------------- - -Golang function debugging is slightly different when compared to Node.JS, -Java, and Python. We require `delve `__ -as the debugger, and wrap your function with it at runtime. The debugger -is run in headless mode, listening on the debug port. - -When debugging, you must compile your function in debug mode: - -``GOARCH=amd64 GOOS=linux go build -gcflags='-N -l' -o `` - -You must compile `delve` to run in the container and provide its local path -via the `--debugger-path` argument. Build delve locally as follows: - -``GOARCH=amd64 GOOS=linux go build -o /dlv github.com/derekparker/delve/cmd/dlv`` - -NOTE: The output path needs to end in `/dlv`. The docker container will expect the dlv binary to be in the -and will cause mounting issue otherwise. - -Then invoke `sam` similar to the following: - -``sam local start-api -d 5986 --debugger-path `` - -NOTE: The ``--debugger-path`` is the path to the directory that contains the `dlv` binary compiled from the above. - -The following is an example launch configuration for Visual Studio Code to -attach to a debug session. - -.. code:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": "Connect to Lambda container", - "type": "go", - "request": "launch", - "mode": "remote", - "remotePath": "", - "port": , - "host": "127.0.0.1", - "program": "${workspaceRoot}", - "env": {}, - "args": [], - }, - ] - } - - -Debugging .NET Core 2.1 / 2.0 Functions ---------------------------------------- - -.NET Core function debugging is similiar to golang function debugging and requires you to have ``vsdbg`` available on your -machine to later provide it to SAM CLI. VS Code will launch debugger inside Lambda container and talk to it using ``pipeTransport`` configuration. - -When debugging, you must compile your function in debug mode: - -Either locally using .NET SDK - -.. code:: bash - - dotnet publish -c Debug -o - -Or via Docker - -.. code:: bash - - docker run --rm --mount type=bind,src=$PWD,dst=/var/task lambci/lambda:build-dotnetcore dotnet publish -c Debug -o - -**NOTE: both of these commands should be run from the directory with .csproj file** - -You must get ``vsdbg`` built for AWS Lambda runtime container on your host machine and provide its local path -via the ``--debugger-path`` argument. Get compatible debugger version as follows: - -.. code:: bash - - # Create directory to store vsdbg - mkdir - - # Get and install vsdbg on runtime container. Mounted folder will let you have it under on your machine too - docker run --rm --mount type=bind,src=,dst=/vsdbg --entrypoint bash lambci/lambda:dotnetcore2.0 -c "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg" - -Then invoke ``sam`` similar to the following: - -``sam local start-api -d --debugger-path `` - -NOTE: The ``--debugger-path`` is the path to the directory that contains the ``vsdbg`` binary installed from the above. - -The following is an example launch configuration for Visual Studio Code to attach to a debug session. - -.. code:: json - - { - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Docker Attach", - "type": "coreclr", - "request": "attach", - "processId": "1", - - "pipeTransport": { - "pipeProgram": "sh", - "pipeArgs": [ - "-c", - "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" - ], - "debuggerPath": "/tmp/lambci_debug_files/vsdbg", - "pipeCwd": "${workspaceFolder}", - }, - - "windows": { - "pipeTransport": { - "pipeProgram": "powershell", - "pipeArgs": [ - "-c", - "docker exec -i $(docker ps -q -f publish=) ${debuggerCommand}" - ], - "debuggerPath": "/tmp/lambci_debug_files/vsdbg", - "pipeCwd": "${workspaceFolder}", - } - }, - - "sourceFileMap": { - "/var/task": "${workspaceFolder}" - } - } - ] - } - - -Passing Additional Runtime Debug Arguments ------------------------------------------- - -To pass additional runtime arguments when debugging your function, use -the environment variable ``DEBUGGER_ARGS``. This will pass a string -of arguments directly into the run command SAM CLI uses to start your -function. - -For example, if you want to load a debugger like iKPdb at runtime of -your Python function, you could pass the following as -``DEBUGGER_ARGS``: -``-m ikpdb --ikpdb-port=5858 --ikpdb-working-directory=/var/task/ --ikpdb-client-working-directory=/myApp --ikpdb-address=0.0.0.0``. -This would load iKPdb at runtime with the other arguments you’ve -specified. In this case, your full SAM CLI command would be: - -.. code:: bash - - $ DEBUGGER_ARGS="-m ikpdb --ikpdb-port=5858 --ikpdb-working-directory=/var/task/ --ikpdb-client-working-directory=/myApp --ikpdb-address=0.0.0.0" echo {} | sam local invoke -d 5858 myFunction - -You may pass debugger arguments to functions of all runtimes. - -To simplify troubleshooting, we added a new command called ``sam logs`` -to SAM CLI. ``sam logs`` lets you fetch logs generated by your Lambda -function from the command line. In addition to printing the logs on the -terminal, this command has several nifty features to help you quickly -find the bug. Note: This command works for all AWS Lambda functions; not -just the ones you deploy using SAM. - -Fetch, tail, and filter Lambda function logs --------------------------------------------- -To simplify troubleshooting, SAM CLI has a command called ``sam logs``. -``sam logs`` lets you fetch logs generated by your Lambda -function from the command line. In addition to printing the logs on the -terminal, this command has several nifty features to help you quickly -find the bug. - -Note: This command works for all AWS Lambda functions; not -just the ones you deploy using SAM. - -**Basic Usage: Using CloudFormation Stack** - -When your function is a part -of a CloudFormation stack, you can fetch logs using the function's -LogicalID: - -.. code:: bash - - sam logs -n HelloWorldFunction --stack-name mystack - -**Basic Usage: Using Lambda Function name** - -Or, you can fetch logs using the function's name - -.. code:: bash - - sam logs -n mystack-HelloWorldFunction-1FJ8PD - -**Tail Logs** - -Add ``--tail`` option to wait for new logs and see them as -they arrive. This is very handy during deployment or when -troubleshooting a production issue. - -.. code:: bash - - sam logs -n HelloWorldFunction --stack-name mystack --tail - -**View logs for specific time range** -You can view logs for specific time range using the ``-s`` and ``-e`` options - -.. code:: bash - - sam logs -n HelloWorldFunction --stack-name mystack -s '10min ago' -e '2min ago' - -**Filter Logs** - -Use the ``--filter`` option to quickly find logs that -match terms, phrases or values in your log events - -.. code:: bash - - sam logs -n HelloWorldFunction --stack-name mystack --filter "error" - -In the output, SAM CLI will underline all occurrences of the word -“error” so you can easily locate the filter keyword within the log -output. - -**Error Highlighting** - -When your Lambda function crashes or times out, -SAM CLI will highlight the timeout message in red. This will help you -easily locate specific executions that are timing out within a giant -stream of log output. - -.. figure:: https://user-images.githubusercontent.com/22755571/42301038-3363a366-7fc8-11e8-9d0e-308b209cb92b.png - :alt: SAM CLI Logs Error Highlighting - - -**JSON pretty printing** - -If your log messages print JSON strings, SAM -CLI will automatically pretty print the JSON to help you visually parse -and understand the JSON. - -.. figure:: https://user-images.githubusercontent.com/22755571/42301064-50c6cffa-7fc8-11e8-8f31-04ef117a9c5a.png - :alt: SAM CLI Logs JSON Pretty Print - -Validate SAM templates ----------------------- - -Validate your templates with ``$ sam validate``. Currently this command -will validate that the template provided is valid JSON / YAML. As with -most SAM CLI commands, it will look for a ``template.[yaml|yml]`` file -in your current working directory by default. You can specify a -different template file/location with the ``-t`` or ``--template`` -option. - -**Syntax** - -.. code:: bash - - $ sam validate - /template.yml is a valid SAM Template - -Note: The validate command requires AWS credentials to be configured. See `IAM Credentials <#iam-credentials>`__. - -Package and Deploy to Lambda ----------------------------- - -Once you have developed and tested your Serverless application locally, -you can deploy to Lambda using ``sam package`` and ``sam deploy`` -command. - -``sam package`` command will zip your code artifacts, upload to S3 -and produce a SAM file that is ready to be deployed to Lambda using AWS -CloudFormation. - -``sam deploy`` command will deploy the packaged SAM template -to CloudFormation. - -Both ``sam package`` and ``sam deploy`` are identical -to their AWS CLI equivalents commands -`aws cloudformation package `__ -and -`aws cloudformation deploy `__ -respectively - Please consult the AWS CLI command documentation for usage. - -Example: - -.. code:: bash - - # Package SAM template - $ sam package --template-file sam.yaml --s3-bucket mybucket --output-template-file packaged.yaml - - # Deploy packaged SAM template - $ sam deploy --template-file ./packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM - -Learn More ----------- - -- `Project Overview <../README.rst>`__ -- `Installation `__ -- `Getting started with SAM and the SAM CLI `__ -- `Packaging and deploying your application `__ -- `Advanced `__ diff --git a/samcli/__init__.py b/samcli/__init__.py index 0151e0ffe5..fb9abb1b79 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = '0.10.0' +__version__ = '0.11.0' diff --git a/samcli/commands/_utils/template.py b/samcli/commands/_utils/template.py index d0602c5305..c520f4d49e 100644 --- a/samcli/commands/_utils/template.py +++ b/samcli/commands/_utils/template.py @@ -14,6 +14,10 @@ from samcli.yamlhelper import yaml_parse, yaml_dump +_METADATA_WITH_LOCAL_PATHS = { + "AWS::ServerlessRepo::Application": ["LicenseUrl", "ReadmeUrl"] +} + _RESOURCES_WITH_LOCAL_PATHS = { "AWS::Serverless::Function": ["CodeUri"], "AWS::Serverless::Api": ["DefinitionUri"], @@ -132,6 +136,22 @@ def _update_relative_paths(template_dict, """ + for resource_type, properties in template_dict.get("Metadata", {}).items(): + + if resource_type not in _METADATA_WITH_LOCAL_PATHS: + # Unknown resource. Skipping + continue + + for path_prop_name in _METADATA_WITH_LOCAL_PATHS[resource_type]: + path = properties.get(path_prop_name) + + updated_path = _resolve_relative_to(path, original_root, new_root) + if not updated_path: + # This path does not need to get updated + continue + + properties[path_prop_name] = updated_path + for _, resource in template_dict.get("Resources", {}).items(): resource_type = resource.get("Type") diff --git a/samcli/commands/deploy/__init__.py b/samcli/commands/deploy/__init__.py index a16380d684..0085b802f5 100644 --- a/samcli/commands/deploy/__init__.py +++ b/samcli/commands/deploy/__init__.py @@ -12,19 +12,40 @@ SHORT_HELP = "Deploy an AWS SAM application. This is an alias for 'aws cloudformation deploy'." -@click.command("deploy", short_help=SHORT_HELP, context_settings={"ignore_unknown_options": True}) +HELP_TEXT = """The sam deploy command creates a Cloudformation Stack and deploys your resources. + +\b +e.g. sam deploy sam deploy --template-file packaged.yaml --stack-name sam-app --capabilities CAPABILITY_IAM + +\b +This is an alias for aws cloudformation deploy. To learn about other parameters you can use, +run aws cloudformation deploy help. +""" + + +@click.command("deploy", short_help=SHORT_HELP, context_settings={"ignore_unknown_options": True}, help=HELP_TEXT) @click.argument("args", nargs=-1, type=click.UNPROCESSED) +@click.option('--template-file', + required=True, + type=click.Path(), + help="The path where your AWS SAM template is located") +@click.option('--stack-name', + required=True, + help="The name of the AWS CloudFormation stack you're deploying to. " + "If you specify an existing stack, the command updates the stack. " + "If you specify a new stack, the command creates it.") @common_options @pass_context -def cli(ctx, args): +def cli(ctx, args, template_file, stack_name): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing + do_cli(args, template_file, stack_name) # pragma: no cover - do_cli(args) # pragma: no cover +def do_cli(args, template_file, stack_name): + args = args + ('--stack-name', stack_name) -def do_cli(args): try: - execute_command("deploy", args, template_file=None) + execute_command("deploy", args, template_file=template_file) except OSError as ex: raise UserException(str(ex)) diff --git a/samcli/commands/init/__init__.py b/samcli/commands/init/__init__.py index 5e858f9baa..9e77e1fa8a 100644 --- a/samcli/commands/init/__init__.py +++ b/samcli/commands/init/__init__.py @@ -77,13 +77,34 @@ def do_cli(ctx, location, runtime, output_dir, name, no_input): LOG.debug("Init command") click.secho("[+] Initializing project structure...", fg="green") + no_build_msg = """ +Project generated: {output_dir}/{name} + +Steps you can take next within the project folder +=================================================== +[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json +[*] Start API Gateway locally: sam local start-api +""".format(output_dir=output_dir, name=name) + + build_msg = """ +Project generated: {output_dir}/{name} + +Steps you can take next within the project folder +=================================================== +[*] Install dependencies +[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json +[*] Start API Gateway locally: sam local start-api +""".format(output_dir=output_dir, name=name) + + no_build_step_required = ( + "python", "python3.7", "python3.6", "python2.7", "nodejs", "nodejs4.3", "nodejs6.10", "nodejs8.10", "ruby2.5") + next_step_msg = no_build_msg if runtime in no_build_step_required else build_msg + try: generate_project(location, runtime, output_dir, name, no_input) - # Custom templates can implement their own visual cues so let's not repeat the message if not location: - click.secho( - "[SUCCESS] - Read {name}/README.md for further instructions on how to proceed" - .format(name=name), bold=True) + click.secho(next_step_msg, bold=True) + click.secho("Read {name}/README.md for further instructions\n".format(name=name), bold=True) click.secho("[*] Project initialization is now complete", fg="green") except GenerateProjectFailedError as e: raise UserException(str(e)) diff --git a/samcli/commands/local/lib/local_api_service.py b/samcli/commands/local/lib/local_api_service.py index ea82c1b60f..df15675191 100644 --- a/samcli/commands/local/lib/local_api_service.py +++ b/samcli/commands/local/lib/local_api_service.py @@ -76,7 +76,7 @@ def start(self): # Print out the list of routes that will be mounted self._print_routes(self.api_provider, self.host, self.port) LOG.info("You can now browse to the above endpoints to invoke your functions. " - "You do not need to restart/reload SAM CLI while working on your functions " + "You do not need to restart/reload SAM CLI while working on your functions, " "changes will be reflected instantly/automatically. You only need to restart " "SAM CLI if you update your AWS SAM template") diff --git a/samcli/commands/local/lib/local_lambda.py b/samcli/commands/local/lib/local_lambda.py index 06aef3b91f..029e9f5856 100644 --- a/samcli/commands/local/lib/local_lambda.py +++ b/samcli/commands/local/lib/local_lambda.py @@ -74,6 +74,10 @@ def invoke(self, function_name, event, stdout=None, stderr=None): function = self.provider.get(function_name) if not function: + all_functions = [f.name for f in self.provider.get_all()] + available_function_message = "{} not found. Possible options in your template: {}"\ + .format(function_name, all_functions) + LOG.info(available_function_message) raise FunctionNotFound("Unable to find a Function with name '%s'", function_name) LOG.debug("Found one Lambda function with name '%s'", function_name) diff --git a/samcli/commands/local/lib/sam_base_provider.py b/samcli/commands/local/lib/sam_base_provider.py index 7b26f3dbb5..bbf4d6381b 100644 --- a/samcli/commands/local/lib/sam_base_provider.py +++ b/samcli/commands/local/lib/sam_base_provider.py @@ -4,10 +4,12 @@ import logging -from samcli.lib.samlib.wrapper import SamTranslatorWrapper from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.intrinsics.actions import RefAction +from samcli.lib.samlib.wrapper import SamTranslatorWrapper +from samcli.lib.samlib.resource_metadata_normalizer import ResourceMetadataNormalizer + LOG = logging.getLogger(__name__) @@ -60,6 +62,7 @@ def get_template(template_dict, parameter_overrides=None): template_dict = SamTranslatorWrapper(template_dict).run_plugins() template_dict = SamBaseProvider._resolve_parameters(template_dict, parameter_overrides) + ResourceMetadataNormalizer.normalize(template_dict) return template_dict @staticmethod diff --git a/samcli/commands/package/__init__.py b/samcli/commands/package/__init__.py index ff16662d09..9c45396e14 100644 --- a/samcli/commands/package/__init__.py +++ b/samcli/commands/package/__init__.py @@ -14,24 +14,44 @@ SHORT_HELP = "Package an AWS SAM application. This is an alias for 'aws cloudformation package'." -@click.command("package", short_help=SHORT_HELP, context_settings={"ignore_unknown_options": True}) +HELP_TEXT = """The SAM package command creates a zip of your code and dependencies and uploads it to S3. The command +returns a copy of your template, replacing references to local artifacts with the S3 location where the command +uploaded the artifacts. + +\b +e.g. sam package --template-file template.yaml --output-template-file packaged.yaml +--s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME + +\b +This is an alias for aws cloudformation package. To learn about other parameters you can use, +run aws cloudformation package help. +""" + + +@click.command("package", short_help=SHORT_HELP, context_settings={"ignore_unknown_options": True}, help=HELP_TEXT) @click.option('--template-file', default=_TEMPLATE_OPTION_DEFAULT_VALUE, type=click.Path(), callback=partial(get_or_default_template_file_name, include_build=True), show_default=False, help="The path where your AWS SAM template is located") +@click.option('--s3-bucket', + required=True, + help="The name of the S3 bucket where this command uploads the artifacts that " + "are referenced in your template.") @click.argument("args", nargs=-1, type=click.UNPROCESSED) @common_options @pass_context -def cli(ctx, args, template_file): +def cli(ctx, args, template_file, s3_bucket): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - do_cli(args, template_file) # pragma: no cover + do_cli(args, template_file, s3_bucket) # pragma: no cover + +def do_cli(args, template_file, s3_bucket): + args = args + ('--s3-bucket', s3_bucket) -def do_cli(args, template_file): try: execute_command("package", args, template_file) except OSError as ex: diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index 4697d5b1b2..77eebedab2 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -22,7 +22,7 @@ This command expects the template's Metadata section to contain an AWS::ServerlessRepo::Application section with application metadata for publishing. For more details on this metadata section, see -https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverless-app-publishing-applications.html +https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-publishing-applications.html \b Examples -------- diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index 2a15fa1ef4..689835ec6e 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -246,31 +246,34 @@ def _build_function_on_container(self, # pylint: disable=too-many-locals options=None) try: - self._container_manager.run(container) - except docker.errors.APIError as ex: - if "executable file not found in $PATH" in str(ex): - raise UnsupportedBuilderLibraryVersionError(container.image, - "{} executable not found in container" - .format(container.executable_name)) - - # Container's output provides status of whether the build succeeded or failed - # stdout contains the result of JSON-RPC call - stdout_stream = io.BytesIO() - # stderr contains logs printed by the builder. Stream it directly to terminal - stderr_stream = osutils.stderr() - container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream) - - stdout_data = stdout_stream.getvalue().decode('utf-8') - LOG.debug("Build inside container returned response %s", stdout_data) - - response = self._parse_builder_response(stdout_data, container.image) - - # Request is successful. Now copy the artifacts back to the host - LOG.debug("Build inside container was successful. Copying artifacts from container to host") - - # "/." is a Docker thing that instructions the copy command to download contents of the folder only - result_dir_in_container = response["result"]["artifacts_dir"] + "/." - container.copy(result_dir_in_container, artifacts_dir) + try: + self._container_manager.run(container) + except docker.errors.APIError as ex: + if "executable file not found in $PATH" in str(ex): + raise UnsupportedBuilderLibraryVersionError(container.image, + "{} executable not found in container" + .format(container.executable_name)) + + # Container's output provides status of whether the build succeeded or failed + # stdout contains the result of JSON-RPC call + stdout_stream = io.BytesIO() + # stderr contains logs printed by the builder. Stream it directly to terminal + stderr_stream = osutils.stderr() + container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream) + + stdout_data = stdout_stream.getvalue().decode('utf-8') + LOG.debug("Build inside container returned response %s", stdout_data) + + response = self._parse_builder_response(stdout_data, container.image) + + # Request is successful. Now copy the artifacts back to the host + LOG.debug("Build inside container was successful. Copying artifacts from container to host") + + # "/." is a Docker thing that instructions the copy command to download contents of the folder only + result_dir_in_container = response["result"]["artifacts_dir"] + "/." + container.copy(result_dir_in_container, artifacts_dir) + finally: + self._container_manager.stop(container) LOG.debug("Build inside container succeeded") return artifacts_dir diff --git a/samcli/lib/samlib/resource_metadata_normalizer.py b/samcli/lib/samlib/resource_metadata_normalizer.py new file mode 100644 index 0000000000..945246bd81 --- /dev/null +++ b/samcli/lib/samlib/resource_metadata_normalizer.py @@ -0,0 +1,63 @@ +""" +Class that Normalizes a Template based on Resource Metadata +""" + +import logging + +RESOURCES_KEY = "Resources" +PROPERTIES_KEY = "Properties" +METADATA_KEY = "Metadata" +ASSET_PATH_METADATA_KEY = "aws:asset:path" +ASSET_PROPERTY_METADATA_KEY = "aws:asset:property" + +LOG = logging.getLogger(__name__) + + +class ResourceMetadataNormalizer(object): + + @staticmethod + def normalize(template_dict): + """ + Normalize all Resources in the template with the Metadata Key on the resource. + + This method will mutate the template + + Parameters + ---------- + template_dict dict + Dictionary representing the template + + """ + resources = template_dict.get(RESOURCES_KEY, {}) + + for logical_id, resource in resources.items(): + resource_metadata = resource.get(METADATA_KEY, {}) + asset_path = resource_metadata.get(ASSET_PATH_METADATA_KEY) + asset_property = resource_metadata.get(ASSET_PROPERTY_METADATA_KEY) + + ResourceMetadataNormalizer._replace_property(asset_property, asset_path, resource, logical_id) + + @staticmethod + def _replace_property(property_key, property_value, resource, logical_id): + """ + Replace a property with an asset on a given resource + + This method will mutate the template + + Parameters + ---------- + property str + The property to replace on the resource + property_value str + The new value of the property + resource dict + Dictionary representing the Resource to change + logical_id str + LogicalId of the Resource + + """ + if property_key and property_value: + resource.get(PROPERTIES_KEY, {})[property_key] = property_value + elif property_key or property_value: + LOG.info("WARNING: Ignoring Metadata for Resource %s. Metadata contains only aws:asset:path or " + "aws:assert:property but not both", logical_id) diff --git a/samcli/lib/utils/tar.py b/samcli/lib/utils/tar.py index b732faee47..38417d6e75 100644 --- a/samcli/lib/utils/tar.py +++ b/samcli/lib/utils/tar.py @@ -23,7 +23,7 @@ def create_tarball(tar_paths): """ tarballfile = TemporaryFile() - with tarfile.open(fileobj=tarballfile, mode='w:gz') as archive: + with tarfile.open(fileobj=tarballfile, mode='w') as archive: for path_on_system, path_in_tarball in tar_paths.items(): archive.add(path_on_system, arcname=path_in_tarball) diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index fdcb02f0ac..05c70980a1 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -182,7 +182,6 @@ def _build_image(self, base_image, docker_tag, layers): self.docker_client.images.build(fileobj=tarballfile, custom_context=True, rm=True, - encoding='gzip', tag=docker_tag, pull=not self.skip_pull_image) except (docker.errors.BuildError, docker.errors.APIError): diff --git a/samcli/local/init/__init__.py b/samcli/local/init/__init__.py index 8ef8c8dd26..02c8a10358 100644 --- a/samcli/local/init/__init__.py +++ b/samcli/local/init/__init__.py @@ -19,9 +19,8 @@ "python2.7": os.path.join(_templates, "cookiecutter-aws-sam-hello-python"), "python": os.path.join(_templates, "cookiecutter-aws-sam-hello-python"), "ruby2.5": os.path.join(_templates, "cookiecutter-aws-sam-hello-ruby"), - "nodejs6.10": os.path.join(_templates, "cookiecutter-aws-sam-hello-nodejs"), + "nodejs6.10": os.path.join(_templates, "cookiecutter-aws-sam-hello-nodejs6"), "nodejs8.10": os.path.join(_templates, "cookiecutter-aws-sam-hello-nodejs"), - "nodejs4.3": os.path.join(_templates, "cookiecutter-aws-sam-hello-nodejs"), "nodejs": os.path.join(_templates, "cookiecutter-aws-sam-hello-nodejs"), "dotnetcore2.0": os.path.join(_templates, "cookiecutter-aws-sam-hello-dotnet"), "dotnetcore2.1": os.path.join(_templates, "cookiecutter-aws-sam-hello-dotnet"), diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md index fdf398c822..fd8a839788 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md @@ -77,7 +77,6 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md index 669be260a7..36ec102297 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md @@ -87,7 +87,6 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md index 68e70f0adc..980f230f17 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md @@ -80,7 +80,6 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/LICENSE b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/LICENSE index f19aaa6d09..14aabc346a 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/LICENSE +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/LICENSE @@ -1,4 +1,4 @@ -MIT No Attribution +Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software @@ -11,4 +11,4 @@ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/Pipfile b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/Pipfile deleted file mode 100644 index a22660daae..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/Pipfile +++ /dev/null @@ -1,13 +0,0 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] - -cookiecutter = "*" -pytest-cookies = "*" -pytest = "*" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/README.md index 4644aac6bf..614a9a5b7a 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/README.md @@ -1,42 +1,20 @@ -# Cookiecutter SAM for Node Lambda functions +# Cookiecutter NodeJS Hello-world for SAM based Serverless App -This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless Hello World App based on Serverless Application Model (SAM) and NodeJS. - -It is important to note that you should not try to `git clone` this project but use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. +A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). ## Requirements -Install `cookiecutter` command line: - -**Pip users**: - -* `pip install cookiecutter` - -**Homebrew users**: - -* `brew install cookiecutter` - -**Windows or Pipenv users**: - -* `pipenv install cookiecutter` - -**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) ## Usage -Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-hello-nodejs`. - -You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. +Generate a boilerplate template in your current project directory using the following syntax: -**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. +* **NodeJS 8**: `sam init --runtime nodejs8.10` +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) # Credits * This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) - -License -------- - -This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/tests/test_cookiecutter.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/tests/test_cookiecutter.py deleted file mode 100644 index cd70e453ff..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/tests/test_cookiecutter.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - Tests cookiecutter baking process and rendered content -""" - - -def test_project_tree(cookies): - result = cookies.bake(extra_context={ - 'project_name': 'hello sam' - }) - assert result.exit_code == 0 - assert result.exception is None - assert result.project.basename == 'hello sam' - assert result.project.isdir() - assert result.project.join('.gitignore').isfile() - assert result.project.join('template.yaml').isfile() - assert result.project.join('README.md').isfile() - assert result.project.join('hello_world').isdir() - assert result.project.join('hello_world', 'app.js').isfile() - assert result.project.join('hello_world', 'package.json').isfile() - assert result.project.join('hello_world', 'yarn.lock').isfile() - assert result.project.join('hello_world', 'tests').isdir() - assert result.project.join('hello_world', 'tests', 'unit', 'test.spec.js').isfile() - - -def test_app_content(cookies): - result = cookies.bake(extra_context={'project_name': 'my_lambda'}) - app_file = result.project.join('hello_world', 'app.js') - app_content = app_file.readlines() - app_content = ''.join(app_content) - - contents = ( - "const axios", - "JSON.stringify", - "location", - "message", - "hello world", - "statusCode" - ) - - for content in contents: - assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/README.md index f0eef64f25..ee97f83fbf 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/README.md @@ -4,48 +4,33 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ```bash . -├── README.md <-- This instructions file -├── hello-world <-- Source code for a lambda function -│ ├── app.js <-- Lambda function code -│ ├── package.json <-- NodeJS dependencies +├── README.MD <-- This instructions file +├── event.json <-- API Gateway Proxy Integration event payload +├── hello_world <-- Source code for a lambda function +│ └── app.js <-- Lambda function code +│ └── package.json <-- NodeJS dependencies and scripts │ └── tests <-- Unit tests │ └── unit -│ └── test_handler.js -└── template.yaml <-- SAM template +│ └── test-handler.js +├── template.yaml <-- SAM template ``` ## Requirements * AWS CLI already configured with Administrator permission -{%- if cookiecutter.runtime == 'nodejs6.10' %} -* [NodeJS 6.10 installed](https://nodejs.org/en/download/releases/) -{%- elif cookiecutter.runtime =='nodejs4.3' %} -* [NodeJS 4.3 installed](https://nodejs.org/en/download/releases/) -{%- else %} * [NodeJS 8.10+ installed](https://nodejs.org/en/download/) -{%- endif %} * [Docker installed](https://www.docker.com/community-edition) ## Setup process -### Building the project +### Local development -[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) with the application as well as its dependencies in a node_modules folder. When you make changes to your source code or dependency manifest, -run the following command to build your project local testing and deployment: - -```bash -sam build -``` +**Invoking function locally using a local sample payload** -If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: ```bash -sam build --use-container +sam local invoke HelloWorldFunction --event event.json ``` -By default, this command writes built artifacts to `.aws-sam/build` folder. - -### Local development - **Invoking function locally through local API Gateway** ```bash @@ -72,7 +57,7 @@ AWS Lambda NodeJS runtime requires a flat folder with all dependencies including ```yaml ... - FirstFunction: + HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-world/ @@ -89,7 +74,6 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` @@ -103,16 +87,29 @@ sam deploy \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs' + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table ``` +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + ## Testing We use `mocha` for testing our code and it is already added in `package.json` under `scripts`, so that we can simply run the following command to run our tests: @@ -123,38 +120,87 @@ npm install npm run test ``` +## Cleanup + +In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: + +```bash +aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} +``` + +## Bringing to the next level + +Here are a few things you can try to get more acquainted with building serverless applications using SAM: + +### Learn how SAM Build can help you with dependencies + +* Uncomment lines on `app.js` +* Build the project with ``sam build --use-container`` +* Invoke with ``sam local invoke HelloWorldFunction --event event.json`` +* Update tests + +### Create an additional API resource + +* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update tests + +### Step-through debugging + +* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) + # Appendix -## AWS CLI commands +## Building the project + +[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) with the application as well as its dependencies in a node_modules folder. When you make changes to your source code or dependency manifest, +run the following command to build your project local testing and deployment: + +```bash +sam build +``` + +If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: +```bash +sam build --use-container +``` + +By default, this command writes built artifacts to `.aws-sam/build` folder. + +## SAM and AWS CLI commands -AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack: +All commands used throughout this document ```bash +# Invoke function locally with event.json as an input +sam local invoke HelloWorldFunction --event event.json + +# Run API Gateway locally +sam local start-api + +# Create S3 bucket +aws s3 mb s3://BUCKET_NAME + +# Package Lambda function defined locally and upload to S3 as an artifact sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME +# Deploy SAM template as a CloudFormation stack sam deploy \ --template-file packaged.yaml \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --capabilities CAPABILITY_IAM \ - --parameter-overrides MyParameterSample=MySampleValue + --capabilities CAPABILITY_IAM +# Describe Output section of CloudFormation stack previously created aws cloudformation describe-stacks \ - --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --query 'Stacks[].Outputs' + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table + +# Tail Lambda function Logs using Logical name defined in SAM Template +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail ``` **NOTE**: Alternatively this could be part of package.json scripts section. - -## Bringing to the next level - -Here are a few ideas that you can use to get more acquainted as to how this overall process works: - -* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update unit test to capture that -* Package & Deploy - -Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: - -* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/event.json new file mode 100644 index 0000000000..070ad8e018 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/app.js b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/app.js index ef39e9a9ee..695d1b5678 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/app.js +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/app.js @@ -1,74 +1,27 @@ -{%- if cookiecutter.runtime == 'nodejs4.3' %} -var axios = require('axios') -var url = 'http://checkip.amazonaws.com/'; -var response; -{%- else %} -const axios = require('axios') -const url = 'http://checkip.amazonaws.com/'; +// const axios = require('axios') +// const url = 'http://checkip.amazonaws.com/'; let response; -{%- endif %} /** * * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format * @param {Object} event - API Gateway Lambda Proxy Input Format - * @param {string} event.resource - Resource path. - * @param {string} event.path - Path parameter. - * @param {string} event.httpMethod - Incoming request's method name. - * @param {Object} event.headers - Incoming request headers. - * @param {Object} event.queryStringParameters - query string parameters. - * @param {Object} event.pathParameters - path parameters. - * @param {Object} event.stageVariables - Applicable stage variables. - * @param {Object} event.requestContext - Request context, including authorizer-returned key-value pairs, requestId, sourceIp, etc. - * @param {Object} event.body - A JSON string of the request payload. - * @param {boolean} event.body.isBase64Encoded - A boolean flag to indicate if the applicable request payload is Base64-encode * * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html * @param {Object} context - * @param {string} context.logGroupName - Cloudwatch Log Group name - * @param {string} context.logStreamName - Cloudwatch Log stream name. - * @param {string} context.functionName - Lambda function name. - * @param {string} context.memoryLimitInMB - Function memory. - * @param {string} context.functionVersion - Function version identifier. - * @param {function} context.getRemainingTimeInMillis - Time in milliseconds before function times out. - * @param {string} context.awsRequestId - Lambda request ID. - * @param {string} context.invokedFunctionArn - Function ARN. * * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html * @returns {Object} object - API Gateway Lambda Proxy Output Format - * @returns {boolean} object.isBase64Encoded - A boolean flag to indicate if the applicable payload is Base64-encode (binary support) - * @returns {string} object.statusCode - HTTP Status Code to be returned to the client - * @returns {Object} object.headers - HTTP Headers to be returned - * @returns {Object} object.body - JSON Payload to be returned * */ -{%- if cookiecutter.runtime == 'nodejs6.10' or cookiecutter.runtime == 'nodejs4.3' %} -exports.lambdaHandler = function (event, context, callback) { - axios(url) - .then(function (ret) { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: 'hello world', - location: ret.data.trim() - }) - } - callback(null, response); - }) - .catch(function (err) { - console.log(err); - callback(err); - }); -}; -{%- else %} exports.lambdaHandler = async (event, context) => { try { - const ret = await axios(url); + // const ret = await axios(url); response = { 'statusCode': 200, 'body': JSON.stringify({ message: 'hello world', - location: ret.data.trim() + // location: ret.data.trim() }) } } catch (err) { @@ -78,4 +31,3 @@ exports.lambdaHandler = async (event, context) => { return response }; -{% endif %} \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/package.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/package.json index 52abf09d0e..6e5abce643 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/package.json +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/package.json @@ -2,7 +2,7 @@ "name": "hello_world", "version": "1.0.0", "description": "hello world sample for NodeJS", - "main": "src/index.js", + "main": "app.js", "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", "author": "SAM CLI", "license": "MIT", diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js index 5453fa9d7a..ae94b9f2fa 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js @@ -5,28 +5,6 @@ const chai = require('chai'); const expect = chai.expect; var event, context; -{% if cookiecutter.runtime == 'nodejs6.10' or cookiecutter.runtime == 'nodejs4.3' %} -describe('Tests Handler', function () { - it('verifies successful response', function (done) { - app.lambdaHandler(event, context, function (err, result) { - try { - expect(result).to.be.an('object'); - expect(result.statusCode).to.equal(200); - expect(result.body).to.be.an('string'); - - let response = JSON.parse(result.body); - - expect(response).to.be.an('object'); - expect(response.message).to.be.equal("hello world"); - expect(response.location).to.be.an("string"); - done(); - } catch (e) { - done(e); - } - }); - }); -}); -{% else %} describe('Tests index', function () { it('verifies successful response', async () => { const result = await app.lambdaHandler(event, context) @@ -39,7 +17,6 @@ describe('Tests index', function () { expect(response).to.be.an('object'); expect(response.message).to.be.equal("hello world"); - expect(response.location).to.be.an("string"); + // expect(response.location).to.be.an("string"); }); }); -{% endif %} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/template.yaml index 24a0f77da7..a6a15cecdd 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs/{{cookiecutter.project_name}}/template.yaml @@ -18,16 +18,7 @@ Resources: Properties: CodeUri: hello-world/ Handler: app.lambdaHandler - {%- if cookiecutter.runtime == 'nodejs6.10' %} - Runtime: nodejs6.10 - {%- elif cookiecutter.runtime =='nodejs4.3' %} - Runtime: nodejs4.3 - {%- else %} Runtime: nodejs8.10 - {%- endif %} - Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object - Variables: - PARAM1: VALUE Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api @@ -37,6 +28,9 @@ Resources: Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/.gitignore b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/.gitignore new file mode 100644 index 0000000000..74ea25e0e5 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/.gitignore @@ -0,0 +1,168 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/osx,linux,python,windows \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/LICENSE b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/LICENSE new file mode 100644 index 0000000000..14aabc346a --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/LICENSE @@ -0,0 +1,14 @@ +Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/README.md new file mode 100644 index 0000000000..4f15d02ce8 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/README.md @@ -0,0 +1,19 @@ +# Cookiecutter legacy NodeJS Hello-world for SAM based Serverless App + +A cookiecutter template to create a legacy NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). + +## Requirements + +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) + +## Usage + +Generate a boilerplate template in your current project directory using the following syntax: + +* **NodeJS 6**: `sam init --runtime nodejs6.10` + +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) + +# Credits + +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/cookiecutter.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/cookiecutter.json new file mode 100644 index 0000000000..14bfbdc74f --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/cookiecutter.json @@ -0,0 +1,4 @@ +{ + "project_name": "Name of the project", + "runtime": "nodejs6.10" +} \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/setup.cfg b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/setup.cfg new file mode 100644 index 0000000000..6302b3a02b --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/setup.cfg @@ -0,0 +1,2 @@ +[install] +prefix= \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/.gitignore b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/.gitignore new file mode 100644 index 0000000000..eb1db5fbec --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/.gitignore @@ -0,0 +1,129 @@ + +# Created by https://www.gitignore.io/api/osx,node,linux,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/osx,node,linux,windows \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/README.md new file mode 100644 index 0000000000..c5b4bf05b3 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/README.md @@ -0,0 +1,211 @@ +# {{ cookiecutter.project_name }} + +This is a sample template for {{ cookiecutter.project_name }} - Below is a brief explanation of what we have generated for you: + +```bash +. +├── README.MD <-- This instructions file +├── event.json <-- API Gateway Proxy Integration event payload +├── hello_world <-- Source code for a lambda function +│ └── app.js <-- Lambda function code +│ └── package.json <-- NodeJS dependencies and scripts +│ └── app-deps.js <-- Lambda function code with dependencies (Bringing to the next level section) +│ └── tests <-- Unit tests +│ └── unit +│ └── test-handler.js +├── template.yaml <-- SAM template +``` + +## Requirements + +* AWS CLI already configured with Administrator permission +* [{{ cookiecutter.runtime }} installed](https://nodejs.org/en/download/releases/) +* [Docker installed](https://www.docker.com/community-edition) + +## Setup process + +### Local development + +**Invoking function locally using a local sample payload** + +```bash +sam local invoke HelloWorldFunction --event event.json +``` + +**Invoking function locally through local API Gateway** + +```bash +sam local start-api +``` + +If the previous command ran successfully you should now be able to hit the following local endpoint to invoke your function `http://localhost:3000/hello` + +**SAM CLI** is used to emulate both Lambda and API Gateway locally and uses our `template.yaml` to understand how to bootstrap this environment (runtime, where the source code is, etc.) - The following excerpt is what the CLI will read in order to initialize an API and its routes: + +```yaml +... +Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get +``` + +## Packaging and deployment + +AWS Lambda NodeJS runtime requires a flat folder with all dependencies including the application. SAM will use `CodeUri` property to know where to look up for both application and dependencies: + +```yaml +... + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: hello-world/ + ... +``` + +Firstly, we need a `S3 bucket` where we can upload our Lambda functions packaged as ZIP before we deploy anything - If you don't have a S3 bucket to store code artifacts then this is a good time to create one: + +```bash +aws s3 mb s3://BUCKET_NAME +``` + +Next, run the following command to package our Lambda function to S3: + +```bash +sam package \ + --output-template-file packaged.yaml \ + --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME +``` + +Next, the following command will create a Cloudformation Stack and deploy your SAM resources. + +```bash +sam deploy \ + --template-file packaged.yaml \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --capabilities CAPABILITY_IAM +``` + +> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** + +After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: + +```bash +aws cloudformation describe-stacks \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table +``` + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Testing + +We use `mocha` for testing our code and it is already added in `package.json` under `scripts`, so that we can simply run the following command to run our tests: + +```bash +cd hello-world +npm install +npm run test +``` + +## Cleanup + +In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: + +```bash +aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} +``` + +## Bringing to the next level + +Here are a few things you can try to get more acquainted with building serverless applications using SAM: + +### Learn how SAM Build can help you with dependencies + +* Delete `hello-world/app.js` +* Rename `hello-world/app-deps.js` to `hello-world/app.js` +* Build the project with ``sam build --use-container`` +* Invoke with ``sam local invoke HelloWorldFunction --event event.json`` +* Update tests + +### Create an additional API resource + +* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update tests + +### Step-through debugging + +* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) + +# Appendix + +### Building the project + +[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) with the application as well as its dependencies in a node_modules folder. When you make changes to your source code or dependency manifest, +run the following command to build your project local testing and deployment: + +```bash +sam build +``` + +If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: +```bash +sam build --use-container +``` + +By default, this command writes built artifacts to `.aws-sam/build` folder. + +## SAM and AWS CLI commands + +All commands used throughout this document + +```bash +# Generate event.json via generate-event command +sam local generate-event apigateway aws-proxy > event.json + +# Invoke function locally with event.json as an input +sam local invoke HelloWorldFunction --event event.json + +# Run API Gateway locally +sam local start-api + +# Create S3 bucket +aws s3 mb s3://BUCKET_NAME + +# Package Lambda function defined locally and upload to S3 as an artifact +sam package \ + --output-template-file packaged.yaml \ + --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME + +# Deploy SAM template as a CloudFormation stack +sam deploy \ + --template-file packaged.yaml \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --capabilities CAPABILITY_IAM + +# Describe Output section of CloudFormation stack previously created +aws cloudformation describe-stacks \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table + +# Tail Lambda function Logs using Logical name defined in SAM Template +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` + +**NOTE**: Alternatively this could be part of package.json scripts section. diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/event.json new file mode 100644 index 0000000000..070ad8e018 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/.npmignore b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/.npmignore new file mode 100644 index 0000000000..e7e1fb04f4 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/.npmignore @@ -0,0 +1 @@ +tests/* diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app-deps.js b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app-deps.js new file mode 100644 index 0000000000..fb7470c3ae --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app-deps.js @@ -0,0 +1,34 @@ +var axios = require('axios') +var url = 'http://checkip.amazonaws.com/'; +var response; + + +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html + * @param {Object} context + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ +exports.lambdaHandler = function (event, context, callback) { + axios(url) + .then(function (ret) { + response = { + 'statusCode': 200, + 'body': JSON.stringify({ + message: 'hello world', + location: ret.data.trim() + }) + } + callback(null, response); + }) + .catch(function (err) { + console.log(err); + callback(err); + }); +}; diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app.js b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app.js new file mode 100644 index 0000000000..cae4cec9a5 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/app.js @@ -0,0 +1,23 @@ + +/** + * + * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + * @param {Object} event - API Gateway Lambda Proxy Input Format + * + * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html + * @param {Object} context + * + * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + * @returns {Object} object - API Gateway Lambda Proxy Output Format + * + */ +exports.lambdaHandler = function (event, context, callback) { + var response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world' + }) + } + + callback(null, response); +}; diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/package.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/package.json new file mode 100644 index 0000000000..80086fd6db --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/package.json @@ -0,0 +1,19 @@ +{ + "name": "hello_world", + "version": "1.0.0", + "description": "hello world sample for NodeJS", + "main": "app.js", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs-legacy", + "author": "SAM CLI", + "license": "MIT", + "dependencies": { + "axios": "^0.18.0" + }, + "scripts": { + "test": "mocha tests/unit/" + }, + "devDependencies": { + "chai": "^4.1.2", + "mocha": "^5.1.1" + } +} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js new file mode 100644 index 0000000000..19b617b9af --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/hello-world/tests/unit/test-handler.js @@ -0,0 +1,27 @@ +'use strict'; + +const app = require('../../app.js'); +const chai = require('chai'); +const expect = chai.expect; +var event, context; + +describe('Tests Handler', function () { + it('verifies successful response', function (done) { + app.lambdaHandler(event, context, function (err, result) { + try { + expect(result).to.be.an('object'); + expect(result.statusCode).to.equal(200); + expect(result.body).to.be.an('string'); + + let response = JSON.parse(result.body); + + expect(response).to.be.an('object'); + expect(response.message).to.be.equal("hello world"); + // expect(response.location).to.be.an("string"); + done(); + } catch (e) { + done(e); + } + }); + }); +}); diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/template.yaml new file mode 100644 index 0000000000..a3e1ecd758 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs6/{{cookiecutter.project_name}}/template.yaml @@ -0,0 +1,44 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + {{ cookiecutter.project_name }} + + Sample SAM Template for {{ cookiecutter.project_name }} + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + + +Resources: + + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello-world/ + Handler: app.lambdaHandler + Runtime: {{ cookiecutter.runtime }} + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + +Outputs: + + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/Pipfile b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/Pipfile deleted file mode 100644 index a22660daae..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/Pipfile +++ /dev/null @@ -1,13 +0,0 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] - -cookiecutter = "*" -pytest-cookies = "*" -pytest = "*" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/README.md index 1b5721f31b..5170effb56 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/README.md @@ -1,42 +1,22 @@ -# Cookiecutter SAM for Python Lambda functions +# Cookiecutter Python Hello-world for SAM based Serverless App -This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless Hello World App based on Serverless Application Model (SAM) and Python. - -It is important to note that you should not try to `git clone` this project but use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. +A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). ## Requirements -Install `cookiecutter` command line: - -**Pip users**: - -* `pip install cookiecutter` - -**Homebrew users**: - -* `brew install cookiecutter` - -**Windows or Pipenv users**: - -* `pipenv install cookiecutter` - -**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) ## Usage -Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-hello-python`. +Generate a boilerplate template in your current project directory using the following syntax: -You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. +* **Python 3.7**: `sam init --runtime python3.7` +* **Python 3.6**: `sam init --runtime python3.6` +* **Python 2.7**: `sam init --runtime python2.7` -**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) # Credits * This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) - - -License -------- - -This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/tests/test_cookiecutter.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/tests/test_cookiecutter.py deleted file mode 100644 index 361b9f2d02..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/tests/test_cookiecutter.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - Tests cookiecutter baking process and rendered content -""" - - -def test_project_tree(cookies): - result = cookies.bake(extra_context={ - 'project_name': 'hello sam' - }) - assert result.exit_code == 0 - assert result.exception is None - assert result.project.basename == 'hello sam' - assert result.project.isdir() - assert result.project.join('requirements.txt').isfile() - assert result.project.join('.gitignore').isfile() - assert result.project.join('template.yaml').isfile() - assert result.project.join('README.md').isfile() - assert result.project.join('hello_world').isdir() - assert result.project.join('hello_world', 'app.py').isfile() - assert result.project.join('hello_world', '__init__.py').isfile() - assert result.project.join('tests').isdir() - assert result.project.join('tests', 'unit', '__init__.py').isfile() - assert result.project.join('tests', 'unit', 'test_handler.py').isfile() - - -def test_app_content(cookies): - result = cookies.bake(extra_context={'project_name': 'my_lambda'}) - app_file = result.project.join('hello_world', 'app.py') - app_content = app_file.readlines() - app_content = ''.join(app_content) - - contents = ( - "import requests", - "Sample pure Lambda function", - "location", - "message", - "hello world", - "statusCode" - ) - - for content in contents: - assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/README.md index bf0d7ee02b..513a84f724 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/README.md @@ -5,10 +5,11 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ```bash . ├── README.md <-- This instructions file +├── event.json <-- API Gateway Proxy Integration event payload ├── hello_world <-- Source code for a lambda function │   ├── __init__.py │   ├── app.py <-- Lambda function code -│   └── requirements.txt <-- Python dependencies +│   ├── requirements.txt <-- Lambda function code ├── template.yaml <-- SAM Template └── tests <-- Unit tests └── unit @@ -18,34 +19,23 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ## Requirements -* AWS CLI already configured with at least PowerUser permission +* AWS CLI already configured with Administrator permission {%- if cookiecutter.runtime == 'python2.7' %} * [Python 2.7 installed](https://www.python.org/downloads/) {%- else %} * [Python 3 installed](https://www.python.org/downloads/) {%- endif %} * [Docker installed](https://www.docker.com/community-edition) -* [Python Virtual Environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/) ## Setup process -### Building the project +### Local development -[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html) with the application as well as its dependencies. When you make changes to your source code or dependency manifest, -run the following command to build your project local testing and deployment: - -```bash -sam build -``` +**Invoking function locally using a local sample payload** -If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: ```bash -sam build --use-container +sam local invoke HelloWorldFunction --event event.json ``` - -By default, this command writes built artifacts to `.aws-sam/build` folder. - -### Local development **Invoking function locally through local API Gateway** @@ -103,88 +93,122 @@ sam deploy \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs' + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table ``` +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + ## Testing -We use **Pytest** and **pytest-mock** for testing our code and you can install it using pip: ``pip install pytest pytest-mock`` -Next, we run `pytest` against our `tests` folder to run our initial unit tests: +Next, we install test dependencies and we run `pytest` against our `tests` folder to run our initial unit tests: ```bash +pip install pytest pytest-mock --user python -m pytest tests/ -v ``` -**NOTE**: It is recommended to use a Python Virtual environment to separate your application development from your system Python installation. +## Cleanup -# Appendix +In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: -### Python Virtual environment +```bash +aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} +``` -{%- if cookiecutter.runtime == 'python2.7' %} -**In case you're new to this**, python2 `virtualenv` module is not available in the standard library so we need to install it and then we can install our dependencies: +## Bringing to the next level + +Here are a few things you can try to get more acquainted with building serverless applications using SAM: + +### Learn how SAM Build can help you with dependencies + +* Uncomment lines on `app.py` +* Build the project with ``sam build --use-container`` +* Invoke with ``sam local invoke HelloWorldFunction --event event.json`` +* Update tests + +### Create an additional API resource + +* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update tests + +### Step-through debugging -1. Create a new virtual environment -2. Install dependencies in the new virtual environment +* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) + +# Appendix + +## Building the project + +[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html) with the application as well as its dependencies in deployment package. When you make changes to your source code or dependency manifest, +run the following command to build your project local testing and deployment: ```bash -pip install virtualenv -virtualenv .venv -. .venv/bin/activate -pip install -r requirements.txt +sam build ``` -{%- else %} -**In case you're new to this**, python3 comes with `virtualenv` library by default so you can simply run the following: - -1. Create a new virtual environment -2. Install dependencies in the new virtual environment +If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: ```bash -python3 -m venv .venv -. .venv/bin/activate -pip install -r requirements.txt +sam build --use-container ``` -{%- endif %} +By default, this command writes built artifacts to `.aws-sam/build` folder. -**NOTE:** You can find more information about Virtual Environment at [Python Official Docs here](https://docs.python.org/3/tutorial/venv.html). Alternatively, you may want to look at [Pipenv](https://github.com/pypa/pipenv) as the new way of setting up development workflows -## AWS CLI commands +## SAM and AWS CLI commands -AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack: +All commands used throughout this document ```bash +# Generate event.json via generate-event command +sam local generate-event apigateway aws-proxy > event.json + +# Invoke function locally with event.json as an input +sam local invoke HelloWorldFunction --event event.json + +# Run API Gateway locally +sam local start-api + +# Create S3 bucket +aws s3 mb s3://BUCKET_NAME + +# Package Lambda function defined locally and upload to S3 as an artifact sam package \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME +# Deploy SAM template as a CloudFormation stack sam deploy \ --template-file packaged.yaml \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --capabilities CAPABILITY_IAM \ - --parameter-overrides MyParameterSample=MySampleValue + --capabilities CAPABILITY_IAM +# Describe Output section of CloudFormation stack previously created aws cloudformation describe-stacks \ - --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --query 'Stacks[].Outputs' -``` - -## Bringing to the next level - -Here are a few ideas that you can use to get more acquainted as to how this overall process works: - -* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update unit test to capture that -* Package & Deploy + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table -Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: +# Tail Lambda function Logs using Logical name defined in SAM Template +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` -* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) -* [Chalice Python Serverless framework](https://github.com/aws/chalice) -* Sample Python with 3rd party dependencies, pipenv and Makefile: ``sam init --location https://github.com/aws-samples/cookiecutter-aws-sam-python`` diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/event.json new file mode 100644 index 0000000000..070ad8e018 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/app.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/app.py index 96a1f669e5..093062037a 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/app.py +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/app.py @@ -1,6 +1,6 @@ import json -import requests +# import requests def lambda_handler(event, context): @@ -11,77 +11,32 @@ def lambda_handler(event, context): event: dict, required API Gateway Lambda Proxy Input Format - { - "resource": "Resource path", - "path": "Path parameter", - "httpMethod": "Incoming request's method name" - "headers": {Incoming request headers} - "queryStringParameters": {query string parameters } - "pathParameters": {path parameters} - "stageVariables": {Applicable stage variables} - "requestContext": {Request context, including authorizer-returned key-value pairs} - "body": "A JSON string of the request payload." - "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode" - } - - https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format context: object, required Lambda Context runtime methods and attributes - Attributes - ---------- - - context.aws_request_id: str - Lambda request ID - context.client_context: object - Additional context when invoked through AWS Mobile SDK - context.function_name: str - Lambda function name - context.function_version: str - Function version identifier - context.get_remaining_time_in_millis: function - Time in milliseconds before function times out - context.identity: - Cognito identity provider context when invoked through AWS Mobile SDK - context.invoked_function_arn: str - Function ARN - context.log_group_name: str - Cloudwatch Log group name - context.log_stream_name: str - Cloudwatch Log stream name - context.memory_limit_in_mb: int - Function memory - - https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------ API Gateway Lambda Proxy Output Format: dict - 'statusCode' and 'body' are required - - { - "isBase64Encoded": true | false, - "statusCode": httpStatusCode, - "headers": {"headerName": "headerValue", ...}, - "body": "..." - } - # api-gateway-simple-proxy-for-lambda-output-format - https: // docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html """ - try: - ip = requests.get("http://checkip.amazonaws.com/") - except requests.RequestException as e: - # Send some context about this error to Lambda Logs - print(e) + # try: + # ip = requests.get("http://checkip.amazonaws.com/") + # except requests.RequestException as e: + # # Send some context about this error to Lambda Logs + # print(e) - raise e + # raise e return { "statusCode": 200, - "body": json.dumps( - {"message": "hello world", "location": ip.text.replace("\n", "")} - ), + "body": json.dumps({ + "message": "hello world", + # "location": ip.text.replace("\n", "") + }), } diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt index c20f36f246..663bd1f6a2 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/hello_world/requirements.txt @@ -1 +1 @@ -requests==2.20.0 +requests \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml index d709a8695a..d8cd417ea1 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml @@ -25,9 +25,6 @@ Resources: {%- else %} Runtime: python3.7 {%- endif %} - Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object - Variables: - PARAM1: VALUE Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api @@ -37,6 +34,9 @@ Resources: Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/tests/unit/test_handler.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/tests/unit/test_handler.py index f85ad6316b..09588d3778 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/tests/unit/test_handler.py +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/tests/unit/test_handler.py @@ -1,4 +1,3 @@ -from collections import namedtuple import json import pytest @@ -65,17 +64,10 @@ def apigw_event(): def test_lambda_handler(apigw_event, mocker): - requests_response_mock = namedtuple("response", ["text"]) - requests_response_mock.text = "1.1.1.1\n" - - request_mock = mocker.patch.object( - app.requests, 'get', side_effect=requests_response_mock) - ret = app.lambda_handler(apigw_event, "") - assert ret["statusCode"] == 200 - - for key in ("message", "location"): - assert key in ret["body"] - data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "message" in ret["body"] assert data["message"] == "hello world" + # assert "location" in data.dict_keys() diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/README.md index 1fee2ad2f0..797eaf66e9 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/README.md @@ -1,62 +1,20 @@ -# Cookiecutter SAM for Ruby Lambda functions +# Cookiecutter Ruby Hello-world for SAM based Serverless App -This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless Hello World App based on Serverless Application Model (SAM) and Ruby. +A cookiecutter template to create a Ruby Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). -## Recommendations +## Requirements -This cookiecutter template is easily accessible via `sam` +* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) -``` -sam init --runtime=ruby2.5 -``` +## Usage -This should create a directory structure called ``sam-app`` with the following output. +Generate a boilerplate template in your current project directory using the following syntax: -```bash -[+] Initializing project structure... -[SUCCESS] - Read sam-app/README.md for further instructions on how to proceed -[*] Project initialization is now complete -``` +* **Ruby 2.5**: `sam init --runtime ruby2.5` -The directory `sam-app` contains a `README.md` with detailed instructions. +> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) -## Alternative Installation -You can also use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. - -### Requirements - -Install `cookiecutter` command line: - -**Pip users**: - -* `pip install cookiecutter` - -**Homebrew users**: - -* `brew install cookiecutter` - -**Windows or Pipenv users**: - -* `pipenv install cookiecutter` - -**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. - -### Usage - -Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-hello-ruby`. - -You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. - -**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. - - -## Credits +# Credits * This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) - - -License -------- - -This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/tests/test_cookiecutter.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/tests/test_cookiecutter.py deleted file mode 100644 index da36d53a9a..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/tests/test_cookiecutter.py +++ /dev/null @@ -1,39 +0,0 @@ -""" - Tests cookiecutter baking process and rendered content -""" - - -def test_project_tree(cookies): - result = cookies.bake(extra_context={ - 'project_name': 'hello sam' - }) - assert result.exit_code == 0 - assert result.exception is None - assert result.project.basename == 'hello sam' - assert result.project.isdir() - assert result.project.join('.gitignore').isfile() - assert result.project.join('template.yaml').isfile() - assert result.project.join('README.md').isfile() - assert result.project.join('hello_world').isdir() - assert result.project.join('hello_world', 'app.rb').isfile() - assert result.project.join('tests').isdir() - assert result.project.join('tests', 'unit', 'test_handler.py').isfile() - - -def test_app_content(cookies): - result = cookies.bake(extra_context={'project_name': 'my_lambda'}) - app_file = result.project.join('hello_world', 'app.rb') - app_content = app_file.readlines() - app_content = ''.join(app_content) - - contents = ( - "require 'httparty'", - "Sample pure Lambda function", - "location", - "message", - "Hello World!", - "statusCode" - ) - - for content in contents: - assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/README.md index f220a6b515..8c6ebb70fc 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/README.md @@ -5,11 +5,12 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ```bash . ├── README.md <-- This instructions file +├── event.json <-- API Gateway Proxy Integration event payload ├── hello_world <-- Source code for a lambda function │ ├── app.rb <-- Lambda function code │ ├── Gemfile <-- Ruby function dependencies -├── Gemfile <-- Ruby test/documentation dependencies ├── template.yaml <-- SAM template +├── Gemfile <-- Ruby test/documentation dependencies └── tests <-- Unit tests └── unit └── test_handler.rb @@ -17,45 +18,20 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ## Requirements -* AWS CLI already configured with at least PowerUser permission -* [Ruby](https://www.ruby-lang.org/en/documentation/installation/) 2.5 installed +* AWS CLI already configured with at Administrator permission +* [Ruby 2.5 installed](https://www.ruby-lang.org/en/documentation/installation/) * [Docker installed](https://www.docker.com/community-edition) -* [Ruby Version Manager](http://rvm.io/) ## Setup process -### Match ruby version with docker image -For high fidelity development environment, make sure the local ruby version matches that of the docker image. To do so lets use [Ruby Version Manager](http://rvm.io/) - -Setup Ruby Version Manager from [Ruby Version Manager](http://rvm.io/) +### Local development -Run following commands +**Invoking function locally using a local sample payload** ```bash -rvm install ruby-2.5.3 -rvm use ruby-2.5.3 -rvm --default use 2.5.3 -``` - -### Building the Project - -```sam-app``` comes with a Gemfile that defines the requirements and manages installing them. The `sam build` command will install the dependencies in your function Gemfile and vendor it for deployment. - -``` -sam build +sam local invoke HelloWorldFunction --event event.json ``` -If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: - -``` -sam build --use-container -``` -By default, this command writes built artifacts to .aws-sam/build folder. - -**NOTE:** As you change your dependencies during development you'll need to run `sam build` again in order to execute your Lambda and/or API Gateway locally. - -### Local development - **Invoking function locally through local API Gateway** ```bash @@ -99,7 +75,6 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` @@ -109,20 +84,33 @@ Next, the following command will create a Cloudformation Stack and deploy your S ```bash sam deploy \ --template-file packaged.yaml \ - --stack-name sam-app \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ - --stack-name sam-app \ - --query 'Stacks[].Outputs' + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table ``` +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + ## Testing Run our initial unit tests: @@ -131,38 +119,91 @@ Run our initial unit tests: ruby tests/unit/test_handler.rb ``` -**NOTE**: It is recommended to use a Ruby Version Manager to manage, and work with multiple ruby environments from interpreters to sets of gems +## Cleanup + +In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: + +```bash +aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} +``` + +## Bringing to the next level + +Here are a few things you can try to get more acquainted with building serverless applications using SAM: + +### Learn how SAM Build can help you with dependencies + +* Uncomment lines on `app.rb` +* Build the project with ``sam build --use-container`` +* Invoke with ``sam local invoke HelloWorldFunction --event event.json`` +* Update tests + +### Create an additional API resource + +* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update tests + +### Step-through debugging + +* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** + +Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) + + # Appendix -## AWS CLI commands +## Building the project + +[AWS Lambda requires a flat folder](https://docs.aws.amazon.com/lambda/latest/dg/ruby-package.html) with the application as well as its dependencies. When you make changes to your source code or dependency manifest, +run the following command to build your project local testing and deployment: + +```bash +sam build +``` + +If your dependencies contain native modules that need to be compiled specifically for the operating system running on AWS Lambda, use this command to build inside a Lambda-like Docker container instead: +```bash +sam build --use-container +``` + +By default, this command writes built artifacts to `.aws-sam/build` folder. + + +## SAM and AWS CLI commands -AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack after building: +All commands used throughout this document ```bash +# Generate event.json via generate-event command +sam local generate-event apigateway aws-proxy > event.json + +# Invoke function locally with event.json as an input +sam local invoke HelloWorldFunction --event event.json + +# Run API Gateway locally +sam local start-api + +# Create S3 bucket +aws s3 mb s3://BUCKET_NAME + +# Package Lambda function defined locally and upload to S3 as an artifact sam package \ - --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME +# Deploy SAM template as a CloudFormation stack sam deploy \ --template-file packaged.yaml \ - --stack-name sam-app \ - --capabilities CAPABILITY_IAM \ - --parameter-overrides MyParameterSample=MySampleValue + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --capabilities CAPABILITY_IAM +# Describe Output section of CloudFormation stack previously created aws cloudformation describe-stacks \ - --stack-name sam-app --query 'Stacks[].Outputs' -``` - -## Bringing to the next level + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ + --output table -Here are a few ideas that you can use to get more acquainted as to how this overall process works: - -* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update unit test to capture that -* Package & Deploy - -Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: - -* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) +# Tail Lambda function Logs using Logical name defined in SAM Template +sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail +``` diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/event.json new file mode 100644 index 0000000000..070ad8e018 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/hello_world/app.rb b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/hello_world/app.rb index 963182dd7c..ed241af8fa 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/hello_world/app.rb +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/hello_world/app.rb @@ -1,4 +1,4 @@ -require 'httparty' +# require 'httparty' require 'json' def lambda_handler(event:, context:) @@ -8,76 +8,31 @@ def lambda_handler(event:, context:) # ---------- # event: Hash, required # API Gateway Lambda Proxy Input Format - - # { - # "resource": "Resource path", - # "path": "Path parameter", - # "httpMethod": "Incoming request's method name" - # "headers": {Incoming request headers} - # "queryStringParameters": {query string parameters } - # "pathParameters": {path parameters} - # "stageVariables": {Applicable stage variables} - # "requestContext": {Request context, including authorizer-returned key-value pairs} - # "body": "A JSON string of the request payload." - # "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode" - # } - - # https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + # Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format # context: object, required # Lambda Context runtime methods and attributes - - # Attributes - # ---------- - - # context.aws_request_id: str - # Lambda request ID - # context.client_context: object - # Additional context when invoked through AWS Mobile SDK - # context.function_name: str - # Lambda function name - # context.function_version: str - # Function version identifier - # context.get_remaining_time_in_millis: function - # Time in milliseconds before function times out - # context.identity: - # Cognito identity provider context when invoked through AWS Mobile SDK - # context.invoked_function_arn: str - # Function ARN - # context.log_group_name: str - # Cloudwatch Log group name - # context.log_stream_name: str - # Cloudwatch Log stream name - # context.memory_limit_in_mb: int - # Function memory + # Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html # Returns # ------ # API Gateway Lambda Proxy Output Format: dict # 'statusCode' and 'body' are required - - # { - # "isBase64Encoded": true | false, - # "statusCode": httpStatusCode, - # "headers": {"headerName": "headerValue", ...}, - # "body": "..." - # } - # # api-gateway-simple-proxy-for-lambda-output-format - # https: // docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + # Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + + # begin + # response = HTTParty.get('http://checkip.amazonaws.com/') + # rescue HTTParty::Error => error + # puts error.inspect + # raise error + # end - begin - response = HTTParty.get('http://checkip.amazonaws.com/') - rescue HTTParty::Error => error - puts error.inspect - raise error - end - { - statusCode: response.code, + statusCode: 200, body: { message: "Hello World!", - location: response.body + # location: response.body }.to_json } end diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/template.yaml index c1bcb7ba61..6f798b1b97 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-ruby/{{cookiecutter.project_name}}/template.yaml @@ -19,9 +19,6 @@ Resources: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: ruby2.5 - Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object - Variables: - PARAM1: VALUE Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api @@ -31,6 +28,9 @@ Resources: Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" diff --git a/setup.cfg b/setup.cfg index 11e9ec40ee..b88034e414 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.rst \ No newline at end of file +description-file = README.md diff --git a/setup.py b/setup.py index dc07fd28f2..67f52049d3 100644 --- a/setup.py +++ b/setup.py @@ -37,11 +37,12 @@ def read_version(): name='aws-sam-cli', version=read_version(), description='AWS SAM CLI is a CLI tool for local development and testing of Serverless applications', - long_description=read('README.rst'), + long_description=read('README.md'), + long_description_content_type='text/markdown', author='Amazon Web Services', author_email='aws-sam-developers@amazon.com', url='https://github.com/awslabs/aws-sam-cli', - license=read('LICENSE'), + license='Apache License 2.0', packages=find_packages(exclude=('tests', 'docs')), keywords="AWS SAM CLI", # Support Python 2.7 and 3.6 or greater diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index d1efc7c99d..d9031d3389 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -2,6 +2,8 @@ import shutil import tempfile +import docker + try: from pathlib import Path except ImportError: @@ -73,6 +75,12 @@ def get_command_list(self, build_dir=None, base_dir=None, manifest_path=None, us return command_list + def verify_docker_container_cleanedup(self, runtime): + docker_client = docker.from_env() + samcli_containers = \ + docker_client.containers.list(all=True, filters={"ancestor": "lambci/lambda:build-{}".format(runtime)}) + self.assertFalse(bool(samcli_containers), "Build containers have not been removed") + def _make_parameter_override_arg(self, overrides): return " ".join([ "ParameterKey={},ParameterValue={}".format(key, value) for key, value in overrides.items() diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index 62c812a42a..2397e63dfe 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -68,6 +68,7 @@ def test_with_default_requirements(self, runtime, use_container): self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected) + self.verify_docker_container_cleanedup(runtime) def _verify_invoke_built_function(self, template_path, function_logical_id, overrides, expected_result): LOG.info("Invoking built function '{}'", function_logical_id) @@ -165,6 +166,7 @@ def test_with_default_package_json(self, runtime, use_container): os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), str(self.default_build_dir)) ) + self.verify_docker_container_cleanedup(runtime) def _verify_built_artifact(self, build_dir, function_logical_id, expected_files, expected_modules): @@ -229,6 +231,7 @@ def test_with_default_gemfile(self, runtime, use_container): os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), str(self.default_build_dir)) ) + self.verify_docker_container_cleanedup(runtime) def _verify_built_artifact(self, build_dir, function_logical_id, expected_files, expected_modules): diff --git a/tests/integration/local/invoke/test_integrations_cli.py b/tests/integration/local/invoke/test_integrations_cli.py index a9c383e10b..d39e400a8f 100644 --- a/tests/integration/local/invoke/test_integrations_cli.py +++ b/tests/integration/local/invoke/test_integrations_cli.py @@ -36,6 +36,17 @@ def test_invoke_returncode_is_zero(self): self.assertEquals(return_code, 0) + def test_function_with_metadata(self): + command_list = self.get_command_list("FunctionWithMetadata", + template_path=self.template_path, + no_event=True) + + process = Popen(command_list, stdout=PIPE) + process.wait() + process_stdout = b"".join(process.stdout.readlines()).strip() + + self.assertEquals(process_stdout.decode('utf-8'), '"Hello World in a different dir"') + def test_invoke_returns_execpted_results(self): command_list = self.get_command_list("HelloWorldServerlessFunction", template_path=self.template_path, diff --git a/tests/integration/publish/publish_app_integ_base.py b/tests/integration/publish/publish_app_integ_base.py index c309db6860..7c93de99d9 100644 --- a/tests/integration/publish/publish_app_integ_base.py +++ b/tests/integration/publish/publish_app_integ_base.py @@ -40,9 +40,9 @@ def setUpClass(cls): license_body = root_path.joinpath("LICENSE").read_text() cls.s3_bucket.put_object(Key="LICENSE", Body=license_body) - readme_body = root_path.joinpath("README.rst").read_text() - cls.s3_bucket.put_object(Key="README.rst", Body=readme_body) - cls.s3_bucket.put_object(Key="README_UPDATE.rst", Body=readme_body) + readme_body = root_path.joinpath("README.md").read_text() + cls.s3_bucket.put_object(Key="README.md", Body=readme_body) + cls.s3_bucket.put_object(Key="README_UPDATE.md", Body=readme_body) code_body = cls.test_data_path.joinpath("main.py").read_text() cls.s3_bucket.put_object(Key="main.py", Body=code_body) @@ -51,8 +51,8 @@ def setUpClass(cls): def tearDownClass(cls): cls.s3_bucket.delete_objects(Delete={ 'Objects': [ - {'Key': 'LICENSE'}, {'Key': 'README.rst'}, - {'Key': 'README_UPDATE.rst'}, {'Key': 'main.py'} + {'Key': 'LICENSE'}, {'Key': 'README.md'}, + {'Key': 'README_UPDATE.md'}, {'Key': 'main.py'} ] }) cls.s3_bucket.delete() diff --git a/tests/integration/testdata/invoke/different_code_location/__init__.py b/tests/integration/testdata/invoke/different_code_location/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/testdata/invoke/different_code_location/main.py b/tests/integration/testdata/invoke/different_code_location/main.py new file mode 100644 index 0000000000..7422a87c67 --- /dev/null +++ b/tests/integration/testdata/invoke/different_code_location/main.py @@ -0,0 +1,2 @@ +def echo_hello_world(event, context): + return "Hello World in a different dir" diff --git a/tests/integration/testdata/invoke/template.yml b/tests/integration/testdata/invoke/template.yml index 8a6e799f2f..3a3d1c51d9 100644 --- a/tests/integration/testdata/invoke/template.yml +++ b/tests/integration/testdata/invoke/template.yml @@ -88,6 +88,15 @@ Resources: Timeout: Ref: DefaultTimeout + FunctionWithMetadata: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.6 + Handler: main.echo_hello_world + Metadata: + aws:asset:property: CodeUri + aws:asset:path: ./different_code_location + EchoEnvWithParameters: Type: AWS::Serverless::Function Properties: diff --git a/tests/integration/testdata/publish/metadata_create_app.json b/tests/integration/testdata/publish/metadata_create_app.json index b83f69724c..55c105f03d 100644 --- a/tests/integration/testdata/publish/metadata_create_app.json +++ b/tests/integration/testdata/publish/metadata_create_app.json @@ -7,7 +7,7 @@ ], "LicenseUrl": "s3:///LICENSE", "Name": "", - "ReadmeUrl": "s3:///README.rst", + "ReadmeUrl": "s3:///README.md", "SemanticVersion": "0.0.1", "SourceCodeUrl": "https://github.com/test/test" } diff --git a/tests/integration/testdata/publish/metadata_create_app_version.json b/tests/integration/testdata/publish/metadata_create_app_version.json index e535512b0d..10c456e956 100644 --- a/tests/integration/testdata/publish/metadata_create_app_version.json +++ b/tests/integration/testdata/publish/metadata_create_app_version.json @@ -5,7 +5,7 @@ "Labels": [ "test-app" ], - "ReadmeUrl": "s3:///README.rst", + "ReadmeUrl": "s3:///README.md", "SemanticVersion": "0.0.2", "SourceCodeUrl": "https://github.com/test/test-new-version" } diff --git a/tests/integration/testdata/publish/metadata_update_app.json b/tests/integration/testdata/publish/metadata_update_app.json index 5f5eff7f75..1b32337781 100644 --- a/tests/integration/testdata/publish/metadata_update_app.json +++ b/tests/integration/testdata/publish/metadata_update_app.json @@ -5,5 +5,5 @@ "Labels": [ "test-app-update" ], - "ReadmeUrl": "s3:///README_UPDATE.rst" + "ReadmeUrl": "s3:///README_UPDATE.md" } diff --git a/tests/integration/testdata/publish/template_create_app.yaml b/tests/integration/testdata/publish/template_create_app.yaml index da516883c3..a9b0f3668a 100644 --- a/tests/integration/testdata/publish/template_create_app.yaml +++ b/tests/integration/testdata/publish/template_create_app.yaml @@ -16,7 +16,7 @@ Metadata: - test-app LicenseUrl: s3:///LICENSE Name: - ReadmeUrl: s3:///README.rst + ReadmeUrl: s3:///README.md SemanticVersion: 0.0.1 SourceCodeUrl: https://github.com/test/test Resources: diff --git a/tests/integration/testdata/publish/template_create_app_version.yaml b/tests/integration/testdata/publish/template_create_app_version.yaml index 4981259ccf..3e6a06d5c5 100644 --- a/tests/integration/testdata/publish/template_create_app_version.yaml +++ b/tests/integration/testdata/publish/template_create_app_version.yaml @@ -16,7 +16,7 @@ Metadata: - test-app LicenseUrl: s3:///LICENSE Name: - ReadmeUrl: s3:///README.rst + ReadmeUrl: s3:///README.md SemanticVersion: 0.0.2 SourceCodeUrl: "https://github.com/test/test-new-version" Resources: diff --git a/tests/integration/testdata/publish/template_not_packaged.yaml b/tests/integration/testdata/publish/template_not_packaged.yaml index 7fb2894e52..b084d70fe0 100644 --- a/tests/integration/testdata/publish/template_not_packaged.yaml +++ b/tests/integration/testdata/publish/template_not_packaged.yaml @@ -16,7 +16,7 @@ Metadata: - test-app LicenseUrl: ./LICENSE Name: - ReadmeUrl: ./README.rst + ReadmeUrl: ./README.md SemanticVersion: 0.0.1 SourceCodeUrl: https://github.com/test/test Resources: diff --git a/tests/integration/testdata/publish/template_update_app.yaml b/tests/integration/testdata/publish/template_update_app.yaml index 5a469a65c1..fb761c2b17 100644 --- a/tests/integration/testdata/publish/template_update_app.yaml +++ b/tests/integration/testdata/publish/template_update_app.yaml @@ -16,7 +16,7 @@ Metadata: - test-app-update LicenseUrl: s3:///LICENSE Name: - ReadmeUrl: s3:///README_UPDATE.rst + ReadmeUrl: s3:///README_UPDATE.md SemanticVersion: 0.0.1 SourceCodeUrl: https://github.com/test/test Resources: diff --git a/tests/unit/commands/_utils/test_template.py b/tests/unit/commands/_utils/test_template.py index fb9876008c..96a24fda46 100644 --- a/tests/unit/commands/_utils/test_template.py +++ b/tests/unit/commands/_utils/test_template.py @@ -7,8 +7,8 @@ from mock import patch, mock_open from parameterized import parameterized, param -from samcli.commands._utils.template import get_template_data, _RESOURCES_WITH_LOCAL_PATHS, _update_relative_paths, \ - move_template +from samcli.commands._utils.template import get_template_data, _METADATA_WITH_LOCAL_PATHS, \ + _RESOURCES_WITH_LOCAL_PATHS, _update_relative_paths, move_template class Test_get_template_data(TestCase): @@ -78,10 +78,41 @@ def setUp(self): self.expected_result = os.path.join("..", "foo", "bar") + @parameterized.expand( + [(resource_type, props) for resource_type, props in _METADATA_WITH_LOCAL_PATHS.items()] + ) + def test_must_update_relative_metadata_paths(self, resource_type, properties): + + for propname in properties: + for path in [self.s3path, self.abspath, self.curpath]: + template_dict = { + "Metadata": { + resource_type: { + propname: path + }, + "AWS::Ec2::Instance": { + propname: path + } + }, + "Parameters": { + "a": "b" + } + } + + expected_template_dict = copy.deepcopy(template_dict) + if path == self.curpath: + expected_template_dict["Metadata"][resource_type][propname] = \ + self.expected_result + + result = _update_relative_paths(template_dict, self.src, self.dest) + + self.maxDiff = None + self.assertEquals(result, expected_template_dict) + @parameterized.expand( [(resource_type, props) for resource_type, props in _RESOURCES_WITH_LOCAL_PATHS.items()] ) - def test_must_update_relative_paths(self, resource_type, properties): + def test_must_update_relative_resource_paths(self, resource_type, properties): for propname in properties: diff --git a/tests/unit/commands/local/lib/test_local_lambda.py b/tests/unit/commands/local/lib/test_local_lambda.py index b0366aae97..5cfb92c266 100644 --- a/tests/unit/commands/local/lib/test_local_lambda.py +++ b/tests/unit/commands/local/lib/test_local_lambda.py @@ -445,8 +445,11 @@ def test_must_work(self): stdout=stdout, stderr=stderr) def test_must_raise_if_function_not_found(self): + function = Mock() + function.name = 'FunctionLogicalId' self.function_provider_mock.get.return_value = None # function not found + self.function_provider_mock.get_all.return_value = [function] with self.assertRaises(FunctionNotFound): self.local_lambda.invoke("name", "event") diff --git a/tests/unit/commands/local/lib/test_sam_base_provider.py b/tests/unit/commands/local/lib/test_sam_base_provider.py index 001a217058..c9d870e95a 100644 --- a/tests/unit/commands/local/lib/test_sam_base_provider.py +++ b/tests/unit/commands/local/lib/test_sam_base_provider.py @@ -178,11 +178,18 @@ def test_must_skip_empty_template(self): class TestSamBaseProvider_get_template(TestCase): + @patch("samcli.commands.local.lib.sam_base_provider.ResourceMetadataNormalizer") @patch("samcli.commands.local.lib.sam_base_provider.SamTranslatorWrapper") @patch.object(SamBaseProvider, "_resolve_parameters") - def test_must_run_translator_plugins(self, resolve_params_mock, SamTranslatorWrapperMock): + def test_must_run_translator_plugins(self, + resolve_params_mock, + SamTranslatorWrapperMock, + resource_metadata_normalizer_patch): translator_instance = SamTranslatorWrapperMock.return_value = Mock() + parameter_resolved_template = {"Key": "Value", "Parameter": "Resolved"} + resolve_params_mock.return_value = parameter_resolved_template + template = {"Key": "Value"} overrides = {'some': 'value'} @@ -191,3 +198,4 @@ def test_must_run_translator_plugins(self, resolve_params_mock, SamTranslatorWra SamTranslatorWrapperMock.assert_called_once_with(template) translator_instance.run_plugins.assert_called_once() resolve_params_mock.assert_called_once() + resource_metadata_normalizer_patch.normalize.assert_called_once_with(parameter_resolved_template) diff --git a/tests/unit/commands/test_deploy.py b/tests/unit/commands/test_deploy.py index 45649c9dd1..a8b74a2706 100644 --- a/tests/unit/commands/test_deploy.py +++ b/tests/unit/commands/test_deploy.py @@ -11,10 +11,11 @@ class TestCli(TestCase): def setUp(self): - self.args = ("--template-file", "file.yaml", "--stack-name", "stackName") + self.args = ('--force-upload',) + self.expected_args = self.args + ("--stack-name", "stackName") @patch("samcli.commands.deploy.execute_command") def test_deploy_must_pass_args(self, execute_command_mock): execute_command_mock.return_value = True - deploy_cli(self.args) - execute_command_mock.assert_called_with("deploy", self.args, template_file=None) + deploy_cli(self.args, "file.yaml", 'stackName') + execute_command_mock.assert_called_with("deploy", self.expected_args, template_file='file.yaml') diff --git a/tests/unit/commands/test_package.py b/tests/unit/commands/test_package.py index 488721c948..56d89674fd 100644 --- a/tests/unit/commands/test_package.py +++ b/tests/unit/commands/test_package.py @@ -11,10 +11,11 @@ class TestCli(TestCase): def setUp(self): - self.args = ("--template-file", "file.yaml", "--s3-bucket", "bucketName") + self.args = (' --use - json',) + self.expected_args = self.args + ("--s3-bucket", "bucketName") @patch("samcli.commands.package.execute_command") def test_package_must_pass_args(self, execute_command_mock): execute_command_mock.return_value = True - package_cli(self.args, "template_file") - execute_command_mock.assert_called_with("package", self.args, "template_file") + package_cli(self.args, "template_file", 'bucketName') + execute_command_mock.assert_called_with("package", self.expected_args, "template_file") diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 90090fb296..0229f4787d 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -318,6 +318,7 @@ def mock_wait_for_logs(stdout, stderr): self.builder._parse_builder_response.assert_called_once_with(stdout_data, container_mock.image) container_mock.copy.assert_called_with(response["result"]["artifacts_dir"] + "/.", "artifacts_dir") + self.container_manager.stop.assert_called_with(container_mock) @patch("samcli.lib.build.app_builder.LambdaBuildContainer") def test_must_raise_on_unsupported_container(self, LambdaBuildContainerMock): @@ -343,6 +344,7 @@ def test_must_raise_on_unsupported_container(self, LambdaBuildContainerMock): "Reason: 'myexecutable executable not found in container'" self.assertEquals(str(ctx.exception), msg) + self.container_manager.stop.assert_called_with(container_mock) class TestApplicationBuilder_parse_builder_response(TestCase): diff --git a/tests/unit/lib/samlib/test_resource_metadata_normalizer.py b/tests/unit/lib/samlib/test_resource_metadata_normalizer.py new file mode 100644 index 0000000000..bd4a9caffa --- /dev/null +++ b/tests/unit/lib/samlib/test_resource_metadata_normalizer.py @@ -0,0 +1,138 @@ +from unittest import TestCase + +from samcli.lib.samlib.resource_metadata_normalizer import ResourceMetadataNormalizer + + +class TestResourceMeatadataNormalizer(TestCase): + + def test_replace_property_with_path(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + }, + "Metadata": { + "aws:asset:path": "new path", + "aws:asset:property": "Code" + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("new path", template_data['Resources']['Function1']['Properties']['Code']) + + def test_replace_all_resources_that_contain_metadata(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + }, + "Metadata": { + "aws:asset:path": "new path", + "aws:asset:property": "Code" + } + }, + "Resource2": { + "Properties": { + "SomeRandomProperty": "some value" + }, + "Metadata": { + "aws:asset:path": "super cool path", + "aws:asset:property": "SomeRandomProperty" + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("new path", template_data['Resources']['Function1']['Properties']['Code']) + self.assertEqual("super cool path", template_data['Resources']['Resource2']['Properties']['SomeRandomProperty']) + + def test_tempate_without_metadata(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("some value", template_data['Resources']['Function1']['Properties']['Code']) + + def test_template_without_asset_property(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + }, + "Metadata": { + "aws:asset:path": "new path", + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("some value", template_data['Resources']['Function1']['Properties']['Code']) + + def test_tempalte_without_asset_path(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + }, + "Metadata": { + "aws:asset:property": "Code" + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("some value", template_data['Resources']['Function1']['Properties']['Code']) + + def test_template_with_empty_metadata(self): + template_data = { + "Resources": { + "Function1": { + "Properties": { + "Code": "some value" + }, + "Metadata": {} + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("some value", template_data['Resources']['Function1']['Properties']['Code']) + + def test_replace_of_property_that_does_not_exist(self): + template_data = { + "Resources": { + "Function1": { + "Properties": {}, + "Metadata": { + "aws:asset:path": "new path", + "aws:asset:property": "Code" + } + } + } + } + + ResourceMetadataNormalizer.normalize(template_data) + + self.assertEqual("new path", template_data['Resources']['Function1']['Properties']['Code']) diff --git a/tests/unit/lib/utils/test_tar.py b/tests/unit/lib/utils/test_tar.py index ffc613af56..d429c8cc37 100644 --- a/tests/unit/lib/utils/test_tar.py +++ b/tests/unit/lib/utils/test_tar.py @@ -25,4 +25,4 @@ def test_generating_tarball(self, temporary_file_patch, tarfile_open_patch): temp_file_mock.flush.assert_called_once() temp_file_mock.seek.assert_called_once_with(0) temp_file_mock.close.assert_called_once() - tarfile_open_patch.assert_called_once_with(fileobj=temp_file_mock, mode='w:gz') + tarfile_open_patch.assert_called_once_with(fileobj=temp_file_mock, mode='w') diff --git a/tests/unit/local/docker/test_lambda_image.py b/tests/unit/local/docker/test_lambda_image.py index 7a50aef1ee..86934ab524 100644 --- a/tests/unit/local/docker/test_lambda_image.py +++ b/tests/unit/local/docker/test_lambda_image.py @@ -171,8 +171,7 @@ def test_build_image(self, generate_dockerfile_patch, path_patch, uuid_patch, cr rm=True, tag="docker_tag", pull=False, - custom_context=True, - encoding='gzip') + custom_context=True) docker_full_path_mock.unlink.assert_called_once() @@ -218,8 +217,7 @@ def test_build_image_fails_with_BuildError(self, rm=True, tag="docker_tag", pull=False, - custom_context=True, - encoding='gzip') + custom_context=True) docker_full_path_mock.unlink.assert_not_called() @@ -264,6 +262,5 @@ def test_build_image_fails_with_ApiError(self, rm=True, tag="docker_tag", pull=False, - custom_context=True, - encoding='gzip') + custom_context=True) docker_full_path_mock.unlink.assert_called_once()