From 87a4577468501e4c414464c836bf1290ff51af64 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Tue, 28 Aug 2018 15:15:11 -0700 Subject: [PATCH] Release: 0.6.0 Release (#636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Functional tests must run on localhost to work in Windows (#552) * fix: spacing typo in Log statement in start-lambda (#559) * docs: Fix syntax highlighting in README.md (#561) * docs: Change jest to mocha in Nodejs init README (#564) * docs: Fix @mhart link in README (#562) * docs(README): removed cloudtrail, added SNS to generate-event (#569) * docs: Update repo name references (#577) * feat(debugging): Fixing issues around debugging Golang functions. (#565) * fix(init): Improve current init samples around docs and fixes (#558) * docs(README): Update launch config to SAM CLI from SAM Local (#587) * docs(README): Update sample code for calling Local Lambda Invoke (#584) * refactor(init): renamed handler for camel case, moved callback call up (#586) * chore: aws-lambda-java-core 1.1.0 -> 1.2.0 for java sam init (#578) * feat(validate): Add profile and region options (#582) Currently, `sam validate` requires AWS Creds (due to the SAM Translator). This commits adds the ability to pass in the credientials through a profile that is configured through `aws configure`. * docs(README): Update README prerequisites to include awscli (#596) * fix(start-lambda): Remove Content-Type Header check (#594) * docs: Disambiguation "Amazon Kinesis" (#599) * docs: Adding instructions for how to add pyenv to your PATH for Windows (#600) * docs: Update README with small grammar fix (#601) * fix: Update link in NodeJS package.json (#603) * docs: Creating instructions for Windows users to install sam (#605) * docs: Adding a note directing Windows users to use pipenv (#606) * fix: Fix stringifying λ environment variables when using Python2 (#579) * feat(generate-event): Added support for 50+ events (#612) * feat(invoke): Add region parameter to all invoke related commands (#608) * docs: Breaking up README into separate files to make it easier to read (#607) * chore: Update JVM size params to match docker-lambda (#615) * feat(invoke): Invoke Function Without Parameters through --no-event (#604) * docs: Update advanced_usage.rst with clarification on --env-vars usage (#610) * docs: Remove an extra word in the sam packaging command (#618) * UX: Improves event names to reflect Lambda Event Sources (#619) * docs: Fix git clone typo in installation docs (#630) * docs(README): Callout go1.x runtime support (#631) * docs(installation): Update sam --version command (#634) * chore(0.6.0): SAM CLI Version bump (#635) --- DEVELOPMENT_GUIDE.rst | 5 + MANIFEST.in | 2 + README.rst | 1147 ++--------------- docs/advanced_usage.rst | 272 ++++ docs/deploying_serverless_applications.rst | 104 +- docs/getting_started.rst | 124 +- docs/installation.rst | 210 +++ ...ugging_serverless_applications_locally.rst | 226 ++-- docs/usage.rst | 553 ++++++++ requirements/base.txt | 3 + requirements/dev.txt | 2 +- samcli/__init__.py | 2 +- .../local/cli_common/invoke_context.py | 117 +- samcli/commands/local/cli_common/options.py | 6 + .../local/cli_common/user_exceptions.py | 7 + .../commands/local/generate_event/api/cli.py | 43 - samcli/commands/local/generate_event/cli.py | 38 +- .../local/generate_event/dynamodb/cli.py | 31 - .../local/generate_event/event_generation.py | 216 ++++ .../local/generate_event/kinesis/__init__.py | 0 .../local/generate_event/kinesis/cli.py | 48 - .../local/generate_event/s3/__init__.py | 0 .../commands/local/generate_event/s3/cli.py | 39 - .../local/generate_event/schedule/__init__.py | 0 .../local/generate_event/schedule/cli.py | 31 - .../local/generate_event/sns/__init__.py | 0 .../commands/local/generate_event/sns/cli.py | 39 - samcli/commands/local/invoke/cli.py | 37 +- samcli/commands/local/lib/debug_context.py | 21 + samcli/commands/local/lib/events.py | 306 ----- .../generated_sample_events}/__init__.py | 0 .../event-mapping.json | 747 +++++++++++ .../lib/generated_sample_events/events.py | 130 ++ .../alexa-skills-kit/AlexaEndSession.json | 36 + .../alexa-skills-kit/AlexaIntentAnswer.json | 62 + .../AlexaIntentGetNewFact.json | 42 + .../AlexaIntentMyColorIs.json | 48 + .../alexa-skills-kit/AlexaIntentRecipe.json | 47 + .../alexa-skills-kit/AlexaStartSession.json | 38 + .../AlexaSmartHomeControlTurnOffRequest.json | 15 + .../AlexaSmartHomeControlTurnOnRequest.json | 15 + .../AlexaSmartHomeDiscoveryRequest.json | 11 + .../apigateway/ApiGatewayAuthorizer.json | 5 + .../events/apigateway/AwsProxy.json | 62 + .../events/batch/BatchGetJob.json | 6 + .../events/batch/BatchSubmitJob.json | 3 + .../cloudformation/CfnCreateRequest.json | 16 + .../events/cloudfront/CloudFrontABTest.json | 36 + .../CloudFrontAccessRequestInResponse.json | 42 + .../cloudfront/CloudFrontHttpRedirect.json | 30 + .../CloudFrontModifyQueryString.json | 37 + .../CloudFrontModifyResponseHeader.json | 35 + ...tMultipleRemoteCallsAggregateResponse.json | 30 + ...NormalizeQuerystringToImproveCacheHit.json | 37 + .../CloudFrontRedirectOnViewerCountry.json | 30 + ...loudFrontRedirectUnauthenticatedUsers.json | 24 + .../CloudFrontResponseGeneration.json | 24 + .../CloudFrontServeObjectOnViewerDevice.json | 48 + .../CloudFrontSimpleRemoteCall.json | 36 + .../events/cloudwatch/CloudwatchLogs.json | 5 + .../events/cloudwatch/ScheduledEvent.json | 12 + .../events/codecommit/CodeCommit.json | 27 + .../events/codepipeline/CodePipeline.json | 45 + .../events/cognito/CognitoSyncTrigger.json | 20 + .../ConfigurationItemChangeNotification.json | 12 + ...edConfigurationItemChangeNotification.json | 12 + .../events/config/PeriodicRule.json | 12 + .../events/dynamodb/DynamoDBUpdate.json | 93 ++ .../events/kinesis/Kinesis.json | 20 + .../events/kinesis/KinesisFirehose.json | 12 + .../kinesis/KinesisFirehoseApachelog.json | 11 + ...inesisFirehoseCloudwatchLogsProcessor.json | 27 + .../KinesisFirehoseStreamsAsSource.json | 19 + .../events/kinesis/KinesisFirehoseSyslog.json | 16 + .../events/lex/LexBookCar.json | 23 + .../events/lex/LexBookHotel.json | 22 + .../events/lex/LexMakeAppointment.json | 21 + .../events/lex/LexOrderFlowers.json | 21 + .../rekognition/RekognitionS3Request.json | 38 + .../events/s3/S3Delete.json | 36 + .../events/s3/S3Put.json | 38 + .../events/ses/SesEmailReceiving.json | 97 ++ .../events/sns/Sns.json | 31 + .../events/sqs/Sqs.json | 20 + .../stepfunctions/StepFunctionsError.json | 1 + samcli/commands/local/lib/local_lambda.py | 32 +- .../local/lib/local_lambda_service.py | 2 +- samcli/commands/local/start_api/cli.py | 19 +- samcli/commands/local/start_lambda/cli.py | 18 +- samcli/commands/validate/validate.py | 8 +- samcli/local/docker/container.py | 13 +- samcli/local/docker/lambda_container.py | 89 +- .../{{cookiecutter.project_name}}/pom.xml | 2 +- .../src/main/java/helloworld/App.java | 2 +- .../{{cookiecutter.project_name}}/README.md | 2 +- .../hello_world/app.js | 53 +- .../hello_world/package.json | 4 +- .../template.yaml | 2 +- .../hello_world/app.py | 82 +- .../template.yaml | 6 +- .../tests/unit/test_handler.py | 95 +- .../local_lambda_invoke_service.py | 4 - samcli/local/lambdafn/env_vars.py | 9 +- samcli/local/lambdafn/runtime.py | 12 +- .../local/lib/test_local_api_service.py | 12 +- .../commands/local/lib/test_local_lambda.py | 2 +- .../local/apigw/test_local_apigw_service.py | 4 +- .../local/docker/test_lambda_container.py | 6 +- .../test_local_lambda_invoke.py | 23 +- .../local/generate_event}/__init__.py | 0 .../local/generate_event/test_cli_integ.py | 19 + .../local/invoke/test_integrations_cli.py | 22 + .../testdata/invoke/runtimes/java8/pom.xml | 2 +- tests/unit/cli/test_main.py | 2 +- .../local/cli_common/test_invoke_context.py | 111 +- .../local/generate_event/api/__init__.py | 0 .../local/generate_event/api/test_cli.py | 23 - .../local/generate_event/dynamodb/__init__.py | 0 .../local/generate_event/dynamodb/test_cli.py | 20 - .../local/generate_event/kinesis/__init__.py | 0 .../local/generate_event/kinesis/test_cli.py | 25 - .../local/generate_event/s3/__init__.py | 0 .../local/generate_event/s3/test_cli.py | 22 - .../local/generate_event/schedule/__init__.py | 0 .../local/generate_event/schedule/test_cli.py | 20 - .../local/generate_event/sns/__init__.py | 0 .../local/generate_event/sns/test_cli.py | 22 - .../generate_event/test_event_generation.py | 157 +++ tests/unit/commands/local/invoke/test_cli.py | 98 +- .../commands/local/lib/test_debug_context.py | 67 + tests/unit/commands/local/lib/test_events.py | 286 ---- .../commands/local/lib/test_local_lambda.py | 140 +- .../unit/commands/local/start_api/test_cli.py | 18 +- .../commands/local/start_lambda/test_cli.py | 17 +- tests/unit/commands/validate/test_cli.py | 18 + tests/unit/local/docker/test_container.py | 14 +- .../local/docker/test_lambda_container.py | 87 +- .../test_local_lambda_invoke_service.py | 17 - tests/unit/local/lambdafn/test_env_vars.py | 1 + tests/unit/local/lambdafn/test_runtime.py | 23 +- 140 files changed, 5180 insertions(+), 2564 deletions(-) create mode 100644 docs/advanced_usage.rst create mode 100644 docs/installation.rst create mode 100644 docs/usage.rst delete mode 100644 samcli/commands/local/generate_event/api/cli.py delete mode 100644 samcli/commands/local/generate_event/dynamodb/cli.py create mode 100644 samcli/commands/local/generate_event/event_generation.py delete mode 100644 samcli/commands/local/generate_event/kinesis/__init__.py delete mode 100644 samcli/commands/local/generate_event/kinesis/cli.py delete mode 100644 samcli/commands/local/generate_event/s3/__init__.py delete mode 100644 samcli/commands/local/generate_event/s3/cli.py delete mode 100644 samcli/commands/local/generate_event/schedule/__init__.py delete mode 100644 samcli/commands/local/generate_event/schedule/cli.py delete mode 100644 samcli/commands/local/generate_event/sns/__init__.py delete mode 100644 samcli/commands/local/generate_event/sns/cli.py create mode 100644 samcli/commands/local/lib/debug_context.py delete mode 100644 samcli/commands/local/lib/events.py rename samcli/commands/local/{generate_event/api => lib/generated_sample_events}/__init__.py (100%) create mode 100644 samcli/commands/local/lib/generated_sample_events/event-mapping.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events.py create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaEndSession.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentAnswer.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentGetNewFact.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentMyColorIs.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentRecipe.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaStartSession.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOffRequest.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOnRequest.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeDiscoveryRequest.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/apigateway/ApiGatewayAuthorizer.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/apigateway/AwsProxy.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/batch/BatchGetJob.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/batch/BatchSubmitJob.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudformation/CfnCreateRequest.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontABTest.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontAccessRequestInResponse.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontHttpRedirect.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyQueryString.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyResponseHeader.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontMultipleRemoteCallsAggregateResponse.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontNormalizeQuerystringToImproveCacheHit.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectOnViewerCountry.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectUnauthenticatedUsers.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontResponseGeneration.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontServeObjectOnViewerDevice.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontSimpleRemoteCall.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudwatch/CloudwatchLogs.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cloudwatch/ScheduledEvent.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/codecommit/CodeCommit.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/codepipeline/CodePipeline.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/cognito/CognitoSyncTrigger.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/config/ConfigurationItemChangeNotification.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/config/OversizedConfigurationItemChangeNotification.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/config/PeriodicRule.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/dynamodb/DynamoDBUpdate.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/Kinesis.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehose.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseApachelog.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseCloudwatchLogsProcessor.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseStreamsAsSource.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseSyslog.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/lex/LexBookCar.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/lex/LexBookHotel.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/lex/LexMakeAppointment.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/lex/LexOrderFlowers.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/rekognition/RekognitionS3Request.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/s3/S3Delete.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/s3/S3Put.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/ses/SesEmailReceiving.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/sns/Sns.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/sqs/Sqs.json create mode 100644 samcli/commands/local/lib/generated_sample_events/events/stepfunctions/StepFunctionsError.json rename {samcli/commands/local/generate_event/dynamodb => tests/integration/local/generate_event}/__init__.py (100%) create mode 100644 tests/integration/local/generate_event/test_cli_integ.py delete mode 100644 tests/unit/commands/local/generate_event/api/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/api/test_cli.py delete mode 100644 tests/unit/commands/local/generate_event/dynamodb/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/dynamodb/test_cli.py delete mode 100644 tests/unit/commands/local/generate_event/kinesis/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/kinesis/test_cli.py delete mode 100644 tests/unit/commands/local/generate_event/s3/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/s3/test_cli.py delete mode 100644 tests/unit/commands/local/generate_event/schedule/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/schedule/test_cli.py delete mode 100644 tests/unit/commands/local/generate_event/sns/__init__.py delete mode 100644 tests/unit/commands/local/generate_event/sns/test_cli.py create mode 100644 tests/unit/commands/local/generate_event/test_event_generation.py create mode 100644 tests/unit/commands/local/lib/test_debug_context.py delete mode 100644 tests/unit/commands/local/lib/test_events.py diff --git a/DEVELOPMENT_GUIDE.rst b/DEVELOPMENT_GUIDE.rst index 59a6f51c35..71beeccbe3 100644 --- a/DEVELOPMENT_GUIDE.rst +++ b/DEVELOPMENT_GUIDE.rst @@ -7,6 +7,8 @@ This document will make your life easier by helping you setup a development envi 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 ----------------- @@ -17,6 +19,8 @@ Follow the idioms from this `excellent cheatsheet`_ to make sure your code is co 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`` @@ -105,3 +109,4 @@ best practices that we have learnt over time. .. _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/ diff --git a/MANIFEST.in b/MANIFEST.in index beb7271dce..d7411b8c8e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,4 +3,6 @@ include requirements/base.txt include requirements/dev.txt recursive-include samcli/local/init/templates * recursive-include samcli/lib *.json +recursive-include samcli/commands/local/lib/generated_sample_events *.json prune tests + diff --git a/README.rst b/README.rst index 6fcb47a0dc..2d070e29f6 100644 --- a/README.rst +++ b/README.rst @@ -1,1027 +1,120 @@ -.. 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. - -- `SAM CLI (Beta) <#sam-cli-beta>`__ - - - `Main features <#main-features>`__ - - `Installation <#installation>`__ - - - `Prerequisites <#prerequisites>`__ - - `Windows, Linux, macOS with PIP <#windows-linux-macos-with-pip>`__ - - `Upgrade from 0.2.11, below or above <#upgrading>`__ - - - `Usage <#usage>`__ - - - `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-source-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>`__ - - `Advanced <#advanced>`__ - - - `Compiled Languages <#compiled-languages>`__ - - - `Java <#java>`__ - - `.NET Core <#net_core>`__ - - - `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>`__ - - - `Advanced/Custom Installation <#advanced-installations>`__ - - - `Build From Source <#build-from-source>`__ - - `Install with PyEnv <#install-with-pyenv>`__ - - `Troubleshooting <#troubleshooting>`__ - - - `Mac <#mac-issues>`__ - - `Project Status <#project-status>`__ - - `Contributing <#contributing>`__ - - `A special thank you <#a-special-thank-you>`__ - - `Examples <#examples>`__ - -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, 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`` - -Installation ------------- - -Prerequisites -~~~~~~~~~~~~~ - -- Docker -- Python2.7 or Python3.6 - -Running Serverless projects and functions locally with SAM CLI requires -Docker to be installed and running. SAM CLI will use the ``DOCKER_HOST`` -environment variable to contact the docker daemon. - -- **macOS**: `Docker for - Mac `__ -- **Windows**: `Docker - For Windows (create an account & follow through to download from the Docker Store) `__ -- **Linux**: Check your distro’s package manager (e.g. yum install docker) - -**Note for macOS and Windows users**: SAM CLI requires that the project directory -(or any parent directory) is listed in `Docker file sharing options `__. - -Verify that docker is working, and that you can run docker commands from -the CLI (e.g. `docker ps`). You do not need to install/fetch/pull any -containers – SAM CLI will do it automatically as required. - -Windows, Linux, macOS with PIP -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Verify Python Version is 2.7 or 3.6. - -.. code:: bash - - $ python --version - -The easiest way to install ``sam`` is to use -`PIP `__. - -.. code:: bash - - $ pip install --user aws-sam-cli - -**Adjust your PATH** to include Python scripts installed under User's directory. - -**NOTE**: As explained in the `Python Developer's Guide `__, the User's directory where the scripts are installed is ``~/.local/bin`` for Unix/Mac and ``%APPDATA%\Python\Scripts`` for Windows. - -The Python command can help to detect the correct path. However, in Unix/Mac systems the command ``python -m site --user-base`` typically print ``~/.local`` path, so that you'll need to add ``/bin`` to obtain the script path, while in Windows systems the command ``py -m site --user-site`` typically print ``%APPDATA%\Roaming\Python\site-packages``, so you'll need to remove the last ``\site-packages`` folder and replace it with the ``\Scripts`` one. - -.. code:: bash - - # Find your Python User Base path (where Python --user will install packages/scripts) - $ USER_BASE_PATH=$(python -m site --user-base) - - # Update your preferred shell configuration - ## Standard bash --> ~/.bash_profile - ## ZSH --> ~/.zshrc - $ export PATH=$PATH:$USER_BASE_PATH/bin - -Restart or Open up a new terminal and verify that the installation worked: - -.. code:: bash - - # Restart current shell - $ exec "$SHELL" - $ sam --version - -Upgrading -~~~~~~~~~~ - -``sam`` can be upgraded via pip: - -.. code:: bash - - $ pip install --user --upgrade aws-sam-cli - -Previous CLI Versions must be uninstalled first (0.2.11 or below) and then follow the `Installation <#windows-linux-macos-with-pip>`__ steps above: - -.. code:: bash - - $ npm uninstall -g aws-sam-local - -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 -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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: - -:: - - 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): - -:: - - import boto3 - - # Set "running_locally" flag if you are running the integration test locally - if running_locally: - - # Create Lambda SDK client to connect to appropriate Lambda endpoint - lambda_client = boto3.client('lambda', - endpoint_url="http://127.0.0.1:3001", - use_ssl=False, - verify=False, - config=Config(signature_version=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 source payloads -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To make local development and testing of Lambda functions easier, you -can generate mock/sample event payloads for the following services: - -- S3 -- Kinesis -- DynamoDB -- Cloudwatch Scheduled Event -- Cloudtrail -- API Gateway - -**Syntax** - -.. code:: bash - - $ sam local generate-event - -Also, you can invoke an individual lambda function locally from a sample -event payload - Here’s an example using S3: - -.. code:: bash - - $ sam local generate-event s3 --bucket --key | sam local invoke - -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. - -.. 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: - -:: - - { - "version": "0.2.0", - "configurations": [ - { - "name": "Attach to SAM Local", - "type": "node", - "request": "attach", - "address": "localhost", - "port": 5858, - "localRoot": "${workspaceRoot}", - "remoteRoot": "/var/task", - "protocol": "legacy" - } - ] - } - -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. - -Debugging Python functions -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Unlike Node.JS and Java, Python requires you to enable remote debugging -in your Lambda function code. If you enable debugging with -``--debug-port`` or ``-d`` for a function that uses one of the Python -runtimes, SAM CLI will just map through that port from your host machine -through to the Lambda runtime container. You will need to enable remote -debugging in your function code. To do this, use a python package such -as `remote-pdb `__. When -configuring the host the debugger listens on in your code, make sure to -use ``0.0.0.0`` not ``127.0.0.1`` to allow Docker to map through the -port to your host machine. - - Please note, due to a `open - bug `__ with - Visual Studio Code, you may get a - ``Debug adapter process has terminated unexpectedly`` error when - attempting to debug Python applications with this IDE. Please track - the `GitHub - issue `__ for - updates. - -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: - -:: - - sam logs -n HelloWorldFunction --stack-name mystack - -**Basic Usage: Using Lambda Function name** - -Or, you can fetch logs using the function's name - -:: - - 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. - -:: - - 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 - -:: - - 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 - -:: - - 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 - -Advanced --------- - -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: - -:: - - 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. - -:: - - // 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: - -:: - - // 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 ``--env-vars`` argument of ``invoke`` or ``start-api`` commands to -provide a JSON file that contains values for environment variables -defined in your function. 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" - -Advanced installations ----------------------- - -Build From Source -~~~~~~~~~~~~~~~~~ - -First, install Python(2.7 or 3.6) on your machine, then run the following: - -.. code:: bash - - # Clone the repository - $ git clone git@github.com/awslabs/aws-sam-cli.git - - # cd into the git - $ cd aws-sam-cli - - # pip install the repository - $ pip install --user -e . - -Install with PyEnv -~~~~~~~~~~~~~~~~~~ -.. code:: bash - - # Install PyEnv (https://github.com/pyenv/pyenv#installation) - $ brew update - $ brew install pyenv - - # Initialize pyenv using bash_profile - $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi\nexport PATH="~/.pyenv/bin:$PATH"' >> ~/.bash_profile - # or using zshrc - $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi\nexport PATH="~/.pyenv/bin:$PATH"' >> ~/.zshrc - - # restart the shell - $ exec "$SHELL" - - # Install Python 2.7 - $ pyenv install 2.7.14 - $ pyenv local 2.7.14 - - # Install the CLI - $ pip install --user aws-sam-cli - - # Verify your installation worked - $ sam –version - -Troubleshooting -~~~~~~~~~~~~~~~ - -Mac Issues -^^^^^^^^^^ - -1. **TLSV1_ALERT_PROTOCOL_VERSION**: - -If you get an error something similar to: - -:: - - Could not fetch URL https://pypi.python.org/simple/click/: There was a problem confirming the ssl certificate: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590) - skipping - -then you are probably using the default version of Python that came with -your Mac. This is outdated. So make sure you install Python again using -homebrew and try again: - -.. code:: bash - - $ brew install python - -Once installed then repeat the `Installation process <#windows-linux-macos-with-pip>`_ - -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`` - - [ ] ``dotnetcore1.0`` - - [x] ``dotnetcore2.0`` - - [x] ``dotnetcore2.1`` - -- [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] ``dotnetcore1.0`` - - [x] ``dotnetcore2.0`` - -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](https://github.com/mhart). - - -.. raw:: html - - - -.. |Build Status| image:: https://travis-ci.org/awslabs/aws-sam-local.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-local.svg?maxAge=2592000 -.. |GitHub-release| image:: https://img.shields.io/github/release/awslabs/aws-sam-local.svg?maxAge=2592000 -.. |PyPI version| image:: https://badge.fury.io/py/aws-sam-cli.svg - +.. 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 <#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] ``go1.x`` + - [ ] ``dotnetcore1.0`` + - [x] ``dotnetcore2.0`` + - [x] ``dotnetcore2.1`` + +- [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] ``go1.x`` + - [x] ``dotnetcore1.0`` + - [x] ``dotnetcore2.0`` + +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/docs/advanced_usage.rst b/docs/advanced_usage.rst new file mode 100644 index 0000000000..eaeb72e819 --- /dev/null +++ b/docs/advanced_usage.rst @@ -0,0 +1,272 @@ + +============== +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/deploying_serverless_applications.rst b/docs/deploying_serverless_applications.rst index 92eab4fd82..9a47ec8cc9 100644 --- a/docs/deploying_serverless_applications.rst +++ b/docs/deploying_serverless_applications.rst @@ -1,48 +1,56 @@ -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 file path/template.yaml \ - --output-template-file packaged.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-template.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. - +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 packaged.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-template.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 index d90c07461e..1631e78a51 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -1,58 +1,66 @@ -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. - +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 new file mode 100644 index 0000000000..66c6dc88bf --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,210 @@ +============== +Installation +============== + +Prerequisites +------------- + +- Docker +- Python2.7 or Python3.6 +- `The AWS CLI `__ + +Running Serverless projects and functions locally with SAM CLI requires +Docker to be installed and running. SAM CLI will use the ``DOCKER_HOST`` +environment variable to contact the docker daemon. + +- **macOS**: `Docker for + Mac `__ +- **Windows**: `Docker + For Windows (create an account & follow through to download from the Docker Store) `__ +- **Linux**: Check your distro’s package manager (e.g. yum install docker) + +**Note for macOS and Windows users**: SAM CLI requires that the project directory +(or any parent directory) is listed in `Docker file sharing options `__. + +Verify that docker is working, and that you can run docker commands from +the CLI (e.g. `docker ps`). You do not need to install/fetch/pull any +containers – SAM CLI will do it automatically as required. + +Using PIP +--------- + +**Step 1.** Verify Python Version is 2.7 or 3.6. + + +.. code:: bash + + $ python --version + +If not installed, go `download python and install `_ + +**Step 2.** Verify Pip is installed. + +The easiest way to install ``sam`` is to use +`PIP `__. + +.. code:: bash + + $ pip --version + +If not installed, `download and install pip `_ + +**Step 3.** Install aws-sam-cli + +.. code:: bash + + $ pip install --user aws-sam-cli + +**Step 4.** **Adjust your PATH** to include Python scripts installed under User's directory. + +macOS & Linux +^^^^^^^^^^^^^ + +In Unix/Mac systems the command ``python -m site --user-base`` typically print ``~/.local`` path, so that you'll need to add ``/bin`` to obtain the script path + +**NOTE**: As explained in the `Python Developer's Guide `__, the User's directory where the scripts are installed is ``~/.local/bin`` for Unix/Mac. + + +.. code:: bash + + # Find your Python User Base path (where Python --user will install packages/scripts) + $ USER_BASE_PATH=$(python -m site --user-base) + + # Update your preferred shell configuration + -- Standard bash --> ~/.bash_profile + -- ZSH --> ~/.zshrc + $ export PATH=$PATH:$USER_BASE_PATH/bin + +Restart or Open up a new terminal and verify that the installation worked: + +.. code:: bash + + # Restart current shell + $ exec "$SHELL" + $ sam --version + +Windows +^^^^^^^ + +In Windows systems the command ``py -m site --user-site`` typically print ``%APPDATA%\Roaming\Python\site-packages``, so you'll need to remove the last ``\site-packages`` folder and replace it with the ``\Scripts`` one. + +.. code:: bash + + $ python -m site --user-base + +Using file explorer, go to the folder indicated in the output, and look for the ``Scripts`` folder. Visually confirm that sam Application is inside this folder. + +Copy the File Path. + +**NOTE**: As explained in the `Python Developer's Guide `__, the User's directory where the scripts are installed is ``%APPDATA%\Python\Scripts`` for Windows. + +Seach Windows for ``Edit the system environment variables``. + +Select **Environmental Variables**. + +Under **System variables**, select **Path**. + +Select **New** and enter the file path to the Python Scripts folder. + +**Step 5.** Verify that sam is installed + +Restart or Open up a new terminal and verify that the installation worked: + +.. code:: bash + + # Restart current shell + $ sam --version + +Upgrading +--------- + +``sam`` can be upgraded via pip: + +.. code:: bash + + $ pip install --user --upgrade aws-sam-cli + +Previous CLI Versions must be uninstalled first (0.2.11 or below) and then follow the `Installation <#windows-linux-macos-with-pip>`__ steps above: + +.. code:: bash + + $ npm uninstall -g aws-sam-local + +Advanced installations +---------------------- + +Build From Source +^^^^^^^^^^^^^^^^^ + +First, install Python(2.7 or 3.6) on your machine, then run the following: + +.. code:: bash + + # Clone the repository + $ git clone git@github.com:awslabs/aws-sam-cli.git + + # cd into the git + $ cd aws-sam-cli + + # pip install the repository + $ pip install --user -e . + +Install with PyEnv +^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + # Install PyEnv (https://github.com/pyenv/pyenv#installation) + $ brew update + $ brew install pyenv + + # Initialize pyenv using bash_profile + $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi\nexport PATH="~/.pyenv/bin:$PATH"' >> ~/.bash_profile + # or using zshrc + $ echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi\nexport PATH="~/.pyenv/bin:$PATH"' >> ~/.zshrc + + # restart the shell + $ exec "$SHELL" + + # Install Python 2.7 + $ pyenv install 2.7.14 + $ pyenv local 2.7.14 + + # Install the CLI + $ pip install --user aws-sam-cli + + # Verify your installation worked + $ sam –-version + +Troubleshooting +--------------- + +Mac Issues +^^^^^^^^^^ + +1. **TLSV1_ALERT_PROTOCOL_VERSION**: + +If you get an error something similar to: + +:: + + Could not fetch URL https://pypi.python.org/simple/click/: There was a problem confirming the ssl certificate: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:590) - skipping + +then you are probably using the default version of Python that came with +your Mac. This is outdated. So make sure you install Python again using +homebrew and try again: + +.. code:: bash + + $ brew install python + +Once installed then repeat the `Installation process <#windows-linux-macos-with-pip>`_ + +Learn More +========== + +- `Project Overview <../README.rst>`__ +- `Getting started with SAM and the SAM CLI `__ +- `Usage `__ +- `Packaging and deploying your application `__ +- `Advanced `__ diff --git a/docs/running_and_debugging_serverless_applications_locally.rst b/docs/running_and_debugging_serverless_applications_locally.rst index 50cb69c0f7..7fa47250c3 100644 --- a/docs/running_and_debugging_serverless_applications_locally.rst +++ b/docs/running_and_debugging_serverless_applications_locally.rst @@ -1,113 +1,113 @@ -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 - +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.rst b/docs/usage.rst new file mode 100644 index 0000000000..48c30a2393 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,553 @@ +===== +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-source-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 source payloads +------------------------------------- + +To make local development and testing of Lambda functions easier, you +can generate mock/sample event payloads for the following services: + +- S3 +- Kinesis Streams +- DynamoDB +- Cloudwatch Scheduled Event +- API Gateway +- SNS + +**Syntax** + +.. code:: bash + + $ sam local generate-event + +Also, you can invoke an individual lambda function locally from a sample +event payload - Here’s an example using S3: + +.. code:: bash + + $ sam local generate-event s3 --bucket --key | sam local invoke + +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. + +.. 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: + +.. code:: json + + { + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to SAM CLI", + "type": "node", + "request": "attach", + "address": "localhost", + "port": 5858, + "localRoot": "${workspaceRoot}", + "remoteRoot": "/var/task", + "protocol": "legacy" + } + ] + } + +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. + +Debugging Python functions +-------------------------- + +Unlike Node.JS and Java, Python requires you to enable remote debugging +in your Lambda function code. If you enable debugging with +``--debug-port`` or ``-d`` for a function that uses one of the Python +runtimes, SAM CLI will just map through that port from your host machine +through to the Lambda runtime container. You will need to enable remote +debugging in your function code. To do this, use a python package such +as `remote-pdb `__. When +configuring the host the debugger listens on in your code, make sure to +use ``0.0.0.0`` not ``127.0.0.1`` to allow Docker to map through the +port to your host machine. + + Please note, due to a `open + bug `__ with + Visual Studio Code, you may get a + ``Debug adapter process has terminated unexpectedly`` error when + attempting to debug Python applications with this IDE. Please track + the `GitHub + issue `__ for + updates. + +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": [], + }, + ] + } + + +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/requirements/base.txt b/requirements/base.txt index 2574b4ac10..87457929b9 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,3 +9,6 @@ aws-sam-translator==1.6.0 docker>=3.3.0 dateparser~=0.7 python-dateutil~=2.6 +pathlib2~=2.3.2; python_version<"3.4" +pystache~=0.5 + diff --git a/requirements/dev.txt b/requirements/dev.txt index 19973793c2..048429dfee 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ pylint==1.7.2 pytest==3.0.7 py==1.4.33 mock==2.0.0 -requests>=2.19.0 +requests==2.19.0 parameterized==0.6.1 pathlib2==2.3.2; python_version<"3.4" futures==3.2.0; python_version<"3.2.3" diff --git a/samcli/__init__.py b/samcli/__init__.py index 1a0028355b..abe4beef25 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = '0.5.0' +__version__ = '0.6.0' diff --git a/samcli/commands/local/cli_common/invoke_context.py b/samcli/commands/local/cli_common/invoke_context.py index 489ed604cb..3e2b4fa459 100644 --- a/samcli/commands/local/cli_common/invoke_context.py +++ b/samcli/commands/local/cli_common/invoke_context.py @@ -2,21 +2,31 @@ Reads CLI arguments and performs necessary preparation to be able to run the function """ -import os -import sys +import errno import json +import sys +import os import yaml -import docker +import docker import requests from samcli.yamlhelper import yaml_parse from samcli.commands.local.lib.local_lambda import LocalLambdaRunner +from samcli.commands.local.lib.debug_context import DebugContext from samcli.local.lambdafn.runtime import LambdaRuntime from samcli.local.docker.manager import ContainerManager -from .user_exceptions import InvokeContextException +from .user_exceptions import InvokeContextException, DebugContextException from ..lib.sam_function_provider import SamFunctionProvider +# This is an attempt to do a controlled import. pathlib is in the +# Python standard library starting at 3.4. This will import pathlib2, +# which is a backport of the Python Standard Library pathlib +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + class InvokeContext(object): """ @@ -38,43 +48,62 @@ def __init__(self, template_file, function_identifier=None, env_vars_file=None, - debug_port=None, - debug_args=None, docker_volume_basedir=None, docker_network=None, log_file=None, skip_pull_image=None, - aws_profile=None): + aws_profile=None, + debug_port=None, + debug_args=None, + debugger_path=None, + aws_region=None): """ Initialize the context - :param string template_file: Name or path to template - :param string function_identifier: Identifier of the function to invoke - :param string env_vars_file: Path to a file containing values for environment variables - :param int debug_port: Port to bind the debugger - :param string docker_volume_basedir: Directory for the Docker volume - :param string docker_network: Docker network identifier - :param string log_file: Path to a file to send container output to. If the file does not exist, it will be + Parameters + ---------- + template_file str + Name or path to template + function_identifier str + Identifier of the function to invoke + env_vars_file str + Path to a file containing values for environment variables + docker_volume_basedir str + Directory for the Docker volume + docker_network str + Docker network identifier + log_file str + Path to a file to send container output to. If the file does not exist, it will be created - :param bool skip_pull_image: Should we skip pulling the Docker container image? - :param string aws_profile: Name of the profile to fetch AWS credentials from + skip_pull_image bool + Should we skip pulling the Docker container image? + aws_profile str + Name of the profile to fetch AWS credentials from + debug_port int + Port to bind the debugger to + debug_args str + Additional arguments passed to the debugger + debugger_path str + Path to the directory of the debugger to mount on Docker """ - self._template_file = template_file self._function_identifier = function_identifier self._env_vars_file = env_vars_file - self._debug_port = debug_port - self._debug_args = debug_args self._docker_volume_basedir = docker_volume_basedir self._docker_network = docker_network self._log_file = log_file self._skip_pull_image = skip_pull_image self._aws_profile = aws_profile + self._aws_region = aws_region + self._debug_port = debug_port + self._debug_args = debug_args + self._debugger_path = debugger_path self._template_dict = None self._function_provider = None self._env_vars_value = None self._log_file_handle = None + self._debug_context = None def __enter__(self): """ @@ -90,6 +119,10 @@ def __enter__(self): self._env_vars_value = self._get_env_vars_value(self._env_vars_file) self._log_file_handle = self._setup_log_file(self._log_file) + self._debug_context = self._get_debug_context(self._debug_port, + self._debug_args, + self._debugger_path) + self._check_docker_connectivity() return self @@ -146,9 +179,9 @@ def local_lambda_runner(self): function_provider=self._function_provider, cwd=self.get_cwd(), env_vars_values=self._env_vars_value, - debug_port=self._debug_port, - debug_args=self._debug_args, - aws_profile=self._aws_profile) + debug_context=self._debug_context, + aws_profile=self._aws_profile, + aws_region=self._aws_region) @property def stdout(self): @@ -271,6 +304,46 @@ def _setup_log_file(log_file): return open(log_file, 'wb') + @staticmethod + def _get_debug_context(debug_port, debug_args, debugger_path): + """ + Creates a DebugContext if the InvokeContext is in a debugging mode + + Parameters + ---------- + debug_port int + Port to bind the debugger to + debug_args str + Additional arguments passed to the debugger + debugger_path str + Path to the directory of the debugger to mount on Docker + + Returns + ------- + samcli.commands.local.lib.debug_context.DebugContext + Object representing the DebugContext + + Raises + ------ + samcli.commands.local.cli_common.user_exceptions.DebugContext + When the debugger_path is not valid + """ + if debug_port and debugger_path: + try: + debugger = Path(debugger_path).resolve(strict=True) + except OSError as error: + if error.errno == errno.ENOENT: + raise DebugContextException("'{}' could not be found.".format(debugger_path)) + else: + raise error + + # We turn off pylint here due to https://github.com/PyCQA/pylint/issues/1660 + if not debugger.is_dir(): # pylint: disable=no-member + raise DebugContextException("'{}' should be a directory with the debugger in it.".format(debugger_path)) + debugger_path = str(debugger) + + return DebugContext(debug_port=debug_port, debug_args=debug_args, debugger_path=debugger_path) + @staticmethod def _check_docker_connectivity(docker_client=None): """ diff --git a/samcli/commands/local/cli_common/options.py b/samcli/commands/local/cli_common/options.py index 0a1cb9724e..aecb13bc02 100644 --- a/samcli/commands/local/cli_common/options.py +++ b/samcli/commands/local/cli_common/options.py @@ -108,6 +108,9 @@ def invoke_common_options(f): "port on localhost.", envvar="SAM_DEBUG_PORT"), + click.option('--debugger-path', + help="Host path to a debugger that will be mounted into the Lambda container."), + click.option('--debug-args', help="Additional arguments to be passed to the debugger.", envvar="DEBUGGER_ARGS"), @@ -135,6 +138,9 @@ def invoke_common_options(f): click.option('--profile', help="Specify which AWS credentials profile to use."), + click.option('--region', + help="Specify which AWS region to use."), + ] # Reverse the list to maintain ordering of options in help text printed with --help diff --git a/samcli/commands/local/cli_common/user_exceptions.py b/samcli/commands/local/cli_common/user_exceptions.py index 43402327c1..b2b739856c 100644 --- a/samcli/commands/local/cli_common/user_exceptions.py +++ b/samcli/commands/local/cli_common/user_exceptions.py @@ -24,3 +24,10 @@ class SamTemplateNotFoundException(UserException): The SAM Template provided could not be found """ pass + + +class DebugContextException(UserException): + """ + Something went wrong when creating the DebugContext + """ + pass diff --git a/samcli/commands/local/generate_event/api/cli.py b/samcli/commands/local/generate_event/api/cli.py deleted file mode 100644 index 29d64ca7d8..0000000000 --- a/samcli/commands/local/generate_event/api/cli.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Generates an Api Gateway Event for a Lambda Invoke -""" -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_api_event - - -@click.command("api", short_help="Generates a sample Amazon API Gateway event") -@click.option("--method", "-m", - type=str, - default="POST", - help='HTTP method (default: "POST")') -@click.option("--body", "-b", - type=str, - default="{ \"test\": \"body\"}", - help='HTTP body (default: "{ \"test\": \"body\"}")') -@click.option("--resource", "-r", - type=str, - default="/{proxy+}", - help='API Gateway resource name (default: "/{proxy+}")') -@click.option("--path", "-p", - type=str, - default="/examplepath", - help=' HTTP path (default: "/examplepath")') -@cli_framework_options -@pass_context -def cli(ctx, method, body, resource, path): - # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, method, body, resource, path) # pragma: no cover - - -def do_cli(ctx, method, body, resource, path): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - event_dict = generate_api_event(method, body, resource, path) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/generate_event/cli.py b/samcli/commands/local/generate_event/cli.py index cd2c329bff..89f911a661 100644 --- a/samcli/commands/local/generate_event/cli.py +++ b/samcli/commands/local/generate_event/cli.py @@ -1,16 +1,11 @@ """ -Command group for "generate-event" suite for commands. +Sets up the cli for generate-event """ import click -from .s3.cli import cli as s3_cli -from .api.cli import cli as api_cli -from .dynamodb.cli import cli as dynamodb_cli -from .kinesis.cli import cli as kinesis_cli -from .schedule.cli import cli as schedule_cli -from .sns.cli import cli as sns_cli - +from samcli.cli.main import pass_context +from samcli.commands.local.generate_event.event_generation import GenerateEventCommand HELP_TEXT = """ You can use this command to generate sample payloads from different event sources @@ -18,22 +13,23 @@ event sources send to your Lambda functions.\n \b Generate the event that S3 sends to your Lambda function when a new object is uploaded -$ sam local generate-event s3 --bucket --key \n +$ sam local generate-event s3 [put/delete]\n +\b +You can even customize the event by adding parameter flags. To find which flags apply to your command, +run:\n +$ sam local generate-event s3 [put/delete] --help\n +Then you can add in those flags that you wish to customize using\n +$ sam local generate-event s3 [put/delete] --bucket --key \n \b After you generate a sample event, you can use it to test your Lambda function locally -$ sam local generate-event s3 --bucket --key | sam local invoke +$ sam local generate-event s3 [put/delete] --bucket --key | sam local invoke """ -@click.group("generate-event", help=HELP_TEXT) -def cli(): +@click.command(name="generate-event", cls=GenerateEventCommand, help=HELP_TEXT) +@pass_context +def cli(self): + """ + Generate an event for one of the services listed below: + """ pass # pragma: no cover - - -# Add individual commands under this group -cli.add_command(s3_cli) -cli.add_command(api_cli) -cli.add_command(dynamodb_cli) -cli.add_command(kinesis_cli) -cli.add_command(schedule_cli) -cli.add_command(sns_cli) diff --git a/samcli/commands/local/generate_event/dynamodb/cli.py b/samcli/commands/local/generate_event/dynamodb/cli.py deleted file mode 100644 index 6071b5f7e4..0000000000 --- a/samcli/commands/local/generate_event/dynamodb/cli.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Generates a DynamoDB Event for a Lambda Invoke -""" -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_dynamodb_event - - -@click.command("dynamodb", short_help="Generates a sample Amazon DynamoDB event") -@click.option("--region", "-r", - type=str, - default="us-east-1", - help='The region the event should come from (default: "us-east-1")') -@pass_context -@cli_framework_options -def cli(ctx, region): - # All of the logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, region) # pragma: no cover - - -def do_cli(ctx, region): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - event_dict = generate_dynamodb_event(region) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/generate_event/event_generation.py b/samcli/commands/local/generate_event/event_generation.py new file mode 100644 index 0000000000..6529498d29 --- /dev/null +++ b/samcli/commands/local/generate_event/event_generation.py @@ -0,0 +1,216 @@ +""" +Generates the services and commands for selection in SAM CLI generate-event +""" + +import functools +import click + +from samcli.cli.options import debug_option +import samcli.commands.local.lib.generated_sample_events.events as events + + +class ServiceCommand(click.MultiCommand): + """ + Top level command that defines the service provided + + Methods + ---------------- + get_command(self, ctx, cmd_name): + Get the subcommand(s) under a given service name. + list_commands(self, ctx): + List all of the subcommands + """ + + def __init__(self, events_lib, *args, **kwargs): + """ + Constructor for the ServiceCommand class + + Parameters + ---------- + events_lib: samcli.commands.local.lib.generated_sample_events.events + The events library that allows for CLI population and substitution + args: list + any arguments passed in before kwargs + kwargs: dict + dictionary containing the keys/values used to construct the ServiceCommand + """ + + super(ServiceCommand, self).__init__(*args, **kwargs) + if not events_lib: + raise ValueError("Events library is necessary to run this command") + + self.events_lib = events_lib + self.all_cmds = self.events_lib.event_mapping + + def get_command(self, ctx, cmd_name): + """ + gets the subcommands under the service name + + Parameters + ---------- + ctx : Context + the context object passed into the method + cmd_name : str + the service name + Returns + ------- + EventTypeSubCommand: + returns subcommand if successful, None if not. + """ + + if cmd_name not in self.all_cmds: + return None + return EventTypeSubCommand(self.events_lib, cmd_name, self.all_cmds[cmd_name]) + + def list_commands(self, ctx): + """ + lists the service commands available + + Parameters + ---------- + ctx: Context + the context object passed into the method + Returns + ------- + list + returns sorted list of the service commands available + """ + + return sorted(self.all_cmds.keys()) + + +class EventTypeSubCommand(click.MultiCommand): + """ + Class that describes the commands underneath a given service type + + Methods + ---------------- + get_command(self, ctx, cmd_name): + Get the subcommand(s) under a given service name. + list_commands(self, ctx): + List all of the subcommands + """ + + TAGS = 'tags' + + def __init__(self, events_lib, top_level_cmd_name, subcmd_definition, *args, **kwargs): + """ + constructor for the EventTypeSubCommand class + + Parameters + ---------- + events_lib: samcli.commands.local.lib.generated_sample_events.events + The events library that allows for CLI population and substitution + top_level_cmd_name: string + the service name + subcmd_definition: dict + the subcommands and their values underneath the service command + args: tuple + any arguments passed in before kwargs + kwargs: dict + key/value pairs passed into the constructor + """ + + super(EventTypeSubCommand, self).__init__(*args, **kwargs) + self.top_level_cmd_name = top_level_cmd_name + self.subcmd_definition = subcmd_definition + self.events_lib = events_lib + + def get_command(self, ctx, cmd_name): + + """ + gets the Click Commands underneath a service name + + Parameters + ---------- + ctx: Context + context object passed in + cmd_name: string + the service name + Returns + ------- + cmd: Click.Command + the Click Commands that can be called from the CLI + """ + + if cmd_name not in self.subcmd_definition: + return None + parameters = [] + for param_name in self.subcmd_definition[cmd_name][self.TAGS].keys(): + default = self.subcmd_definition[cmd_name][self.TAGS][param_name]["default"] + parameters.append(click.Option( + ["--{}".format(param_name)], + default=default, + help="Specify the {} name you'd like, otherwise the default = {}".format(param_name, default) + )) + + command_callback = functools.partial(self.cmd_implementation, + self.events_lib, + self.top_level_cmd_name, + cmd_name) + cmd = click.Command(name=cmd_name, + help=self.subcmd_definition[cmd_name]["help"], + params=parameters, + callback=command_callback) + + cmd = debug_option(cmd) + return cmd + + def list_commands(self, ctx): + """ + lists the commands underneath a particular event + + Parameters + ---------- + ctx: Context + the context object passed in + Returns + ------- + the sorted list of commands under a service + """ + return sorted(self.subcmd_definition.keys()) + + def cmd_implementation(self, events_lib, top_level_cmd_name, subcmd_name, *args, **kwargs): + """ + calls for value substitution in the event json and returns the + customized json as a string + + Parameters + ---------- + events_lib + top_level_cmd_name: string + the name of the service + subcmd_name: string + the name of the event under the service + args: tuple + any arguments passed in before kwargs + kwargs: dict + the keys and values for substitution in the json + Returns + ------- + event: string + returns the customized event json as a string + """ + event = events_lib.generate_event(top_level_cmd_name, subcmd_name, kwargs) + click.echo(event) + return event + + +class GenerateEventCommand(ServiceCommand): + """ + Class that brings ServiceCommand and EventTypeSubCommand into one for easy execution + """ + + def __init__(self, *args, **kwargs): + """ + Constructor for GenerateEventCommand class that brings together + ServiceCommand and EventTypeSubCommand into one class + + Parameters + ---------- + args: tuple + any arguments passed in before kwargs + kwargs: dict + commands, subcommands, and parameters for generate-event + """ + super(GenerateEventCommand, self).__init__(events.Events(), *args, **kwargs) diff --git a/samcli/commands/local/generate_event/kinesis/__init__.py b/samcli/commands/local/generate_event/kinesis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/samcli/commands/local/generate_event/kinesis/cli.py b/samcli/commands/local/generate_event/kinesis/cli.py deleted file mode 100644 index 2ade43e34d..0000000000 --- a/samcli/commands/local/generate_event/kinesis/cli.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Generates a Kinesis Event for a Lambda Invoke -""" -import base64 -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_kinesis_event - - -@click.command("kinesis", short_help="Generates a sample Amazon Kinesis event") -@click.option("--region", "-r", - type=str, - default="us-east-1", - help='The region the event should come from (default: "us-east-1")') -@click.option("--partition", "-p", - type=str, - default="partitionKey-03", - help='The Kinesis partition key (default: "partitionKey-03")') -@click.option("--sequence", "-s", - type=str, - default="49545115243490985018280067714973144582180062593244200961", - help='The Kinesis sequence number (default: "49545115243490985018280067714973144582180062593244200961")') -@click.option("--data", "-d", - type=str, - default="Hello, this is a test 123.", - help='The Kinesis message payload. There is no need to base64 this - sam will do this for you ' - '(default: "Hello, this is a test 123.")') -@cli_framework_options -@pass_context -def cli(ctx, region, partition, sequence, data): - # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, region, partition, sequence, data) # pragma: no cover - - -def do_cli(ctx, region, partition, sequence, data): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - - # base64 encode the data - date_base64 = base64.urlsafe_b64encode(data.encode('utf8')) - event_dict = generate_kinesis_event(region, partition, sequence, date_base64) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/generate_event/s3/__init__.py b/samcli/commands/local/generate_event/s3/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/samcli/commands/local/generate_event/s3/cli.py b/samcli/commands/local/generate_event/s3/cli.py deleted file mode 100644 index 89046f5109..0000000000 --- a/samcli/commands/local/generate_event/s3/cli.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Generates a S3 Event for a Lambda Invoke -""" -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_s3_event - - -@click.command("s3", short_help="Generates a sample Amazon S3 event") -@click.option("--region", "-r", - type=str, - default="us-east-1", - help='The region the event should come from (default: "us-east-1")') -@click.option("--bucket", "-b", - type=str, - default="example-bucket", - help='The S3 bucket the event should reference (default: "example-bucket")') -@click.option("--key", "-k", - type=str, - default="test/key", - help='The S3 key the event should reference (default: "test/key")') -@cli_framework_options -@pass_context -def cli(ctx, region, bucket, key): - # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, region, bucket, key) # pragma: no cover - - -def do_cli(ctx, region, bucket, key): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - event_dict = generate_s3_event(region, bucket, key) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/generate_event/schedule/__init__.py b/samcli/commands/local/generate_event/schedule/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/samcli/commands/local/generate_event/schedule/cli.py b/samcli/commands/local/generate_event/schedule/cli.py deleted file mode 100644 index 107e7fad84..0000000000 --- a/samcli/commands/local/generate_event/schedule/cli.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Generates a Schedule Event for a Lambda Invoke -""" -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_schedule_event - - -@click.command("schedule", short_help="Generates a sample scheduled event") -@click.option("--region", "-r", - type=str, - default="us-east-1", - help='The region the event should come from (default: "us-east-1")') -@cli_framework_options -@pass_context -def cli(ctx, region): - # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, region) # pragma: no cover - - -def do_cli(ctx, region): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - event_dict = generate_schedule_event(region) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/generate_event/sns/__init__.py b/samcli/commands/local/generate_event/sns/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/samcli/commands/local/generate_event/sns/cli.py b/samcli/commands/local/generate_event/sns/cli.py deleted file mode 100644 index 9075a2e7a8..0000000000 --- a/samcli/commands/local/generate_event/sns/cli.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Generates a SNS Event for a Lambda Invoke -""" -import json - -import click - -from samcli.cli.main import pass_context, common_options as cli_framework_options -from samcli.commands.local.lib.events import generate_sns_event - - -@click.command("sns", short_help="Generates a sample Amazon SNS event") -@click.option("--message", "-m", - type=str, - default="example message", - help='The SNS message body (default: "example message")') -@click.option("--topic", "-t", - type=str, - default="arn:aws:sns:us-east-1:111122223333:ExampleTopic", - help='The SNS topic (default: "arn:aws:sns:us-east-1:111122223333:ExampleTopic")') -@click.option("--subject", "-s", - type=str, - default="example subject", - help='The SNS subject (default: "example subject")') -@cli_framework_options -@pass_context -def cli(ctx, message, topic, subject): - # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - - do_cli(ctx, message, topic, subject) # pragma: no cover - - -def do_cli(ctx, message, topic, subject): - """ - Implementation of the ``cli`` method, just separated out for unit testing purposes - """ - event_dict = generate_sns_event(message, topic, subject) - event = json.dumps(event_dict, indent=4) - click.echo(event) diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index 85e178842d..f8c7067d23 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -26,51 +26,62 @@ Invoking a Lambda function using input from stdin $ echo '{"message": "Hey, are you there?" }' | sam local invoke "HelloWorldFunction" \n """ +STDIN_FILE_NAME = "-" @click.command("invoke", help=HELP_TEXT, short_help="Invokes a local Lambda function once.") @click.option("--event", '-e', type=click.Path(), - default="-", # Defaults to stdin + default=STDIN_FILE_NAME, # Defaults to stdin help="JSON file containing event data passed to the Lambda function during invoke. If this option " "is not specified, we will default to reading JSON from stdin") +@click.option("--no-event", is_flag=True, default=False, help="Invoke Function with an empty event") @invoke_common_options @cli_framework_options @click.argument('function_identifier', required=False) @pass_context -def cli(ctx, function_identifier, template, event, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile): +def cli(ctx, function_identifier, template, event, no_event, env_vars, debug_port, debug_args, debugger_path, + docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - do_cli(ctx, function_identifier, template, event, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile) # pragma: no cover + do_cli(ctx, function_identifier, template, event, no_event, env_vars, debug_port, debug_args, debugger_path, + docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region) # pragma: no cover -def do_cli(ctx, function_identifier, template, event, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile): +def do_cli(ctx, function_identifier, template, event, no_event, env_vars, debug_port, # pylint: disable=R0914 + debug_args, debugger_path, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, + region): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ LOG.debug("local invoke command is called") - event_data = _get_event(event) + if no_event and event != STDIN_FILE_NAME: + # Do not know what the user wants. no_event and event both passed in. + raise UserException("no_event and event cannot be used together. Please provide only one.") + + if no_event: + event_data = "{}" + else: + event_data = _get_event(event) # Pass all inputs to setup necessary context to invoke function locally. # Handler exception raised by the processor for invalid args and print errors try: - with InvokeContext(template_file=template, function_identifier=function_identifier, env_vars_file=env_vars, - debug_port=debug_port, - debug_args=debug_args, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - aws_profile=profile) as context: + aws_profile=profile, + debug_port=debug_port, + debug_args=debug_args, + debugger_path=debugger_path, + aws_region=region) as context: # Invoke the function context.local_lambda_runner.invoke(context.function_name, @@ -92,7 +103,7 @@ def _get_event(event_file_name): :return string: Contents of the event file or stdin """ - if event_file_name == "-": + if event_file_name == STDIN_FILE_NAME: # If event is empty, listen to stdin for event data until EOF LOG.info("Reading invoke payload from stdin (you can also pass it from file with --event)") diff --git a/samcli/commands/local/lib/debug_context.py b/samcli/commands/local/lib/debug_context.py new file mode 100644 index 0000000000..27056077dd --- /dev/null +++ b/samcli/commands/local/lib/debug_context.py @@ -0,0 +1,21 @@ +""" +Information and debug options for a specific runtime. +""" + + +class DebugContext(object): + + def __init__(self, + debug_port=None, + debugger_path=None, + debug_args=None): + + self.debug_port = debug_port + self.debugger_path = debugger_path + self.debug_args = debug_args + + def __bool__(self): + return bool(self.debug_port) + + def __nonzero__(self): + return self.__bool__() diff --git a/samcli/commands/local/lib/events.py b/samcli/commands/local/lib/events.py deleted file mode 100644 index 428bcda18d..0000000000 --- a/samcli/commands/local/lib/events.py +++ /dev/null @@ -1,306 +0,0 @@ -""" -File that holds functions for generating different types of events -""" - -from samcli.local.events.api_event import ContextIdentity, ApiGatewayLambdaEvent, RequestContext - - -def generate_s3_event(region, bucket, key): - """ - Generates a S3 Event - - :param str region: AWS Region - :param str bucket: Name of the S3 bucket - :param str key: S3 Bucket Key - :return dict: Dictionary representing the S3 Event - """ - return { - "Records": [{ - "eventVersion": "2.0", - "eventTime": "1970-01-01T00:00:00.000Z", - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "s3": { - "configurationId": "testConfigRule", - "object": { - "eTag": "0123456789abcdef0123456789abcdef", - "sequencer": "0A1B2C3D4E5F678901", - "key": key, - "size": 1024 - }, - "bucket": { - "arn": "arn:aws:s3:::{}".format(bucket), - "name": bucket, - "ownerIdentity": { - "principalId": "EXAMPLE" - } - }, - "s3SchemaVersion": "1.0" - }, - "responseElements": { - "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", - "x-amz-request-id": "EXAMPLE123456789" - }, - "awsRegion": region, - "eventName": "ObjectCreated:Put", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "eventSource": "aws:s3" - }] - } - - -def generate_sns_event(message, topic, subject): - """ - Generates a SNS Event - - :param str message: Message sent to the topic - :param str topic: Name of the Topic - :param str subject: Subject of the Topic - :return dict: Dictionary representing the SNS Event - """ - return { - "Records": [{ - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", - "EventSource": "aws:sns", - "Sns": { - "SignatureVersion": "1", - "Timestamp": "1970-01-01T00:00:00.000Z", - "Signature": "EXAMPLE", - "SigningCertUrl": "EXAMPLE", - "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", - "Message": message, - "MessageAttributes": { - "Test": { - "Type": "String", - "Value": "TestString" - }, - "TestBinary": { - "Type": "Binary", - "Value": "TestBinary" - } - }, - "Type": "Notification", - "UnsubscribeUrl": "EXAMPLE", - "TopicArn": topic, - "Subject": subject - } - }] - } - - -def generate_schedule_event(region): - """ - Generates a Scheduled Event - - :param str region: AWS Region - :return dict: Dictionary representing the Schedule Event - """ - return { - "version": "0", - "account": "123456789012", - "region": region, - "detail": {}, - "detail-type": "Scheduled Event", - "source": "aws.events", - "time": "1970-01-01T00:00:00Z", - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "resources": [ - "arn:aws:events:us-east-1:123456789012:rule/my-schedule" - ] - } - - -def generate_dynamodb_event(region): - """ - Generates a DynamoDG Event - - :param str region: AWS region - :return dict: Dictionary representing the DynamoDB Event - """ - return { - "Records": [ - { - "eventID": "1", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": region, - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:{}:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899".format(region), - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "eventVersion": "1.0", - "dynamodb": { - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "222", - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 59, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": region, - "eventName": "MODIFY", - "eventSourceARN": - "arn:aws:dynamodb:{}:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899".format(region), - "eventSource": "aws:dynamodb" - }, - { - "eventID": "3", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 38, - "SequenceNumber": "333", - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": region, - "eventName": "REMOVE", - "eventSourceARN": "arn:aws:dynamodb:{}:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899".format(region), - "eventSource": "aws:dynamodb" - } - ] - } - - -def generate_kinesis_event(region, partition, sequence, data): - """ - Generates a Kinesis Event - - :param str region: AWS Region - :param str partition: PartitionKey in Kinesis - :param str sequence: Sequence Number as a string - :param str data: Data for the stream - :return dict: Dictionary representing the Kinesis Event - """ - return { - "Records": [{ - "eventID": "shardId-000000000000:{}".format(sequence), - "eventVersion": "1.0", - "kinesis": { - "approximateArrivalTimestamp": 1428537600, - "partitionKey": partition, - "data": data, - "kinesisSchemaVersion": "1.0", - "sequenceNumber": sequence - }, - "invokeIdentityArn": "arn:aws:iam::EXAMPLE", - "eventName": "aws:kinesis:record", - "eventSourceARN": "arn:aws:kinesis:EXAMPLE", - "eventSource": "aws:kinesis", - "awsRegion": region - }] - } - - -def generate_api_event(method, body, resource, path): - """ - Generates an Api Event - - :param str method: HTTP Method of the request - :param str body: Body of the request - :param str resource: Api Gateway resource path - :param str path: Request path - :return dict: Dictionary representing the Api Event - """ - headers = { - "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "Accept-Language": "en-US,en;q=0.8", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Mobile-Viewer": "false", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "CloudFront-Viewer-Country": "US", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Upgrade-Insecure-Requests": "1", - "X-Forwarded-Port": "443", - "Host": "1234567890.execute-api.us-east-1.amazonaws.com", - "X-Forwarded-Proto": "https", - "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", - "CloudFront-Is-Tablet-Viewer": "false", - "Cache-Control": "max-age=0", - "User-Agent": "Custom User Agent String", - "CloudFront-Forwarded-Proto": "https", - "Accept-Encoding": "gzip, deflate, sdch" - } - - query_params = { - "foo": "bar" - } - - path_params = { - "proxy": path - } - - identity = ContextIdentity(source_ip='127.0.0.1') - - context = RequestContext(resource_path=resource, - http_method=method, - stage="prod", - identity=identity, - path=resource) - - event = ApiGatewayLambdaEvent(http_method=method, - body=body, - resource=resource, - request_context=context, - query_string_params=query_params, - headers=headers, - path_parameters=path_params, - path=path) - - return event.to_dict() diff --git a/samcli/commands/local/generate_event/api/__init__.py b/samcli/commands/local/lib/generated_sample_events/__init__.py similarity index 100% rename from samcli/commands/local/generate_event/api/__init__.py rename to samcli/commands/local/lib/generated_sample_events/__init__.py diff --git a/samcli/commands/local/lib/generated_sample_events/event-mapping.json b/samcli/commands/local/lib/generated_sample_events/event-mapping.json new file mode 100644 index 0000000000..da59dbfad2 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/event-mapping.json @@ -0,0 +1,747 @@ +{ + "alexa-skills-kit": { + "end-session": { + "filename": "AlexaEndSession", + "help": "Generates an Amazon Alexa End Session Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + }, + "intent-answer": { + "filename": "AlexaIntentAnswer", + "help": "Generates an Amazon Alexa Intent Answer Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + }, + "intent-getnewfact": { + "filename": "AlexaIntentGetNewFact", + "help": "Generates an Amazon Alexa Intent GetNewFact Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + }, + "intent-mycoloris": { + "filename": "AlexaIntentMyColorIs", + "help": "Generates an Amazon Alexa Intent MyColorIs Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + }, + "intent-recipe": { + "filename": "AlexaIntentRecipe", + "help": "Generates an Amazon Alexa Intent Recipe Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + }, + "start-session": { + "filename": "AlexaStartSession", + "help": "Generates an Amazon Alexa Start Session Event", + "tags": { + "session-id": { + "default": "123456789012" + }, + "user-id": { + "default": "testUser" + }, + "application-id": { + "default": "987654321" + }, + "request-id": { + "default": "1234" + } + } + } + }, + "alexa-smart-home": { + "smart-home-control-turn-off-request": { + "filename": "AlexaSmartHomeControlTurnOffRequest", + "help": "Generates an Amazon Alexa Smart Home Control Turn Off Request Event", + "tags": {} + }, + "smart-home-control-turn-on-request": { + "filename": "AlexaSmartHomeControlTurnOnRequest", + "help": "Generates an Amazon Alexa Smart Home Control Turn On Request Event", + "tags": {} + }, + "smart-home-discovery-request": { + "filename": "AlexaSmartHomeDiscoveryRequest", + "help": "Generates an Amazon Alexa Smart Home Discovery Request Event", + "tags": {} + } + }, + "apigateway": { + "authorizer": { + "filename": "ApiGatewayAuthorizer", + "help": "Generates an Amazon API Gateway Authorizer Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "stage": { + "type": "string", + "default": "prod" + }, + "method": { + "type": "string", + "default": "POST" + }, + "resource": { + "type": "string", + "default": "{proxy+}" + }, + "restApiId": { + "type": "string", + "default": "example" + } + } + }, + "aws-proxy": { + "filename": "AwsProxy", + "help": "Generates an Amazon API Gateway AWS Proxy Event", + "tags": { + "body": { + "type": "string", + "default": "{\"test\":\"body\"}", + "encoding": "base64" + }, + "stage": { + "type": "string", + "default": "prod" + }, + "method": { + "type": "string", + "default": "POST" + }, + "resource": { + "type": "string", + "default": "/{proxy+}" + }, + "path": { + "type": "string", + "default": "path/to/resource" + }, + "account-id": { + "default": "123456789012" + } + } + } + }, + "batch": { + "get-job": { + "filename": "BatchGetJob", + "help": "Generates an AWS Batch Get Job Event", + "tags": {} + }, + "submit-job": { + "filename": "BatchSubmitJob", + "help": "Generates an AWS Batch Submit Job Event", + "tags": {} + } + }, + "cloudformation": { + "create-request": { + "filename": "CfnCreateRequest", + "help": "Generates an AWS CloudFormation Create Request Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "stack": { + "type": "string", + "default": "MyStack" + } + } + } + }, + "cloudfront": { + "ab-test": { + "filename": "CloudFrontABTest", + "help": "Generates an Amazon CloudFront A/B Test Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "access-request-in-response": { + "filename": "CloudFrontAccessRequestInResponse", + "help": "Generates an Amazon CloudFront Access Request in Response Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "http-redirect": { + "filename": "CloudFrontHttpRedirect", + "help": "Generates an Amazon CloudFront HTTP Redirect Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "modify-querystring": { + "filename": "CloudFrontModifyQueryString", + "help": "Generates an Amazon CloudFront Modify QueryString Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "modify-response-header": { + "filename": "CloudFrontModifyResponseHeader", + "help": "Generates an Amazon CloudFront Modify Response Header Event", + "tags": {} + }, + "multiple-remote-calls-aggregate-response": { + "filename": "CloudFrontMultipleRemoteCallsAggregateResponse", + "help": "Generates an Amazon CloudFront Multiple Remote Calls Aggregate Response Event", + "tags": { + "uri": { + "default": "/forecast/Seattle:NewYork:Chicago:SanFrancisco" + }, + "method": { + "default": "GET" + } + } + }, + "normalize-querystring-to-improve-cache-hit": { + "filename": "CloudFrontNormalizeQuerystringToImproveCacheHit", + "help": "Generates an Amazon CloudFront Normalize Querystring Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "redirect-on-viewer-country": { + "filename": "CloudFrontRedirectOnViewerCountry", + "help": "Generates an Amazon CloudFront Redirect on Viewer Country Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "redirect-unauthenticated-users": { + "filename": "CloudFrontRedirectUnauthenticatedUsers", + "help": "Generates an Amazon CloudFront Redirect Unauthenticated Users Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "response-generation": { + "filename": "CloudFrontResponseGeneration", + "help": "Generates an Amazon CloudFront Response Generation Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "serve-object-on-viewer-device": { + "filename": "CloudFrontServeObjectOnViewerDevice", + "help": "Generates an Amazon CloudFront Serve Object on Viewer Device Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + }, + "simple-remote-call": { + "filename": "CloudFrontSimpleRemoteCall", + "help": "Generates an Amazon CloudFront Simple Remote Call Event", + "tags": { + "uri": { + "default": "/test" + }, + "method": { + "default": "GET" + } + } + } + }, + "codecommit": { + "repository": { + "filename": "CodeCommit", + "help": "Generates an AWS CodeCommit Repository Event ", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "repository": { + "type": "string", + "default": "my-repo" + }, + "trigger": { + "type": "string", + "default": "my-trigger" + }, + "custom-data": { + "type": "string", + "default": "this is custom data" + } + } + } + }, + "codepipeline": { + "job": { + "filename": "CodePipeline", + "help": "Generates an AWS CodePipeline Job Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "input-bucket": { + "type": "string", + "default": "example-bucket" + }, + "input-key": { + "type": "string", + "default": "test/key" + }, + "output-bucket": { + "type": "string", + "default": "example-bucket2" + }, + "output-key": { + "type": "string", + "default": "test/key2" + } + } + } + }, + "cognito": { + "sync-trigger": { + "filename": "CognitoSyncTrigger", + "help": "Generates an Amazon Cognito Sync Trigger Event", + "tags": { + "region": { + "default": "us-east-1" + } + } + } + }, + "config": { + "item-change-notification": { + "filename": "ConfigurationItemChangeNotification", + "help": "Generates an AWS Config Configuration Item Change Notification Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + } + } + }, + "oversized-item-change-notification": { + "filename": "OversizedConfigurationItemChangeNotification", + "help": "Generates an AWS Config Oversized Configuration Item Change Notification Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + } + } + }, + "periodic-rule": { + "filename": "PeriodicRule", + "help": "Generates an AWS Config Periodic Rule Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + } + } + } + }, + "dynamodb": { + "update": { + "filename": "DynamoDBUpdate", + "help": "Generates an Amazon DynamoDB Update Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "table": { + "type": "string", + "default": "ExampleTableWithStream" + } + } + } + }, + "cloudwatch": { + "logs": { + "filename": "CloudwatchLogs", + "help": "Generates an Amazon Cloudwatch Logs Event", + "tags": {} + }, + "scheduled-event": { + "filename": "ScheduledEvent", + "help": "Generates an Amazon CloudWatch Events Scheduled Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "rule": { + "type": "string", + "default": "ExampleRule" + } + } + } + }, + "kinesis": { + "get-records": { + "filename": "Kinesis", + "help": "Generates an Amazon Kinesis Data Stream Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "sequence": { + "type": "string", + "default": "49545115243490985018280067714973144582180062593244200961" + }, + "data": { + "type": "string", + "default": "Hello, this is a test 123.", + "encoding": "base64" + }, + "partition": { + "type": "string", + "default": "partitionKey-03" + } + } + }, + "kinesis-firehose": { + "filename": "KinesisFirehose", + "help": "Generates an Amazon Kinesis Data Firehose Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "data": { + "default": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" + }, + "partition": { + "type": "string", + "default": "partitionKey-03" + } + } + }, + "apachelog": { + "filename": "KinesisFirehoseApachelog", + "help": "Generates an Amazon Kinesis Data Firehose Apachelog Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "data": { + "default": "NjQuMjQyLjg4LjEwIC0gLSBbMDcvTWFyLzIwMDQ6MTY6MTA6MDIgLTA4MDBdICJHRVQgL21haWxtYW4vbGlzdGluZm8vaHNkaXZpc2lvbiBIVFRQLzEuMSIgMjAwIDYyOTE==" + } + } + }, + "cloudwatch-logs-processor": { + "filename": "KinesisFirehoseCloudwatchLogsProcessor", + "help": "Generates an Amazon Kinesis Data Firehose Cloudwatch Logs Processor Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + } + } + }, + "streams-as-source": { + "filename": "KinesisFirehoseStreamsAsSource", + "help": "Generates an Amazon Kinesis Data Firehose Streams as Source Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "partition": { + "type": "string", + "default": "partitionKey-03" + }, + "data": { + "type": "string", + "default": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" + }, + "sequence": { + "type": "string", + "default": "49545115243490985018280067714973144582180062593244200961" + } + } + }, + "syslog": { + "filename": "KinesisFirehoseSyslog", + "help": "Generates an Amazon Kinesis Data Firehose Syslog Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "data": { + "type": "string", + "default": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=" + } + } + } + }, + "lex": { + "book-car": { + "filename": "LexBookCar", + "help": "Generates an Amazon Lex Book Car Event", + "tags": {} + }, + "book-hotel": { + "filename": "LexBookHotel", + "help": "Generates an Amazon Lex Book Hotel Event", + "tags": {} + }, + "make-appointment": { + "filename": "LexMakeAppointment", + "help": "Generates an Amazon Lex Make Appointment Event", + "tags": {} + }, + "order-flowers": { + "filename": "LexOrderFlowers", + "help": "Generates an Amazon Lex Order Flowers Event", + "tags": {} + } + }, + "rekognition": { + "s3-request": { + "filename": "RekognitionS3Request", + "help": "Generates an Amazon Rekognition S3 Request Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "bucket": { + "type": "string", + "default": "example-bucket" + }, + "key": { + "type": "string", + "default": "test/key", + "encoding": "url" + } + } + } + }, + "s3": { + "delete": { + "filename": "S3Delete", + "help": "Generates an Amazon S3 Delete Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "bucket": { + "type": "string", + "default": "example-bucket" + }, + "key": { + "type": "string", + "default": "test/key", + "encoding": "url" + } + } + }, + "put": { + "filename": "S3Put", + "help": "Generates an Amazon S3 Put Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "bucket": { + "type": "string", + "default": "example-bucket" + }, + "key": { + "type": "string", + "default": "test/key", + "encoding": "url" + } + } + } + }, + "ses": { + "email-receiving": { + "filename": "SesEmailReceiving", + "help": "Generates an Amazon SES Email Receiving Event", + "tags": { + "region": { + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + } + } + } + }, + "sns": { + "notification": { + "filename": "Sns", + "help": "Generates an Amazon SNS Topic Notification Event", + "tags": { + "account-id": { + "default": "123456789012" + }, + "region": { + "default": "us-east-1" + }, + "topic": { + "type": "string", + "default": "ExampleTopic" + }, + "subject": { + "type": "string", + "default": "example subject" + }, + "message": { + "type": "string", + "default": "example message" + } + } + } + }, + "sqs": { + "receive-message": { + "filename": "Sqs", + "help": "Generates an Amazon SQS Event", + "tags": { + "region": { + "type": "string", + "default": "us-east-1" + }, + "account-id": { + "default": "123456789012" + }, + "queue-name": { + "default": "MyQueue" + }, + "partition": { + "default": "partitionKey-03" + } + } + } + }, + "stepfunctions": { + "error": { + "filename": "StepFunctionsError", + "help": "Generates an AWS StepFunctions Error Event", + "tags": {} + } + } +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events.py b/samcli/commands/local/lib/generated_sample_events/events.py new file mode 100644 index 0000000000..2046fc4698 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events.py @@ -0,0 +1,130 @@ +""" +Methods to expose the event types and generate the event jsons for use in SAM CLI generate-event +""" + +import os +import json +import base64 +from requests.utils import quote + +import pystache + + +class Events(object): + + """ + Events library class that loads and customizes event json files + + Methods + --------------- + expose_event_metadata(self): + return the event mapping file + generate-event(self, service_name, event_type, values_to_sub): + load in and substitute values into json file (if necessary) + """ + + def __init__(self): + """ + Constructor for event library + """ + + this_folder = os.path.dirname(os.path.abspath(__file__)) + file_name = os.path.join(this_folder, "event-mapping.json") + with open(file_name) as f: + self.event_mapping = json.load(f) + + def encode(self, tags, encoding, values_to_sub): + """ + reads the encoding type from the event-mapping.json + and determines whether a value needs encoding + + Parameters + ---------- + tags: dict + the values of a particular event that can be substituted + within the event json + encoding: string + string that helps navigate to the encoding field of the json + values_to_sub: dict + key/value pairs that will be substituted into the json + Returns + ------- + values_to_sub: dict + the encoded (if need be) values to substitute into the json. + """ + + for tag in tags: + if tags[tag].get(encoding) != "None": + if tags[tag].get(encoding) == "url": + values_to_sub[tag] = self.url_encode(values_to_sub[tag]) + if tags[tag].get(encoding) == "base64": + values_to_sub[tag] = self.base64_utf_encode(values_to_sub[tag]) + return values_to_sub + + def url_encode(self, value): + """ + url encodes the value passed in + + Parameters + ---------- + value: string + the value that needs to be encoded in the json + Returns + ------- + string: the url encoded value + """ + + return quote(value) + + def base64_utf_encode(self, value): + """ + base64 utf8 encodes the value passed in + + Parameters + ---------- + value: string + value that needs to be encoded in the json + Returns + ------- + string: the base64_utf encoded value + """ + + return base64.b64encode(value.encode('utf8')).decode('utf-8') + + def generate_event(self, service_name, event_type, values_to_sub): + """ + opens the event json, substitutes the values in, and + returns the customized event json + + Parameters + ---------- + service_name: string + name of the top level service (S3, apigateway, etc) + event_type: string + name of the event underneath the service + values_to_sub: dict + key/value pairs to substitute into the json + Returns + ------- + renderer.render(): string + string version of the custom event json + """ + + # set variables for easy calling + tags = self.event_mapping[service_name][event_type]['tags'] + values_to_sub = self.encode(tags, 'encoding', values_to_sub) + + # construct the path to the Events json file + this_folder = os.path.dirname(os.path.abspath(__file__)) + file_name = self.event_mapping[service_name][event_type]['filename'] + ".json" + file_path = os.path.join(this_folder, "events", service_name, file_name) + + # open the file + with open(file_path) as f: + data = json.load(f) + + data = json.dumps(data, indent=2) + + # return the substituted file + renderer = pystache.Renderer() + return renderer.render(data, values_to_sub) diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaEndSession.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaEndSession.json new file mode 100644 index 0000000000..a2ca73fd8c --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaEndSession.json @@ -0,0 +1,36 @@ +{ + "version": "1.0", + "session": { + "new": false, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "attributes": {}, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + } + }, + "context": { + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "SessionEndedRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T21:11:41Z", + "locale": "en-US", + "reason": "USER_INITIATED" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentAnswer.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentAnswer.json new file mode 100644 index 0000000000..85aea4c50d --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentAnswer.json @@ -0,0 +1,62 @@ +{ + "version": "1.0", + "session": { + "new": false, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "attributes": { + "score": 0, + "currentQuestionIndex": 0, + "speechOutput": "Question 1. What was Rudolph's father's name? 1. Blixen. 2. Comet. 3. Donner. 4. Dasher. ", + "correctAnswerText": "Donner", + "repromptText": "Question 1. What was Rudolph's father's name? 1. Blixen. 2. Comet. 3. Donner. 4. Dasher. ", + "questions": [ + 16, + 20, + 29, + 2, + 19 + ], + "STATE": "_TRIVIAMODE", + "correctAnswerIndex": 3 + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + } + }, + "context": { + "AudioPlayer": { + "playerActivity": "IDLE" + }, + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "IntentRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T21:06:28Z", + "locale": "en-US", + "intent": { + "name": "AnswerIntent", + "slots": { + "Item": { + "name": "Answer", + "value": "1" + } + } + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentGetNewFact.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentGetNewFact.json new file mode 100644 index 0000000000..5d509306ef --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentGetNewFact.json @@ -0,0 +1,42 @@ +{ + "version": "1.0", + "session": { + "new": false, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "attributes": {}, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + } + }, + "context": { + "AudioPlayer": { + "playerActivity": "IDLE" + }, + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "IntentRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T21:06:28Z", + "locale": "en-US", + "intent": { + "name": "GetNewFactIntent", + "slots": {} + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentMyColorIs.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentMyColorIs.json new file mode 100644 index 0000000000..d2ab58905c --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentMyColorIs.json @@ -0,0 +1,48 @@ +{ + "version": "1.0", + "session": { + "new": false, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "attributes": {}, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + + } + }, + "context": { + "AudioPlayer": { + "playerActivity": "IDLE" + }, + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "IntentRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T21:06:28Z", + "locale": "en-US", + "intent": { + "name": "MyColorIsIntent", + "slots": { + "Color": { + "name": "Color", + "value": "blue" + } + } + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentRecipe.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentRecipe.json new file mode 100644 index 0000000000..db1c52baf6 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaIntentRecipe.json @@ -0,0 +1,47 @@ +{ + "version": "1.0", + "session": { + "new": false, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "attributes": {}, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + } + }, + "context": { + "AudioPlayer": { + "playerActivity": "IDLE" + }, + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.userId" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "IntentRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T21:06:28Z", + "locale": "en-US", + "intent": { + "name": "RecipeIntent", + "slots": { + "Item": { + "name": "Item", + "value": "snowball" + } + } + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaStartSession.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaStartSession.json new file mode 100644 index 0000000000..328781170e --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-skills-kit/AlexaStartSession.json @@ -0,0 +1,38 @@ +{ + "version": "1.0", + "session": { + "new": true, + "sessionId": "amzn1.echo-api.session.{{session_id}}", + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "attributes": {} + }, + "context": { + "AudioPlayer": { + "playerActivity": "IDLE" + }, + "System": { + "application": { + "applicationId": "amzn1.ask.skill.{{application_id}}" + }, + "user": { + "userId": "amzn1.ask.account.{{user_id}}" + }, + "device": { + "supportedInterfaces": { + "AudioPlayer": {} + } + } + } + }, + "request": { + "type": "LaunchRequest", + "requestId": "amzn1.echo-api.request.{{request_id}}", + "timestamp": "2016-10-27T18:21:44Z", + "locale": "en-US" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOffRequest.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOffRequest.json new file mode 100644 index 0000000000..0dc25663d6 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOffRequest.json @@ -0,0 +1,15 @@ +{ + "header": { + "messageId": "E2D17F2F-EBD0-4B1F-BB1E-8CEE81ADCBBC", + "name": "TurnOffRequest", + "namespace": "Alexa.ConnectedHome.Control", + "payloadVersion": "2" + }, + "payload": { + "accessToken": "1", + "appliance": { + "additionalApplianceDetails": {}, + "applianceId": "unique-id-for-non-dimmable-bulb-specific-to-user1" + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOnRequest.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOnRequest.json new file mode 100644 index 0000000000..6883b2fca2 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeControlTurnOnRequest.json @@ -0,0 +1,15 @@ +{ + "header": { + "messageId": "9484BF6A-854E-47B8-86CB-54B22F15740D", + "name": "TurnOnRequest", + "namespace": "Alexa.ConnectedHome.Control", + "payloadVersion": "2" + }, + "payload": { + "accessToken": "1", + "appliance": { + "additionalApplianceDetails": {}, + "applianceId": "unique-id-for-non-dimmable-bulb-specific-to-user1" + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeDiscoveryRequest.json b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeDiscoveryRequest.json new file mode 100644 index 0000000000..51696a0931 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/alexa-smart-home/AlexaSmartHomeDiscoveryRequest.json @@ -0,0 +1,11 @@ +{ + "header": { + "messageId": "F8752B11-69BB-4246-B923-3BFB27C06C7D", + "name": "DiscoverAppliancesRequest", + "namespace": "Alexa.ConnectedHome.Discovery", + "payloadVersion": "2" + }, + "payload": { + "accessToken": "1" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/apigateway/ApiGatewayAuthorizer.json b/samcli/commands/local/lib/generated_sample_events/events/apigateway/ApiGatewayAuthorizer.json new file mode 100644 index 0000000000..84a4e302f6 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/apigateway/ApiGatewayAuthorizer.json @@ -0,0 +1,5 @@ +{ + "type": "TOKEN", + "authorizationToken": "incoming-client-token", + "methodArn": "arn:aws:execute-api:{{region}}:{{account_id}}:{{restApiId}}/{{stage}}/{{method}}/{{resource}}" +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/apigateway/AwsProxy.json b/samcli/commands/local/lib/generated_sample_events/events/apigateway/AwsProxy.json new file mode 100644 index 0000000000..0f8c704c39 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/apigateway/AwsProxy.json @@ -0,0 +1,62 @@ +{ + "body": "{{body}}", + "resource": "{{resource}}", + "path": "/{{path}}", + "httpMethod": "{{method}}", + "isBase64Encoded": "false", + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/{{path}}" + }, + "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": "{{account_id}}", + "resourceId": "123456", + "stage": "{{stage}}", + "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": "/{{stage}}/{{path}}", + "resourcePath": "{{resource}}", + "httpMethod": "{{method}}", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/batch/BatchGetJob.json b/samcli/commands/local/lib/generated_sample_events/events/batch/BatchGetJob.json new file mode 100644 index 0000000000..109005dfc7 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/batch/BatchGetJob.json @@ -0,0 +1,6 @@ +{ + "jobName": "hello_world", + "jobQueue": "default", + "jobDefinition": "hello_world" +} + diff --git a/samcli/commands/local/lib/generated_sample_events/events/batch/BatchSubmitJob.json b/samcli/commands/local/lib/generated_sample_events/events/batch/BatchSubmitJob.json new file mode 100644 index 0000000000..a7a5ee0a91 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/batch/BatchSubmitJob.json @@ -0,0 +1,3 @@ +{ + "jobId": "38fd08ce-5f5e-434c-8997-6bba5e3f5a74" +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudformation/CfnCreateRequest.json b/samcli/commands/local/lib/generated_sample_events/events/cloudformation/CfnCreateRequest.json new file mode 100644 index 0000000000..931ec43abf --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudformation/CfnCreateRequest.json @@ -0,0 +1,16 @@ +{ + "RequestType": "Create", + "ResponseURL": "http://pre-signed-S3-url-for-response", + "StackId": "arn:aws:cloudformation:{{region}}:{{account_id}}:stack/{{stack}}/guid", + "RequestId": "unique id for this create request", + "ResourceType": "Custom::TestResource", + "LogicalResourceId": "MyTestResource", + "ResourceProperties": { + "StackName": "{{stack}}", + "List": [ + "1", + "2", + "3" + ] + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontABTest.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontABTest.json new file mode 100644 index 0000000000..f461799455 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontABTest.json @@ -0,0 +1,36 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "user-agent": [ + { + "key": "User-Agent", + "value": "Test Agent" + } + ], + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "cookie": [ + { + "key": "Cookie", + "value": "SomeCookie=1; AnotherOne=A; X-Experiment-Name=B" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontAccessRequestInResponse.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontAccessRequestInResponse.json new file mode 100644 index 0000000000..24ce5c85a6 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontAccessRequestInResponse.json @@ -0,0 +1,42 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "user-name": [ + { + "key": "User-Name", + "value": "CloudFront" + } + ] + }, + "clientIp": "2001:cdba::3257:9652", + "uri": "{{uri}}", + "method": "{{method}}" + }, + "response": { + "status": "200", + "statusDescription": "OK", + "headers": { + "x-cache": [ + { + "key": "X-Cache", + "value": "Hello from Cloudfront" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontHttpRedirect.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontHttpRedirect.json new file mode 100644 index 0000000000..87b3cf65cd --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontHttpRedirect.json @@ -0,0 +1,30 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "user-agent": [ + { + "key": "User-Agent", + "value": "test-agent" + } + ], + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyQueryString.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyQueryString.json new file mode 100644 index 0000000000..47e1572ee2 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyQueryString.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "querystring": "auth=test&foo=bar", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "user-agent": [ + { + "key": "User-Agent", + "value": "Test Agent" + } + ], + "user-name": [ + { + "key": "User-Name", + "value": "aws-cloudfront" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyResponseHeader.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyResponseHeader.json new file mode 100644 index 0000000000..3e1b2f9f05 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontModifyResponseHeader.json @@ -0,0 +1,35 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "response": { + "status": "200", + "statusDescription": "OK", + "headers": { + "vary": [ + { + "key": "Vary", + "value": "*" + } + ], + "last-modified": [ + { + "key": "Last-Modified", + "value": "2016-11-25" + } + ], + "x-amz-meta-last-modified": [ + { + "key": "X-Amz-Meta-Last-Modified", + "value": "2016-01-01" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontMultipleRemoteCallsAggregateResponse.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontMultipleRemoteCallsAggregateResponse.json new file mode 100644 index 0000000000..595d8a7150 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontMultipleRemoteCallsAggregateResponse.json @@ -0,0 +1,30 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "user-agent": [ + { + "key": "User-Agent", + "value": "Test Agent" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontNormalizeQuerystringToImproveCacheHit.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontNormalizeQuerystringToImproveCacheHit.json new file mode 100644 index 0000000000..22858b9cb7 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontNormalizeQuerystringToImproveCacheHit.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "querystring": "size=LARGE&color=RED", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "user-agent": [ + { + "key": "User-Agent", + "value": "Test Agent" + } + ], + "user-name": [ + { + "key": "User-Name", + "value": "aws-cloudfront" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectOnViewerCountry.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectOnViewerCountry.json new file mode 100644 index 0000000000..d72c39f194 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectOnViewerCountry.json @@ -0,0 +1,30 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "cloudfront-viewer-country": [ + { + "key": "CloudFront-Viewer-Country", + "value": "TW" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectUnauthenticatedUsers.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectUnauthenticatedUsers.json new file mode 100644 index 0000000000..7a63fd5019 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontRedirectUnauthenticatedUsers.json @@ -0,0 +1,24 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "querystring": "foo=bar", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontResponseGeneration.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontResponseGeneration.json new file mode 100644 index 0000000000..74e08dc6ad --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontResponseGeneration.json @@ -0,0 +1,24 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontServeObjectOnViewerDevice.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontServeObjectOnViewerDevice.json new file mode 100644 index 0000000000..7297fd6570 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontServeObjectOnViewerDevice.json @@ -0,0 +1,48 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "cloudfront-is-dektop-viewer": [ + { + "key": "CloudFront-Is-Desktop-Viewer", + "value": "true" + } + ], + "cloudfront-is-mobile-viewer": [ + { + "key": "CloudFront-Is-Mobile-Viewer", + "value": "false" + } + ], + "cloudfront-is-smarttv-viewer": [ + { + "key": "CloudFront-Is-SmartTV-Viewer", + "value": "false" + } + ], + "cloudfront-is-tablet-viewer": [ + { + "key": "CloudFront-Is-Tablet-Viewer", + "value": "false" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontSimpleRemoteCall.json b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontSimpleRemoteCall.json new file mode 100644 index 0000000000..1f5808951c --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudfront/CloudFrontSimpleRemoteCall.json @@ -0,0 +1,36 @@ +{ + "Records": [ + { + "cf": { + "config": { + "distributionId": "EXAMPLE" + }, + "request": { + "uri": "{{uri}}", + "method": "{{method}}", + "clientIp": "2001:cdba::3257:9652", + "headers": { + "host": [ + { + "key": "Host", + "value": "d123.cf.net" + } + ], + "user-agent": [ + { + "key": "User-Agent", + "value": "Test Agent" + } + ], + "user-name": [ + { + "key": "User-Name", + "value": "aws-cloudfront" + } + ] + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/CloudwatchLogs.json b/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/CloudwatchLogs.json new file mode 100644 index 0000000000..aa184c1d01 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/CloudwatchLogs.json @@ -0,0 +1,5 @@ +{ + "awslogs": { + "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/ScheduledEvent.json b/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/ScheduledEvent.json new file mode 100644 index 0000000000..b2b3fb6502 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cloudwatch/ScheduledEvent.json @@ -0,0 +1,12 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "{{account-id}}", + "time": "1970-01-01T00:00:00Z", + "region": "{{region}}", + "resources": [ + "arn:aws:events:{{region}}:{{account_id}}:rule/{{rule}}" + ], + "detail": {} +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/codecommit/CodeCommit.json b/samcli/commands/local/lib/generated_sample_events/events/codecommit/CodeCommit.json new file mode 100644 index 0000000000..1901ee6448 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/codecommit/CodeCommit.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "awsRegion": "{{region}}", + "codecommit": { + "references": [ + { + "commit": "5c4ef1049f1d27deadbeeff313e0730018be182b", + "ref": "refs/heads/master" + } + ] + }, + "customData": "{{custom_data}}", + "eventId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventName": "TriggerEventTest", + "eventPartNumber": 1, + "eventSource": "aws:codecommit", + "eventSourceARN": "arn:aws:codecommit:{{region}}:{{account_id}}:{{repository}}", + "eventTime": "2016-01-01T23:59:59.000+0000", + "eventTotalParts": 1, + "eventTriggerConfigId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventTriggerName": "{{trigger}}", + "eventVersion": "1.0", + "userIdentityARN": "arn:aws:iam::{{account_id}}:root" + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/codepipeline/CodePipeline.json b/samcli/commands/local/lib/generated_sample_events/events/codepipeline/CodePipeline.json new file mode 100644 index 0000000000..10d93ea61b --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/codepipeline/CodePipeline.json @@ -0,0 +1,45 @@ +{ + "CodePipeline.job": { + "data": { + "artifactCredentials": { + "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken": "token", + "accessKeyId": "AKIAIOSFODNN7EXAMPLE" + }, + "actionConfiguration": { + "configuration": { + "FunctionName": "my-function", + "UserParameters": "user-parameter-string" + } + }, + "inputArtifacts": [ + { + "revision": "ca2bdeadbeef7d1932acb4977e08c803295d9896", + "name": "input-artifact", + "location": { + "type": "S3", + "s3Location": { + "objectKey": "{{input_key}}", + "bucketName": "{{input_bucket}}" + } + } + } + ], + "outputArtifacts": [ + { + "revision": null, + "name": "output-artifact", + "location": { + "type": "S3", + "s3Location": { + "objectKey": "{{output_key}}", + "bucketName": "{{output_bucket}}" + } + } + } + ] + }, + "id": "c968ef10-6415-4127-80b1-42502218a8c7", + "accountId": "{{account_id}}" + } +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/cognito/CognitoSyncTrigger.json b/samcli/commands/local/lib/generated_sample_events/events/cognito/CognitoSyncTrigger.json new file mode 100644 index 0000000000..ba5b7f5c40 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/cognito/CognitoSyncTrigger.json @@ -0,0 +1,20 @@ +{ + "version": 2, + "eventType": "SyncTrigger", + "region": "{{region}}", + "identityPoolId": "identityPoolId", + "identityId": "identityId", + "datasetName": "datasetName", + "datasetRecords": { + "SampleKey1": { + "oldValue": "oldValue1", + "newValue": "newValue1", + "op": "replace" + }, + "SampleKey2": { + "oldValue": "oldValue2", + "newValue": "newValue2", + "op": "replace" + } + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/config/ConfigurationItemChangeNotification.json b/samcli/commands/local/lib/generated_sample_events/events/config/ConfigurationItemChangeNotification.json new file mode 100644 index 0000000000..6a63989b52 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/config/ConfigurationItemChangeNotification.json @@ -0,0 +1,12 @@ +{ + "invokingEvent": "{\"configurationItem\":{\"configurationItemCaptureTime\":\"2016-10-06T16:46:16.261Z\",\"awsAccountId\":\"{{account_id}}\",\"configurationItemStatus\":\"OK\",\"resourceId\":\"i-00000000\",\"resourceName\":\"foo\",\"configurationStateMd5Hash\":\"8f1ee69b297895a0f8bc5753eca68e96\",\"resourceCreationTime\":\"2016-10-06T16:46:10.489Z\",\"configurationStateId\":0,\"configurationItemVersion\":\"1.2\",\"ARN\":\"arn:aws:ec2:{{region}}:{{account_id}}:instance/i-00000000\",\"awsRegion\":\"{{region}}\",\"availabilityZone\":\"{{region}}\",\"resourceType\":\"AWS::EC2::Instance\",\"tags\":{\"\":\"\"},\"relationships\":[{\"resourceId\":\"eipalloc-00000000\",\"resourceType\":\"AWS::EC2::EIP\",\"name\":\"Is attached to ElasticIp\"}],\"configuration\":{\"\":\"\"}},\"messageType\":\"ConfigurationItemChangeNotification\"}", + "ruleParameters": "{\"\":\"\"}", + "resultToken": "myResultToken", + "eventLeftScope": false, + "executionRoleArn": "arn:aws:iam::{{account_id}}:role/config-role", + "configRuleArn": "arn:aws:config:{{region}}:{{account_id}}:config-rule/config-rule-0123456", + "configRuleName": "change-triggered-config-rule", + "configRuleId": "config-rule-0123456", + "accountId": "{{account_id}}", + "version": "1.0" +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/config/OversizedConfigurationItemChangeNotification.json b/samcli/commands/local/lib/generated_sample_events/events/config/OversizedConfigurationItemChangeNotification.json new file mode 100644 index 0000000000..512ef731dd --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/config/OversizedConfigurationItemChangeNotification.json @@ -0,0 +1,12 @@ +{ + "invokingEvent": "{\"configurationItemSummary\": {\"changeType\": \"UPDATE\",\"configurationItemVersion\": \"1.2\",\"configurationItemCaptureTime\":\"2016-10-06T16:46:16.261Z\",\"configurationStateId\": 0,\"awsAccountId\":\"{{account_id}}\",\"configurationItemStatus\": \"OK\",\"resourceType\": \"AWS::EC2::Instance\",\"resourceId\":\"i-00000000\",\"resourceName\":null,\"ARN\":\"arn:aws:ec2:{{region}}:{{account_id}}:instance/i-00000000\",\"awsRegion\": \"{{region}}\",\"availabilityZone\":\"{{region}}\",\"configurationStateMd5Hash\":\"8f1ee69b287895a0f8bc5753eca68e96\",\"resourceCreationTime\":\"2016-10-06T16:46:10.489Z\"},\"messageType\":\"OversizedConfigurationItemChangeNotification\"}", + "ruleParameters": "{\"\":\"\"}", + "resultToken": "myResultToken", + "eventLeftScope": false, + "executionRoleArn": "arn:aws:iam::{{account_id}}:role/config-role", + "configRuleArn": "arn:aws:config:{{region}}:{{account_id}}:config-rule/config-rule-0123456", + "configRuleName": "change-triggered-config-rule", + "configRuleId": "config-rule-0123456", + "accountId": "{{account_id}}", + "version": "1.0" +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/config/PeriodicRule.json b/samcli/commands/local/lib/generated_sample_events/events/config/PeriodicRule.json new file mode 100644 index 0000000000..d927771340 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/config/PeriodicRule.json @@ -0,0 +1,12 @@ +{ + "invokingEvent": "{\"awsAccountId\":\"{{account_id}}\",\"notificationCreationTime\":\"1970-01-01T00:00:00.0Z\",\"messageType\":\"ScheduledNotification\",\"recordVersion\":\"1.0\"}", + "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", + "resultToken": "myResultToken", + "eventLeftScope": false, + "executionRoleArn": "arn:aws:iam::{{account_id}}:role/config-role", + "configRuleArn": "arn:aws:config:{{region}}:{{account_id}}:config-rule/config-rule-0123456", + "configRuleName": "periodic-config-rule", + "configRuleId": "config-rule-0123456", + "accountId": "{{account_id}}", + "version": "1.0" +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/dynamodb/DynamoDBUpdate.json b/samcli/commands/local/lib/generated_sample_events/events/dynamodb/DynamoDBUpdate.json new file mode 100644 index 0000000000..fc354e8891 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/dynamodb/DynamoDBUpdate.json @@ -0,0 +1,93 @@ +{ + "Records": [ + { + "eventID": "c4ca4238a0b923820dcc509a6f75849b", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "{{region}}", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439091", + "SizeBytes": 26, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:{{region}}:{{account_id}}:table/{{table}}/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "c81e728d9d4c2f636f067f89cc14862c", + "eventName": "MODIFY", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "{{region}}", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "NewImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "New item!" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439092", + "SizeBytes": 59, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:{{region}}:{{account_id}}:table/{{table}}/stream/2015-06-27T00:48:05.899" + }, + { + "eventID": "eccbc87e4b5ce2fe28308fd9f2a7baf3", + "eventName": "REMOVE", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "{{region}}", + "dynamodb": { + "Keys": { + "Id": { + "N": "101" + } + }, + "OldImage": { + "Message": { + "S": "This item has changed" + }, + "Id": { + "N": "101" + } + }, + "ApproximateCreationDateTime": 1428537600, + "SequenceNumber": "4421584500000000017450439093", + "SizeBytes": 38, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:{{region}}:{{account_id}}:table/{{table}}/stream/2015-06-27T00:48:05.899" + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/Kinesis.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/Kinesis.json new file mode 100644 index 0000000000..b484f1e5a3 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/Kinesis.json @@ -0,0 +1,20 @@ +{ + "Records": [ + { + "kinesis": { + "partitionKey": "{{partition}}", + "kinesisSchemaVersion": "1.0", + "data": "{{data}}", + "sequenceNumber": "{{sequence}}", + "approximateArrivalTimestamp": 1428537600.0 + }, + "eventSource": "aws:kinesis", + "eventID": "shardId-000000000000:{{sequence}}", + "invokeIdentityArn": "arn:aws:iam::EXAMPLE", + "eventVersion": "1.0", + "eventName": "aws:kinesis:record", + "eventSourceARN": "arn:aws:kinesis:EXAMPLE", + "awsRegion": "{{region}}" + } + ] +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehose.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehose.json new file mode 100644 index 0000000000..b440909879 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehose.json @@ -0,0 +1,12 @@ +{ + "invocationId": "invocationIdExample", + "deliveryStreamArn": "arn:{{partition}}:kinesis:EXAMPLE", + "region": "{{region}}", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "{{data}}" + } + ] +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseApachelog.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseApachelog.json new file mode 100644 index 0000000000..a52e1be6c0 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseApachelog.json @@ -0,0 +1,11 @@ +{ + "invocationId": "invocationIdExample", + "region": "{{region}}", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "{{data}}" + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseCloudwatchLogsProcessor.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseCloudwatchLogsProcessor.json new file mode 100644 index 0000000000..1954e74d6d --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseCloudwatchLogsProcessor.json @@ -0,0 +1,27 @@ +{ + "records": [ + { + "recordId": "49578734086442259037497492980620233840400173390482112514000000", + "data": "H4sIAAAAAAAAADWO0QqCMBiFX2XsWiJFi7wLUW8sIYUuQmLpnxvpJttMQnz3Ztrlxzmc8424BaVIDfmnA+zjID3nlzS5n8IsO8YhtrAYOMg5aURfDUSXNBG1MkEj6liKvjPZQpmWQNoFVf9QpWSdZoJHrNEgFfZvxa8XvoHrGUfMqqWumdHQpDVjtmdvHc91dwdn71p/vVngmqBVD616PgoolC/Ga0SBNJoi8USVWWKczM8oYhKoULDBUzF9Aeua5yHsAAAA", + "approximateArrivalTimestamp": 1510254469499 + }, + { + "recordId": "49578734086442259037497492980621442766219788363254202370000000", + "data": "H4sIAAAAAAAAAJWRTWsbMRCG/8ueLZjRjL5yc9NNLnZDapemlFAkrTYstb3Lep0Qgv97x00KgTSHnAQzmkeP3nmqtmW/j3dl/TiU6qz6PF/Pfy3r1Wp+WVezqn/YlVHK2pK3Hr0Jxkt5099djv1hkE7uh0eVHzZqE7epiarb3fe/ixzDYVJoELRhssYQqsXLlEJ3jd8//biy4QYWz7jVNJa4/TDveQwV+qsada0v/HnthLg/pH0eu2Hq+t1Ft5nKuK/Ofn4EvnpDUAu7Xi6/LL9en3/z1e1f7fq+7KYT+qnqGrEnsi54AGS2wbHWxjCjoWAYGawmzawByIG3Dp0JzjOxsaI8dbKJKW4l1BcTdgg+zP5tSPCeQ/Bso/I+o+I2kUptjgrRlQyasslUHWdvZRwGJ4+HYJGCtiKgQTYKSJ4gODLgAkpFk3f0rkyA1zLGSsvoVsVCRTFakUkNqKxt1IyFc8T/y0gEmoHZo5a/W9HhU0TeWHMyIJaoQC6zDvC+DL6WSW3MqZSkiolJcWoalWybJSNIJTXcRgjV8fb4BwwLrNzwAgAA", + "approximateArrivalTimestamp": 1510254473773 + }, + { + "recordId": "49578734086442259037497492980622651692039402992428908546000000", + "data": "H4sIAAAAAAAAAJWRbWsbMQyA/8t9jkGSJdnut2zLCiXZyJKyZaMM352vHEty4e7SUkr++9yXwUbXD8Vgg2w9eizdF7s0DPE6re8OqTgrPkzX05+L2Wo1PZ8Vk6K73ac+h0mtV49egvgc3nbX5313POSbqjvcmep2a7ZxV9bRtPub7lfKx+E4GhQEErYqYtHMn7MMuiV+fbf5rOEbzJ9wq7FPcfdm3lOaNReXyws/3cw2fvk9A4djOVR9exjbbv+x3Y6pH4qzH29hr14QzFzXi8WnxZfl+0tfXD1az27SfnxA3xdtneWtVRc8ADJrcEwkwoxigzAyiBNxzkJuIxGrei+g3gbgrDy2eRBj3OWePpuwQ/Bh8mdAGR+J69pJMFXKihwTGJ+aYJArpkjYQB2K0+SljMPgyFIIijaQgs2BAMEyexbns1NeoqpsCV+VCfCPTOVLLgUMU4h5S5UpE4BRm6ROqCEF/r8MExBDro3ED0XBMigFVM0iQlkRvZLml9a/LoN/yzSYKoIKTOmVTf6VNTHZxkjTIElkqlCL09XpN5PgkxrvAgAA", + "approximateArrivalTimestamp": 1510254474027 + }, + { + "recordId": "49578734086442259037497492980623860617859017621603614722000000", + "data": "H4sIAAAAAAAAAJWRW28aQQyF/8s+M9J47LHHeaMtzUOhEQXSVlVUDctstCqwCJZEUcR/r3OpFCnNQ17mcjw+8+n4vtqUwyFfl/ndrlRn1afhfPh7MprNhuejalB1t9uyNzkwJk6QosZk8rq7Pt93x51V6m535+rbtVvnzXKVXbu96f4U23bH3kEEHyIhx4jgxs9dDmQK3z/8vGD94cdPdrN+X/Lm3X5PbcHp5QLkYrqYLC6/mOHhuDzU+3bXt932c7vuy/5Qnf16j/fslYMb83wy+Tr5Nv24SNXVI/Xopmz7B+v7ql0ZPCKLJu+BiFUohBiJIKJGAvIkSTgpsU8aVBNangymsCH3rQ2izxvL9JmEBOzh4N+AzL6gX3JD7CLn4kg8OiVduahNkIa0BtbqNHgNI6AS0P5kQA3sUcA4IDCElCBKwgdgiCoI+CaM+pcwbAVfN8F5r2owGV0OdpWkS8kp52a1/D8MBR8sDUoQKDIbDnqlhAgQLTMWz8YbRQT92zDwEkbIQ10YHUZbKGfvUmrAIWodih2btKpOV6e/zXGIX+8CAAA=", + "approximateArrivalTimestamp": 1510254474388 + } + ], + "region": "{{region}}", + "deliveryStreamArn": "arn:aws:firehose:{{region}}:{{account_id}}:deliverystream/copy-cwl-lambda-invoke-input-151025436553-Firehose-8KILJ01Q5OBN", + "invocationId": "a7234216-12b6-4bc0-96d7-82606c0e80cf" +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseStreamsAsSource.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseStreamsAsSource.json new file mode 100644 index 0000000000..7e38cb7f1e --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseStreamsAsSource.json @@ -0,0 +1,19 @@ +{ + "invocationId": "invocationIdExample", + "deliverySteamArn": "arn:{{partition}}:kinesis:EXAMPLE", + "region": "{{region}}", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "kinesisRecordMetadata": { + "sequenceNumber": "{{sequence}}", + "subsequenceNumber": "123456", + "partitionKey": "{{partition}}", + "shardId": "shardId-000000000000", + "approximateArrivalTimestamp": 1495072949453 + }, + "data": "{{data}}" + } + ] +} \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseSyslog.json b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseSyslog.json new file mode 100644 index 0000000000..1e06518f6d --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/kinesis/KinesisFirehoseSyslog.json @@ -0,0 +1,16 @@ +{ + "invocationId": "fir", + "region": "{{region}}", + "records": [ + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": 1495072949453, + "data": "{{data}}" + }, + { + "recordId": "49546986683135544286507457936321625675700192471156785154", + "approximateArrivalTimestamp": "2012-04-23T18:25:43.511Z", + "data": "{{data}}" + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookCar.json b/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookCar.json new file mode 100644 index 0000000000..161303f758 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookCar.json @@ -0,0 +1,23 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": {}, + "bot": { + "name": "BookTrip", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "BookCar", + "slots": { + "PickUpCity": "Chicago", + "PickUpDate": "2030-11-08", + "ReturnDate": "2030-11-08", + "CarType": "economy", + "DriverAge": 21 + }, + "confirmationStatus": "None" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookHotel.json b/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookHotel.json new file mode 100644 index 0000000000..a8c725eb07 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/lex/LexBookHotel.json @@ -0,0 +1,22 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": {}, + "bot": { + "name": "BookTrip", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "BookHotel", + "slots": { + "Location": "Chicago", + "CheckInDate": "2030-11-08", + "Nights": 4, + "RoomType": "queen" + }, + "confirmationStatus": "None" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/lex/LexMakeAppointment.json b/samcli/commands/local/lib/generated_sample_events/events/lex/LexMakeAppointment.json new file mode 100644 index 0000000000..3e1985ea86 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/lex/LexMakeAppointment.json @@ -0,0 +1,21 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": {}, + "bot": { + "name": "MakeAppointment", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "MakeAppointment", + "slots": { + "AppointmentType": "whitening", + "Date": "2030-11-08", + "Time": "10:00" + }, + "confirmationStatus": "None" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/lex/LexOrderFlowers.json b/samcli/commands/local/lib/generated_sample_events/events/lex/LexOrderFlowers.json new file mode 100644 index 0000000000..5f0d0aee69 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/lex/LexOrderFlowers.json @@ -0,0 +1,21 @@ +{ + "messageVersion": "1.0", + "invocationSource": "DialogCodeHook", + "userId": "John", + "sessionAttributes": {}, + "bot": { + "name": "OrderFlowers", + "alias": "$LATEST", + "version": "$LATEST" + }, + "outputDialogMode": "Text", + "currentIntent": { + "name": "OrderFlowers", + "slots": { + "FlowerType": "lilies", + "PickupDate": "2030-11-08", + "PickupTime": "10:00" + }, + "confirmationStatus": "None" + } +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/rekognition/RekognitionS3Request.json b/samcli/commands/local/lib/generated_sample_events/events/rekognition/RekognitionS3Request.json new file mode 100644 index 0000000000..7204b90bbf --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/rekognition/RekognitionS3Request.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "{{region}}", + "eventTime": "1970-01-01T00:00:00.000Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "{{bucket}}", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::{{bucket}}" + }, + "object": { + "key": "{{key}}", + "size": 1024, + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/s3/S3Delete.json b/samcli/commands/local/lib/generated_sample_events/events/s3/S3Delete.json new file mode 100644 index 0000000000..f544a46218 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/s3/S3Delete.json @@ -0,0 +1,36 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "{{region}}", + "eventTime": "1970-01-01T00:00:00.000Z", + "eventName": "ObjectRemoved:Delete", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "{{bucket}}", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::{{bucket}}" + }, + "object": { + "key": "{{key}}", + "sequencer": "0A1B2C3D4E5F678901" + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/s3/S3Put.json b/samcli/commands/local/lib/generated_sample_events/events/s3/S3Put.json new file mode 100644 index 0000000000..7204b90bbf --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/s3/S3Put.json @@ -0,0 +1,38 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "{{region}}", + "eventTime": "1970-01-01T00:00:00.000Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "EXAMPLE123456789", + "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "{{bucket}}", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::{{bucket}}" + }, + "object": { + "key": "{{key}}", + "size": 1024, + "eTag": "0123456789abcdef0123456789abcdef", + "sequencer": "0A1B2C3D4E5F678901" + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/ses/SesEmailReceiving.json b/samcli/commands/local/lib/generated_sample_events/events/ses/SesEmailReceiving.json new file mode 100644 index 0000000000..dead427cc5 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/ses/SesEmailReceiving.json @@ -0,0 +1,97 @@ +{ + "Records": [ + { + "eventSource": "aws:ses", + "eventVersion": "1.0", + "ses": { + "mail": { + "commonHeaders": { + "date": "Wed, 7 Oct 2015 12:34:56 -0700", + "from": [ + "Jane Doe " + ], + "messageId": "<0123456789example.com>", + "returnPath": "janedoe@example.com", + "subject": "Test Subject", + "to": [ + "johndoe@example.com" + ] + }, + "destination": [ + "johndoe@example.com" + ], + "headers": [ + { + "name": "Return-Path", + "value": "" + }, + { + "name": "Received", + "value": "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.{{region}}.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for johndoe@example.com; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=example; h=mime-version:from:date:message-id:subject:to:content-type; bh=jX3F0bCAI7sIbkHyy3mLYO28ieDQz2R0P8HwQkklFj4=; b=sQwJ+LMe9RjkesGu+vqU56asvMhrLRRYrWCbVt6WJulueecwfEwRf9JVWgkBTKiL6m2hr70xDbPWDhtLdLO+jB3hzjVnXwK3pYIOHw3vxG6NtJ6o61XSUwjEsp9tdyxQjZf2HNYee873832l3K1EeSXKzxYk9Pwqcpi3dMC74ct9GukjIevf1H46hm1L2d9VYTL0LGZGHOAyMnHmEGB8ZExWbI+k6khpurTQQ4sp4PZPRlgHtnj3Zzv7nmpTo7dtPG5z5S9J+L+Ba7dixT0jn3HuhaJ9b+VThboo4YfsX9PMNhWWxGjVksSFOcGluPO7QutCPyoY4gbxtwkN9W69HA==" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "From", + "value": "Jane Doe " + }, + { + "name": "Date", + "value": "Wed, 7 Oct 2015 12:34:56 -0700" + }, + { + "name": "Message-ID", + "value": "<0123456789example.com>" + }, + { + "name": "Subject", + "value": "Test Subject" + }, + { + "name": "To", + "value": "johndoe@example.com" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + } + ], + "headersTruncated": false, + "messageId": "o3vrnil0e2ic28trm7dfhrc2v0clambda4nbp0g1", + "source": "janedoe@example.com", + "timestamp": "1970-01-01T00:00:00.000Z" + }, + "receipt": { + "action": { + "functionArn": "arn:aws:lambda:{{region}}:{{account_id}}:function:Example", + "invocationType": "Event", + "type": "Lambda" + }, + "dkimVerdict": { + "status": "PASS" + }, + "processingTimeMillis": 574, + "recipients": [ + "johndoe@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "timestamp": "1970-01-01T00:00:00.000Z", + "virusVerdict": { + "status": "PASS" + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/sns/Sns.json b/samcli/commands/local/lib/generated_sample_events/events/sns/Sns.json new file mode 100644 index 0000000000..aa0b9acd99 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/sns/Sns.json @@ -0,0 +1,31 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:{{region}}:{{accountId}}:{{topic}}", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:{{region}}:{{account_id}}:{{topic}}", + "Subject": "{{subject}}", + "Message": "{{message}}", + "Timestamp": "1970-01-01T00:00:00.000Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertUrl": "EXAMPLE", + "UnsubscribeUrl": "EXAMPLE", + "MessageAttributes": { + "Test": { + "Type": "String", + "Value": "TestString" + }, + "TestBinary": { + "Type": "Binary", + "Value": "TestBinary" + } + } + } + } + ] +} diff --git a/samcli/commands/local/lib/generated_sample_events/events/sqs/Sqs.json b/samcli/commands/local/lib/generated_sample_events/events/sqs/Sqs.json new file mode 100644 index 0000000000..44efa925f3 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/sqs/Sqs.json @@ -0,0 +1,20 @@ +{ + "Records": [ + { + "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", + "receiptHandle": "MessageReceiptHandle", + "body": "Hello from SQS!", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1523232000000", + "SenderId": "{{account_id}}", + "ApproximateFirstReceiveTimestamp": "1523232000001" + }, + "messageAttributes": {}, + "md5OfBody": "7b270e59b47ff90a553787216d55d91d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:{{partition}}:sqs:{{region}}:{{account_id}}:{{queue_name}}", + "awsRegion": "{{region}}" + } + ] + } \ No newline at end of file diff --git a/samcli/commands/local/lib/generated_sample_events/events/stepfunctions/StepFunctionsError.json b/samcli/commands/local/lib/generated_sample_events/events/stepfunctions/StepFunctionsError.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/samcli/commands/local/lib/generated_sample_events/events/stepfunctions/StepFunctionsError.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/samcli/commands/local/lib/local_lambda.py b/samcli/commands/local/lib/local_lambda.py index 2e7c9805e1..ff20cdc1ac 100644 --- a/samcli/commands/local/lib/local_lambda.py +++ b/samcli/commands/local/lib/local_lambda.py @@ -19,16 +19,16 @@ class LocalLambdaRunner(object): of actually running the function on a Docker container. """ PRESENT_DIR = "." + MAX_DEBUG_TIMEOUT = 36000 # 10 hours in seconds def __init__(self, local_runtime, function_provider, cwd, env_vars_values=None, - debug_port=None, - debug_args=None, - aws_profile=None - ): + aws_profile=None, + debug_context=None, + aws_region=None): """ Initializes the class @@ -40,15 +40,16 @@ def __init__(self, :param integer debug_port: Optional. Port to bind the debugger to :param string debug_args: Optional. Additional arguments passed to the debugger :param string aws_profile: Optional. AWS Credentials profile to use + :param string aws_region: Optional. AWS region to use """ self.local_runtime = local_runtime self.provider = function_provider self.cwd = cwd self.env_vars_values = env_vars_values or {} - self.debug_port = debug_port - self.debug_args = debug_args self.aws_profile = aws_profile + self.aws_region = aws_region + self.debug_context = debug_context def invoke(self, function_name, event, stdout=None, stderr=None): """ @@ -76,8 +77,7 @@ def invoke(self, function_name, event, stdout=None, stderr=None): config = self._get_invoke_config(function) # Invoke the function - self.local_runtime.invoke(config, event, debug_port=self.debug_port, debug_args=self.debug_args, - stdout=stdout, stderr=stderr) + self.local_runtime.invoke(config, event, debug_context=self.debug_context, stdout=stdout, stderr=stderr) def is_debugging(self): """ @@ -89,7 +89,7 @@ def is_debugging(self): True, if we are debugging the invoke ie. the Docker container will break into the debugger and wait for attach """ - return bool(self.debug_port) + return bool(self.debug_context) def _get_invoke_config(self, function): """ @@ -104,12 +104,20 @@ def _get_invoke_config(self, function): LOG.debug("Resolved absolute path to code is %s", code_abs_path) + function_timeout = function.timeout + + # The Runtime container handles timeout inside the container. When debugging with short timeouts, this can + # cause the container execution to stop. When in debug mode, we set the timeout in the container to a max 10 + # hours. This will ensure the container doesn't unexpectedly stop while debugging function code + if self.is_debugging(): + function_timeout = self.MAX_DEBUG_TIMEOUT + return FunctionConfig(name=function.name, runtime=function.runtime, handler=function.handler, code_abs_path=code_abs_path, memory=function.memory, - timeout=function.timeout, + timeout=function_timeout, env_vars=env_vars) def _make_env_vars(self, function): @@ -190,7 +198,9 @@ def get_aws_creds(self): result = {} LOG.debug("Loading AWS credentials from session with profile '%s'", self.aws_profile) - session = boto3.session.Session(profile_name=self.aws_profile) + # TODO: Consider changing it to use boto3 default session. We already have an annotation + # to pass command line arguments for region & profile to setup boto3 default session + session = boto3.session.Session(profile_name=self.aws_profile, region_name=self.aws_region) if not session: return result diff --git a/samcli/commands/local/lib/local_lambda_service.py b/samcli/commands/local/lib/local_lambda_service.py index 9e7f8a4f7f..c56de5dd4b 100644 --- a/samcli/commands/local/lib/local_lambda_service.py +++ b/samcli/commands/local/lib/local_lambda_service.py @@ -53,6 +53,6 @@ def start(self): service.create() LOG.info("Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template" - "through the endpoint.") + " through the endpoint.") service.run() diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 04b9c68f31..f30177a584 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -45,16 +45,17 @@ def cli(ctx, host, port, static_dir, # Common Options for Lambda Invoke - template, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile): + template, env_vars, debug_port, debug_args, debugger_path, docker_volume_basedir, + docker_network, log_file, skip_pull_image, profile, region + ): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - do_cli(ctx, host, port, static_dir, template, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile) # pragma: no cover + do_cli(ctx, host, port, static_dir, template, env_vars, debug_port, debug_args, debugger_path, + docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region) # pragma: no cover def do_cli(ctx, host, port, static_dir, template, env_vars, debug_port, debug_args, # pylint: disable=R0914 - docker_volume_basedir, docker_network, log_file, skip_pull_image, profile): + debugger_path, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ @@ -68,13 +69,15 @@ def do_cli(ctx, host, port, static_dir, template, env_vars, debug_port, debug_ar with InvokeContext(template_file=template, function_identifier=None, # Don't scope to one particular function env_vars_file=env_vars, - debug_port=debug_port, - debug_args=debug_args, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - aws_profile=profile) as invoke_context: + aws_profile=profile, + debug_port=debug_port, + debug_args=debug_args, + debugger_path=debugger_path, + aws_region=region) as invoke_context: service = LocalApiService(lambda_invoke_context=invoke_context, port=port, diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 1466f48ac2..770b4113ca 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -58,17 +58,17 @@ def cli(ctx, host, port, # Common Options for Lambda Invoke - template, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile + template, env_vars, debug_port, debug_args, debugger_path, docker_volume_basedir, + docker_network, log_file, skip_pull_image, profile, region ): # All logic must be implemented in the ``do_cli`` method. This helps with easy unit testing - do_cli(ctx, host, port, template, env_vars, debug_port, debug_args, docker_volume_basedir, - docker_network, log_file, skip_pull_image, profile) # pragma: no cover + do_cli(ctx, host, port, template, env_vars, debug_port, debug_args, debugger_path, docker_volume_basedir, + docker_network, log_file, skip_pull_image, profile, region) # pragma: no cover def do_cli(ctx, host, port, template, env_vars, debug_port, debug_args, # pylint: disable=R0914 - docker_volume_basedir, docker_network, log_file, skip_pull_image, profile): + debugger_path, docker_volume_basedir, docker_network, log_file, skip_pull_image, profile, region): """ Implementation of the ``cli`` method, just separated out for unit testing purposes """ @@ -82,13 +82,15 @@ def do_cli(ctx, host, port, template, env_vars, debug_port, debug_args, # pylin with InvokeContext(template_file=template, function_identifier=None, # Don't scope to one particular function env_vars_file=env_vars, - debug_port=debug_port, - debug_args=debug_args, docker_volume_basedir=docker_volume_basedir, docker_network=docker_network, log_file=log_file, skip_pull_image=skip_pull_image, - aws_profile=profile) as invoke_context: + aws_profile=profile, + debug_port=debug_port, + debug_args=debug_args, + debugger_path=debugger_path, + aws_region=region) as invoke_context: service = LocalLambdaService(lambda_invoke_context=invoke_context, port=port, diff --git a/samcli/commands/validate/validate.py b/samcli/commands/validate/validate.py index 059c94db56..40080a208f 100644 --- a/samcli/commands/validate/validate.py +++ b/samcli/commands/validate/validate.py @@ -4,11 +4,12 @@ import os import boto3 +from botocore.exceptions import NoCredentialsError import click from samtranslator.translator.managed_policy_translator import ManagedPolicyLoader - -from samcli.cli.main import pass_context, common_options as cli_framework_options +from samcli.commands.exceptions import UserException +from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options from samcli.commands.local.cli_common.options import template_common_option as template_option from samcli.commands.local.cli_common.user_exceptions import InvalidSamTemplateException, SamTemplateNotFoundException from samcli.yamlhelper import yaml_parse @@ -19,6 +20,7 @@ @click.command("validate", short_help="Validate an AWS SAM template.") @template_option +@aws_creds_options @cli_framework_options @pass_context def cli(ctx, template): @@ -43,6 +45,8 @@ def do_cli(ctx, template): except InvalidSamDocumentException as e: click.secho("Template provided at '{}' was invalid SAM Template.".format(template), bg='red') raise InvalidSamTemplateException(str(e)) + except NoCredentialsError as e: + raise UserException("AWS Credentials are required. Please configure your credentials.") click.secho("{} is a valid SAM Template".format(template), fg='green') diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index aa2357c5d7..09b73ff773 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -33,7 +33,9 @@ def __init__(self, exposed_ports=None, entrypoint=None, env_vars=None, - docker_client=None): + docker_client=None, + container_opts=None, + additional_volumes=None): """ Initializes the class with given configuration. This does not automatically create or run the container. @@ -57,6 +59,8 @@ def __init__(self, self._env_vars = env_vars self._memory_limit_mb = memory_limit_mb self._network_id = None + self._container_opts = container_opts + self._additional_volumes = additional_volumes # Use the given Docker client or create new one self.docker_client = docker_client or docker.from_env() @@ -94,6 +98,12 @@ def create(self): "tty": False } + if self._container_opts: + kwargs.update(self._container_opts) + + if self._additional_volumes: + kwargs["volumes"].update(self._additional_volumes) + if self._env_vars: kwargs["environment"] = self._env_vars @@ -120,7 +130,6 @@ def delete(self): """ Removes a container that was created earlier. """ - if not self.is_created(): LOG.debug("Container was not created. Skipping deletion") return diff --git a/samcli/local/docker/lambda_container.py b/samcli/local/docker/lambda_container.py index ac50ec3293..74c7ab51df 100644 --- a/samcli/local/docker/lambda_container.py +++ b/samcli/local/docker/lambda_container.py @@ -39,14 +39,20 @@ class LambdaContainer(Container): _IMAGE_REPO_NAME = "lambci/lambda" _WORKING_DIR = "/var/task" + # The Volume Mount path for debug files in docker + _DEBUGGER_VOLUME_MOUNT_PATH = "/tmp/lambci_debug_files" + _DEFAULT_CONTAINER_DBG_GO_PATH = _DEBUGGER_VOLUME_MOUNT_PATH + "/dlv" + + # This is the dictionary that represents where the debugger_path arg is mounted in docker to as readonly. + _DEBUGGER_VOLUME_MOUNT = {"bind": _DEBUGGER_VOLUME_MOUNT_PATH, "mode": "ro"} + def __init__(self, runtime, handler, code_dir, memory_mb=128, env_vars=None, - debug_port=None, - debug_args=None): + debug_options=None): """ Initializes the class @@ -56,16 +62,17 @@ def __init__(self, to the container to execute :param int memory_mb: Optional. Max limit of memory in MegaBytes this Lambda function can use. :param dict env_vars: Optional. Dictionary containing environment variables passed to container - :param int debug_port: Optional. Bind the runtime's debugger to this port - :param string debug_args: Optional. Extra arguments passed to the entry point to setup debugging + :param DebugContext debug_options: Optional. Contains container debugging info (port, debugger path) """ if not Runtime.has_value(runtime): raise ValueError("Unsupported Lambda runtime {}".format(runtime)) image = LambdaContainer._get_image(runtime) - ports = LambdaContainer._get_exposed_ports(debug_port) - entry = LambdaContainer._get_entry_point(runtime, debug_port, debug_args) + ports = LambdaContainer._get_exposed_ports(debug_options) + entry = LambdaContainer._get_entry_point(runtime, debug_options) + additional_options = LambdaContainer._get_additional_options(runtime, debug_options) + additional_volumes = LambdaContainer._get_additional_volumes(debug_options) cmd = [handler] super(LambdaContainer, self).__init__(image, @@ -75,10 +82,12 @@ def __init__(self, memory_limit_mb=memory_mb, exposed_ports=ports, entrypoint=entry, - env_vars=env_vars) + env_vars=env_vars, + container_opts=additional_options, + additional_volumes=additional_volumes) @staticmethod - def _get_exposed_ports(debug_port): + def _get_exposed_ports(debug_options): """ Return Docker container port binding information. If a debug port is given, then we will ask Docker to bind to same port both inside and outside the container ie. Runtime process is started in debug mode with @@ -87,12 +96,48 @@ def _get_exposed_ports(debug_port): :param int debug_port: Optional, integer value of debug port :return dict: Dictionary containing port binding information. None, if debug_port was not given """ - if not debug_port: + if not debug_options: return None return { # container port : host port - debug_port: debug_port + debug_options.debug_port: debug_options.debug_port + } + + @staticmethod + def _get_additional_options(runtime, debug_options): + """ + Return additional Docker container options. Used by container debug mode to enable certain container + security options. + :param DebugContext debug_options: DebugContext for the runtime of the container. + :return dict: Dictionary containing additional arguments to be passed to container creation. + """ + if not debug_options: + return None + + opts = {} + + if runtime == Runtime.go1x.value: + # These options are required for delve to function properly inside a docker container on docker < 1.12 + # See https://github.com/moby/moby/issues/21051 + opts["security_opt"] = ["seccomp:unconfined"] + opts["cap_add"] = ["SYS_PTRACE"] + + return opts + + @staticmethod + def _get_additional_volumes(debug_options): + """ + Return additional volumes to be mounted in the Docker container. Used by container debug for mapping + debugger executable into the container. + :param DebugContext debug_options: DebugContext for the runtime of the container. + :return dict: Dictionary containing volume map passed to container creation. + """ + if not debug_options or not debug_options.debugger_path: + return None + + return { + debug_options.debugger_path: LambdaContainer._DEBUGGER_VOLUME_MOUNT } @staticmethod @@ -106,7 +151,7 @@ def _get_image(runtime): return "{}:{}".format(LambdaContainer._IMAGE_REPO_NAME, runtime) @staticmethod - def _get_entry_point(runtime, debug_port=None, debug_args=None): + def _get_entry_point(runtime, debug_options=None): """ Returns the entry point for the container. The default value for the entry point is already configured in the Dockerfile. We override this default specifically when enabling debugging. The overridden entry point includes @@ -119,12 +164,13 @@ def _get_entry_point(runtime, debug_port=None, debug_args=None): ie. if command is ``node index.js arg1 arg2``, then this list will be ["node", "index.js", "arg1", "arg2"] """ - if not debug_port: + if not debug_options: return None + debug_port = debug_options.debug_port debug_args_list = [] - if debug_args: - debug_args_list = debug_args.split(" ") + if debug_options.debug_args: + debug_args_list = debug_options.debug_args.split(" ") # configs from: https://github.com/lambci/docker-lambda # to which we add the extra debug mode options @@ -135,9 +181,9 @@ def _get_entry_point(runtime, debug_port=None, debug_args=None): + debug_args_list \ + [ "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=" + str(debug_port), - "-XX:MaxHeapSize=1336935k", - "-XX:MaxMetaspaceSize=157286k", - "-XX:ReservedCodeCacheSize=78643k", + "-XX:MaxHeapSize=2834432k", + "-XX:MaxMetaspaceSize=163840k", + "-XX:ReservedCodeCacheSize=81920k", "-XX:+UseSerialGC", # "-Xshare:on", doesn't work in conjunction with the debug options "-XX:-TieredCompilation", @@ -146,6 +192,15 @@ def _get_entry_point(runtime, debug_port=None, debug_args=None): "/var/runtime/lib/LambdaJavaRTEntry-1.0.jar", ] + elif runtime == Runtime.go1x.value: + entrypoint = ["/var/runtime/aws-lambda-go"] \ + + debug_args_list \ + + [ + "-debug=true", + "-delvePort=" + str(debug_port), + "-delvePath=" + LambdaContainer._DEFAULT_CONTAINER_DBG_GO_PATH, + ] + elif runtime == Runtime.nodejs.value: entrypoint = ["/usr/bin/node"] \ diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/pom.xml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/pom.xml index b0346a7f5f..60aba8fb29 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/pom.xml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-core - 1.1.0 + 1.2.0 junit diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/main/java/helloworld/App.java b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/main/java/helloworld/App.java index f89a7f499d..fdbf4f188b 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/main/java/helloworld/App.java +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/main/java/helloworld/App.java @@ -25,7 +25,7 @@ public Object handleRequest(final Object input, final Context context) { String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); return new GatewayResponse(output, headers, 200); } catch (IOException e) { - return new GatewayResponse("{}", headers, 400); + return new GatewayResponse("{}", headers, 500); } } 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 181cfcd428..3ff8cce757 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 @@ -109,7 +109,7 @@ aws cloudformation describe-stacks \ ## Testing -We use `jest` 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: +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 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 e56ef07f4a..ef39e9a9ee 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 @@ -8,8 +8,42 @@ const url = 'http://checkip.amazonaws.com/'; let response; {%- endif %} -{% if cookiecutter.runtime == 'nodejs6.10' or cookiecutter.runtime == 'nodejs4.3' %} -exports.lambda_handler = function (event, context, callback) { +/** + * + * 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 = { @@ -23,11 +57,11 @@ exports.lambda_handler = function (event, context, callback) { }) .catch(function (err) { console.log(err); - callback(err, ""); - }); + callback(err); + }); }; -{% else %} -exports.lambda_handler = async (event, context, callback) => { +{%- else %} +exports.lambdaHandler = async (event, context) => { try { const ret = await axios(url); response = { @@ -37,12 +71,11 @@ exports.lambda_handler = async (event, context, callback) => { location: ret.data.trim() }) } - } - catch (err) { + } catch (err) { console.log(err); - callback(err, null); + return err; } - callback(null, response) + 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 1c0951db9a..52abf09d0e 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 @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "hello world sample for NodeJS", "main": "src/index.js", - "repository": "https://github.com/aws-samples/cookiecutter-aws-sam-hello-nodejs", + "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", "author": "SAM CLI", "license": "MIT", "dependencies": { @@ -16,4 +16,4 @@ "chai": "^4.1.2", "mocha": "^5.1.1" } -} \ No newline at end of file +} 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 bae7593a3d..a37e62a72f 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 @@ -17,7 +17,7 @@ Resources: 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.lambda_handler + Handler: app.lambdaHandler {%- if cookiecutter.runtime == 'nodejs6.10' %} Runtime: nodejs6.10 {%- elif cookiecutter.runtime =='nodejs4.3' %} 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 741d41760c..96a1f669e5 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 @@ -6,20 +6,82 @@ def lambda_handler(event, context): """Sample pure Lambda function - Arguments: - event LambdaEvent -- Lambda Event received from Invoke API - context LambdaContext -- Lambda Context runtime methods and attributes + Parameters + ---------- + event: dict, required + API Gateway Lambda Proxy Input Format - Returns: - dict -- {'statusCode': int, 'body': dict} + { + "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 + + 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 + + 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 """ - ip = requests.get('http://checkip.amazonaws.com/') + 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 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}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-python/{{cookiecutter.project_name}}/template.yaml index c45a873c21..e7a21a0e5f 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 @@ -18,10 +18,10 @@ Resources: Properties: CodeUri: hello_world/build/ Handler: app.lambda_handler - {%- if cookiecutter.runtime == 'python3.6' %} - Runtime: python3.6 - {%- else %} + {%- if cookiecutter.runtime == 'python2.7' %} Runtime: python2.7 + {%- else %} + Runtime: python3.6 {%- endif %} Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object Variables: 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 522935a3ce..f85ad6316b 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,5 +1,8 @@ +from collections import namedtuple import json + import pytest + from hello_world import app @@ -8,7 +11,7 @@ def apigw_event(): """ Generates API GW Event""" return { - "body": "{ \"test\": \"body\"}", + "body": '{ "test": "body"}', "resource": "/{proxy+}", "requestContext": { "resourceId": "123456", @@ -28,69 +31,51 @@ def apigw_event(): "cognitoIdentityId": "", "cognitoAuthenticationProvider": "", "sourceIp": "127.0.0.1", - "accountId": "" + "accountId": "", }, - "stage": "prod" - }, - "queryStringParameters": { - "foo": "bar" + "stage": "prod", }, + "queryStringParameters": {"foo": "bar"}, "headers": { - "Via": - "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "Accept-Language": - "en-US,en;q=0.8", - "CloudFront-Is-Desktop-Viewer": - "true", - "CloudFront-Is-SmartTV-Viewer": - "false", - "CloudFront-Is-Mobile-Viewer": - "false", - "X-Forwarded-For": - "127.0.0.1, 127.0.0.2", - "CloudFront-Viewer-Country": - "US", - "Accept": - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Upgrade-Insecure-Requests": - "1", - "X-Forwarded-Port": - "443", - "Host": - "1234567890.execute-api.us-east-1.amazonaws.com", - "X-Forwarded-Proto": - "https", - "X-Amz-Cf-Id": - "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", - "CloudFront-Is-Tablet-Viewer": - "false", - "Cache-Control": - "max-age=0", - "User-Agent": - "Custom User Agent String", - "CloudFront-Forwarded-Proto": - "https", - "Accept-Encoding": - "gzip, deflate, sdch" - }, - "pathParameters": { - "proxy": "/examplepath" + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "CloudFront-Viewer-Country": "US", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Upgrade-Insecure-Requests": "1", + "X-Forwarded-Port": "443", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "X-Forwarded-Proto": "https", + "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", + "CloudFront-Is-Tablet-Viewer": "false", + "Cache-Control": "max-age=0", + "User-Agent": "Custom User Agent String", + "CloudFront-Forwarded-Proto": "https", + "Accept-Encoding": "gzip, deflate, sdch", }, + "pathParameters": {"proxy": "/examplepath"}, "httpMethod": "POST", - "stageVariables": { - "baz": "qux" - }, - "path": "/examplepath" + "stageVariables": {"baz": "qux"}, + "path": "/examplepath", } -def test_lambda_handler(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 + assert ret["statusCode"] == 200 - for key in ('message', 'location'): - assert key in ret['body'] + for key in ("message", "location"): + assert key in ret["body"] - data = json.loads(ret['body']) - assert data['message'] == 'hello world' + data = json.loads(ret["body"]) + assert data["message"] == "hello world" diff --git a/samcli/local/lambda_service/local_lambda_invoke_service.py b/samcli/local/lambda_service/local_lambda_invoke_service.py index c2b53f8b9c..b080e181b3 100644 --- a/samcli/local/lambda_service/local_lambda_invoke_service.py +++ b/samcli/local/lambda_service/local_lambda_invoke_service.py @@ -92,10 +92,6 @@ def validate_request(): LOG.debug("Query parameters are in the request but not supported") return LambdaErrorResponses.invalid_request_content("Query Parameters are not supported") - if flask_request.content_type and flask_request.content_type.lower() != "application/json": - LOG.debug("%s media type is not supported. Must be application/json", flask_request.content_type) - return LambdaErrorResponses.unsupported_media_type(flask_request.content_type) - request_headers = CaseInsensitiveDict(flask_request.headers) log_type = request_headers.get('X-Amz-Log-Type', 'None') diff --git a/samcli/local/lambdafn/env_vars.py b/samcli/local/lambdafn/env_vars.py index 6badb4b027..33790db10e 100644 --- a/samcli/local/lambdafn/env_vars.py +++ b/samcli/local/lambdafn/env_vars.py @@ -2,6 +2,8 @@ Supplies the environment variables necessary to set up Local Lambda runtime """ +import sys + class EnvironmentVariables(object): """ @@ -191,7 +193,12 @@ def _stringify_value(self, value): result = "false" # value is a scalar type like int, str which can be stringified - else: + # do not stringify unicode in Py2, Py3 str supports unicode + elif sys.version_info.major > 2: result = str(value) + elif not isinstance(value, unicode): # noqa: F821 pylint: disable=undefined-variable + result = str(value) + else: + result = value return result diff --git a/samcli/local/lambdafn/runtime.py b/samcli/local/lambdafn/runtime.py index d62188005c..4f55134aab 100644 --- a/samcli/local/lambdafn/runtime.py +++ b/samcli/local/lambdafn/runtime.py @@ -37,8 +37,7 @@ def __init__(self, container_manager): def invoke(self, function_config, event, - debug_port=None, - debug_args=None, + debug_context=None, stdout=None, stderr=None): """ @@ -53,8 +52,7 @@ def invoke(self, :param FunctionConfig function_config: Configuration of the function to invoke :param event: String input event passed to Lambda function - :param integer debug_port: Optional, port to attach debugger to - :param string debug_args: Optional, additional args to pass to debugger + :param DebugContext debug_context: Debugging context for the function (includes port, args, and path) :param io.IOBase stdout: Optional. IO Stream to that receives stdout text from container. :param io.IOBase stderr: Optional. IO Stream that receives stderr text from container :raises Keyboard @@ -68,14 +66,12 @@ def invoke(self, env_vars = environ.resolve() with self._get_code_dir(function_config.code_abs_path) as code_dir: - container = LambdaContainer(function_config.runtime, function_config.handler, code_dir, memory_mb=function_config.memory, env_vars=env_vars, - debug_port=debug_port, - debug_args=debug_args) + debug_options=debug_context) try: @@ -90,7 +86,7 @@ def invoke(self, timer = self._configure_interrupt(function_config.name, function_config.timeout, container, - bool(debug_port)) + bool(debug_context)) # NOTE: BLOCKING METHOD # Block the thread waiting to fetch logs from the container. This method will return after container diff --git a/tests/functional/commands/local/lib/test_local_api_service.py b/tests/functional/commands/local/lib/test_local_api_service.py index bb1c5c4b9b..d8a77598f2 100644 --- a/tests/functional/commands/local/lib/test_local_api_service.py +++ b/tests/functional/commands/local/lib/test_local_api_service.py @@ -26,7 +26,7 @@ class TestFunctionalLocalLambda(TestCase): def setUp(self): - self.host = "0.0.0.0" + self.host = "127.0.0.1" self.port = random.randint(30000, 40000) # get a random port self.url = "http://{}:{}".format(self.host, self.port) @@ -66,7 +66,7 @@ def setUp(self): manager = ContainerManager() local_runtime = LambdaRuntime(manager) lambda_runner = LocalLambdaRunner(local_runtime, self.mock_function_provider, self.cwd, env_vars_values=None, - debug_args=None, debug_port=None, aws_profile=None) + debug_context=None, aws_profile=None) self.lambda_invoke_context_mock.local_lambda_runner = lambda_runner self.lambda_invoke_context_mock.get_cwd.return_value = self.cwd @@ -74,8 +74,8 @@ def tearDown(self): shutil.rmtree(self.code_abs_path) @patch("samcli.commands.local.lib.local_api_service.SamApiProvider") - def test_must_start_service_and_serve_endpoints(self, SamApiProviderMock): - SamApiProviderMock.return_value = self.api_provider_mock + def test_must_start_service_and_serve_endpoints(self, sam_api_provider_mock): + sam_api_provider_mock.return_value = self.api_provider_mock local_service = LocalApiService(self.lambda_invoke_context_mock, self.port, @@ -94,8 +94,8 @@ def test_must_start_service_and_serve_endpoints(self, SamApiProviderMock): self.assertEquals(response.status_code, 403) # "HTTP GET /post" must not exist @patch("samcli.commands.local.lib.local_api_service.SamApiProvider") - def test_must_serve_static_files(self, SamApiProviderMock): - SamApiProviderMock.return_value = self.api_provider_mock + def test_must_serve_static_files(self, sam_api_provider_mock): + sam_api_provider_mock.return_value = self.api_provider_mock local_service = LocalApiService(self.lambda_invoke_context_mock, self.port, diff --git a/tests/functional/commands/local/lib/test_local_lambda.py b/tests/functional/commands/local/lib/test_local_lambda.py index 4ac734075d..a4a85583e6 100644 --- a/tests/functional/commands/local/lib/test_local_lambda.py +++ b/tests/functional/commands/local/lib/test_local_lambda.py @@ -67,7 +67,7 @@ def test_must_invoke(self): manager = ContainerManager() local_runtime = LambdaRuntime(manager) runner = LocalLambdaRunner(local_runtime, self.mock_function_provider, self.cwd, self.env_var_overrides, - debug_args=None, debug_port=None, aws_profile=None) + debug_context=None, aws_profile=None) # Append the real AWS credentials to the expected values. creds = runner.get_aws_creds() diff --git a/tests/functional/local/apigw/test_local_apigw_service.py b/tests/functional/local/apigw/test_local_apigw_service.py index 7ae8fa8939..a36410b5ae 100644 --- a/tests/functional/local/apigw/test_local_apigw_service.py +++ b/tests/functional/local/apigw/test_local_apigw_service.py @@ -620,7 +620,7 @@ def make_service(list_of_routes, function_provider, cwd): service = LocalApigwService(list_of_routes, lambda_runner, port=port) scheme = "http" - url = '{}://0.0.0.0:{}'.format(scheme, port) + url = '{}://127.0.0.1:{}'.format(scheme, port) return service, port, url, scheme @@ -647,7 +647,7 @@ def make_service_response(port, method, scheme, resourcePath, resolvedResourcePa for header, value in headers.items(): response["headers"][header] = value - response["headers"]["Host"] = "0.0.0.0:{}".format(port) + response["headers"]["Host"] = "127.0.0.1:{}".format(port) response["headers"]["X-Forwarded-Port"] = str(port) response["headers"]["X-Forwarded-Proto"] = scheme diff --git a/tests/functional/local/docker/test_lambda_container.py b/tests/functional/local/docker/test_lambda_container.py index 40fecc9ffe..e80492dc93 100644 --- a/tests/functional/local/docker/test_lambda_container.py +++ b/tests/functional/local/docker/test_lambda_container.py @@ -11,6 +11,7 @@ from contextlib import contextmanager from unittest import TestCase +from samcli.commands.local.lib.debug_context import DebugContext from tests.functional.function_code import nodejs_lambda from samcli.local.docker.lambda_container import LambdaContainer from samcli.local.docker.manager import ContainerManager @@ -47,6 +48,9 @@ def setUp(self): self.expected_docker_image = self.IMAGE_NAME self.handler = "index.handler" self.debug_port = _rand_port() + self.debug_context = DebugContext(debug_port=self.debug_port, + debugger_path=None, + debug_args=None) self.code_dir = nodejs_lambda(self.HELLO_WORLD_CODE) self.network_prefix = "sam_cli_test_network" @@ -79,7 +83,7 @@ def test_basic_creation(self): def test_debug_port_is_created_on_host(self): - container = LambdaContainer(self.runtime, self.handler, self.code_dir, debug_port=self.debug_port) + container = LambdaContainer(self.runtime, self.handler, self.code_dir, debug_options=self.debug_context) with self._create(container): diff --git a/tests/functional/local/lambda_service/test_local_lambda_invoke.py b/tests/functional/local/lambda_service/test_local_lambda_invoke.py index 472b6d4074..9faa07144d 100644 --- a/tests/functional/local/lambda_service/test_local_lambda_invoke.py +++ b/tests/functional/local/lambda_service/test_local_lambda_invoke.py @@ -152,6 +152,16 @@ def test_mock_response_is_returned(self): self.assertEquals(actual, expected) self.assertEquals(response.status_code, 200) + def test_binary_octet_stream_format(self): + expected = {"key1": "value1"} + + response = requests.post(self.url + '/2015-03-31/functions/HelloWorld/invocations', json={"key1": "value1"}, headers={"Content-Type":"binary/octet-stream"}) + + actual = response.json() + + self.assertEquals(actual, expected) + self.assertEquals(response.status_code, 200) + def test_function_executed_when_no_data_provided(self): expected = {} @@ -273,19 +283,6 @@ def test_invocation_type_dry_run_in_request(self): self.assertEquals(response.headers.get('Content-Type'), 'application/json') self.assertEquals(response.headers.get('x-amzn-errortype'), 'NotImplemented') - def test_media_type_is_not_application_json(self): - expected = {"Type": "User", - "Message": 'Unsupported content type: image/gif'} - - response = requests.post(self.url + '/2015-03-31/functions/HelloWorld/invocations', headers={'Content-Type':'image/gif'}) - - actual = response.json() - - self.assertEquals(actual, expected) - self.assertEquals(response.status_code, 415) - self.assertEquals(response.headers.get('x-amzn-errortype'), 'UnsupportedMediaType') - self.assertEquals(response.headers.get('Content-Type'), 'application/json') - def test_generic_404_error_when_request_to_nonexisting_endpoint(self): expected_data = {'Type': 'LocalService', 'Message': 'PathNotFoundException'} diff --git a/samcli/commands/local/generate_event/dynamodb/__init__.py b/tests/integration/local/generate_event/__init__.py similarity index 100% rename from samcli/commands/local/generate_event/dynamodb/__init__.py rename to tests/integration/local/generate_event/__init__.py diff --git a/tests/integration/local/generate_event/test_cli_integ.py b/tests/integration/local/generate_event/test_cli_integ.py new file mode 100644 index 0000000000..2bad1fb072 --- /dev/null +++ b/tests/integration/local/generate_event/test_cli_integ.py @@ -0,0 +1,19 @@ +from unittest import TestCase +from subprocess import Popen +import os + + +class Test_EventGeneration_Integ(TestCase): + + def test_generate_event_substitution(self): + process = Popen([Test_EventGeneration_Integ._get_command(), "local", "generate-event", "s3", "put"]) + return_code = process.wait() + self.assertEquals(return_code, 0) + + @staticmethod + def _get_command(): + command = "sam" + if os.getenv("SAM_CLI_DEV"): + command = "samdev" + + return command diff --git a/tests/integration/local/invoke/test_integrations_cli.py b/tests/integration/local/invoke/test_integrations_cli.py index b494cefbee..39d089fbd4 100644 --- a/tests/integration/local/invoke/test_integrations_cli.py +++ b/tests/integration/local/invoke/test_integrations_cli.py @@ -94,3 +94,25 @@ def test_invoke_when_function_writes_stderr(self): process_stderr = b"".join(process.stderr.readlines()).strip() self.assertIn("Docker Lambda is writing to stderr", process_stderr.decode('utf-8')) + + def test_invoke_returns_expected_result_when_no_event_given(self): + command_list = self.get_command_list("EchoEventFunction", template_path=self.template_path) + command_list.append("--no-event") + process = Popen(command_list, stdout=PIPE) + return_code = process.wait() + process_stdout = b"".join(process.stdout.readlines()).strip() + + self.assertEquals(return_code, 0) + self.assertEquals("{}", process_stdout.decode('utf-8')) + + def test_invoke_raises_exception_with_noargs_and_event(self): + command_list = self.get_command_list("HelloWorldLambdaFunction", + template_path=self.template_path, + event_path=self.event_path) + command_list.append("--no-event") + process = Popen(command_list, stderr=PIPE) + process.wait() + + process_stderr = b"".join(process.stderr.readlines()).strip() + error_output = process_stderr.decode('utf-8') + self.assertIn("no_event and event cannot be used together. Please provide only one.", error_output) diff --git a/tests/integration/testdata/invoke/runtimes/java8/pom.xml b/tests/integration/testdata/invoke/runtimes/java8/pom.xml index b0346a7f5f..60aba8fb29 100644 --- a/tests/integration/testdata/invoke/runtimes/java8/pom.xml +++ b/tests/integration/testdata/invoke/runtimes/java8/pom.xml @@ -15,7 +15,7 @@ com.amazonaws aws-lambda-java-core - 1.1.0 + 1.2.0 junit diff --git a/tests/unit/cli/test_main.py b/tests/unit/cli/test_main.py index d7009498ee..0e3568fe39 100644 --- a/tests/unit/cli/test_main.py +++ b/tests/unit/cli/test_main.py @@ -25,5 +25,5 @@ def test_cli_some_command(self): def test_cli_with_debug(self): runner = CliRunner() - result = runner.invoke(cli, ["local", "generate-event", "s3", "--debug"]) + result = runner.invoke(cli, ["local", "generate-event", "s3", "put", "--debug"]) self.assertEquals(result.exit_code, 0) diff --git a/tests/unit/commands/local/cli_common/test_invoke_context.py b/tests/unit/commands/local/cli_common/test_invoke_context.py index d0d8d9c34d..253cbda615 100644 --- a/tests/unit/commands/local/cli_common/test_invoke_context.py +++ b/tests/unit/commands/local/cli_common/test_invoke_context.py @@ -1,15 +1,15 @@ """ Tests the InvokeContext class """ - +import errno import os import sys import yaml -import docker +import docker import requests -from samcli.commands.local.cli_common.user_exceptions import InvokeContextException +from samcli.commands.local.cli_common.user_exceptions import InvokeContextException, DebugContextException from samcli.commands.local.cli_common.invoke_context import InvokeContext from unittest import TestCase @@ -32,13 +32,15 @@ def test_must_read_from_necessary_files(self, SamFunctionProviderMock): invoke_context = InvokeContext(template_file=template_file, function_identifier="id", env_vars_file=env_vars_file, - debug_port=123, - debug_args="args", docker_volume_basedir="volumedir", docker_network="network", log_file=log_file, skip_pull_image=True, - aws_profile="profile") + aws_profile="profile", + debug_port=1111, + debugger_path="path-to-debugger", + debug_args='args', + aws_region="region") template_dict = "template_dict" invoke_context._get_template_data = Mock() @@ -52,6 +54,10 @@ def test_must_read_from_necessary_files(self, SamFunctionProviderMock): invoke_context._setup_log_file = Mock() invoke_context._setup_log_file.return_value = log_file_handle + debug_context_mock = Mock() + invoke_context._get_debug_context = Mock() + invoke_context._get_debug_context.return_value = debug_context_mock + invoke_context._check_docker_connectivity = Mock() # Call Enter method manually for testing purposes @@ -62,11 +68,13 @@ def test_must_read_from_necessary_files(self, SamFunctionProviderMock): self.assertEquals(invoke_context._function_provider, function_provider) self.assertEquals(invoke_context._env_vars_value, env_vars_value) self.assertEquals(invoke_context._log_file_handle, log_file_handle) + self.assertEquals(invoke_context._debug_context, debug_context_mock) invoke_context._get_template_data.assert_called_with(template_file) SamFunctionProviderMock.assert_called_with(template_dict) invoke_context._get_env_vars_value.assert_called_with(env_vars_file) invoke_context._setup_log_file.assert_called_with(log_file) + invoke_context._get_debug_context.assert_called_once_with(1111, "args", "path-to-debugger") invoke_context._check_docker_connectivity.assert_called_with() @@ -105,13 +113,15 @@ def test_must_work_in_with_statement(self, ExitMock, EnterMock): with InvokeContext(template_file="template_file", function_identifier="id", env_vars_file="env_vars_file", - debug_port=123, - debug_args="args", docker_volume_basedir="volumedir", docker_network="network", log_file="log_file", skip_pull_image=True, - aws_profile="profile") as context: + aws_profile="profile", + debug_port=1111, + debugger_path="path-to-debugger", + debug_args='args', + aws_region="region") as context: self.assertEquals(context_obj, context) EnterMock.assert_called_with() @@ -152,13 +162,15 @@ def setUp(self): self.context = InvokeContext(template_file="template_file", function_identifier="id", env_vars_file="env_vars_file", - debug_port=123, - debug_args="args", docker_volume_basedir="volumedir", docker_network="network", log_file="log_file", skip_pull_image=True, - aws_profile="profile") + aws_profile="profile", + debug_port=1111, + debugger_path="path-to-debugger", + debug_args='args', + aws_region="region") @patch("samcli.commands.local.cli_common.invoke_context.ContainerManager") @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime") @@ -187,10 +199,10 @@ def test_must_create_runner(self, LocalLambdaMock, LambdaRuntimeMock, ContainerM LocalLambdaMock.assert_called_with(local_runtime=runtime_mock, function_provider=ANY, cwd=cwd, + debug_context=None, env_vars_values=ANY, - debug_port=123, - debug_args="args", - aws_profile="profile") + aws_profile="profile", + aws_region="region") class TestInvokeContext_stdout_property(TestCase): @@ -366,6 +378,75 @@ def test_must_open_file_for_writing(self): m.assert_called_with(filename, 'wb') +class TestInvokeContext_get_debug_context(TestCase): + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debugger_path_not_found(self, pathlib_mock): + error = OSError() + error.errno = errno.ENOENT + pathlib_mock.side_effect = error + + with self.assertRaises(DebugContextException): + InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path='somepath') + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debugger_path_not_dir(self, pathlib_mock): + pathlib_path_mock = Mock() + resolve_path_mock = Mock() + pathlib_path_mock.resolve.return_value = resolve_path_mock + resolve_path_mock.is_dir.return_value = False + pathlib_mock.return_value = pathlib_path_mock + + with self.assertRaises(DebugContextException): + InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path='somepath') + + def test_no_debug_port(self): + debug_context = InvokeContext._get_debug_context(None, None, None) + + self.assertEquals(debug_context.debugger_path, None) + self.assertEquals(debug_context.debug_port, None) + self.assertEquals(debug_context.debug_args, None) + + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_non_path_not_found_oserror_is_thrown(self, pathlib_mock): + pathlib_mock.side_effect = OSError() + + with self.assertRaises(OSError): + InvokeContext._get_debug_context(debug_port=1111, debug_args=None, debugger_path='somepath') + + @patch("samcli.commands.local.cli_common.invoke_context.DebugContext") + def test_debug_port_given_without_debugger_path(self, debug_context_mock): + debug_context_mock.return_value = "I am the DebugContext" + + debug_context = InvokeContext._get_debug_context(1111, None, None) + + self.assertEquals(debug_context, "I am the DebugContext") + + debug_context_mock.assert_called_once_with(debug_port=1111, debug_args=None, debugger_path=None) + + @patch("samcli.commands.local.cli_common.invoke_context.DebugContext") + @patch("samcli.commands.local.cli_common.invoke_context.Path") + def test_debugger_path_resolves(self, pathlib_mock, debug_context_mock): + pathlib_path_mock = Mock() + resolve_path_mock = Mock() + pathlib_path_mock.resolve.return_value = resolve_path_mock + resolve_path_mock.is_dir.return_value = True + resolve_path_mock.__str__ = Mock() + resolve_path_mock.__str__.return_value = "full/path" + pathlib_mock.return_value = pathlib_path_mock + + debug_context_mock.return_value = "I am the DebugContext" + + debug_context = InvokeContext._get_debug_context(1111, "args", "./path") + + self.assertEquals(debug_context, "I am the DebugContext") + + debug_context_mock.assert_called_once_with(debug_port=1111, debug_args="args", debugger_path="full/path") + resolve_path_mock.is_dir.assert_called_once() + pathlib_path_mock.resolve.assert_called_once_with(strict=True) + pathlib_mock.assert_called_once_with("./path") + + class TestInvokeContext_check_docker_connectivity(TestCase): def test_must_call_ping(self): diff --git a/tests/unit/commands/local/generate_event/api/__init__.py b/tests/unit/commands/local/generate_event/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/api/test_cli.py b/tests/unit/commands/local/generate_event/api/test_cli.py deleted file mode 100644 index cb8d16f9e2..0000000000 --- a/tests/unit/commands/local/generate_event/api/test_cli.py +++ /dev/null @@ -1,23 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.api.cli import do_cli as api_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.api.cli.json") - @patch("samcli.commands.local.generate_event.api.cli.click") - @patch("samcli.commands.local.generate_event.api.cli.generate_api_event") - def test_generate_schedule_event(self, api_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - method = "method" - body = "body" - resource = "resource" - path = "path" - - api_cli(ctx=None, method=method, body=body, resource=resource, path=path) - - api_event_patch.assert_called_once_with(method, body, resource, path) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/dynamodb/__init__.py b/tests/unit/commands/local/generate_event/dynamodb/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/dynamodb/test_cli.py b/tests/unit/commands/local/generate_event/dynamodb/test_cli.py deleted file mode 100644 index 56a45f5d19..0000000000 --- a/tests/unit/commands/local/generate_event/dynamodb/test_cli.py +++ /dev/null @@ -1,20 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.dynamodb.cli import do_cli as dynamodb_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.dynamodb.cli.json") - @patch("samcli.commands.local.generate_event.dynamodb.cli.click") - @patch("samcli.commands.local.generate_event.dynamodb.cli.generate_dynamodb_event") - def test_generate_schedule_event(self, dynamodb_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - region = "region" - - dynamodb_cli(ctx=None, region=region) - - dynamodb_event_patch.assert_called_once_with(region) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/kinesis/__init__.py b/tests/unit/commands/local/generate_event/kinesis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/kinesis/test_cli.py b/tests/unit/commands/local/generate_event/kinesis/test_cli.py deleted file mode 100644 index ae91e3fb70..0000000000 --- a/tests/unit/commands/local/generate_event/kinesis/test_cli.py +++ /dev/null @@ -1,25 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.kinesis.cli import do_cli as kinesis_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.kinesis.cli.json") - @patch("samcli.commands.local.generate_event.kinesis.cli.click") - @patch("samcli.commands.local.generate_event.kinesis.cli.generate_kinesis_event") - def test_generate_schedule_event(self, kinesis_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - region = "region" - partition = "partition" - sequence = "sequence" - data = "data" - - data_base64 = b"ZGF0YQ==" - - kinesis_cli(ctx=None, region=region, partition=partition, sequence=sequence, data=data) - - kinesis_event_patch.assert_called_once_with(region, partition, sequence, data_base64) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/s3/__init__.py b/tests/unit/commands/local/generate_event/s3/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/s3/test_cli.py b/tests/unit/commands/local/generate_event/s3/test_cli.py deleted file mode 100644 index da2d86035d..0000000000 --- a/tests/unit/commands/local/generate_event/s3/test_cli.py +++ /dev/null @@ -1,22 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.s3.cli import do_cli as s3_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.s3.cli.json") - @patch("samcli.commands.local.generate_event.s3.cli.click") - @patch("samcli.commands.local.generate_event.s3.cli.generate_s3_event") - def test_generate_schedule_event(self, s3_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - region = "region" - bucket = "bucket" - key = "key" - - s3_cli(ctx=None, region=region, bucket=bucket, key=key) - - s3_event_patch.assert_called_once_with(region, bucket, key) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/schedule/__init__.py b/tests/unit/commands/local/generate_event/schedule/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/schedule/test_cli.py b/tests/unit/commands/local/generate_event/schedule/test_cli.py deleted file mode 100644 index 0bd874608a..0000000000 --- a/tests/unit/commands/local/generate_event/schedule/test_cli.py +++ /dev/null @@ -1,20 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.schedule.cli import do_cli as schedule_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.schedule.cli.json") - @patch("samcli.commands.local.generate_event.schedule.cli.click") - @patch("samcli.commands.local.generate_event.schedule.cli.generate_schedule_event") - def test_generate_schedule_event(self, schedule_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - region = "us-east-1" - - schedule_cli(ctx=None, region=region) - - schedule_event_patch.assert_called_once_with(region) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/sns/__init__.py b/tests/unit/commands/local/generate_event/sns/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit/commands/local/generate_event/sns/test_cli.py b/tests/unit/commands/local/generate_event/sns/test_cli.py deleted file mode 100644 index a3091c6a98..0000000000 --- a/tests/unit/commands/local/generate_event/sns/test_cli.py +++ /dev/null @@ -1,22 +0,0 @@ -from unittest import TestCase -from mock import patch - -from samcli.commands.local.generate_event.sns.cli import do_cli as sns_cli - - -class TestCli(TestCase): - - @patch("samcli.commands.local.generate_event.sns.cli.json") - @patch("samcli.commands.local.generate_event.sns.cli.click") - @patch("samcli.commands.local.generate_event.sns.cli.generate_sns_event") - def test_generate_schedule_event(self, sns_event_patch, click_patch, json_patch): - json_patch.dumps.return_value = "This to be echoed by click" - - message = "message" - topic = "topic" - subject = "subject" - - sns_cli(ctx=None, message=message, topic=topic, subject=subject) - - sns_event_patch.assert_called_once_with(message, topic, subject) - click_patch.echo.assert_called_once_with("This to be echoed by click") diff --git a/tests/unit/commands/local/generate_event/test_event_generation.py b/tests/unit/commands/local/generate_event/test_event_generation.py new file mode 100644 index 0000000000..4d1e08125e --- /dev/null +++ b/tests/unit/commands/local/generate_event/test_event_generation.py @@ -0,0 +1,157 @@ +from unittest import TestCase +from mock import Mock +from mock import patch + +from samcli.commands.local.lib.generated_sample_events import events +from samcli.commands.local.generate_event.event_generation import ServiceCommand +from samcli.commands.local.generate_event.event_generation import EventTypeSubCommand + + +class TestEvents(TestCase): + + def setUp(self): + self.values_to_sub = {"hello": "world"} + + def test_base64_encoding(self): + tags = {"hello": {"encoding": "base64"}} + e = events.Events().encode(tags, 'encoding', self.values_to_sub) + self.assertEqual(e, {"hello": "d29ybGQ="}) + + def test_url_encoding(self): + tags = {"hello": {"encoding": "url"}} + e = events.Events().encode(tags, 'encoding', self.values_to_sub) + self.assertEqual(e, {"hello": "world"}) + + def test_if_encoding_is_none(self): + tags = {"hello": {"encoding": "None"}} + e = events.Events().encode(tags, 'encoding', self.values_to_sub) + self.assertEqual(e, {"hello": "world"}) + + def test_if_tags_is_empty(self): + tags = {} + e = events.Events().encode(tags, 'encoding', {}) + self.assertEqual(e, {}) + + def test_if_tags_is_two_or_more(self): + tags = {"hello": {"encoding": "base64"}, "hi": {"encoding": "url"}, "bop": {"encoding": "None"}} + values_to_sub = {"bop": "dop", "hello": "world", "hi": "yo"} + e = events.Events().encode(tags, 'encoding', values_to_sub) + self.assertEqual(e, {"bop": "dop", "hello": "d29ybGQ=", "hi": "yo"}) + + +class TestServiceCommand(TestCase): + + def setUp(self): + self.service_cmd_name = "myservice" + self.event_type_name = "myevent" + self.all_cmds = {"hello": "world", "hi": "you"} + self.events_lib_mock = Mock() + self.events_lib_mock.event_mapping = self.all_cmds + self.s = ServiceCommand(self.events_lib_mock) + + def test_init_has_correct_all_cmds(self): + self.assertEqual(self.s.all_cmds, self.all_cmds) + + def test_init_events_lib_is_not_valid(self): + with self.assertRaises(ValueError): + ServiceCommand(events_lib=None) + + def test_init_events_lib_is_valid(self): + s = ServiceCommand(self.events_lib_mock) + self.assertEqual(s.events_lib, self.events_lib_mock) + + def test_get_command_returns_none_when_not_in_all_cmds(self): + cmd_name = "howdy" + e = self.s.get_command(None, cmd_name) + self.assertIsNone(e) + + def test_list_commands_must_return_commands_name(self): + expected = self.s.list_commands(ctx=None) + self.assertEqual(expected, ['hello', 'hi']) + + def test_get_command_return_value(self): + command_name = "hello" + output = self.s.get_command(None, command_name) + self.assertEqual(output.top_level_cmd_name, "hello") + self.assertEqual(output.events_lib, self.events_lib_mock) + self.assertEqual(output.subcmd_definition, "world") + + +class TestEventTypeSubCommand(TestCase): + + def setUp(self): + self.service_cmd_name = "myservice" + self.event_type_name = "myevent" + self.all_cmds = '{"hello": "world", "hi": "you"}' + self.events_lib_mock = Mock() + self.s = EventTypeSubCommand(self.events_lib_mock, self.service_cmd_name, self.all_cmds) + + def test_subcommand_accepts_events_lib(self): + events_lib = Mock() + events_lib.expose_event_metadata.return_value = self.all_cmds + s = EventTypeSubCommand(events_lib, self.service_cmd_name, self.all_cmds) + self.assertEqual(s.events_lib, events_lib) + + def test_subcommand_accepts_top_level_cmd_name(self): + top_lvl_cmd = "myservice" + self.assertEqual(top_lvl_cmd, self.service_cmd_name) + + def test_subcommand_accepts_subcmd_definition(self): + self.assertEqual(self.s.subcmd_definition, self.all_cmds) + + def test_subcommand_get_accepts_cmd_name_returns_none(self): + subcmd_definition = '{"hello": { "tags : { "world" }}}' + s = EventTypeSubCommand(self.events_lib_mock, self.service_cmd_name, subcmd_definition) + e = s.get_command(None, "heyyo") + self.assertIsNone(e) + + @patch("samcli.cli.options.click") + @patch("samcli.commands.local.generate_event.event_generation.functools") + @patch("samcli.commands.local.generate_event.event_generation.click") + def test_subcommand_get_command_return_value(self, click_mock, functools_mock, options_click_mock): + all_commands = {"hi": {"help": "Generates a hello Event", "tags": {}}} + command_object_mock = Mock() + click_mock.Command.return_value = command_object_mock + option_mock = Mock() + options_click_mock.Option.return_value = option_mock + callback_object_mock = Mock() + functools_mock.partial.return_value = callback_object_mock + s = EventTypeSubCommand(self.events_lib_mock, "hello", all_commands) + s.get_command(None, "hi") + click_mock.Command.assert_called_once_with(name="hi", + help="Generates a hello Event", + params=[], + callback=callback_object_mock) + + def test_subcommand_list_return_value(self): + subcmd_def = {"hello": "world", "hi": "you"} + self.events_lib_mock.expose_event_metadata.return_value = subcmd_def + s = EventTypeSubCommand(self.events_lib_mock, "hello", subcmd_def) + expected = ['hello', 'hi'] + self.assertEquals(s.list_commands(ctx=None), expected) + + def test_must_print_sample_event_json(self): + event_json = '{"hello": "world"}' + self.events_lib_mock.generate_event.return_value = event_json + s = EventTypeSubCommand(self.events_lib_mock, "hello", event_json) + event = s.cmd_implementation(self.events_lib_mock, + self.service_cmd_name, + self.event_type_name, + {}) + self.events_lib_mock.generate_event.assert_called_with(self.service_cmd_name, + self.event_type_name, + {}) + self.assertEqual(event, event_json) + + def test_must_accept_keyword_args(self): + event_json = '{"hello": "world"}' + self.events_lib_mock.generate_event.return_value = event_json + s = EventTypeSubCommand(self.events_lib_mock, "hello", event_json) + event = s.cmd_implementation(self.events_lib_mock, + self.service_cmd_name, + self.event_type_name, + key="value") + self.events_lib_mock.generate_event.assert_called_with(self.service_cmd_name, + self.event_type_name, + {"key": "value"}) + self.assertEqual(event, event_json) diff --git a/tests/unit/commands/local/invoke/test_cli.py b/tests/unit/commands/local/invoke/test_cli.py index 9c67d015e5..b8bf4f1b81 100644 --- a/tests/unit/commands/local/invoke/test_cli.py +++ b/tests/unit/commands/local/invoke/test_cli.py @@ -11,6 +11,8 @@ from samcli.commands.exceptions import UserException from samcli.commands.local.invoke.cli import do_cli as invoke_cli, _get_event as invoke_cli_get_event +STDIN_FILE_NAME = "-" + class TestCli(TestCase): @@ -21,11 +23,14 @@ def setUp(self): self.env_vars = "env-vars" self.debug_port = 123 self.debug_args = "args" + self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" self.docker_network = "network" self.log_file = "logfile" self.skip_pull_image = True self.profile = "profile" + self.no_event = False + self.region = "region" @patch("samcli.commands.local.invoke.cli.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") @@ -41,25 +46,30 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo function_identifier=self.function_id, template=self.template, event=self.eventfile, + no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_port, debug_args=self.debug_args, + debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - profile=self.profile) + profile=self.profile, + region=self.region) InvokeContextMock.assert_called_with(template_file=self.template, function_identifier=self.function_id, env_vars_file=self.env_vars, - debug_port=self.debug_port, - debug_args=self.debug_args, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - aws_profile=self.profile) + aws_profile=self.profile, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + aws_region=self.region) context_mock.local_lambda_runner.invoke.assert_called_with(context_mock.function_name, event=event_data, @@ -67,6 +77,74 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo stderr=context_mock.stderr) get_event_mock.assert_called_with(self.eventfile) + @patch("samcli.commands.local.invoke.cli.InvokeContext") + @patch("samcli.commands.local.invoke.cli._get_event") + def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock): + self.no_event = True + # Mock the __enter__ method to return a object inside a context manager + context_mock = Mock() + InvokeContextMock.return_value.__enter__.return_value = context_mock + invoke_cli(ctx=None, + function_identifier=self.function_id, + template=self.template, + event=STDIN_FILE_NAME, + no_event=self.no_event, + env_vars=self.env_vars, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + docker_volume_basedir=self.docker_volume_basedir, + docker_network=self.docker_network, + log_file=self.log_file, + skip_pull_image=self.skip_pull_image, + profile=self.profile, + region=self.region) + + InvokeContextMock.assert_called_with(template_file=self.template, + function_identifier=self.function_id, + env_vars_file=self.env_vars, + docker_volume_basedir=self.docker_volume_basedir, + docker_network=self.docker_network, + log_file=self.log_file, + skip_pull_image=self.skip_pull_image, + aws_profile=self.profile, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + aws_region=self.region) + + context_mock.local_lambda_runner.invoke.assert_called_with(context_mock.function_name, + event="{}", + stdout=context_mock.stdout, + stderr=context_mock.stderr) + get_event_mock.assert_not_called() + + @patch("samcli.commands.local.invoke.cli.InvokeContext") + @patch("samcli.commands.local.invoke.cli._get_event") + def test_must_raise_user_exception_on_no_event_and_event(self, get_event_mock, InvokeContextMock): + self.no_event = True + + with self.assertRaises(UserException) as ex_ctx: + + invoke_cli(ctx=None, + function_identifier=self.function_id, + template=self.template, + event=self.eventfile, + no_event=self.no_event, + env_vars=self.env_vars, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + docker_volume_basedir=self.docker_volume_basedir, + docker_network=self.docker_network, + log_file=self.log_file, + skip_pull_image=self.skip_pull_image, + profile=self.profile, + region=self.region) + + msg = str(ex_ctx.exception) + self.assertEquals(msg, "no_event and event cannot be used together. Please provide only one.") + @patch("samcli.commands.local.invoke.cli.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_function_not_found(self, get_event_mock, InvokeContextMock): @@ -85,14 +163,17 @@ def test_must_raise_user_exception_on_function_not_found(self, get_event_mock, I function_identifier=self.function_id, template=self.template, event=self.eventfile, + no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_port, debug_args=self.debug_args, + debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - profile=self.profile) + profile=self.profile, + region=self.region) msg = str(ex_ctx.exception) self.assertEquals(msg, "Function {} not found in template".format(self.function_id)) @@ -111,14 +192,17 @@ def test_must_raise_user_exception_on_invalid_sam_template(self, get_event_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, + no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_port, debug_args=self.debug_args, + debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - profile=self.profile) + profile=self.profile, + region=self.region) msg = str(ex_ctx.exception) self.assertEquals(msg, "bad template") @@ -127,7 +211,7 @@ def test_must_raise_user_exception_on_invalid_sam_template(self, get_event_mock, class TestGetEvent(TestCase): @parameterized.expand([ - param("-"), + param(STDIN_FILE_NAME), param("somefile") ]) @patch("samcli.commands.local.invoke.cli.click") diff --git a/tests/unit/commands/local/lib/test_debug_context.py b/tests/unit/commands/local/lib/test_debug_context.py new file mode 100644 index 0000000000..6d8f0a347a --- /dev/null +++ b/tests/unit/commands/local/lib/test_debug_context.py @@ -0,0 +1,67 @@ +from unittest import TestCase + +from parameterized import parameterized + +from samcli.commands.local.lib.debug_context import DebugContext + + +class TestDebugContext(TestCase): + + def test_init(self): + context = DebugContext('port', 'debuggerpath', 'debug_args') + + self.assertEquals(context.debug_port, 'port') + self.assertEquals(context.debugger_path, 'debuggerpath') + self.assertEquals(context.debug_args, 'debug_args') + + @parameterized.expand([ + ('1000', 'debuggerpath', 'debug_args'), + ('1000', None, None), + ('1000', None, 'debug_args'), + ('1000', 'debuggerpath', None), + (1000, 'debuggerpath', 'debug_args'), + (1000, None, None), + (1000, None, 'debug_args'), + (1000, 'debuggerpath', None) + ]) + def test_bool_truthy(self, port, debug_path, debug_ars): + debug_context = DebugContext(port, debug_path, debug_ars) + + self.assertTrue(debug_context.__bool__()) + + @parameterized.expand([ + (None, 'debuggerpath', 'debug_args'), + (None, None, None), + (None, None, 'debug_args'), + (None, 'debuggerpath', None), + ]) + def test_bool_falsy(self, port, debug_path, debug_ars): + debug_context = DebugContext(port, debug_path, debug_ars) + + self.assertFalse(debug_context.__bool__()) + + @parameterized.expand([ + ('1000', 'debuggerpath', 'debug_args'), + ('1000', None, None), + ('1000', None, 'debug_args'), + ('1000', 'debuggerpath', None), + (1000, 'debuggerpath', 'debug_args'), + (1000, None, None), + (1000, None, 'debug_args'), + (1000, 'debuggerpath', None) + ]) + def test_nonzero_thruthy(self, port, debug_path, debug_ars): + debug_context = DebugContext(port, debug_path, debug_ars) + + self.assertTrue(debug_context.__nonzero__()) + + @parameterized.expand([ + (None, 'debuggerpath', 'debug_args'), + (None, None, None), + (None, None, 'debug_args'), + (None, 'debuggerpath', None) + ]) + def test_nonzero_falsy(self, port, debug_path, debug_ars): + debug_context = DebugContext(port, debug_path, debug_ars) + + self.assertFalse(debug_context.__nonzero__()) diff --git a/tests/unit/commands/local/lib/test_events.py b/tests/unit/commands/local/lib/test_events.py deleted file mode 100644 index dd12eec2ba..0000000000 --- a/tests/unit/commands/local/lib/test_events.py +++ /dev/null @@ -1,286 +0,0 @@ -from unittest import TestCase - -from samcli.commands.local.lib.events import generate_api_event, generate_dynamodb_event, generate_kinesis_event, \ - generate_schedule_event, generate_sns_event, generate_s3_event - - -class TestGeneratedEvent(TestCase): - - def test_s3_event(self): - actual_event = generate_s3_event("us-east-1", "bucket", "key") - - expected_event = { - "Records": [{ - "eventVersion": "2.0", - "eventTime": "1970-01-01T00:00:00.000Z", - "requestParameters": { - "sourceIPAddress": "127.0.0.1" - }, - "s3": { - "configurationId": "testConfigRule", - "object": { - "eTag": "0123456789abcdef0123456789abcdef", - "sequencer": "0A1B2C3D4E5F678901", - "key": "key", - "size": 1024 - }, - "bucket": { - "arn": "arn:aws:s3:::bucket", - "name": "bucket", - "ownerIdentity": { - "principalId": "EXAMPLE" - } - }, - "s3SchemaVersion": "1.0" - }, - "responseElements": { - "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH", - "x-amz-request-id": "EXAMPLE123456789" - }, - "awsRegion": "us-east-1", - "eventName": "ObjectCreated:Put", - "userIdentity": { - "principalId": "EXAMPLE" - }, - "eventSource": "aws:s3" - }] - } - - self.assertEquals(actual_event, expected_event) - - def test_sns_event(self): - actual_event = generate_sns_event("message", "topic", "subject") - - expected_event = { - "Records": [{ - "EventVersion": "1.0", - "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", - "EventSource": "aws:sns", - "Sns": { - "SignatureVersion": "1", - "Timestamp": "1970-01-01T00:00:00.000Z", - "Signature": "EXAMPLE", - "SigningCertUrl": "EXAMPLE", - "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", - "Message": "message", - "MessageAttributes": { - "Test": { - "Type": "String", - "Value": "TestString" - }, - "TestBinary": { - "Type": "Binary", - "Value": "TestBinary" - } - }, - "Type": "Notification", - "UnsubscribeUrl": "EXAMPLE", - "TopicArn": "topic", - "Subject": "subject" - } - }] - } - - self.assertEquals(actual_event, expected_event) - - def test_api_event(self): - actual_event = generate_api_event("GET", "body of the request", "/path", "/path") - self.maxDiff = None - - expected_event = { - 'body': 'body of the request', - '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': 'aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==', - 'X-Forwarded-For': '127.0.0.1, 127.0.0.2', - 'X-Forwarded-Port': '443', - 'X-Forwarded-Proto': 'https' - }, - 'httpMethod': 'GET', - 'isBase64Encoded': False, - 'path': '/path', - 'pathParameters': {'proxy': '/path'}, - 'queryStringParameters': {'foo': 'bar'}, - 'requestContext': { - 'accountId': "123456789012", - 'apiId': "1234567890", - 'extendedRequestId': None, - 'httpMethod': 'GET', - 'identity': { - 'accountId': None, - 'apiKey': None, - 'caller': None, - 'cognitoAuthenticationProvider': None, - 'cognitoAuthenticationType': None, - 'cognitoIdentityPoolId': None, - 'sourceIp': '127.0.0.1', - 'user': None, - 'userAgent': "Custom User Agent String", - 'userArn': None - }, - 'path': '/path', - 'requestId': "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", - 'resourceId': "123456", - 'resourcePath': '/path', - 'stage': 'prod' - }, - 'resource': '/path', - 'stageVariables': None - } - - self.assertEquals(actual_event, expected_event) - - def test_dynamodb_event(self): - actual_event = generate_dynamodb_event("us-east-1") - - expected_event = { - "Records": [ - { - "eventID": "1", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "NewImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": "us-east-1", - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:us-east-1:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "eventVersion": "1.0", - "dynamodb": { - "OldImage": { - "Message": { - "S": "New item!" - }, - "Id": { - "N": "101" - } - }, - "SequenceNumber": "222", - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 59, - "NewImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "us-east-1", - "eventName": "MODIFY", - "eventSourceARN": "arn:aws:dynamodb:us-east-1:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "3", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "Id": { - "N": "101" - } - }, - "SizeBytes": 38, - "SequenceNumber": "333", - "OldImage": { - "Message": { - "S": "This item has changed" - }, - "Id": { - "N": "101" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "awsRegion": "us-east-1", - "eventName": "REMOVE", - "eventSourceARN": "arn:aws:dynamodb:us-east-1:account-id:table/" - "ExampleTableWithStream/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - } - ] - } - - self.assertEquals(actual_event, expected_event) - - def test_schedule_event(self): - actual_event = generate_schedule_event("us-east-1") - - expected_event = { - "version": "0", - "account": "123456789012", - "region": "us-east-1", - "detail": {}, - "detail-type": "Scheduled Event", - "source": "aws.events", - "time": "1970-01-01T00:00:00Z", - "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", - "resources": [ - "arn:aws:events:us-east-1:123456789012:rule/my-schedule" - ] - } - - self.assertEquals(actual_event, expected_event) - - def test_kinesis_event(self): - actual_event = generate_kinesis_event("us-east-1", "partition", "sequence", "this is data") - - expected_event = { - "Records": [{ - "eventID": "shardId-000000000000:sequence", - "eventVersion": "1.0", - "kinesis": { - "approximateArrivalTimestamp": 1428537600, - "partitionKey": "partition", - "data": "this is data", - "kinesisSchemaVersion": "1.0", - "sequenceNumber": "sequence" - }, - "invokeIdentityArn": "arn:aws:iam::EXAMPLE", - "eventName": "aws:kinesis:record", - "eventSourceARN": "arn:aws:kinesis:EXAMPLE", - "eventSource": "aws:kinesis", - "awsRegion": "us-east-1" - }] - } - - self.assertEquals(actual_event, expected_event) diff --git a/tests/unit/commands/local/lib/test_local_lambda.py b/tests/unit/commands/local/lib/test_local_lambda.py index 812ce60daf..8417f4fc5a 100644 --- a/tests/unit/commands/local/lib/test_local_lambda.py +++ b/tests/unit/commands/local/lib/test_local_lambda.py @@ -24,17 +24,17 @@ def setUp(self): self.function_provider_mock = Mock() self.cwd = "cwd" self.env_vars_values = {} - self.debug_port = 123 - self.debug_args = "abc" + self.debug_context = None self.aws_profile = "myprofile" + self.aws_region = "region" self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + debug_context=self.debug_context, + aws_profile=self.aws_profile, + aws_region=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_get_from_boto_session(self, boto3_mock): @@ -59,7 +59,7 @@ def test_must_get_from_boto_session(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_region_name(self, boto3_mock): @@ -83,7 +83,7 @@ def test_must_work_with_no_region_name(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_access_key(self, boto3_mock): @@ -107,7 +107,7 @@ def test_must_work_with_no_access_key(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_secret_key(self, boto3_mock): @@ -131,7 +131,7 @@ def test_must_work_with_no_secret_key(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_session_token(self, boto3_mock): @@ -155,7 +155,7 @@ def test_must_work_with_no_session_token(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_credentials(self, boto3_mock): @@ -167,7 +167,7 @@ def test_must_work_with_no_credentials(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) @patch("samcli.commands.local.lib.local_lambda.boto3") def test_must_work_with_no_session(self, boto3_mock): @@ -177,7 +177,7 @@ def test_must_work_with_no_session(self, boto3_mock): actual = self.local_lambda.get_aws_creds() self.assertEquals(expected, actual) - boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile) + boto3_mock.session.Session.assert_called_with(profile_name=self.aws_profile, region_name=self.aws_region) class TestLocalLambda_get_code_path(TestCase): @@ -187,9 +187,9 @@ def setUp(self): self.function_provider_mock = Mock() self.cwd = "/my/current/working/directory" self.env_vars_values = {} - self.debug_port = 123 - self.debug_args = "abc" + self.debug_context = None self.aws_profile = "myprofile" + self.aws_region = "region" self.relative_codeuri = "./my/path" self.absolute_codeuri = "/home/foo/bar" # Some absolute path to use @@ -198,10 +198,10 @@ def setUp(self): self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + aws_profile=self.aws_profile, + debug_context=self.debug_context, + aws_region=self.aws_region) @parameterized.expand([ ("."), @@ -267,9 +267,9 @@ def setUp(self): self.runtime_mock = Mock() self.function_provider_mock = Mock() self.cwd = "/my/current/working/directory" - self.debug_port = 123 - self.debug_args = "abc" + self.debug_context = None self.aws_profile = "myprofile" + self.aws_region = "region" self.env_vars_values = {} self.environ = { @@ -281,10 +281,10 @@ def setUp(self): self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + debug_context=self.debug_context, + aws_profile=self.aws_profile, + aws_region=self.aws_region) self.aws_creds = {"key": "key"} self.local_lambda.get_aws_creds = Mock() @@ -367,21 +367,24 @@ def setUp(self): self.runtime_mock = Mock() self.function_provider_mock = Mock() self.cwd = "/my/current/working/directory" - self.debug_port = 123 - self.debug_args = "abc" self.aws_profile = "myprofile" + self.debug_context = None self.env_vars_values = {} + self.aws_region = "region" self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + aws_profile=self.aws_profile, + debug_context=self.debug_context, + aws_region=self.aws_region) + @patch('samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging') @patch('samcli.commands.local.lib.local_lambda.FunctionConfig') - def test_must_work(self, FunctionConfigMock): + def test_must_work(self, FunctionConfigMock, is_debugging_mock): + is_debugging_mock.return_value = False + env_vars = "envvars" self.local_lambda._make_env_vars = Mock() self.local_lambda._make_env_vars.return_value = env_vars @@ -415,6 +418,44 @@ def test_must_work(self, FunctionConfigMock): self.local_lambda._get_code_path.assert_called_with(function.codeuri) self.local_lambda._make_env_vars.assert_called_with(function) + @patch('samcli.commands.local.lib.local_lambda.LocalLambdaRunner.is_debugging') + @patch('samcli.commands.local.lib.local_lambda.FunctionConfig') + def test_timeout_set_to_max_during_debugging(self, FunctionConfigMock, is_debugging_mock): + is_debugging_mock.return_value = True + + env_vars = "envvars" + self.local_lambda._make_env_vars = Mock() + self.local_lambda._make_env_vars.return_value = env_vars + + codepath = "codepath" + self.local_lambda._get_code_path = Mock() + self.local_lambda._get_code_path.return_value = codepath + + function = Function(name="function_name", + runtime="runtime", + memory=1234, + timeout=36000, + handler="handler", + codeuri="codeuri", + environment=None, + rolearn=None) + + config = "someconfig" + FunctionConfigMock.return_value = config + actual = self.local_lambda._get_invoke_config(function) + self.assertEquals(actual, config) + + FunctionConfigMock.assert_called_with(name=function.name, + runtime=function.runtime, + handler=function.handler, + code_abs_path=codepath, + memory=function.memory, + timeout=function.timeout, + env_vars=env_vars) + + self.local_lambda._get_code_path.assert_called_with(function.codeuri) + self.local_lambda._make_env_vars.assert_called_with(function) + class TestLocalLambda_invoke(TestCase): @@ -422,18 +463,18 @@ def setUp(self): self.runtime_mock = Mock() self.function_provider_mock = Mock() self.cwd = "/my/current/working/directory" - self.debug_port = 123 - self.debug_args = "abc" + self.debug_context = None self.aws_profile = "myprofile" + self.aws_region = "region" self.env_vars_values = {} self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + aws_profile=self.aws_profile, + debug_context=self.debug_context, + aws_region=self.aws_region) def test_must_work(self): name = "name" @@ -450,8 +491,7 @@ def test_must_work(self): self.local_lambda.invoke(name, event, stdout, stderr) self.runtime_mock.invoke.assert_called_with(invoke_config, event, - debug_port=self.debug_port, - debug_args=self.debug_args, + debug_context=None, stdout=stdout, stderr=stderr) def test_must_raise_if_function_not_found(self): @@ -467,18 +507,18 @@ def setUp(self): self.runtime_mock = Mock() self.function_provider_mock = Mock() self.cwd = "/my/current/working/directory" - self.debug_port = 123 - self.debug_args = "abc" + self.debug_context = Mock() self.aws_profile = "myprofile" + self.aws_region = "region" self.env_vars_values = {} self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - self.debug_port, - self.debug_args, - self.aws_profile) + env_vars_values=self.env_vars_values, + aws_profile=self.aws_profile, + debug_context=self.debug_context, + aws_region=self.aws_region) def test_must_be_on(self): self.assertTrue(self.local_lambda.is_debugging()) @@ -488,9 +528,9 @@ def test_must_be_off(self): self.local_lambda = LocalLambdaRunner(self.runtime_mock, self.function_provider_mock, self.cwd, - self.env_vars_values, - debug_port=None, # No debug port - debug_args=self.debug_args, - aws_profile=self.aws_profile) + env_vars_values=self.env_vars_values, + debug_context=None, + aws_profile=self.aws_profile, + aws_region=self.aws_region) self.assertFalse(self.local_lambda.is_debugging()) diff --git a/tests/unit/commands/local/start_api/test_cli.py b/tests/unit/commands/local/start_api/test_cli.py index 55f9bb6e8b..c4ef3a8665 100644 --- a/tests/unit/commands/local/start_api/test_cli.py +++ b/tests/unit/commands/local/start_api/test_cli.py @@ -18,11 +18,13 @@ def setUp(self): self.env_vars = "env-vars" self.debug_port = 123 self.debug_args = "args" + self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" self.docker_network = "network" self.log_file = "logfile" self.skip_pull_image = True self.profile = "profile" + self.region = "region" self.host = "host" self.port = 123 @@ -30,8 +32,8 @@ def setUp(self): @patch("samcli.commands.local.start_api.cli.InvokeContext") @patch("samcli.commands.local.start_api.cli.LocalApiService") - def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, invoke_context_mock): - + def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, + invoke_context_mock): # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() invoke_context_mock.return_value.__enter__.return_value = context_mock @@ -44,13 +46,15 @@ def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, invoke_context_mock.assert_called_with(template_file=self.template, function_identifier=None, env_vars_file=self.env_vars, - debug_port=self.debug_port, - debug_args=self.debug_args, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - aws_profile=self.profile) + aws_profile=self.profile, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + aws_region=self.region) local_api_service_mock.assert_called_with(lambda_invoke_context=context_mock, port=self.port, @@ -99,8 +103,10 @@ def call_cli(self): env_vars=self.env_vars, debug_port=self.debug_port, debug_args=self.debug_args, + debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - profile=self.profile) + profile=self.profile, + region=self.region) diff --git a/tests/unit/commands/local/start_lambda/test_cli.py b/tests/unit/commands/local/start_lambda/test_cli.py index d7f4b7c921..1e2747482b 100644 --- a/tests/unit/commands/local/start_lambda/test_cli.py +++ b/tests/unit/commands/local/start_lambda/test_cli.py @@ -13,18 +13,21 @@ def setUp(self): self.env_vars = "env-vars" self.debug_port = 123 self.debug_args = "args" + self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" self.docker_network = "network" self.log_file = "logfile" self.skip_pull_image = True self.profile = "profile" + self.region = "region" self.host = "host" self.port = 123 @patch("samcli.commands.local.start_lambda.cli.InvokeContext") @patch("samcli.commands.local.start_lambda.cli.LocalLambdaService") - def test_cli_must_setup_context_and_start_service(self, local_lambda_service_mock, invoke_context_mock): + def test_cli_must_setup_context_and_start_service(self, local_lambda_service_mock, + invoke_context_mock): # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() invoke_context_mock.return_value.__enter__.return_value = context_mock @@ -37,13 +40,15 @@ def test_cli_must_setup_context_and_start_service(self, local_lambda_service_moc invoke_context_mock.assert_called_with(template_file=self.template, function_identifier=None, env_vars_file=self.env_vars, - debug_port=self.debug_port, - debug_args=self.debug_args, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - aws_profile=self.profile) + aws_profile=self.profile, + debug_port=self.debug_port, + debug_args=self.debug_args, + debugger_path=self.debugger_path, + aws_region=self.region) local_lambda_service_mock.assert_called_with(lambda_invoke_context=context_mock, port=self.port, @@ -70,8 +75,10 @@ def call_cli(self): env_vars=self.env_vars, debug_port=self.debug_port, debug_args=self.debug_args, + debugger_path=self.debugger_path, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, - profile=self.profile) + profile=self.profile, + region=self.region) diff --git a/tests/unit/commands/validate/test_cli.py b/tests/unit/commands/validate/test_cli.py index d440eab06c..1fe75d5fe0 100644 --- a/tests/unit/commands/validate/test_cli.py +++ b/tests/unit/commands/validate/test_cli.py @@ -1,6 +1,9 @@ from unittest import TestCase from mock import Mock, patch +from botocore.exceptions import NoCredentialsError + +from samcli.commands.exceptions import UserException from samcli.commands.local.cli_common.user_exceptions import SamTemplateNotFoundException, InvalidSamTemplateException from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException from samcli.commands.validate.validate import do_cli, _read_sam_file @@ -47,6 +50,21 @@ def test_template_fails_validation(self, read_sam_file_patch, click_patch, templ do_cli(ctx=None, template=template_path) + @patch('samcli.commands.validate.validate.SamTemplateValidator') + @patch('samcli.commands.validate.validate.click') + @patch('samcli.commands.validate.validate._read_sam_file') + def test_no_credentials_provided(self, read_sam_file_patch, click_patch, template_valiadator): + template_path = 'path_to_template' + read_sam_file_patch.return_value = {"a": "b"} + + is_valid_mock = Mock() + is_valid_mock.is_valid.side_effect = NoCredentialsError + template_valiadator.return_value = is_valid_mock + + with self.assertRaises(UserException): + do_cli(ctx=None, + template=template_path) + @patch('samcli.commands.validate.validate.SamTemplateValidator') @patch('samcli.commands.validate.validate.click') @patch('samcli.commands.validate.validate._read_sam_file') diff --git a/tests/unit/local/docker/test_container.py b/tests/unit/local/docker/test_container.py index 3c83178849..63d6f5ab99 100644 --- a/tests/unit/local/docker/test_container.py +++ b/tests/unit/local/docker/test_container.py @@ -58,6 +58,8 @@ def setUp(self): self.exposed_ports = {123: 123} self.entrypoint = ["a", "b", "c"] self.env_vars = {"key": "value"} + self.container_opts = {"container": "opts"} + self.additional_volumes = {'/somepath': {"blah": "blah value"}} self.mock_docker_client = Mock() self.mock_docker_client.containers = Mock() @@ -108,7 +110,8 @@ def test_must_create_container_including_all_optional_values(self): self.host_dir: { "bind": self.working_dir, "mode": "ro" - } + }, + '/somepath': {"blah": "blah value"} } expected_memory = "{}m".format(self.memory_mb) @@ -124,7 +127,10 @@ def test_must_create_container_including_all_optional_values(self): exposed_ports=self.exposed_ports, entrypoint=self.entrypoint, env_vars=self.env_vars, - docker_client=self.mock_docker_client) + docker_client=self.mock_docker_client, + container_opts=self.container_opts, + additional_volumes=self.additional_volumes + ) container_id = container.create() self.assertEquals(container_id, generated_id) @@ -138,7 +144,9 @@ def test_must_create_container_including_all_optional_values(self): environment=self.env_vars, ports=self.exposed_ports, entrypoint=self.entrypoint, - mem_limit=expected_memory) + mem_limit=expected_memory, + container='opts' + ) self.mock_docker_client.networks.get.assert_not_called() def test_must_connect_to_network_on_create(self): diff --git a/tests/unit/local/docker/test_lambda_container.py b/tests/unit/local/docker/test_lambda_container.py index 0fbaed363d..0e2b6b8ef5 100644 --- a/tests/unit/local/docker/test_lambda_container.py +++ b/tests/unit/local/docker/test_lambda_container.py @@ -6,9 +6,11 @@ from mock import patch from parameterized import parameterized, param +from samcli.commands.local.lib.debug_context import DebugContext from samcli.local.docker.lambda_container import LambdaContainer, Runtime RUNTIMES_WITH_ENTRYPOINT = [Runtime.java8.value, + Runtime.go1x.value, Runtime.nodejs.value, Runtime.nodejs43.value, Runtime.nodejs610.value, @@ -27,33 +29,39 @@ def setUp(self): self.code_dir = "codedir" self.env_var = {"var": "value"} self.memory_mb = 1024 - self.debug_port = 1235 - self.debug_arg = "a=b c=d e=f" + self.debug_options = DebugContext(debug_args="a=b c=d e=f", debug_port=1235) @patch.object(LambdaContainer, "_get_image") @patch.object(LambdaContainer, "_get_exposed_ports") @patch.object(LambdaContainer, "_get_entry_point") + @patch.object(LambdaContainer, "_get_additional_options") + @patch.object(LambdaContainer, "_get_additional_volumes") def test_must_configure_container_properly(self, + get_additional_volumes_mock, + get_additional_options_mock, get_entry_point_mock, get_exposed_ports_mock, get_image_mock): image = "image" ports = {"a": "b"} + addtl_options = {} + addtl_volumes = {} entry = [1, 2, 3] expected_cmd = [self.handler] get_image_mock.return_value = image get_exposed_ports_mock.return_value = ports get_entry_point_mock.return_value = entry + get_additional_options_mock.return_value = addtl_options + get_additional_volumes_mock.return_value = addtl_volumes container = LambdaContainer(self.runtime, self.handler, self.code_dir, env_vars=self.env_var, memory_mb=self.memory_mb, - debug_port=self.debug_port, - debug_args=self.debug_arg) + debug_options=self.debug_options) self.assertEquals(image, container._image) self.assertEquals(expected_cmd, container._cmd) @@ -65,8 +73,10 @@ def test_must_configure_container_properly(self, self.assertEquals(self.memory_mb, container._memory_limit_mb) get_image_mock.assert_called_with(self.runtime) - get_exposed_ports_mock.assert_called_with(self.debug_port) - get_entry_point_mock.assert_called_with(self.runtime, self.debug_port, self.debug_arg) + get_exposed_ports_mock.assert_called_with(self.debug_options) + get_entry_point_mock.assert_called_with(self.runtime, self.debug_options) + get_additional_options_mock.assert_called_with(self.runtime, self.debug_options) + get_additional_volumes_mock.assert_called_with(self.debug_options) def test_must_fail_for_unsupported_runtime(self): @@ -82,9 +92,9 @@ class TestLambdaContainer_get_exposed_ports(TestCase): def test_must_map_same_port_on_host_and_container(self): - port = 12345 - expected = {port: port} - result = LambdaContainer._get_exposed_ports(port) + debug_options = DebugContext(debug_port=12345) + expected = {debug_options.debug_port: debug_options.debug_port} + result = LambdaContainer._get_exposed_ports(debug_options) self.assertEquals(expected, result) @@ -109,15 +119,16 @@ def setUp(self): self.debug_port = 1235 self.debug_args = "a=b c=d e=f" + self.debug_options = DebugContext(debug_port=1235, debug_args="a=b c=d e=f") def test_must_skip_if_debug_port_is_not_specified(self): - self.assertIsNone(LambdaContainer._get_entry_point("runtime", None, self.debug_args), + self.assertIsNone(LambdaContainer._get_entry_point("runtime", None), "Must not provide entrypoint if debug port is not given") @parameterized.expand([param(r) for r in ALL_RUNTIMES]) def test_must_provide_entrypoint_for_certain_runtimes_only(self, runtime): - result = LambdaContainer._get_entry_point(runtime, self.debug_port, self.debug_args) + result = LambdaContainer._get_entry_point(runtime, self.debug_options) if runtime in RUNTIMES_WITH_ENTRYPOINT: self.assertIsNotNone(result, "{} runtime must provide entrypoint".format(runtime)) @@ -130,14 +141,62 @@ def test_debug_arg_must_be_split_by_spaces_and_appended_to_entrypoint(self, runt Debug args list is appended starting at second position in the array """ expected_debug_args = ["a=b", "c=d", "e=f"] - result = LambdaContainer._get_entry_point(runtime, self.debug_port, self.debug_args) + result = LambdaContainer._get_entry_point(runtime, self.debug_options) actual = result[1:4] self.assertEquals(actual, expected_debug_args) @parameterized.expand([param(r) for r in RUNTIMES_WITH_ENTRYPOINT]) def test_must_provide_entrypoint_even_without_debug_args(self, runtime): - - result = LambdaContainer._get_entry_point(runtime, self.debug_port) + debug_options = DebugContext(debug_port=1235, debug_args=None) + result = LambdaContainer._get_entry_point(runtime, debug_options) self.assertIsNotNone(result) + + +class TestLambdaContainer_get_additional_options(TestCase): + + def test_no_additional_options_when_debug_options_is_none(self): + debug_options = DebugContext(debug_port=None) + + result = LambdaContainer._get_additional_options('runtime', debug_options) + self.assertIsNone(result) + + @parameterized.expand([param(r) for r in RUNTIMES_WITH_ENTRYPOINT if not r.startswith('go')]) + def test_default_value_returned_for_non_go_runtimes(self, runtime): + debug_options = DebugContext(debug_port=1235) + + result = LambdaContainer._get_additional_options(runtime, debug_options) + self.assertEquals(result, {}) + + @parameterized.expand([param(r) for r in RUNTIMES_WITH_ENTRYPOINT if r.startswith('go')]) + def test_go_runtime_returns_additional_options(self, runtime): + expected = {"security_opt": ["seccomp:unconfined"], "cap_add": ["SYS_PTRACE"]} + + debug_options = DebugContext(debug_port=1235) + + result = LambdaContainer._get_additional_options(runtime, debug_options) + self.assertEquals(result, expected) + + +class TestLambdaContainer_get_additional_volumes(TestCase): + + def test_no_additional_volumes_when_debug_options_is_none(self): + debug_options = DebugContext(debug_port=None) + + result = LambdaContainer._get_additional_volumes(debug_options) + self.assertIsNone(result) + + def test_no_additional_volumes_when_debuggr_path_is_none(self): + debug_options = DebugContext(debug_port=1234) + + result = LambdaContainer._get_additional_volumes(debug_options) + self.assertIsNone(result) + + def test_additional_volumes_returns_volume_with_debugger_path_is_set(self): + expected = {'/somepath': {"bind": "/tmp/lambci_debug_files", "mode": "ro"}} + + debug_options = DebugContext(debug_port=1234, debugger_path='/somepath') + + result = LambdaContainer._get_additional_volumes(debug_options) + self.assertEquals(result, expected) diff --git a/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py b/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py index b555b78369..4ca10d65b5 100644 --- a/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py +++ b/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py @@ -205,23 +205,6 @@ def test_request_with_query_strings(self, flask_request, lambda_error_responses_ lambda_error_responses_mock.invalid_request_content.assert_called_once_with( "Query Parameters are not supported") - @patch('samcli.local.lambda_service.local_lambda_invoke_service.LambdaErrorResponses') - @patch('samcli.local.lambda_service.local_lambda_invoke_service.request') - def test_request_with_content_type_not_application_json(self, flask_request, lambda_error_responses_mock): - flask_request.get_data.return_value = None - flask_request.headers = {} - flask_request.content_type = 'image/gif' - flask_request.args = {} - - lambda_error_responses_mock.unsupported_media_type.return_value = "UnsupportedMediaType" - - response = LocalLambdaInvokeService.validate_request() - - self.assertEquals(response, "UnsupportedMediaType") - - lambda_error_responses_mock.unsupported_media_type.assert_called_once_with( - "image/gif") - @patch('samcli.local.lambda_service.local_lambda_invoke_service.LambdaErrorResponses') @patch('samcli.local.lambda_service.local_lambda_invoke_service.request') def test_request_log_type_not_None(self, flask_request, lambda_error_responses_mock): diff --git a/tests/unit/local/lambdafn/test_env_vars.py b/tests/unit/local/lambdafn/test_env_vars.py index 0b67355bc7..6b34ad22e9 100644 --- a/tests/unit/local/lambdafn/test_env_vars.py +++ b/tests/unit/local/lambdafn/test_env_vars.py @@ -325,6 +325,7 @@ def test_must_replace_non_scalar_with_blank_values(self, input): (False, "false"), (1234, "1234"), (3.14, "3.14"), + (u"mystring\xe0", u"mystring\xe0"), ("mystring", "mystring"), ]) def test_must_stringify(self, input, expected): diff --git a/tests/unit/local/lambdafn/test_runtime.py b/tests/unit/local/lambdafn/test_runtime.py index 9c0b714176..6a6ef35a6d 100644 --- a/tests/unit/local/lambdafn/test_runtime.py +++ b/tests/unit/local/lambdafn/test_runtime.py @@ -33,13 +33,12 @@ def setUp(self): @patch("samcli.local.lambdafn.runtime.LambdaContainer") def test_must_run_container_and_wait_for_logs(self, LambdaContainerMock): event = "event" - debug_port = 123 - debug_arg = "abc" code_dir = "some code dir" stdout = "stdout" stderr = "stderr" container = Mock() timer = Mock() + debug_options = Mock() self.runtime = LambdaRuntime(self.manager_mock) @@ -55,8 +54,7 @@ def test_must_run_container_and_wait_for_logs(self, LambdaContainerMock): self.runtime.invoke(self.func_config, event, - debug_port=debug_port, - debug_args=debug_arg, + debug_context=debug_options, stdout=stdout, stderr=stderr) @@ -72,7 +70,7 @@ def test_must_run_container_and_wait_for_logs(self, LambdaContainerMock): # Make sure the container is created with proper values LambdaContainerMock.assert_called_with(self.lang, self.handler, code_dir, memory_mb=self.DEFAULT_MEMORY, env_vars=self.env_var_value, - debug_port=debug_port, debug_args=debug_arg) + debug_options=debug_options) # Run the container and get results self.manager_mock.run.assert_called_with(container) @@ -86,8 +84,6 @@ def test_must_run_container_and_wait_for_logs(self, LambdaContainerMock): @patch("samcli.local.lambdafn.runtime.LambdaContainer") def test_exception_from_run_must_trigger_cleanup(self, LambdaContainerMock): event = "event" - debug_port = 123 - debug_arg = "abc" code_dir = "some code dir" stdout = "stdout" stderr = "stderr" @@ -109,8 +105,7 @@ def test_exception_from_run_must_trigger_cleanup(self, LambdaContainerMock): with self.assertRaises(ValueError): self.runtime.invoke(self.func_config, event, - debug_port=debug_port, - debug_args=debug_arg, + debug_context=None, stdout=stdout, stderr=stderr) @@ -128,13 +123,12 @@ def test_exception_from_run_must_trigger_cleanup(self, LambdaContainerMock): @patch("samcli.local.lambdafn.runtime.LambdaContainer") def test_exception_from_wait_for_logs_must_trigger_cleanup(self, LambdaContainerMock): event = "event" - debug_port = 123 - debug_arg = "abc" code_dir = "some code dir" stdout = "stdout" stderr = "stderr" container = Mock() timer = Mock() + debug_options = Mock() self.runtime = LambdaRuntime(self.manager_mock) @@ -151,8 +145,7 @@ def test_exception_from_wait_for_logs_must_trigger_cleanup(self, LambdaContainer with self.assertRaises(ValueError): self.runtime.invoke(self.func_config, event, - debug_port=debug_port, - debug_args=debug_arg, + debug_context=debug_options, stdout=stdout, stderr=stderr) @@ -170,8 +163,6 @@ def test_exception_from_wait_for_logs_must_trigger_cleanup(self, LambdaContainer @patch("samcli.local.lambdafn.runtime.LambdaContainer") def test_keyboard_interrupt_must_not_raise(self, LambdaContainerMock): event = "event" - debug_port = 123 - debug_arg = "abc" code_dir = "some code dir" stdout = "stdout" stderr = "stderr" @@ -190,8 +181,6 @@ def test_keyboard_interrupt_must_not_raise(self, LambdaContainerMock): self.runtime.invoke(self.func_config, event, - debug_port=debug_port, - debug_args=debug_arg, stdout=stdout, stderr=stderr)