SAM Template: multiple microservice lambdas sharing a single AWS::ApiGateway::DomainName - aws-lambda

My goal is allow several independent lambda resources (microservices) to share a common custom domain for test environment. Using the below template, deploying microservice #1 went off without issue. However, when deploying microservice #2, CloudFormation fails and rolls back because the domain name is assigned to microservice #1.
Surely it must be possible to share a common custom domain among several microservices with unique paths?
Service #1 template.yaml
Resources:
ApiGatewayApi:
Type: AWS::Serverless::Api
Properties:
StageName: !Sub ${apiGatewayStageName}
DefinitionBody:
swagger: "2.0"
info:
title: !Sub ${functionName}
paths:
"/service_one":
post:
responses: {}
x-amazon-apigateway-integration:
uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${functionName}:live/invocations"
httpMethod: POST
type: aws_proxy
ApiGatewayCustomDomainName:
Type: AWS::ApiGateway::DomainName
Properties:
CertificateArn: !Sub "arn:aws:acm:${AWS::Region}:${AWS::AccountId}:certificate/${apiGatewayCDNCertificateId}"
DomainName: !Sub "${envName}.${apiGatewayCustomDomainNameSuffix}"
DependsOn:
- "LogsSubscriptionFilter"
- "ApiGatewayApi"
ApiGatewayBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Ref ApiGatewayCustomDomainName
RestApiId: !Ref ApiGatewayApi
Stage: !Ref apiGatewayStageName
DependsOn: "ApiGatewayCustomDomainName"
The Service #2 template is the same, except with a different path (.e.g. /service_two.

Worked with AWS support to get to the bottom of this one. In its current form (May 2018), if you want separate SAM templates for each lambda for deployment reasons, but have a shared API Gateway resource for those lambdas, then you must have a separate dedicated API Gateway SAM template. Additionally, the Events parameter in AWS::Serverless::Function must be excluded in favor of paths definitions in AWS::Serverless::Api.
Lastly, the AWS::Serverless::Function(s) must be exported under Output and then imported by the AWS::Serverless::Api resource.

Related

How to debug and run multiple lambdas locally

I would like to build .NET HTTP API using aws lambdas. These lambdas will be called by UI and some other systems via api gateway. Obviously in local environment I would like to run/debug these.
What I have tried:
a) Using the mock tool that comes with AWS Visual Studio templates. You can call individual lambdas but I couldn't figure out how I can call them from e.g. postman using normal rest calls. I don't know how mock tool makes those calls as chrome/firefox doesn't show them.
b) Using sam local start-api. Here is what I did:
sam --version
SAM CLI, version 1.22.0
sam init (choose aws quick start template, package type Image and amazon/dotnet5.0-base as base image)
I can build the solution with sam build, run it wit sam local start-api and I can browse to http://localhost:3000/hello and it works. Problem is that I would need to do build in VS + do those steps every time I change code. Also no easy way to attach debugger.
So what is the recommended way to do this? I know you can run whole .NET web api inside lambda but that doesn't sound like a good technical solution. I am assuming I am not the first person building HTTP api using lambdas.
It might be worth considering running a lambda-like environment in Docker.
While including the dotnet tools you need might not be feasable in actual Lambda, It might be feasible to either include them in a Docker image, or bind mounted to a docker container. These images from lambci can help with that: https://hub.docker.com/r/lambci/lambda/
You can use sam local
https://github.com/thoeni/aws-sam-local
Create API with API gateway example
Resources:
ApiGatewayToLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: ['apigateway.amazonaws.com']
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaRole
- arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: test
EndpointConfiguration: REGIONAL
DefinitionBody:
swagger: "2.0"
info:
title: "TestAPI"
description: TestAPI description in Markdown.
paths:
/create:
post:
x-amazon-apigateway-integration:
uri:
!Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations
credentials: !GetAtt ApiGatewayToLambdaRole.Arn
responses: {}
httpMethod: POST
type: aws
x-amazon-apigateway-request-validators:
Validate query string parameters and headers:
validateRequestParameters: true
validateRequestBody: false
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: '2012-10-17'
Path: /
Policies:
- PolicyName: CodeBuildAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- logs:*
- lambda:*
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Effect: Allow
Resource: "*"
Version: '2012-10-17'
MyLambda:
Type: AWS::Serverless::Function
Properties:
Role: !GetAtt LambdaRole.Arn
Handler: myfunctionname.lambda_handler
CodeUri: ./src/myfunctionname
Events:
SCAPIGateway:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /create
Method: POST
...
Build :
Time sam build --use-container --template backend/template.yam
Invoke Lambda Locally:
The command to invoke Lambda locally is sam local invoke and -e flag is used to specify the path to the Lambda event.
$ sam local invoke -e event.json
When it is run, it will look something like this:
$ sam local invoke MyLambda -e event.json
2021-04-20 11:11:09 Invoking index.handler
2021-04-20 11:11:09 Found credentials in shared credentials file:
~/.aws/credentials
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-invoke.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-start-api.html
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-using-debugging.html
https://github.com/ashiina/lambda-local

