Following excerpt from AWS Developer site:
The AWS Serverless Application Model (AWS SAM) is an open-source framework that you can use to build serverless applications on AWS.
A serverless application is a combination of Lambda functions, event sources, and other resources that work together to perform tasks. Note that a serverless application is more than just a Lambda function—it can include additional resources such as APIs, databases, and event source mappings.
You can use AWS SAM to define your serverless applications. AWS SAM consists of the following components:
- AWS SAM template specification.You use this specification to define your serverless application. It provides you with a simple and clean syntax to describe the functions, APIs, permissions, configurations, and events that make up a serverless application. You use an AWS SAM template file to operate on a single, deployable, versioned entity that’s your serverless application. For the full AWS SAM template specification, see AWS Serverless Application Model Specification.
- AWS SAM command line interface (AWS SAM CLI). You use this tool to build serverless applications that are defined by AWS SAM templates. The CLI provides commands that enable you to verify that AWS SAM template files are written according to the specification, invoke Lambda functions locally, step-through debug Lambda functions, package and deploy serverless applications to the AWS Cloud, and so on. For details about how to use the AWS SAM CLI, including the full AWS SAM CLI Command Reference, see AWS SAM CLI.
AWS SAM is a higher-level abstraction of AWS CloudFormation that simplifies serverless application development. AWS SAM template files are AWS CloudFormation template files with a few additional resource types defined that are specific to serverless applications—such as API Gateway endpoints and Lambda functions. This means that AWS SAM supports the full suite of resources, intrinsic functions, and other template features that are available in AWS CloudFormation.
In order to use AWS SAM it must be installed on the client. This can be done here:
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
SAM Templates
SAM is driven by templates. The format of an AWS SAM template closely follows the format of an AWS CloudFormation template, which is described in Template Anatomy in the AWS CloudFormation User Guide.
The primary differences between AWS SAM templates and AWS CloudFormation templates are the following:
- Transform declaration. The declaration Transform: AWS::Serverless-2016-10-31 is required for AWS SAM templates. This declaration identifies an AWS CloudFormation template as an AWS SAM template. For more information about transforms, see Transform in the AWS CloudFormation User Guide.
- Globals section. The Globals section is unique to AWS SAM. It defines properties that are common to all your serverless functions and APIs. All the AWS::Serverless::Function, AWS::Serverless::Api, and AWS::Serverless::SimpleTable resources inherit the properties that are defined in the Globals section. For more information about the Globals section, see Globals Section of the Template in the AWS Serverless Application Model Developer Guide.
- Resources section. In AWS SAM templates the Resources section can contain a combination of AWS CloudFormation resources and AWS SAM resources. For more information about AWS CloudFormation resources, see AWS Resource and Property Types Reference in the AWS CloudFormation User Guide. For more information about AWS SAM resources see AWS SAM Resource and Property Reference in the AWS Serverless Application Model Developer Guide.
Sample template format shown below (YAML).
Transform: AWS::Serverless-2016-10-31 Globals: set of globals Description: String Metadata: template metadata Parameters: set of parameters Mappings: set of mappings Conditions: set of conditions Resources: set of resources Outputs: set of outputs
Quick Command Reference
- sam build
- sam deploy
- sam init
- sam local generate-event
- sam local invoke
- sam local start-api
- sam local start-lambda
- sam logs
- sam package
- sam publish
- sam validate
Sample commands:
jlee:$ aws s3 mb s3://samtest make_bucket: samtest jlee$ sam build 2019-04-19 10:28:16 Building resource 'samtest' 2019-04-19 10:28:16 Running NodejsNpmBuilder:NpmPack 2019-04-19 10:28:16 Running NodejsNpmBuilder:CopyNpmrc 2019-04-19 10:28:16 Running NodejsNpmBuilder:CopySource 2019-04-19 10:28:16 Running NodejsNpmBuilder:NpmInstall 2019-04-19 10:28:18 Running NodejsNpmBuilder:CleanUpNpmrc Build Succeeded Built Artifacts : .aws-sam/build Built Template : .aws-sam/build/template.yaml Commands you can use next ========================= [*] Invoke Function: sam local invoke [*] Package: sam package --s3-bucket jlee$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket samtest Successfully packaged artifacts and wrote output template to file packaged.yaml. Execute the following command to deploy the packaged template aws cloudformation deploy --template-file /Users/jlee/packaged.yaml --stack-name jlee$ sam deploy --template-file packaged.yaml --capabilities CAPABILITY_IAM --stack-name samtest Waiting for changeset to be created.. Waiting for stack create/update to complete Successfully created/updated stack - samtest
Some other things to note
Sample Project
This is a small SAM app with a front end SPA (single page app) powered by Angular, and backend using API Gateway and Lambda. Both sides use SAM for build and deployment. The app allows users to view EC2 instances and either shut them down or turn them on. For simplicity, some things have been hard coded – such as regions and instance types (using tags).
The backend has two api endpoints, one for getting a list of EC2 instances. This is the ec2monitorGet app.
let response; exports.lambdaHandler = async (event, context) => { let AWS = require('aws-sdk'); AWS.config.region = 'us-west-2'; let ec2 = new AWS.EC2({ apiVersion: '2016-11-15' }); response = { 'statusCode': 200 }; try { if (event.httpMethod && event.httpMethod == 'GET') { let params = { Filters: [ { Name: "tag:autoshutdown", Values: [ "true" ] } ] }; let data = await ec2.describeInstances(params).promise(); let instances = []; if (data.Reservations.length > 0) { for (r in data.Reservations) { if (data.Reservations[r].Instances.length > 0) { for (i in data.Reservations[r].Instances) { let nametag = data.Reservations[r].Instances[i].Tags.filter(function (tag) { return tag.Key == 'Name'; }); let instance = { 'name': nametag.length > 0 ? nametag[0].Value : 'Unknown', 'instanceid': data.Reservations[r].Instances[i].InstanceId, 'state': data.Reservations[r].Instances[i].State.Name }; instances.push(instance); } } } } let body = { message: instances.length + ' instances found', instances: instances } response['body'] = JSON.stringify(body); } else { response['body'] = "Invalid HTTP Method"; } } catch (err) { console.log(err); return err; } return response; };
The second api is for posting commands, to either shut down or start up an instance. This is the ec2monitorPost app.
let response; exports.lambdaHandler = async (event, context) => { let AWS = require('aws-sdk'); AWS.config.region = 'us-west-2'; let ec2 = new AWS.EC2({ apiVersion: '2016-11-15' }); response = { 'statusCode': 200 }; try { if (event.httpMethod && event.httpMethod == 'POST') { let body = JSON.parse(event.body); let params = { Filters: [ { Name: "tag:autoshutdown", Values: [ "true" ] } ] }; let data = await ec2.describeInstances(params).promise(); if (data.Reservations.length > 0) { for (r in data.Reservations) { if (data.Reservations[r].Instances.length > 0) { for (i in data.Reservations[r].Instances) { // TODO if (body.action == 'stop') { response['body'] = 'Stopping EC2 Instance: ' + body.instanceid; } else if (body.action == 'start') { response['body'] = 'Starting EC2 Instance: ' + body.instanceid; } else { response['body'] = 'Error - no action given for: ' + body.instanceid; } } } } } } else { response['body'] = "Invalid HTTP Method"; } } catch (err) { console.log(err); return err; } return response; };
Note that for the code examples above I’m using Nodejs v8. In this version we have the new async/await abilities. The AWSSDK works with this version by offering ‘promise()’ methods we can call for each of the APIs. This new feature makes coding a bit cleaner without having to write out callback functions. Also, this gives us simplified try/catch statement usage for handling any of the errors that may come up during the API calls.
Another thing to note is regarding global variables in the template file. We can set globals for things like lambda function timeout, which by default is 3 seconds but since in this example we’re dealing with EC2 instructions I’ve increased it to 10 seconds. Other things we can configure are API Gateway CORS. Below is example of the template file used by SAM.
BBBBBBBBB
As shown in the previous section, I use the SAM CLI and run the build, package and deploy commands to get the backend deployed. Once the backend is deployed, I can deploy the frontend. For this example app my frontend is an Angular app that will be deployed on S3 as a static website. I use the AWS CLI to setup the bucket and deploy the front end as shown below.
jlee:$ aws s3 mb s3://samtest-web jlee:$ aws s3 cp samtest/ s3://samtest-web --recursive --exclude 'node_modules/*' upload: samtest/favicon.ico to s3://samtest-web/favicon.ico upload: samtest/index.html to s3://samtest-web/index.html upload: samtest/runtime.js to s3://samtest-web/runtime.js upload: samtest/main.js.map to s3://samtest-web/main.js.map upload: samtest/runtime.js.map to s3://samtest-web/runtime.js.map upload: samtest/es2015-polyfills.js.map to s3://samtest-web/es2015-polyfills.js.map upload: samtest/polyfills.js.map to s3://samtest-web/polyfills.js.map upload: samtest/es2015-polyfills.js to s3://samtest-web/es2015-polyfills.js upload: samtest/styles.js to s3://samtest-web/styles.js upload: samtest/polyfills.js to s3://samtest-web/polyfills.js upload: samtest/main.js to s3://samtest-web/main.js upload: samtest/styles.js.map to s3://samtest-web/styles.js.map upload: samtest/vendor.js.map to s3://samtest-web/vendor.js.map upload: samtest/vendor.js to s3://samtest-web/vendor.js jlee:$ aws s3 ls s3://samtest-web 2019-04-23 10:18:47 290826 es2015-polyfills.js 2019-04-23 10:18:47 211178 es2015-polyfills.js.map 2019-04-23 10:18:47 5430 favicon.ico 2019-04-23 10:18:47 743 index.html 2019-04-23 10:18:47 20127 main.js 2019-04-23 10:18:47 14017 main.js.map 2019-04-23 10:18:47 241644 polyfills.js 2019-04-23 10:18:47 240220 polyfills.js.map 2019-04-23 10:18:47 6224 runtime.js 2019-04-23 10:18:47 6214 runtime.js.map 2019-04-23 10:18:47 183880 styles.js 2019-04-23 10:18:47 196554 styles.js.map 2019-04-23 10:18:47 7095571 vendor.js 2019-04-23 10:18:47 7432815 vendor.js.map jlee:$ aws s3 website s3://samtest-web/ --index-document index.html --error-document index.html jlee:$ aws s3api put-bucket-policy --bucket samtest-web --policy file://s3policy.json jlee:$ ng build --prod Date: 2019-04-23T23:54:33.813Z Hash: 1aaaab5c8973af42f3fe Time: 32122ms chunk {0} runtime.26209474bfa8dc87a77c.js (runtime) 1.41 kB [entry] [rendered] chunk {1} es2015-polyfills.c5dd28b362270c767b34.js (es2015-polyfills) 56.4 kB [initial] [rendered] chunk {2} main.71f1b500ef528a11da6b.js (main) 526 kB [initial] [rendered] chunk {3} polyfills.8bbb231b43165d65d357.js (polyfills) 41 kB [initial] [rendered] chunk {4} styles.7775c3807c26fe8ac360.css (styles) 61.2 kB [initial] [rendered] jlee$ aws s3 cp dist/samtest/ s3://samtest-web --recursive --exclude 'node_modules/*' upload: dist/samtest/runtime.26209474bfa8dc87a77c.js to s3://samtest-web/runtime.26209474bfa8dc87a77c.js upload: dist/samtest/index.html to s3://samtest-web/index.html upload: dist/samtest/favicon.ico to s3://samtest-web/favicon.ico upload: dist/samtest/polyfills.8bbb231b43165d65d357.js to s3://samtest-web/polyfills.8bbb231b43165d65d357.js upload: dist/samtest/3rdpartylicenses.txt to s3://samtest-web/3rdpartylicenses.txt upload: dist/samtest/main.71f1b500ef528a11da6b.js to s3://samtest-web/main.71f1b500ef528a11da6b.js upload: dist/samtest/es2015-polyfills.c5dd28b362270c767b34.js to s3://samtest-web/es2015-polyfills.c5dd28b362270c767b34.js upload: dist/samtest/styles.7775c3807c26fe8ac360.css to s3://samtest-web/styles.7775c3807c26fe8ac360.css
I can take all the above steps and write them into a script. I can then use this script as part of my CI/CD to automate the deployment process for both frontend and backend.
References
AWS SAM
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html
AWSLABS SAM Repository
https://github.com/awslabs/serverless-application-model
Using SAM and the SAM Repositories
https://docs.aws.amazon.com/serverlessrepo/latest/devguide/using-aws-sam.html
NodeJS v8 with AWSSDKs?
https://serverless.com/blog/common-node8-mistakes-in-lambda/
Example lessons on SAM
https://github.com/lucpod/ticketless/tree/master/lessons
Sample Lambda Function shutting down EC2s
https://github.com/geekmuse/iot-button-ec2-controller/blob/master/index.js