diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/Pipfile b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/Pipfile new file mode 100644 index 0000000000..a22660daae --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/Pipfile @@ -0,0 +1,13 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +cookiecutter = "*" +pytest-cookies = "*" +pytest = "*" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/README.md index 6aaa7074b6..73c92bf004 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/README.md @@ -1,21 +1,42 @@ -# Cookiecutter DotNet Core Hello-world for SAM based Serverless App +# Cookiecutter SAM for dotNet based Lambda functions -A cookiecutter template to create a DotNet Core Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). +This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless Hello World App based on Serverless Application Model (SAM) and dotnet 2.0. + +It is important to note that you should not try to `git clone` this project but use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. ## Requirements -* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) +Install `cookiecutter` command line: + +**Pip users**: + +* `pip install cookiecutter` + +**Homebrew users**: + +* `brew install cookiecutter` + +**Windows or Pipenv users**: + +* `pipenv install cookiecutter` + +**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. ## Usage -Generate a boilerplate template in your current project directory using the following syntax: +Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-hello-dotnet`. -* **DotNet Core 2.1**: `sam init --runtime dotnetcore2.1` -* **DotNet Core 2.0**: `sam init --runtime dotnetcore2.0` +You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. -> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) +**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. # Credits * This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + + +License +------- + +This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/tests/test_cookiecutter.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/tests/test_cookiecutter.py new file mode 100644 index 0000000000..285b06b26c --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/tests/test_cookiecutter.py @@ -0,0 +1,47 @@ +""" + Tests cookiecutter baking process and rendered content +""" + + +def test_project_tree(cookies): + result = cookies.bake(extra_context={ + 'project_name': 'hello sam' + }) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == 'hello sam' + assert result.project.isdir() + assert result.project.join('.gitignore').isfile() + assert result.project.join('template.yaml').isfile() + assert result.project.join('README.md').isfile() + assert result.project.join('src').isdir() + assert result.project.join('test').isdir() + assert result.project.join('src', 'HelloWorld').isdir() + assert result.project.join( + 'src', 'HelloWorld', 'HelloWorld.csproj').isfile() + assert result.project.join('src', 'HelloWorld', 'Program.cs').isfile() + assert result.project.join( + 'src', 'HelloWorld', 'aws-lambda-tools-defaults.json').isfile() + assert result.project.join( + 'test', 'HelloWorld.Test', 'FunctionTest.cs').isfile() + assert result.project.join( + 'test', 'HelloWorld.Test', 'HelloWorld.Tests.csproj').isfile() + + +def test_app_content(cookies): + result = cookies.bake(extra_context={'project_name': 'my_lambda'}) + app_file = result.project.join('src', 'HelloWorld', 'Program.cs') + app_content = app_file.readlines() + app_content = ''.join(app_content) + + contents = ( + "GetCallingIP", + "GetStringAsync", + "location", + "message", + "hello world", + "StatusCode" + ) + + for content in contents: + assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md index 068f06c3d9..bc72bf778c 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/README.md @@ -2,7 +2,6 @@ This is a sample template for {{ cookiecutter.project_name }} - ## Requirements * AWS CLI already configured with Administrator permission @@ -35,12 +34,6 @@ build.ps1 --target=Package ### Local development -**Invoking function locally using a local sample payload** - -```bash -sam local invoke HelloWorldFunction --event event.json -``` - **Invoking function locally through local API Gateway** ```bash @@ -53,7 +46,7 @@ sam local start-api ... Events: HelloWorldFunction: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get @@ -84,6 +77,7 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ + --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` @@ -97,19 +91,26 @@ sam deploy \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table + --query 'Stacks[].Outputs' ``` ## Testing +For testing our code, we use XUnit and you can use `dotnet test` to run tests defined under `test/` + +```bash +dotnet test test/HelloWorld.Test +``` + +Alternatively, you can use Cake. It discovers and executes all the tests. + ### Linux & macOS ```bash @@ -124,52 +125,48 @@ build.ps1 --target=Test # Appendix -## Bringing to the next level - -Here are a few things you can try to get more acquainted with building serverless applications using SAM: +## AWS CLI commands -### Create an additional API resource +AWS CLI commands to package, deploy and describe outputs defined within the AWS CloudFormation stack: -* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update tests +```bash +aws cloudformation package \ + --template-file template.yaml \ + --output-template-file packaged.yaml \ + --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME -### Step-through debugging +aws cloudformation deploy \ + --template-file packaged.yaml \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides MyParameterSample=MySampleValue -* **[Enable step-through debugging docs for supported runtimes](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html)** +aws cloudformation describe-stacks \ + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --query 'Stacks[].Outputs' +``` -Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) +## Next Steps -## SAM and AWS CLI commands +Create your own .NET Core solution template to use with SAM CLI. [Cookiecutter for AWS SAM and .NET](https://github.com/aws-samples/cookiecutter-aws-sam-dotnet) provides you with a sample implementation how to use cookiecutter templating library to standardise how you initialise your Serverless projects. -All commands used throughout this document +``` bash + sam init --location gh:aws-samples/cookiecutter-aws-sam-dotnet +``` -```bash -# Invoke function locally with event.json as an input -sam local invoke HelloWorldFunction --event event.json +For more information and examples of how to use `sam init` run -# Run API Gateway locally -sam local start-api +``` bash +sam init --help +``` -# Create S3 bucket -aws s3 mb s3://BUCKET_NAME +## Bringing to the next level -# Package Lambda function defined locally and upload to S3 as an artifact -sam package \ - --output-template-file packaged.yaml \ - --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME +Here are a few ideas that you can use to get more acquainted as to how this overall process works: -# Deploy SAM template as a CloudFormation stack -sam deploy \ - --template-file packaged.yaml \ - --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --capabilities CAPABILITY_IAM +* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update unit test to capture that +* Package & Deploy -# Describe Output section of CloudFormation stack previously created -aws cloudformation describe-stacks \ - --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table +Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: -# Tail Lambda function Logs using Logical name defined in SAM Template -sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail -``` +* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/event.json deleted file mode 100644 index 070ad8e018..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/event.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "body": "{\"message\": \"hello world\"}", - "resource": "/{proxy+}", - "path": "/path/to/resource", - "httpMethod": "POST", - "isBase64Encoded": false, - "queryStringParameters": { - "foo": "bar" - }, - "pathParameters": { - "proxy": "/path/to/resource" - }, - "stageVariables": { - "baz": "qux" - }, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "Cache-Control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "1234567890.execute-api.us-east-1.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Custom User Agent String", - "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "requestContext": { - "accountId": "123456789012", - "resourceId": "123456", - "stage": "prod", - "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", - "requestTime": "09/Apr/2015:12:34:56 +0000", - "requestTimeEpoch": 1428582896000, - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "accessKey": null, - "sourceIp": "127.0.0.1", - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Custom User Agent String", - "user": null - }, - "path": "/prod/path/to/resource", - "resourcePath": "/{proxy+}", - "httpMethod": "POST", - "apiId": "1234567890", - "protocol": "HTTP/1.1" - } -} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/Program.cs b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/Program.cs index 5d067402aa..32f4d6b7e0 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/Program.cs +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/Program.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; using Newtonsoft.Json; using Amazon.Lambda.Core; @@ -16,12 +18,27 @@ namespace HelloWorld public class Function { + private static readonly HttpClient client = new HttpClient(); + + private static async Task GetCallingIP() + { + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Add("User-Agent", "AWS Lambda .Net Client"); + + var stringTask = client.GetStringAsync("http://checkip.amazonaws.com/").ConfigureAwait(continueOnCapturedContext:false); + + var msg = await stringTask; + return msg.Replace("\n",""); + } + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) { + string location = GetCallingIP().Result; Dictionary body = new Dictionary { - { "message", "hello world" } + { "message", "hello world" }, + { "location", location }, }; return new APIGatewayProxyResponse diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml index abcf65330b..29b766d1a4 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml @@ -12,7 +12,7 @@ Globals: Resources: HelloWorldFunction: - Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-function + 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: ./artifacts/HelloWorld.zip Handler: HelloWorld::HelloWorld.Function::FunctionHandler @@ -21,18 +21,18 @@ Resources: {%- elif cookiecutter.runtime == 'dotnetcore2.1' or cookiecutter.runtime == 'dotnet' %} Runtime: dotnetcore2.1 {%- endif %} + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + PARAM1: VALUE Events: HelloWorld: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Outputs: - # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function - # Find out more about other implicit resources you can reference within SAM - # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/FunctionTest.cs b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/FunctionTest.cs index 9982645b40..96881f0c67 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/FunctionTest.cs +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/FunctionTest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; using Newtonsoft.Json; using Xunit; @@ -13,6 +15,18 @@ namespace HelloWorld.Tests { public class FunctionTest { + private static readonly HttpClient client = new HttpClient(); + + private static async Task GetCallingIP() + { + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Add("User-Agent", "AWS Lambda .Net Client"); + + var stringTask = client.GetStringAsync("http://checkip.amazonaws.com/").ConfigureAwait(continueOnCapturedContext:false); + + var msg = await stringTask; + return msg.Replace("\n",""); + } [Fact] public void TestHelloWorldFunctionHandler() @@ -23,9 +37,11 @@ public void TestHelloWorldFunctionHandler() request = new APIGatewayProxyRequest(); context = new TestLambdaContext(); + string location = GetCallingIP().Result; Dictionary body = new Dictionary { - { "message", "hello world" } + { "message", "hello world" }, + { "location", location }, }; var ExpectedResponse = new APIGatewayProxyResponse diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/README.md index 0067e8d6d9..d6af33a924 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/README.md @@ -1,19 +1,49 @@ -# Cookiecutter Golang Hello-world for SAM based Serverless App +# Cookiecutter SAM for golang Lambda functions -A cookiecutter template to create a NodeJS Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). +This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless App based on Serverless Application Model (SAM). + +It is important to note that you should not try to `git clone` this project but use `cookiecutter` CLI instead as ``\\{\\{cookiecutter.project_slug\\}\\}`` will be rendered based on your input and therefore all variables and files will be rendered properly. ## Requirements -* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) +Install `cookiecutter` command line: + +**Pip users**: + +* `pip install cookiecutter` + +**Homebrew users**: + +* `brew install cookiecutter` + +**Windows or Pipenv users**: + +* `pipenv install cookiecutter` + +**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. ## Usage -Generate a boilerplate template in your current project directory using the following syntax: +Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-golang`. + +You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. + +**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. + +## Options + +Option | Description +------------------------------------------------- | --------------------------------------------------------------------------------- +`include_apigw` | Includes sample code for API Gateway Proxy integration for Lambda and a Catch All method in SAM as a starting point +`include_xray` | Includes both sample code for getting started with AWS X-Ray and adds necessary permission and `Tracing` to your function +`include_safe_deployment` | Sends by default 10% of traffic for every 1 minute to a newly deployed function using [CodeDeploy + SAM integration](https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst) - Linear10PercentEvery1Minute +`include_experimental_make` | Includes a `Makefile` for advanced users to automate packaging, build, tests and SAM Local - Only works on OSX/Linux at the moment -* **Go 1.x**: `sam init --runtime go1.x` +## Credits -> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) +* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) +* [Bruno Alla's Lambda function template](https://github.com/browniebroke/cookiecutter-lambda-function) -# Credits +## License -* This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) \ No newline at end of file +This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/cookiecutter.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/cookiecutter.json index 2d08743794..4ed0aa2924 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/cookiecutter.json +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/cookiecutter.json @@ -1,4 +1,3 @@ { - "project_name": "Name of the project", - "runtime": "go1.x" + "project_name": "Name of the project" } \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/requirements-dev.txt b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/requirements-dev.txt new file mode 100644 index 0000000000..b10c8d1048 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/requirements-dev.txt @@ -0,0 +1,4 @@ +cookiecutter==1.6.0 +flake8==3.5.0 +pytest==3.3.2 +pytest-cookies==0.3.0 diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/tests/__init__.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/tests/test_bake_project.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/tests/test_bake_project.py new file mode 100644 index 0000000000..9d32aebfe0 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/tests/test_bake_project.py @@ -0,0 +1,79 @@ +from contextlib import contextmanager + +import os +import subprocess + + +@contextmanager +def inside_dir(dirpath): + """ + Execute code from inside the given directory + :param dirpath: String, path of the directory the command is being run. + """ + old_path = os.getcwd() + try: + os.chdir(dirpath) + yield + finally: + os.chdir(old_path) + + +def test_project_tree(cookies): + result = cookies.bake(extra_context={'project_name': 'test_project'}) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == 'test_project' + + assert result.project.isdir() + assert result.project.join('README.md').isfile() + assert result.project.join('template.yaml').isfile() + assert result.project.join('hello-world').isdir() + assert result.project.join('hello-world', 'main.go').isfile() + assert result.project.join('hello-world', 'main_test.go').isfile() + + +def test_app_content(cookies): + result = cookies.bake(extra_context={'project_name': 'test_project'}) + app_file = result.project.join('hello-world', 'main.go') + app_content = app_file.readlines() + app_content = ''.join(app_content) + + contents = ( + "github.com/aws/aws-lambda-go/events", + "resp, err := http.Get(DefaultHTTPGetAddress)", + "lambda.Start(handler)" + ) + + for content in contents: + assert content in app_content + + +def test_app_test_content(cookies): + result = cookies.bake(extra_context={'project_name': 'test_project'}) + app_file = result.project.join('hello-world', 'main_test.go') + app_content = app_file.readlines() + app_content = ''.join(app_content) + + contents = ( + "DefaultHTTPGetAddress = \"http://127.0.0.1:12345\"", + "DefaultHTTPGetAddress = ts.URL", + "Successful Request" + ) + + for content in contents: + assert content in app_content + + +def test_app_template_content(cookies): + result = cookies.bake(extra_context={'project_name': 'test_project'}) + app_file = result.project.join('template.yaml') + app_content = app_file.readlines() + app_content = ''.join(app_content) + + contents = ( + "Runtime: go1.x", + "HelloWorldFunction", + ) + + for content in contents: + assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/Makefile b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/Makefile index 16c42e38e6..78a2494dba 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/Makefile +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/Makefile @@ -7,10 +7,4 @@ clean: rm -rf ./hello-world/hello-world build: - GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world - -invoke: - sam local invoke HelloWorldFunction -e event.json - -start: - sam local start-api + GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md index 8a89adea45..669be260a7 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/README.md @@ -5,7 +5,6 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief ```bash . ├── Makefile <-- Make to automate build -├── event.json <-- API Gateway Proxy Integration event payload ├── README.md <-- This instructions file ├── hello-world <-- Source code for a lambda function │ ├── main.go <-- Lambda function code @@ -29,19 +28,21 @@ In this example we use the built-in `go get` and the only dependency we need is go get -u github.com/aws/aws-lambda-go/... ``` -### Local development +**NOTE:** As you change your application code as well as dependencies during development, you might want to research how to handle dependencies in Golang at scale. + +### Building + +Golang is a staticly compiled language, meaning that in order to run it you have to build the executeable target. -**Building an executable target for Lambda runtime** +You can issue the following command in a shell to build it: ```shell GOOS=linux GOARCH=amd64 go build -o hello-world/hello-world ./hello-world ``` -**Invoking function locally using a local sample payload** +**NOTE**: If you're not building the function on a Linux machine, you will need to specify the `GOOS` and `GOARCH` environment variables, this allows Golang to build your function for another system architecture and ensure compatability. -```bash -sam local invoke HelloWorldFunction --event event.json -``` +### Local development **Invoking function locally through local API Gateway** @@ -57,7 +58,7 @@ If the previous command ran successfully you should now be able to hit the follo ... Events: HelloWorld: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get @@ -72,7 +73,7 @@ AWS Lambda Python runtime requires a flat folder with all dependencies including FirstFunction: Type: AWS::Serverless::Function Properties: - CodeUri: hello-world/ + CodeUri: hello_world/ ... ``` @@ -86,6 +87,7 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ + --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` @@ -99,29 +101,16 @@ sam deploy \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table + --query 'Stacks[].Outputs' ``` -## Fetch, tail, and filter Lambda function logs - -To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. - -`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. - -```bash -sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail -``` - -You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). - ### Testing We use `testing` package that is built-in in Golang and you can simply run the following command to run our tests: @@ -129,63 +118,70 @@ We use `testing` package that is built-in in Golang and you can simply run the f ```shell go test -v ./hello-world/ ``` +# Appendix -## Cleanup - -In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: - -```bash -aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} -``` +### Golang installation -# Appendix +Please ensure Go 1.x (where 'x' is the latest version) is installed as per the instructions on the official golang website: https://golang.org/doc/install -## Bringing to the next level +A quickstart way would be to use Homebrew, chocolatey or your linux package manager. -Here are a few things you can try to get more acquainted with building serverless applications using SAM: +#### Homebrew (Mac) -### Create an additional API resource +Issue the following command from the terminal: -* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update tests +```shell +brew install golang +``` -### Step-through debugging +If it's already installed, run the following command to ensure it's the latest version: -* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** +```shell +brew update +brew upgrade golang +``` -Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) +#### Chocolatey (Windows) -## SAM and AWS CLI commands +Issue the following command from the powershell: -All commands used throughout this document +```shell +choco install golang +``` -```bash -# Invoke function locally with event.json as an input -sam local invoke HelloWorldFunction --event event.json +If it's already installed, run the following command to ensure it's the latest version: -# Run API Gateway locally -sam local start-api +```shell +choco upgrade golang +``` +## AWS CLI commands -# Create S3 bucket -aws s3 mb s3://BUCKET_NAME +AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack: -# Package Lambda function defined locally and upload to S3 as an artifact +```bash sam package \ + --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME -# Deploy SAM template as a CloudFormation stack sam deploy \ --template-file packaged.yaml \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --capabilities CAPABILITY_IAM + --capabilities CAPABILITY_IAM \ + --parameter-overrides MyParameterSample=MySampleValue -# Describe Output section of CloudFormation stack previously created aws cloudformation describe-stacks \ - --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table - -# Tail Lambda function Logs using Logical name defined in SAM Template -sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --query 'Stacks[].Outputs' ``` + +## Bringing to the next level + +Here are a few ideas that you can use to get more acquainted as to how this overall process works: + +* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update unit test to capture that +* Package & Deploy + +Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: + +* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/event.json deleted file mode 100644 index b9853ba934..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/event.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "body": "{\"message\": \"hello world\"}", - "resource": "/{proxy+}", - "path": "/path/to/resource", - "httpMethod": "POST", - "isBase64Encoded": false, - "queryStringParameters": { - "foo": "bar" - }, - "pathParameters": { - "proxy": "/path/to/resource" - }, - "stageVariables": { - "baz": "qux" - }, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "Cache-Control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "1234567890.execute-api.us-east-1.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Custom User Agent String", - "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "requestContext": { - "accountId": "123456789012", - "resourceId": "123456", - "stage": "prod", - "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", - "requestTime": "09/Apr/2015:12:34:56 +0000", - "requestTimeEpoch": 1428582896000, - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "accessKey": null, - "sourceIp": "127.0.0.1", - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Custom User Agent String", - "user": null - }, - "path": "/prod/path/to/resource", - "resourcePath": "/{proxy+}", - "httpMethod": "POST", - "apiId": "1234567890", - "protocol": "HTTP/1.1" - } -} \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main.go b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main.go index 7178514155..8c105bd0ac 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main.go +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main.go @@ -1,14 +1,47 @@ package main import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) +var ( + // DefaultHTTPGetAddress Default Address + DefaultHTTPGetAddress = "https://checkip.amazonaws.com" + + // ErrNoIP No IP found in response + ErrNoIP = errors.New("No IP in HTTP response") + + // ErrNon200Response non 200 status code in response + ErrNon200Response = errors.New("Non 200 Response found") +) + func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { + resp, err := http.Get(DefaultHTTPGetAddress) + if err != nil { + return events.APIGatewayProxyResponse{}, err + } + + if resp.StatusCode != 200 { + return events.APIGatewayProxyResponse{}, ErrNon200Response + } + + ip, err := ioutil.ReadAll(resp.Body) + if err != nil { + return events.APIGatewayProxyResponse{}, err + } + + if len(ip) == 0 { + return events.APIGatewayProxyResponse{}, ErrNoIP + } return events.APIGatewayProxyResponse{ - Body: "hello world", + Body: fmt.Sprintf("Hello, %v", string(ip)), StatusCode: 200, }, nil } diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main_test.go b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main_test.go index a0d831ca77..bb1d2a2f1c 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main_test.go +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/hello-world/main_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" "net/http/httptest" "testing" @@ -9,20 +10,55 @@ import ( ) func TestHandler(t *testing.T) { + t.Run("Unable to get IP", func(t *testing.T) { + DefaultHTTPGetAddress = "http://127.0.0.1:12345" + + _, err := handler(events.APIGatewayProxyRequest{}) + if err == nil { + t.Fatal("Error failed to trigger with an invalid request") + } + }) + + t.Run("Non 200 Response", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + defer ts.Close() + + DefaultHTTPGetAddress = ts.URL + + _, err := handler(events.APIGatewayProxyRequest{}) + if err != nil && err.Error() != ErrNon200Response.Error() { + t.Fatalf("Error failed to trigger with an invalid HTTP response: %v", err) + } + }) + + t.Run("Unable decode IP", func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + defer ts.Close() + + DefaultHTTPGetAddress = ts.URL + + _, err := handler(events.APIGatewayProxyRequest{}) + if err == nil { + t.Fatal("Error failed to trigger with an invalid HTTP response") + } + }) + t.Run("Successful Request", func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) + fmt.Fprintf(w, "127.0.0.1") })) - defer ts.Close() - resp, err := handler(events.APIGatewayProxyRequest{}) + DefaultHTTPGetAddress = ts.URL + + _, err := handler(events.APIGatewayProxyRequest{}) if err != nil { t.Fatal("Everything should be ok") } - - if resp.Body != "hello world" { - t.Fatal("Return should be hello world") - } }) } diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/template.yaml index 77bd2f048b..96d7aa160b 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-golang/{{cookiecutter.project_name}}/template.yaml @@ -7,37 +7,36 @@ Description: > # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: - Function: - Timeout: 5 + Function: + Timeout: 5 Resources: - - HelloWorldFunction: - Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-function - Properties: - CodeUri: hello-world/ - Handler: hello-world - Runtime: go1.x - Events: - HelloWorld: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api - Properties: - Path: /hello - Method: get + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello-world/ + Handler: hello-world + Runtime: go1.x + Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html + Events: + CatchAll: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: GET + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + PARAM1: VALUE Outputs: + HelloWorldAPI: + Description: "API Gateway endpoint URL for Prod environment for First Function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" - # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function - # Find out more about other implicit resources you can reference within SAM - # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api - HelloWorldAPI: - Description: "API Gateway endpoint URL for Prod environment for First Function" - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" - - HelloWorldFunction: - Description: "First Lambda Function ARN" - Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunction: + Description: "First Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn - HelloWorldFunctionIamRole: - Description: "Implicit IAM Role created for Hello World function" - Value: !GetAtt HelloWorldFunctionRole.Arn \ No newline at end of file + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn \ No newline at end of file diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/README.md index 9509c71e18..ec9954e48b 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/README.md @@ -1,20 +1,42 @@ -# Cookiecutter Java Hello-world for SAM based Serverless App +# Cookiecutter SAM for Java Lambda functions -A cookiecutter template to create a Java Hello world boilerplate using [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model). +This is a [Cookiecutter](https://github.com/audreyr/cookiecutter) template to create a Serverless Hello World App based on Serverless Application Model (SAM) and Java. + +It is important to note that you should not try to `git clone` this project but use `cookiecutter` CLI instead as ``{{cookiecutter.project_name}}`` will be rendered based on your input and therefore all variables and files will be rendered properly. ## Requirements -* [AWS SAM CLI](https://github.com/awslabs/aws-sam-cli) +Install `cookiecutter` command line: + +**Pip users**: + +* `pip install cookiecutter` + +**Homebrew users**: + +* `brew install cookiecutter` + +**Windows or Pipenv users**: + +* `pipenv install cookiecutter` + +**NOTE**: [`Pipenv`](https://github.com/pypa/pipenv) is the new and recommended Python packaging tool that works across multiple platforms and makes Windows a first-class citizen. ## Usage -Generate a boilerplate template in your current project directory using the following syntax: +Generate a new SAM based Serverless App: `cookiecutter gh:aws-samples/cookiecutter-aws-sam-hello-java`. -* **Java 8**: `sam init --runtime java8` +You'll be prompted a few questions to help this cookiecutter template to scaffold this project and after its completed you should see a new folder at your current path with the name of the project you gave as input. -> **NOTE**: ``--name`` allows you to specify a different project folder name (`sam-app` is the default) +**NOTE**: After you understand how cookiecutter works (cookiecutter.json, mainly), you can fork this repo and apply your own mechanisms to accelerate your development process and this can be followed for any programming language and OS. # Credits * This project has been generated with [Cookiecutter](https://github.com/audreyr/cookiecutter) + + +License +------- + +This project is licensed under the terms of the [MIT License with no attribution](/LICENSE) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/tests/test_cookiecutter.py b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/tests/test_cookiecutter.py new file mode 100644 index 0000000000..8f2a6cf519 --- /dev/null +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/tests/test_cookiecutter.py @@ -0,0 +1,41 @@ +""" + Tests cookiecutter baking process and rendered content +""" + +def test_project_tree(cookies): + result = cookies.bake(extra_context={ + 'project_name': 'hello sam' + }) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == 'hello sam' + assert result.project.isdir() + assert result.project.join('template.yaml').isfile() + assert result.project.join('README.md').isfile() + assert result.project.join('src').isdir() + assert result.project.join('src', 'main').isdir() + assert result.project.join('src', 'main', 'java').isdir() + assert result.project.join('src', 'main', 'java', 'helloworld').isdir() + assert result.project.join('src', 'main', 'java', 'helloworld', 'App.java').isfile() + assert result.project.join('src', 'main', 'java', 'helloworld', 'GatewayResponse.java').isfile() + assert result.project.join('src', 'test', 'java').isdir() + assert result.project.join('src', 'test', 'java', 'helloworld').isdir() + assert result.project.join('src', 'test', 'java', 'helloworld', 'AppTest.java').isfile() + + +def test_app_content(cookies): + result = cookies.bake(extra_context={'project_name': 'my_lambda'}) + app_file = result.project.join('src', 'main', 'java', 'helloworld', 'App.java') + app_content = app_file.readlines() + app_content = ''.join(app_content) + + contents = ( + "package helloword", + "class App implements RequestHandler", + "https://checkip.amazonaws.com", + "return new GatewayResponse", + "getPageContents", + ) + + for content in contents: + assert content in app_content diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md index 53b7b315b2..68e70f0adc 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/README.md @@ -6,7 +6,6 @@ This is a sample template for {{ cookiecutter.project_name }} - Below is a brief . ├── README.md <-- This instructions file ├── pom.xml <-- Java dependencies -├── event.json <-- API Gateway Proxy Integration event payload ├── src │ ├── main │ │ └── java @@ -36,15 +35,8 @@ We use `maven` to install our dependencies and package our application into a JA ```bash mvn package ``` - ### Local development -**Invoking function locally using a local sample payload** - -```bash -sam local invoke HelloWorldFunction --event event.json -``` - **Invoking function locally through local API Gateway** ```bash @@ -59,7 +51,7 @@ If the previous command ran successfully you should now be able to hit the follo ... Events: HelloWorld: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get @@ -88,6 +80,7 @@ Next, run the following command to package our Lambda function to S3: ```bash sam package \ + --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME ``` @@ -101,30 +94,16 @@ sam deploy \ --capabilities CAPABILITY_IAM ``` -> **See [Serverless Application Model (SAM) HOWTO Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) for more details in how to get started.** +> **See [Serverless Application Model (SAM) HOWTO Guide](https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md) for more details in how to get started.** After deployment is complete you can run the following command to retrieve the API Gateway Endpoint URL: ```bash aws cloudformation describe-stacks \ --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table -``` - -## Fetch, tail, and filter Lambda function logs - -To simplify troubleshooting, SAM CLI has a command called sam logs. sam logs lets you fetch logs generated by your Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. - -`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. - -```bash -sam logs -n HelloWorldFunction --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --tail + --query 'Stacks[].Outputs' ``` -You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). - - ## Testing We use `JUnit` for testing our code and you can simply run the following command to run our tests: @@ -133,66 +112,36 @@ We use `JUnit` for testing our code and you can simply run the following command mvn test ``` -## Cleanup - -In order to delete our Serverless Application recently deployed you can use the following AWS CLI Command: - -```bash -aws cloudformation delete-stack --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} -``` - # Appendix -## Bringing to the next level - -Here are a few things you can try to get more acquainted with building serverless applications using SAM: +## AWS CLI commands -### Create an additional API resource - -* Create a catch all resource (e.g. /hello/{proxy+}) and return the name requested through this new path -* Update tests - -### Step-through debugging - -* **[Enable step-through debugging docs for supported runtimes]((https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html))** - -Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) - - -## SAM and AWS CLI commands - -All commands used throughout this document +AWS CLI commands to package, deploy and describe outputs defined within the cloudformation stack: ```bash -# Invoke function locally with event.json as an input -sam local invoke HelloWorldFunction --event event.json - -# Run API Gateway locally -sam local start-api - -# Create S3 bucket -aws s3 mb s3://BUCKET_NAME - -# Package Lambda function defined locally and upload to S3 as an artifact sam package \ + --template-file template.yaml \ --output-template-file packaged.yaml \ --s3-bucket REPLACE_THIS_WITH_YOUR_S3_BUCKET_NAME -# Deploy SAM template as a CloudFormation stack sam deploy \ --template-file packaged.yaml \ - --stack-name node-tests \ - --capabilities CAPABILITY_IAM + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} \ + --capabilities CAPABILITY_IAM \ + --parameter-overrides MyParameterSample=MySampleValue -# Describe Output section of CloudFormation stack previously created aws cloudformation describe-stacks \ - --stack-name node-tests \ - --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \ - --output table - -# Tail Lambda function Logs using Logical name defined in SAM Template -sam logs -n HelloWorldFunction --stack-name node-tests --tail + --stack-name {{ cookiecutter.project_name.lower().replace(' ', '-') }} --query 'Stacks[].Outputs' ``` -**NOTE**: Alternatively this could be part of package.json scripts section. +## Bringing to the next level + +Here are a few ideas that you can use to get more acquainted as to how this overall process works: + +* Create an additional API resource (e.g. /hello/{proxy+}) and return the name requested through this new path +* Update unit test to capture that +* Package & Deploy + +Next, you can use the following resources to know more about beyond hello world samples and how others structure their Serverless applications: +* [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/) diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/event.json b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/event.json deleted file mode 100644 index 070ad8e018..0000000000 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/event.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "body": "{\"message\": \"hello world\"}", - "resource": "/{proxy+}", - "path": "/path/to/resource", - "httpMethod": "POST", - "isBase64Encoded": false, - "queryStringParameters": { - "foo": "bar" - }, - "pathParameters": { - "proxy": "/path/to/resource" - }, - "stageVariables": { - "baz": "qux" - }, - "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate, sdch", - "Accept-Language": "en-US,en;q=0.8", - "Cache-Control": "max-age=0", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Host": "1234567890.execute-api.us-east-1.amazonaws.com", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Custom User Agent String", - "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", - "X-Forwarded-For": "127.0.0.1, 127.0.0.2", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "requestContext": { - "accountId": "123456789012", - "resourceId": "123456", - "stage": "prod", - "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", - "requestTime": "09/Apr/2015:12:34:56 +0000", - "requestTimeEpoch": 1428582896000, - "identity": { - "cognitoIdentityPoolId": null, - "accountId": null, - "cognitoIdentityId": null, - "caller": null, - "accessKey": null, - "sourceIp": "127.0.0.1", - "cognitoAuthenticationType": null, - "cognitoAuthenticationProvider": null, - "userArn": null, - "userAgent": "Custom User Agent String", - "user": null - }, - "path": "/prod/path/to/resource", - "resourcePath": "/{proxy+}", - "httpMethod": "POST", - "apiId": "1234567890", - "protocol": "HTTP/1.1" - } -} diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-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 cdd785d6a0..18890033e1 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 @@ -1,7 +1,12 @@ package helloworld; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; @@ -14,8 +19,20 @@ public class App implements RequestHandler { public Object handleRequest(final Object input, final Context context) { Map headers = new HashMap<>(); headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", "application/json"); + try { + final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); + String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); + return new GatewayResponse(output, headers, 200); + } catch (IOException e) { + return new GatewayResponse("{}", headers, 500); + } + } - String output = String.format("{ \"message\": \"hello world\"}"); - return new GatewayResponse(output, headers, 200); + private String getPageContents(String address) throws IOException{ + URL url = new URL(address); + try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { + return br.lines().collect(Collectors.joining(System.lineSeparator())); + } } } diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/test/java/helloworld/AppTest.java b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/test/java/helloworld/AppTest.java index e6870f3671..e64bdfc55a 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/test/java/helloworld/AppTest.java +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/src/test/java/helloworld/AppTest.java @@ -16,5 +16,6 @@ public void successfulResponse() { assertNotNull(content); assertTrue(content.contains("\"message\"")); assertTrue(content.contains("\"hello world\"")); + assertTrue(content.contains("\"location\"")); } } diff --git a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/template.yaml b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/template.yaml index 2e22ee7f02..bd687f3e32 100644 --- a/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/local/init/templates/cookiecutter-aws-sam-hello-java/{{cookiecutter.project_name}}/template.yaml @@ -14,23 +14,23 @@ Globals: Resources: HelloWorldFunction: - Type: AWS::Serverless::Function # More info about Function Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-function + 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: target/HelloWorld-1.0.jar Handler: helloworld.App::handleRequest Runtime: java8 + Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + Variables: + PARAM1: VALUE Events: HelloWorld: - Type: Api # More info about API Event Source: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template.html#serverless-sam-template-api + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Outputs: - # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function - # Find out more about other implicit resources you can reference within SAM - # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api HelloWorldApi: Description: "API Gateway endpoint URL for Prod stage for Hello World function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"