Multiple microservices inside same AWS API Gateway

I'm developing a series of microservices which need to share the same AWS API Gateway. Here's my structure:
/
/assessments
/skills
/work-values
/graphql
/skills, /work-values, and /graphql are 3 different microservices I'm trying to register with the same AWS API Gateway. The problem I'm having is getting the serverless.yaml files for /skills, /work-values routes to nest under 'assessments'. There is no functionality for /assessments in-and-of-itself. It exists just so we can organize all of our assessments under the same URL path structure.
Here's my serverless.yaml file for `/work-values':
service:
name: assessments-workvalues
...
custom:
stage: ${opt:stage, self:provider.stage}
provider:
...
apiGateway:
restApiId:
# THE FOLLOWING REFERENCES A VARIABLE FROM MY API GATEWAY ROOT
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
restApiRootResourceId:
'Fn::ImportValue': # HOW DO I GET THE PROPER VALUE HERE TO MAP TO `/assessments`?
...
functions:
...
Here's my serverless.yaml file for `/assessments':
service:
name: assessments
custom:
stage: ${opt:stage, self:provider.stage}
provider:
...
apiGateway:
restApiId:
# THE FOLLOWING REFERENCES A VARIABLE FROM MY API GATEWAY ROOT
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiId
restApiRootResourceId:
'Fn::ImportValue': ${self:custom.stage}-ApiGatewayRestApiRootResourceId
functions:
...
resources:
Outputs:
ApiGatewayRestApiId:
Value:
Ref: ApiGatewayRestApi
Export:
Name: ${self:custom.stage}-Assessments-ApiGatewayRestApiId
ApiGatewayRestApiRootResourceId:
Value:
Fn::GetAtt:
- ApiGatewayRestApi
- RootResourceId
Export:
Name: ${self:custom.stage}-Assessments-ApiGatewayRestApiRootResourceId
The problem seems to be coding the Outputs in serverless.yaml file for assessments route. When I run serverless deploy, I get this error message:
Error: The CloudFormation template is invalid: Unresolved resource dependencies [ApiGatewayRestApi] in the Outputs block of the template
At the end of Share an API Endpoint Between Services article, the author mentions 'You HAVE TO import /billing from the billing-api, so the new service will only need to create the /billing/xyz part.' (which seems to be the situation I'm in). But, the author does not explain how to import /billing. Or in my case, how do I import /assessments into the serverless.yaml files for each assessment microservice?
After further research, I found this link:
Splitting Your Serverless Framework API on AWS
I ended up reworking my original approach following what's in the article above. The piece I was missing was having a root or base serverless file which is used to create your routing in AWS API Gateway and expose those placeholders as output which your subsequent child serverless files consume as input for wiring up your child lambda functions to routes under the API Gateway umbrella.

AWS SAM : Creating/Exporting API Gateway resource and importing it in another stack

I am trying to use SAM to automate my deployment on AWS.
In the first SAM template, I want to create an API Gateway, Cognito authoriser and a lambda function which uses this API gateway.
I am trying to export this API gateway resource, from the first template.
I want to use this API gateway resource in another SAM template, using import statement.
Is there any good example which explains exporting API gateway resource from the first SAM template and then using it in the second SAM template.
Try this (not tested):
From here Fn::ImportValue
Stack A Export
Outputs:
ServerlessRestApi:
Value: !Ref MyApiGatewayResource
Export:
Name: !Sub "${AWS::StackName}-ServerlessRestApi"
Stack B Import
Import to a Lambda Function:
MyLambda:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-MyLambda"
Handler: ....
CodeUri: .....
Events:
MyApi:
Type: Api
Properties:
Path: /example
Method: post
RestApiId:
Fn::ImportValue:
!Sub "${StackANameParameter}-ServerlessRestApi"
Hope this helps

serverless-offline: remote Lambda custom authorizer for API Gateway

I have declared an HTTP Proxy method on my API Gateway resource using CloudFormation syntax (detailed in this earlier post.)
I am trying to attach a custom authorizer, of type "Request", which uses an existing AWS Lambda function. The serverless docs have a decent example of setting up custom authorizers using CloudFormation. Meanwhile, serverless-offline clearly supports (source) request-scoped custom authorizers.
I have closely cross-referenced the relevant AWS CloudFormation documentation for AWS::ApiGateway::Resource and AWS::ApiGateway::Authorizer, together with the related serverless docs, in creating the below serverless template. But so far, I am not getting the expected output or behavior from serverless-offline.
Resources are created without authorizers
When I boot up the below serverless configuration, no authorizers are loaded for my resource. I don't see what is wrong or missing in my serverless.yml file. Do you have any tips? Thanks in advance!
➜ serverless-offline-attempt git:(master) ✗ npm start
> # start /Users/freen/src/apig/serverless-offline-attempt
> ./node_modules/serverless/bin/serverless offline
Serverless: Starting Offline: dev/us-east-1.
Serverless: Routes defined in resources:
Serverless: ANY /upstream/{proxy*} -> http://upstream.company.cool/{proxy}
Serverless: Offline listening on http://localhost:3000
serverless.yml
The below template file includes the APIG resources and the authorizer configuration.
service: company-apig
provider:
name: aws
stage: dev
runtime: python2.7
plugins:
- serverless-offline
custom:
serverless-offline:
resourceRoutes: true
resources:
Resources:
# Parent APIG RestApi
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: company-apig
Description: 'The main entry point of the APIG'
# Shared Authorizers
AuthorizeCompanyJWTAccessToken:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: AuthorizeCompanyJWTAccessToken
Type: REQUEST
RestApiId:
Ref: ApiGatewayRestApi
AuthorizerUri:
Fn::Join:
- ""
-
- "arn:aws:apigateway:"
- Ref: "AWS::Region"
- ":lambda:path/2015-03-31/functions/"
- "arn:aws:lambda:us-east-1:123456789012:function:jwt-tokens-staging-AccessTokenAuthorizer"
- "/invocations"
# Resource /upstream
UpstreamResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt:
- ApiGatewayRestApi
- RootResourceId
PathPart: 'upstream'
RestApiId:
Ref: ApiGatewayRestApi
# Resource /upstream/{proxy+}
UpstreamProxyPath:
Type: AWS::ApiGateway::Resource
Properties:
ParentId:
Ref: UpstreamResource
PathPart: '{proxy+}'
RestApiId:
Ref: ApiGatewayRestApi
# Method ANY /upstream/{proxy+}
UpstreamProxyAnyMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: CUSTOM
AuthorizerId:
Ref: AuthorizeCompanyJWTAccessToken
HttpMethod: ANY
Integration:
IntegrationHttpMethod: ANY
Type: HTTP_PROXY
Uri: http://upstream.company.cool/{proxy}
PassthroughBehavior: WHEN_NO_MATCH
MethodResponses:
- StatusCode: 200
ResourceId:
Ref: UpstreamProxyPath
RestApiId:
Ref: ApiGatewayRestApi
Related:
Shared Lambda authorizer setup in Serverless Framework (only loosely; the OP seeks a CloudFormation solution, but the answer does not use CF)

How can I change the name of the API stage in a SAM template?

I'm using SAM to deploy a Lambda function and make it callable over HTTP with via API Gateway using roughly this template snipplet:
MyFunction:
Type: AWS::Serverless::Function
Properties:
…
Events:
MyApi:
Type: Api
Properties:
Path: /
Method: any
This works, but it creates an API stage called "Prod", which has to be used as a prefix for all URLs. I don't want my URLs to be "https://something/Prod/foo", I want them to be "https://something/v1/foo", i.e. something that I choose.
How do I change the name of the stage?
I have tried declaring the API as a separate resource, and used the StageName property to set the name of the stage, however, that requires me to also set DefinitionBody, and that seems to be a deep rabbit hole.
MyFunction:
Type: AWS::Serverless::Function
Properties:
…
Events:
MyApi:
Type: Api
Properties:
Path: /
Method: any
RestApiId: !Ref MyApi
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: v1
DefinitionBody:
???
I know that ??? in the example above is supposed to be Swagger, but I would prefer to not have to write anything there, the template is getting verbose enough at is. Since I don't have to write this part if I can just live with the stage name "Prod" it seems to me that there must be a way to avoid having to write anything there and just set the stage name.
How can I change the name of the stage without having to write a lot of template code and/or Swagger?
SAM Version 1.7.0 removed the need to specify a DefinitionBody or DefinitionUri so you should now be able to do exactly what you mention on your second example without having to include a DefinitionBody:
MyFunction:
Type: AWS::Serverless::Function
Properties:
…
Events:
MyApi:
Type: Api
Properties:
Path: /
Method: any
RestApiId: !Ref MyApi
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: v1

Resources