I'm trying to build a REST API with SAM. My YAML file looks like this:
AWSTemplateFormatVersion: "2010-09-09"
Description: >-
example-rest-api
Transform:
- AWS::Serverless-2016-10-31
Resources:
allEquipmentsFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/myModel.getAll
Runtime: nodejs18.x
Architectures:
- x86_64
MemorySize: 128
Timeout: 100
Description: example description
Events:
ApiEvent:
Type: Api
Properties:
Path: /
Method: GET
saveEquipmentFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/myModel.save
Runtime: nodejs18.x
Architectures:
- x86_64
MemorySize: 128
Timeout: 100
Description: example description
Events:
Api:
Type: Api
Properties:
Path: /
Method: POST
ApplicationResourceGroup:
Type: AWS::ResourceGroups::Group
Properties:
Name:
Fn::Join:
- ''
- - ApplicationInsights-SAM-
- Ref: AWS::StackName
ResourceQuery:
Type: CLOUDFORMATION_STACK_1_0
ApplicationInsightsMonitoring:
Type: AWS::ApplicationInsights::Application
Properties:
ResourceGroupName:
Fn::Join:
- ''
- - ApplicationInsights-SAM-
- Ref: AWS::StackName
AutoConfigurationEnabled: 'true'
DependsOn: ApplicationResourceGroup
Outputs:
WebEndpoint:
Description: API Gateway endpoint URL for Prod stage
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/{Stage}"
As I understand it, SAM creates some resources for me in the background. For example:
"ServerlessRestApi": {
"Type": "AWS::ApiGateway::RestApi"
...
How can I override these generated resources? I want to add API Gateway validation model. For this I need to override background generated resources. AWS Resources is not clean enough. What approach should I take if I can't override?
You cannot "override" properties per se, but you can directly create resources if you need greater control.
When you specify an Event: ApiEvent on your function, SAM creates a AWS::ApiGateway::RestApi resource for you. As the docs say, you can reference the RestApi its Logical ID ServerlessRestApi. This is helpful for passing the RestApi to another resource, but not configuring the RestApi itself.
If you need greater control over the Api properties, you should instead explicily create a AWS::Serverless::Api resource. Configure it as required. Then pass the Api reference to your functions event source configuration as the RestApiId.
SAM is a superset of CloudFormation. You can also include AWS::ApiGateway:: resources directly in your SAM template if SAM's abstractions don't fit your use case. This gives you the greatest level of control.
I am trying to have a customer name given to my API gateway created through SAM template.
and also restrict creating only one stage while deploying.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Specification template describing your function.
Resources:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: Development
Cors: "'*'"
GetAllUser:
Type: AWS::Serverless::Function
Properties:
FunctionName: loadeo_get_all_user
CodeUri: code/
Handler: get_all_user.lambda_handler
Timeout: 5
Runtime: python3.8
Role: lambda_execution
MemorySize: 128
Events:
GetAllUser:
Type: Api
Properties:
Path: /get-all-user
Method: get
RestApiId:
Ref: ApiGatewayApi
All is working fine as I want, but
It is creating the API with the name of the stack (I want to give a custom name)
Along with the "Development" it is also adding "stage" while deploying.
How I can achieve these two cases?
To specify Name for your ApiGatewayApi, you have to use Name property:
A name for the API Gateway RestApi resource
Thus your ApiGatewayApi would be:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: Development
Name: MyApiName
Cors: "'*'"
I'm not sure I understand the second issue, thus can't comment on it right now.
As explained here you can add the following to fix the Stage issue:
Globals:
Api:
OpenApiVersion: 3.0.1
I want to set a stage name for the API Gateway in a SAM template.yaml.
But whatever I try I'm not succeeding. Without trying to name my stage, everything works as expected but with the default stage names Prod and Stage.
My sam-cli version is 0.47.0
I did find three comparable questions here on Stackoverflow but none of the answers work for me.
How can I change the name of the API stage in a SAM template?
How can I use api gateway stages via cloudformation or sam?
Using SAM file to remove default “Stages” in AWS ApiGateway?
I always get an error something like this:
Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template
So how do I get a stage name I choose myself. I don't care much if Prod and Stage coexist with my chosen name.
Just to be complete, my template.yaml file is below:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
Globals:
Function:
Timeout: 3
Api:
Cors:
AllowMethods: "'OPTIONS,PUT'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
AllowOrigin: "'*'"
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello-world
Method: put
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello-world/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
I probably don't understand the intended workflow behind this. Why have 2 stage names when the lambda function the API Gateway is pointing to, is the same?
I will have 'dev' and 'prod' environments but they will use different stack names so I can never mix up the different environments.
I always use deploy-dev.sh and deploy-pod.sh scripts that check if I'm on the development or master (production) branch before actually deploying something. So those scripts would point to a different template.yaml files because they are called from different git branches. I'm using this way for deployment already for a long time and it works well for me.
On a side note: Why the existing stage names start with a capital? It looks so ugly and unusual.
So I found my own answer which is sort of a combination of two answers to the questions I found on StackOverflow that are mentioned in my question.
I still don't understand why this is so needlessly complicated.
I added a parameter to the top level of the template.yaml file. The use of a parameter is not strictly needed. I added this so I can have a single template file that is called from both my deploy-dev.sh and deploy-prod.sh scripts.
Below is the parameter declaration:
Parameters:
Stage:
Type: String
Default: dev
Then, under the Resources group, I added a new ApiDeployment resource. The name you use is totally up to you as long as you use the exact same name elsewhere as a !Ref. The only reason to add this resource is that you are not allowed to simply use the StageName in the properties of the Api section of the function event. You are also not allowed to put StageName in the Globals Api section.
ApiDeployment:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref Stage <- this is referencing the parameter but it could be a fixed value
Then, under the Events section of the Lambda function I added a property RestApiId that is referencing the ApiDeployment resource. The last line in the block below.
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello-world
Method: put
RestApiId: !Ref ApiDeployment
As I mentioned in my question, I got errors complaining about the output section of the yaml file. It turns out that the output section is optional anyway. So when I commented it out, everything worked.
But I used the output section in my deploy script to show me the URL of the API Gateway so with some trying I got that working too.
The error was caused in the 4th line. It originally had ${ServerlessRestApi}. Just replace it with the new resource name I added to the yaml file: ${ApiDeployment} and everything is fine.
Outputs:
ApiDeployment:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
Because I use a parameter in the yaml file, you need to call sam deploy with the parameter name and value. The exact syntax for this is, like a lot of AWS's documentation, very well hidden. So below is the way you start your deployment:
sam deploy --parameter-overrides "ParameterKey=Stage,ParameterValue=dev"
You probably still have the Stage stage in the API Gateway console under Stages but you can delete that without any repercussions.
For completeness here is my full template.yaml file which is, by the way, the file you get when you do sam init
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app: Sample SAM Template for sam-app
Parameters:
Stage:
Type: String
Default: dev
Globals:
Function:
Timeout: 3
Api:
Cors:
AllowMethods: "'OPTIONS,PUT'"
AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
AllowOrigin: "'*'"
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello-world
Method: put
RestApiId: !Ref ApiDeployment
ApiDeployment:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref Stage
Outputs:
ApiDeployment:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
Here is an update for anyone landing on this question.
I'm not using SAM anymore. I switched to AWS CDK.
The CDK lets you define your entire AWS infrastructure in Code!
You can use Javascript, Typescript, Python, C# and Java although most examples seem to be in Typescript.
It's not a very easy switch but it is worthwhile. SAM seems to be a dead-end street.
SAM only covers a small subset of all AWS has to offer but the CDK covers everything.
It's quite new and it's a moving target, plus the devs don't give a rat's ass about breaking updates as they are still moving things around between modules.
But after a few days, you'll start to get a grasp and it has infinite flexibility because you are using a normal programming language to set up things like API Gateways, Lambdas, Custom Domains, IAM rules, etc.
It's also very compact (compared to SAM templates).
I used it to have different stacks for staging and production based upon the got branch I'm in. So when I deploy while my repo is on the dev branch I'll have a different environment (including different domain names and so) then when I would deploy from the master or prod branch. The names of the different services also differ, depending on the git branch.
To deploy, you just run "cdk deploy"
To get started look at this excellent workshop: https://cdkworkshop.com/
Below is an example of this branch switching. I only show parts of the method for the dev branch. For the prod branch, I just have a copy of the same method in the same file but the method has a different name and the variables for the service names also differ. Just look at the piece of (incomplete) code and you should get an idea of how it works.
import * as branchName from 'current-git-branch'
const branch = branchName()
/*-------- This is the development stack --------*/
export class StripePaymentsDev extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
if (branch !== 'dev') {
throw new Error('Not on dev branch')
}
// Env vars from Parameter Store
const STRIPE_PUBLIC = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_PUBLIC`)
const STRIPE_SECRET = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_SECRET`)
const STRIPE_API_VERSION = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_API_VERSION_PAYMENTS`)
// Names for the dev environment
const domainMapName = 'PaymentsDev'
const eventBusName = 'WebhooksBusDev'
const ruleName = 'WebhooksRuleDev'
const eventBus = new EventBus(stackScope, eventBusName, { eventBusName })
const cert = Certificate.fromCertificateArn(stackScope, certName, certArn)
const stackScope = this
// IAM rules
const lambdaPolicy = new iam.PolicyStatement({
actions: ['events:*'],
resources: ['*']
})
const sqsPolicy = new iam.PolicyStatement({
actions: ['sqs:*'],
resources: ['*']
})
const webhooks = new lambda.Function(stackScope, lambdaWebhooksName, {
runtime: lambda.Runtime.NODEJS_12_X,
code: lambda.Code.fromAsset('webhook-handler'),
handler: 'webhooks.handler',
timeout: Duration.seconds(600),
description: 'Processes Stripe Webhooks',
retryAttempts: 0,
environment: {
STRIPE_PUBLIC,
STRIPE_SECRET,
STRIPE_API_VERSION,
MONGO_URL,
MONGO_DB,
MONGO_PORT,
DEBUG
}
})
webhooks.addToRolePolicy(sqsPolicy)
const rule = new Rule(stackScope, ruleName, {
description: 'Triggers lambda to process stipe webhooks',
enabled: true,
eventBus: eventBus,
eventPattern: {
detailType: ['transaction'],
source: ['custom.payments']
},
ruleName: ruleName
})
rule.addTarget(new eventTargets.LambdaFunction(webhooks))
new HttpApi(stackScope, apiName, {
defaultIntegration: new LambdaProxyIntegration({ handler: payments }),
defaultDomainMapping: {
domainName: new DomainName(stackScope, domainMapName, {
domainName: PAYMENT_DOMAIN,
certificate: cert
})
}
})
}
}
This way creates only your specified stage instead of creating one more stage named Stage.
This setup did trick.
Globals:
Api:
OpenApiVersion: 3.0.1
I've also created the new AWS::Serverless::Api named RestApi to overwrite implicit ServerlessRestApi. Remember to set RestApi into RestApiId of every API event.
template.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: >-
app-sam
Transform:
- AWS::Serverless-2016-10-31
# ====================================
# PARAMETERS SETUP
# ====================================
Parameters:
StageName:
Type: String
Default: dev
Description: (Required) Enter dev, prod. Default is dev.
AllowedValues:
- dev
- prod
ProjectName:
Type: String
Default: sam-api
Description: (Required) The name of the project
MinLength: 3
MaxLength: 50
AllowedPattern: ^[A-Za-z_-]+$
ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
ExistingTable:
Type: String
Default: example-table
Description: (Required) The name of existing DynamoDB
MinLength: 3
MaxLength: 50
AllowedPattern: ^[A-Za-z_-]+$
ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
# ====================================
# GLOBAL SETUP
# ====================================
Globals:
Api:
OpenApiVersion: 3.0.1
Function:
Runtime: nodejs14.x
Timeout: 180
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ExistingTable
Resources:
# Reference this one to overwrite implicit stage
# https://github.com/aws/serverless-application-model/issues/191#issuecomment-580412747
RestApi:
Type: AWS::Serverless::Api
Properties:
Name: !Ref ProjectName
StageName: !Ref StageName
# This is a Lambda function config associated with the source code: get-all-items.js
getAllItemsFunction:
Type: AWS::Serverless::Function
Properties:
Handler: src/handlers/get-all-items.getAllItemsHandler
Description: A simple example includes a HTTP get method to get all items from a DynamoDB table.
Policies:
# Give Create/Read/Update/Delete Permissions to the ExistingTable
- DynamoDBCrudPolicy:
TableName: !Ref ExistingTable
Events:
Api:
Type: Api
Properties:
Path: /
Method: GET
RestApiId: !Ref RestApi
Outputs:
WebEndpoint:
Description: "API Gateway endpoint URL for Prod stage"
Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/"
I am working on a SAM application having a lambda function with API Gateway as source of event. API Endpoint is a POST Method requiring a set of parameters in request body. API Gateway provides us the capability of validating request body by specifying a request Model using AWS Console.
Refer Screenshots below of AWS Console options:
I need to set similar options via SAM template and able to link a Model with the request body but not able to set request validator option and is not able to find any documentation or example also.
Below is my SAM Template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template
Parameters:
Stage:
Type: String
Default: dev
Resources:
MyApiGateway:
Type: AWS::Serverless::Api
Properties:
Name: My AWS Serverless API
StageName: !Ref Stage
Models:
ExchangeRate:
$schema: "http://json-schema.org/draft-04/schema#"
properties:
base:
type: string
target:
type: string
required:
- base
- target
title: User
type: object
ExchangeRateFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/exchange-rate/
Handler: index.handler
Runtime: nodejs12.x
Description: Function to Get Currency Exchange Rate
MemorySize: 128
Timeout: 3
Policies:
- AWSLambdaBasicExecutionRole
Events:
HelloWorld:
Type: Api
Properties:
RestApiId: !Ref MyApiGateway
Path: /exchange
Method: POST
RequestModel:
Model: ExchangeRate
Required: true
Outputs:
ExchangeRateFunction:
Description: "Exchange Rate Lambda Function ARN"
Value: !GetAtt ExchangeRateFunction.Arn
MyApiGateway:
Description: "My Seed API EndPoint"
Value: !Sub "https://${MyApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${Stage}"
Documentation referred
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-api.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html
Please let me know how can I set 'Request Validator' to 'Validate body' option using SAM template. Will appreciate the help
Add ValidateBody: true i.e.
RequestModel:
Model: ExchangeRate
Required: true
ValidateBody: true
I've ran into the same problem, apparently this feature is lacking from SAM for a while, as you can see from this previous question:
How to add a request validator in a AWS SAM template for AWS::Serverless::Api?
Also, a few issues have been opened in GitHub, the last one being:
https://github.com/awslabs/serverless-application-model/issues/1403
I've hacked a solution that includes two additional properties in the SAM specification to solve this issue, but I wouldn't expect it to actually become a PR. I can provide further instructions if you'd like to use my forked repo to deploy from a develop branch.
I have a problem with AWS SAM and provisioning API Gateway configurations. I am trying to do a few things:
Configure the API gateway to require api-key in the headers
Create my own stage as defined in my config files.
The API gateway model defined in my file is not being created
Currently, the API gateway gets provisioned and linked to my lambda function but it fails in the two requirements above. Below are my files: template.yaml and swagger.yaml.
Template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-nfeed-s3
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 60
Api:
EndpointConfiguration: REGIONAL
Resources:
SAMnfeedS3API:
Type: AWS::Serverless::Api
Properties:
StageName: alpha
DefinitionUri: ./swagger.yaml
Resources:
SAMnfeedS3Lambda:
Type: AWS::Serverless::Function
Properties:
CodeUri: test-function-sam/
Handler: nfeed_vdp_clusters.lambda_handler
Runtime: python3.6
Role: arn:aws:iam::XXXXXXX:role/Lambda
Events:
SAMnfeedS3API:
Type: Api
Properties:
Path: /vdp_clusters
Method: GET
Environment:
Variables:
TEST: test
Outputs:
SAMnfeedS3API:
Description: "API Gateway endpoint URL for Staging env"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Staging/vdp_clusters"
SAMnfeedS3Lambda:
Description: "Lambda Function ARN"
Value: !GetAtt SAMnfeedS3Lambda.Arn
Swagger.yaml
---
swagger: '2.0'
info:
title: !Ref AWS::StackName
basePath: "/alpha"
schemes:
- "https"
x-amazon-apigateway-api-key-source : "HEADER"
paths:
"/vdp_clusters":
get:
consumes:
- application/json
produces:
- application/json
parameters:
- name: x-api-key
in: header
required: true
type: string
responses:
200:
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
uri: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:XXXXXXXXX:function:${SAMnfeedS3Lambda.Arn}/invocations
responses:
default:
statusCode: "200"
httpMethod: "POST"
type: aws_proxy
security:
- api_key: []
securityDefinitions:
api_key:
type: "apiKey"
name: "x-api-key"
in: "header"
definitions:
Empty:
type: "object"
title: "Empty Schema"
$schema: "http://json-schema.org/draft-04/schema#"
As defined in my swagger and template files, "alpha" stage should be created for the gateway but nothing appears. The "Empty" model and api-key requirement also do not appear. Any help, would be appreciated.
The problem is you have duplicated the Resources key in the template.
I recommend always using the yamllint utility on your SAM templates, because it detects YAML formatting issues that sam validate can't always detect. Here is what I got:
▶ yamllint sam-app/template.yaml
sam-app/template.yaml
...
18:1 error duplication of key "Resources" in mapping (key-duplicates)
If you then look in the packaged.yml file that is created by the sam build step, you'll notice that the API you defined will be missing. That's because it's impossible for a dict in Python to contain duplicate keys. The second Resources block you specified just overwrites the first one when the Python YAML library reads the file in.
SAM then generates the implicit API SAMnfeedS3API based on the API you specified in Events using its own generated Swagger rather than the one you (thought you) provided.
Note also that, after you fix up the duplicate key issue, you will also need to reference your API from the Events with a line like:
Events:
SAMnfeedS3API:
Type: Api
Properties:
Path: /vdp_clusters
Method: GET
RestApiId: !Ref SAMnfeedS3API ## ADD THIS LINE
See also my earlier answer here.