AWS SAM - How to override generated resources - amazon-web-services

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.

Related

Referencing an AWS Lambda's role inside a SAM Template (CF Stack) YAML

I have an AWS SAM template defining, amongst many other things, a JavaScript Lambda:
Resources:
notesFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Zip
CodeUri: notes/
Handler: app.lambdaHandler
Runtime: nodejs18.x
Policies:
- AmazonDynamoDBFullAccess
Architectures:
- x86_64
Events:
Fetchnotes:
Type: Api
Properties:
Path: /notes
Method: get
Givenotes:
Type: Api
Properties:
Path: /notes
Method: post
Users:
Type: Api
Properties:
Path: /notes/users
Method: get
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: "es2020"
Sourcemap: true
EntryPoints:
- app.ts
Later on in the template, I am trying to reference this Lambda's role (example: Role: !Ref <MyLambdaRole>) but not sure how to do that, since the role is created on the fly when deploying the SAM template (CloudFormation stack). Any ideas how I can do this?
If you do not provide a role in your AWS::Serverless::Function definition, SAM creates a role with a Logical ID of <function‑LogicalId>Role.
In your case, this would be !Ref notesFunctionRole.

aws ApiGateway deploy to specific stage

I'm using this configuration to deploy to the 'Prod' Stage:
"ApiGatewayApi":
{
"Type": "AWS::Serverless::Api",
"Properties": {
"StageName": "Prod",
"Name" : "MainGateway",
...
I want to deploy different code to the 'Stage' stage.
I tried to change 'StageName' to "Stage" but I get this error:
"Stage already exists".
How do I deploy different code to different stages?
This solution is based on YAML format same can used in JSON format also.
There is a bug in SAM whenever you creating StageName its creating default Stage along with stage name which you provided like Prod. First you delete your current one then you can applied this changes.
To solve this issue there is two ways by adding OpenApiVersion: '2.0' in your YAML file :
Approach 1: Under properties following to StageName can add this. This properties can be added for AWS::Serverless::Api or other resources like AWS::Serverless::Lambda.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SAM template with a simple API definition
Resources:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: 'V1'
OpenApiVersion: '2.0'
ApiFunction: # Adds a GET api endpoint at "/" to the ApiGatewayApi via an Api event
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: Api
Properties:
Path: /
Method: get
RestApiId:
Ref: ApiGatewayApi
Runtime: python3.7
Handler: index.handler
InlineCode: |
def handler(event, context):
return {'body': 'Hello World!', 'statusCode': 200}
Approach 2: The following to your SAM template at the top level AND be sure you have defined a stage using "StageName" on your AWS::Serverless:Api resource. This will global level if you multiple resource like API or lambda etc.
Globals:
Api:
OpenApiVersion: 3.0.1
Cors: '*'
Resources:
ImplicitApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/member_portal.zip
Handler: index.gethtml
Runtime: nodejs12.x
Events:
GetHtml:
Type: Api
Properties:
Path: /
Method: get
ExplicitApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Note: This solutions works ONLY when one creates API from scratch. If an API was created before, and user adds OpenApiVersion: '2.0' to it, it doesn't remove "Stage" stage. It needs to be added from the beginning.
AWS::Serverless::Api is a very simple implementation and is not capable of managing multi stage under SAM, better use AWS::ApiGateway::RestApi and multiple AWS::ApiGateway::Stage referring to RestApi resource.
Reference :
https://github.com/aws/serverless-application-model/blob/master/tests/translator/input/api_with_open_api_version.yaml#L3
https://github.com/aws/serverless-application-model/issues/191#issuecomment-551051431

Integrating API gateway with SAM template to have custom name and stage

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

Aws-Sam Local Invoke: Layer endpoint not found

I'm trying to set up a local dev environment for my Lambda functions using SAM. I had everything working until I added a reference to a layer in my configuration.
I followed the instructions here: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-layers.html. I added my ARN for my layer version in my template.ymal as follows:
# template.ymal
TestLayerFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: TestLayer
Role: arn:aws:iam::111111111111:role/ReadStreamingTable
CodeUri: src/streaming/test-layer/
Handler: app.handler
Runtime: nodejs8.10
Layers:
- arn:aws:lambda:eu-west-1:111111111111:layer:Global:7
However when running a "sam local invoke" I get the following error:
botocore.exceptions.EndpointConnectionError: Could not connect to the endpoint URL:
"https://lambda.eu-west-1a.amazonaws.com/2018-10-31/layers/arn%3Aaws%3Alambda%3Aeu-west-1%3A111111111111%3Alayer%3AGlobal/versions/7"
The way I've added the layer ARN in the configuration seems to be exactly how they do it in the example so I'm not sure what is causing the error.
I know it's not exactly a solution but can you not have your layer as part of your SAM file?
If you have a look on this article on the AWS site they use both the layer and the lambda function on the same yaml file so you'd end up with something like this:
Resources:
TempConversionFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Layers:
- !Ref TempConversionDepLayer
Events:
HelloWorld:
Type: Api
Properties:
Path: /{conversion}/{value}
Method: get
TempConversionDepLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: sam-app-dependencies
Description: Dependencies for sam app [temp-units-conv]
ContentUri: dependencies/
CompatibleRuntimes:
- nodejs6.10
- nodejs8.10
LicenseInfo: 'MIT'
RetentionPolicy: Retain

Serverless Shared API Gateway Error when deploying to different stages

I'm using serverless version 1.29.2
I have a created an initial cloudformation script that creates an API GateWay REST API that will be used by other services. So Here is the cloudformation script responsible for it.
{
"AWSTemplateFormatVersion":"2010-09-09",
"Description":"API",
"Resources":{
"APIGw":{
"Type":"AWS::ApiGateway::RestApi",
"Properties":{
"Name":"API-GW"
}
}
},
"Outputs":{
"ApiGwRestApiId":{
"Value":{
"Ref":"APIGw"
},
"Export":{
"Name":"apigw-restApiId"
}
},
"eyesApiGwRestApiRootResourceId":{
"Value":{
"Fn::GetAtt":[
"APIGw",
"RootResourceId"
]
},
"Export":{
"Name":"apigw-rootResourceId"
}
}
}
}
Here is serverless.yml for the application I was trying to deploy.
service: template-test-service
provider:
name: aws
runtime: python3.6
region: eu-central-1
stage: ${self:custom.environment.stage}
environment:
stage: ${self:custom.environment.stage}
apiGateway:
restApiId:
'Fn::ImportValue': apigw-restApiId
restApiRootResourceId:
'Fn::ImportValue': apigw-rootResourceId
When I perform an sls deploy --stage dev everything works fine, However when I perform another deploy to sls deploy --stage prod
This error shows up.
Another resource with the same parent already has this name
I've struggled with this one for a week now and the issue is to do with the way API Gateway is constructred out of resources and methods. From the documentation
In Amazon API Gateway, you build a REST API as a collection of programmable entities known as API Gateway resources. For example, you use a RestApi resource to represent an API that can contain a collection of Resource entities. Each Resource entity can in turn have one or more Method resources. Expressed in the request parameters and body, a Method defines the application programming interface for the client to access the exposed Resource and represents an incoming request submitted by the client.
Serverless CLI creates all the resource/methods for you when you have a function trigged by an http event.
functions:
GetScenesInGame:
handler: handler.hello
layers: arn:aws:lambda:eu-west-1:xxxxxxxxx:layer:pynamodb-layer:1
events:
- http:
method: GET
path: api/v1/game/{gameId}/scene
From the example above this creates five resources ( api, v1, game, gameIdParam, scene) and finally adds a GET method on the the final resource.
Unfortunately when you have two seperate stacks (as you might in a microservice setup) if they are any part of the above methods then it errors with Another resource with the same parent already has this name
The solution is highlighted in this article from serverless deploy serverless microservice on aws although its not very explict and easily missed.
Firstly there is a top level cloudformation template which configures the required resources.
For the resource you want to add a serverless microservice to you export the id of the resource as an output variable in your stack.
Then in the serverless.yml file you import the api gateway reference and apigateway resource id.
You can then deploy each service without getting a clash of resource names in the api structure.
The templates below show having a top level set of resources.
api/v1/game/{gameId}/page
api/v1/game/{gameId}/scene
And then attaching PageService to the page resource and SceneService to the scene resource.
api-gateway.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: "S3 template for deploying S3 to be used by ACS s3 connector."
Resources:
TestApiGw:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub 'test-apigw-throwaway'
ApiResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !GetAtt
- TestApiGw
- RootResourceId
PathPart: "api"
VersionResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !Ref ApiResource
PathPart: "v1"
GameResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !Ref VersionResource
PathPart: "game"
GameParamResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !Ref GameResource
PathPart: "{gameId}"
SceneResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !Ref GameParamResource
PathPart: "scene"
PageResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref TestApiGw
ParentId: !Ref GameParamResource
PathPart: "page"
Outputs:
ApiRestApiId:
Value: !Ref TestApiGw
Export:
Name: !Sub ${AWS::StackName}-TestApiId
ApiRootResourceId:
Value:
Fn::GetAtt:
- TestApiGw
- RootResourceId
Export:
Name: !Sub ${AWS::StackName}-ApiRootResourceVar
ApiSceneResourceVar:
Value: !Ref SceneResource
Export:
# variable names are global so this will need more work to make it unique across stages.
Name: !Sub ${AWS::StackName}-ApiSceneResourceVar
ApiPageResourceVar:
Value: !Ref PageResource
Export:
# variable names are global so this will need more work to make it unique across stages.
Name: !Sub ${AWS::StackName}-ApiPageResourceVar
serverless.yml ( Serverless Cli file for Page Service)
service: scrap-page-service
provider:
name: aws
runtime: python2.7
apiGateway:
restApiId:
"Fn::ImportValue": throw-stack-1-TestApiId
restApiRootResourceId:
"Fn::ImportValue": throw-stack-1-ApiPageResourceVar
functions:
hello:
handler: handler.hello
events:
- http:
path: ""
method: get
serverless.yml ( Serverless Cli file for Scene Service)
service: scrap-scene-service
provider:
name: aws
runtime: python2.7
apiGateway:
restApiId:
"Fn::ImportValue": throw-stack-1-TestApiId
restApiRootResourceId:
"Fn::ImportValue": throw-stack-1-ApiSceneResourceVar
functions:
hello:
handler: handler.hello
events:
- http:
path: ""
method: get
Hopefully this helps others getting this issue and if somebody has a better way of doing it I'd be keen to know :-)
Did take a look at Serverless documentation on sharing API Gateway ?
It seems that you have to create common resource path components are CloudFormed objects