Notes On Cloudformation
Table of Contents
# Step 1. Install serverless globally
$ npm install serverless -g
To workaround error:
sudo npm install -g try-thread-sleep
sudo npm install -g serverless --ignore-scripts spawn-sync
# Step 2. Create a serverless function
$ serverless create --template hello-world
# Step 3. deploy to cloud provider
$ serverless deploy
# Your function is deployed!
$ http://xyz.amazonaws.com/hello-world
Examples:
t-lambda.yml (service: t-lambda)
Cloudformation Stack Name: t-lambda-dev
API URL https://dbp1ufswd1.execute-api.us-east-1.amazonaws.com/dev/
The API Name is called "t-lambda-dev" with physical ID: dbp1ufswd1
Various Resources Created::
------------------------------------------------------------------------------
Resource-Type Physical-ID Comments
------------------------------------------------------------------------------
AWS::ApiGateway::RestApi dbp1ufswd1 Single Rest API per stack.
event.requestContext.apiId
AWS::Lambda::Function t-lambda-dev-hello <stack-name>-<function>
AWS::Lambda::Function t-lambda-dev-pubhello <stack-name>-<function>
AWS::ApiGateway::Resource n1wvn3 ApiGatewayResourceHello
This is /hello path resource
AWS::ApiGateway::Resource p4udjd ApiGatewayResourcePubhello
This is /pubhello path resource
event.requestContext.resourceId
resourcePath is /pubhello.
AWS::Logs::LogGroup /aws/lambda/t-lambda-dev-hello
One LogGroup per Lambda Function
AWS::IAM::Role t-lambda-dev-us-east-1-lambdaRole
# Note: Single lambda exec Role for all functions by default.
AWS::S3::Bucket t-lambda-dev-serverlessdeploymentbucket-1pa32il0ys7t3
# Single deployment S3 bucket.
Other Abstract Artifacts:
AWS::Lambda::Permission # One permission per function.
# Says who can invoke this lambda function.
# Used in statement-id within Function Policy permissions.
AWS::ApiGateway::Deployment # 1:1 relation with AWS::ApiGateway::RestApi
# The need to have this resource is unclear.
# Can you keep RestAPI but still undeploy it ? Not sure.
AWS::version # One per function.
Describes who can invoke this function ...
{
...
"Statement": [
{
"Sid": "t-lambda-d
"Effect": "Allow",
"Principal": { "Service": "apigateway.amazonaws.com" },
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:us-east-1:027212312845:function:t-lambda-dev-hello",
# To restrict API principal to invoke lambda only when API URL is like:
# https://dbp1ufswd1.execute-api.us-east-1.amazonaws.com/dev/
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:027212312845:dbp1ufswd1/*/*"
# event.requestContext.identity.userarn
# arn:aws:execute-api:us-east-1:027212312845:dbp1ufswd1/*/GET/hello
}
}
},
....
// Also if principal : sns.amazonaws.com
// Restrict source-arn to be specific topic:
// arn:aws:sns:us-east-1:123456789012:my-topic
...
}
For Amazon S3, the source is a bucket whose ARN doesn't have an account ID in it. It's possible that you could delete the bucket and another account could create a bucket with the same name. Use the account-id option to ensure that only resources in your account can invoke the function. :
$ aws lambda add-permission
--function-name my-function
--action lambda:InvokeFunction
--statement-id s3-account
--principal s3.amazonaws.com
--source-arn arn:aws:s3:::my-bucket-123456
--source-account 123456789012
To grant access to another account root user:
aws lambda add-permission --function-name my-function:prod
--statement-id xaccount --action lambda:InvokeFunction \
--principal 210987654321 --output text
You can remove part of the policy permission using satement-id option :
$ aws lambda get-policy --function-name my-function:PROD
# policy contains permissions each with one statement id.
$ aws lambda remove-permission --function-name my-function --statement-id sns
ToDO: Create S3 bucket through API and see who is listed as owner of that bucket.
serverless deploy function --function helloWorld
serverless deploy function --function helloWorld --stage dev --region us-east-1
# Only update config changes ...
serverless deploy function --function helloWorld --update-config
First get your API Gateway account properties to see if cloudwatchRoleArn is present :
$ aws apigateway get-account
{
"apiKeyVersion": "4",
"throttleSettings": {
"rateLimit": 10000.0,
"burstLimit": 5000
},
"features": [ "UsagePlans" ]
}
You need something like below in your apigateway account settings:
"cloudwatchRoleArn":
(your own Role )
"arn:aws:iam::123412341234:role/APIGatewayToCloudWatchLogsRole"
with Amazon managed policy:
"arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
But make sure that role has trusted entity set to :
- apigateway.amazonaws.com
# See Also:
# https://lorenstewart.me/2017/09/18/serverless-framework-terminal-commands/
sls create --template aws-nodejs
sls deploy -s dev # -s is --stage
sls remove -s dev
### Show Info
sls info -s dev -v (verbose)
### Monitor Logs ....
sls logs -f my_function # Default -s is for --stage dev
sls logs -f my_function -s production -r us-west-2
sls logs -f my_function --startTime 30m
sls logs -f my_function -t # See live stream of logs
### Local Invocation ...
sls invoke local -f my_function
sls invoke local -f my_function -d 'Hello!' # Body contains 'Hello!'
sls invoke local -f my_function -d '{"productName": 'Thing', "price": 12.99}'
sls invoke -s production -f my_function -d '{...json-data...}'
### Local Dev Plugins
npm install --save-dev serverless-offline
# serverless.yml
provider:
name: aws
runtime: nodejs6.10
stage: qa
plugins:
- serverless-offline
### Start local API server
serverless offline start --port 3333 # Default port is 3000
serverless plugin install -n serverless-offline
### Login
sls login
sls run # Run event gateway and emulator
### Dir structure
|-- mailman
|-- README.md
|-- frontend
`-- services
See https://github.com/serverless/event-gateway-example
sls create --help :
// Available Templates for sls create -t template_name
"aws-nodejs",
"aws-nodejs-typescript",
"aws-nodejs-ecma-script",
"aws-python",
"aws-python3",
"aws-java-maven",
"fn-nodejs",
"google-nodejs",
"google-python",
"hello-world"
aws-node-fetch-file-and-store-in-s3/
aws-node-env-variables/
aws-node-dynamic-image-resizer/
aws-node-dynamodb-backup/
aws-node-recursive-function/
aws-node-rekognition-analysis-s3-image/
aws-node-auth0-cognito-custom-authorizers-api/
aws-node-auth0-custom-authorizers-api/
aws-node-rest-api-mongodb/
aws-node-rest-api-with-dynamodb/
aws-node-scheduled-cron/
aws-node-serve-dynamic-html-via-http-endpoint/
aws-node-simple-http-endpoint/
aws-node-single-page-app-via-cloudfront/
aws-node-stripe-integration/ (Payment gateway)
aws-node-text-analysis-via-sns-post-processing/
aws-node-twilio-send-text-message/
aws-python-rest-api-with-dynamodb/
aws-python-scheduled-cron/
aws-python-simple-http-endpoint/
Integrate REST API with user pool: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html
At API Integration Console:
Token Source: method.request.header.Authorization (Header name)
# serverless.yml file ...
environment: ${file(env.yml):${self:provider.stage}}
# env.yml file ...
dev:
EMAIL_POP3_HOST: “pop.gmail.com”
EMAIL_POP3_USERNAME: “me@gmail.com”
# The env file could have .json extension as well .. e.g. env.json
{
"dev" : { "EMAIN_POP3_HOST" : "pop.gmail.com" , ... }
}
# You can directly refer to nested values in files.
# Immediately after file you should have ":" else all "." only.
environment:
DB_NAME: ${file(config.json):${self:provider.stage}.database}
serverless invoke local -f getProduct [ -p example.json ]
# example.json -- stringified json
{
"body": "{ \"somekey\" : \"somevalue\" }"
}
# Assign unique IAM role to Function.
functions:
usersCreate: # A Function
handler: users.create
# role: arn:aws:iam::XXXXXX:role/role # IAM
role: Fn::GetAtt:[MyLocalRoleInResources, Arn]
# You can also just refer to the role !!!
role: MyLocalRoleInResources
If you want to get Arn of the function:
Global roles defined under provider is applicable to all functions. These roles can not be addressed from other places using Ref: or Fn::GetAtt: and such functions :
provider:
iamRoleStatements:
- Effect: Allow
Action:
- sqs:SendMessage
Resource:
Fn::GetAtt: [ MessageQueue, Arn ]
iamManagedPolicies:
- 'some:aws:arn:xxx:*:*'
Note that Amazon predefined iamManagedPolicies are merged into the default service wide generated Role. You can also define provider:role directly :
provider:
role: ....
# iamRoleStatements and iamManagedPolicies are ignored.
provider:
name: aws
runtime: nodejs8.10
resourcePolicy: ${self:custom.resourcePolicies}
custom:
resourcePolicies:
- Effect: Allow
Principal: "*"
Action: execute-api:Invoke
Resource:
- execute-api:/*/*/*
Condition:
IpAddress:
aws:SourceIp: ${self:custom.ipWhitelist}
ipWhitelist:
- "123.123.123.123/24"
provider:
iamRoleStatements:
- Effect: 'Allow'
Action:
- 's3:ListBucket'
Resource:
Fn::Join:
- ''
- - 'arn:aws:s3:::'
- Ref: ServerlessDeploymentBucket
serverless.xml does not directly support accessing lambda function ARN. However, since the arn follows this format, you can use this :
provider:
environment:
AWS_ACCOUNT: 1234567890 # use your own AWS ACCOUNT number here
# define the ARN of the function that you want to invoke
FUNCTION_ARN: "arn:aws:lambda:${self:provider.region}:
${self:provider.environment.AWS_ACCOUNT}:
function:${self:service}-
${self:provider.stage}-lambdaTwo"
From within inline policy you can refer like this:
Statement:
- Effect: "Allow"
Action:
- "lambda:InvokeFunction"
Resource: "${self:provider.environment.FUNCTION_ARN}"
plugins:
- serverless-pseudo-parameters
# You can now use #{AWS::AccountId}, #{AWS::Region}, etc.
# Proceses this with Fn::Sub CloudFormation function.
# This works now: #{myAwesomeResource},
# This also works: #{myAwesomeResource.property}
plugins:
- serverless-iam-roles-per-function
# Define iamRoleStatements definitions at the function level:
functions:
func1:
handler: handler.get
# Optional custom role name vs default generated one
iamRoleStatementsName: my-custom-role-name
iamRoleStatements:
- Effect: "Allow"
Action:
- dynamodb:GetItem
Resource: "arn:aws:dynamodb:us-east-1:*:table/mytable"
provider:
environment:
DB_ADDR:
Fn::GetAtt:
- DBCluster
- Endpoint.Address
resources:
Resources:
DBCluster:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora
...
If you want to see if the output produces what you want .. try...
provider:
environment:
myenvvar : ${someResource}
resources:
Resources:
someResource:
Type: AWS::...
Outputs:
UsersTableArn:
Description: The ARN for the User's Table.
Value: 'Fn::GetAtt:[usersTable, Arn]'
# Curretly the default output is
FunctionQualifiedArn (one for each function)
ServiceEndpoint
ServerlessDeploymentBucketName
$ sls print
resources:
Resources:
myCustRole0:
Type: AWS::IAM::Role
Properties:
Path: /my/cust/path/
RoleName: MyCustRole0
AssumeRolePolicyDocument: # Define Trusted Entities.
Version: '2017'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: myPolicyName
PolicyDocument:
Version: '2017'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
...
YML is a superset of JSON.
# flow style # block style
{ foo: 1, bar: 2 } foo: 1
bar: 2
[ foo, bar ] - foo
- bar
{"AAPL": [ AAPL:
{ - shares: -75.088
"shares": -75.088, date: 11/27/2015
"date": "11/27/2015" - shares: 75.088
}, date: 11/26/2015
{
"shares": 75.088,
"date": "11/26/2015"
},
]}