Serverless Deployment API Gateway with Lambda Integration - amazon-web-services

I'm attempting to deploy a lambda function with API gateway lambda integration. My api specification is written in openAPI 3 in an external yml file.
I would like to pass the name of the arn of the lambda into the api specification.
My serverless.yml:
service: my-test-service
provider:
name: aws
runtime: java8
functions:
mylambda-test:
handler: com.sample.MyHandler
name: mylambda-test
description: test lambda with api gateway
package:
artifact: myexample-1.0-SNAPSHOT-jar-with-dependencies.jar
individually: true
resources:
Resources:
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: test-api
Body:
${file(api.yml)}
in the api.yml:
openapi: "3.0.1"
info:
title: "test-api"
version: "0.0.1-oas3"
paths:
/test:
get:
*
*
*
x-amazon-apigateway-integration:
uri: {arn of mylambda-test}

How about using endly e2e runner to deploy and setup your API gatweay
the deployment workflow may looks like the following
pipeline:
setupFunction1:
action: aws/lambda:deploy
credentials: $awsCredentials
functionname: $functionName1
runtime: go1.x
handler: loginfo
code:
zipfile: $LoadBinary(${codeZip})
rolename: lambda-loginfo-executor
define:
- policyname: s3-${functionName}-role
policydocument: $Cat('${privilegePolicy}')
attach:
- policyarn: arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
setupFunction2:
action: aws/lambda:deploy
credentials: $awsCredentials
functionname: $functionName2
runtime: go1.x
handler: loginfo
code:
zipfile: $LoadBinary(${codeZip})
rolename: lambda-loginfo-executor
define:
- policyname: s3-${functionName}-role
policydocument: $Cat('${privilegePolicy}')
attach:
- policyarn: arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
setupAPI:
action: aws/apigateway:setupRestAPI
credentials: aws
'#name': loginfoAPI
resources:
- path: /path1
methods:
- httpMethod: ANY
functionname: $functionName1
- path: /path2
methods:
- httpMethod: ANY
functionname: $functionName2
sleepTimeMs: 15000
post:
endpointURL: ${setupAPI.EndpointURL}
Here is example of deployment workflow
You can also check out actual examples implmeneting e2e testing with lambda including API Gateway

Related

How to use api gateway with openAPI to pass json to step function using serverless framework?

How do I invoke an AWS Step Function using an API Gateway POST request using openAPI and then pass the JSON on to a step function, all defined using the serverless framework?
Below is what i've got so far, i've given the correct permissions to the execution role but still i'm receiving a 500 error on postman.
I would also like to be able to pass receive a json payload and then pass it on the step function?
Any pointers appreciated, thanks
Serverless.yml file:
service: webhook-json
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs18.x
openApiIntegration:
autoMock: true
package: true
inputFile: serverless.doc.yml
outputFile: api.yml
resources:
Resources:
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
ApiKeySourceType: HEADER
Body: ~ #autogenerated by plugin
Description: "Some Description"
FailOnWarnings: false
Name: dev-some-name
EndpointConfiguration:
Types:
- REGIONAL
ApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref: ApiGatewayRestApi
StageName: dev
testRole:
Type: AWS::IAM::Role
Properties:
RoleName: testRole
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: allow-execution
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: states:StartExecution
Resource: '*'
stepFunctions:
stateMachines:
hellostepfunc1:
name: testermachine
definition:
Comment: "test machine"
StartAt: StartState
States:
StartState:
Type: Pass
End: true
plugins:
- serverless-step-functions
- serverless-openapi-documentation
- serverless-openapi-integration-helper
OpenAPI schema:
# ./schema.yml
openapi: 3.0.0
info:
description: test
version: 1.0.0
title: testing
paths:
/api:
post:
x-amazon-apigateway-integration:
credentials:
Fn::GetAtt: [ testRole, Arn ]
uri:
Fn::Sub: arn:aws:apigateway:${AWS::Region}:states:action/StartExecution
httpMethod: POST
type: aws
responses:
default:
statusCode: 200
summary: test
responses:
200:
description: Success
I've taken a look at the code and saw some issues.
You are missing a path to the uri field in x-amazon-apigateway-integration.
Also missing request mapping template in the OpenAPI schema, I believe you need this to map JSON payload from POST request to input for the step function.
Something like this
requestTemplates:
application/json: |
{
"input": "$input.json('$')"
}
responses:
default:
statusCode: 200
summary: test
requestBody:
required: true
content:
application/json:
schema:
type: object

How to get reference to aws api gateway in serverless framework

provider:
name: aws
runtime: nodejs14.x
functions:
hello:
handler: handler.hello
events:
- httpApi:
path: /
method: get
RestApiId:
Ref: TestApi // How to get reference of AWS::Serverless::Api i.e. TestApi here
resources:
Resources:
authFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: handler.hello
TestApi:
DependsOn: AuthFunction
Type: AWS::Serverless::Api
Properties:
StageName: dev
Auth:
DefaultAuthorizer: LambdaRequestAuthorizer
Authorizers:
LambdaRequestAuthorizer:
FunctionPayloadType: REQUEST
FunctionArn: !GetAtt AuthFunction.Arn
Getting error:
Configuration error at 'functions.hello': unrecognized property 'RestApiId'
Let's first clarify a few things.
The httpApi event is using HTTP API, not REST API from AWS Api Gateway.
You can set externally created HTTP API by specifying it in the following way:
provider:
httpApi:
id: <your http api reference>
If you'd like to use REST API, then you would need to use http event type and set it like this:
provider:
apiGateway:
restApiId: <your rest api reference>

When using HttpApi via SAM is the OpenApi definition the single point of truth?

I've spent the day getting up to speed with SAM and in particular the HttpApi, but have hit a brick wall. Here's what I want to achieve (the dream):
Create a REST API in Open API 3 (the contract)
Pass the api.yaml to HttpApi and have it generate all the boilerplate for the api
Feel a warm glow now that my api docs and my api routes are always the same (as per the contract)
Hand the Open API spec to the frontend guys and the backend guys and have them all nicely meet in the middle
I'm pretty sure that's how it's supposed to work anyway. But here's what is confusing me. I'm adding the integrations to my api.yaml e.g.:
get:
summary: get item
tags:
- Items
responses:
'200':
description: Item returned successfully
content:
application/json:
schema:
$ref: '#/components/schemas/ItemResource'
'404':
$ref: '#/components/responses/NotFound'
x-amazon-apigateway-integration:
payloadFormatVersion: "2.0"
type: "aws_proxy"
httpMethod: "POST"
uri:
Fn::GetAtt [GetItemFunction, Arn]
connectionType: "INTERNET"
Now I was expecting that with this HttpApi would connect the routes in my OpenApi file with the Lambda I'm pointing to in the integration "GetItemFunction". So I could just define my Lambda like so:
GetItemFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: get-item.handler
Runtime: nodejs12.x
But that doesn't seem to set the routes as expected (I'm testing with "sam local start-api"). Instead to make it work I need to do this:
GetItemFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: get-item.handler
Runtime: nodejs12.x
Events:
GetAllItems:
Type: HttpApi
Properties:
ApiId: !Ref MyHttpApi
Path: /items/{id}
Method: get
This works, but now I'm defining the routes in the template.yaml file and not the api.yaml file which for my use case defeats the purpose. It also seems to behave in exactly the same way regardless of whether I add the 'x-amazon-apigateway-integration' in the api.yaml or not.
I'm sure I'm missing something here, if someone could set me on the right path it would be greatly appreciated!
When you declare the connection in SAM using the eventsource, then SAM creates the permissions. If you do not use an eventsoursce then SAM does not build the permissions and API gateway does not have rights to invoke the Lambda function. If you want to manually build it then create a role in the SAM template. here is a working example (simplified to one template):
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: OpenAPI test
Resources:
MyHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
DefinitionBody:
openapi: "3.0.1"
info:
title: "OpenAPI test"
paths:
/:
get:
responses:
default:
description: Item returned successfully
x-amazon-apigateway-integration:
payloadFormatVersion: "2.0"
credentials: !GetAtt MyHttpApiRole.Arn
type: "aws_proxy"
httpMethod: "POST"
uri: !GetAtt GetItemFunction.Arn
connectionType: "INTERNET"
x-amazon-apigateway-importexport-version: "1.0"
MyHttpApiRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
Policies:
- PolicyName: ApiDirectWriteToSQS
PolicyDocument:
Version: '2012-10-17'
Statement:
Action:
- lambda:InvokeFunction
Effect: Allow
Resource:
- !GetAtt GetItemFunction.Arn
GetItemFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Runtime: nodejs12.x

Permissions for Lambda invocation conflicts with API Gateway permissions

I've got an app deployed to Lambda and I'm using API Gateway to forward HTTP requests over to the app.
My problem is that it looks like API Gateway isn't forwarding requests that aren't to the base URL of the app.
In other words, the code inside my Lambda handler gets executed when there's an HTTP request to https://blabblahblah.execute-api.us-west-2.amazonaws.com/Prod/. However, an HTTP request to https://blabblahblah.execute-api.us-west-2.amazonaws.com/Prod/pong throws a 500 and no code gets executed. I've got some logging statements inside my handler and they don't log anything for non-base-URL requests.
I've narrowed the problem down to a permissions issue.
I have 2 sets of permission that aren't working well together.
I need to allow the API Gateway to invoke my lambda function
The lambda function needs to be able to call itself.
I thought I had both these permissions working correctly but any HTTP requests that aren't aimed at the base URL throw a 500 in the API Gateway (i.e. I see no Cloudwatch log entries for the request but the response is 500).
I think that means that there must be some error in my SAM template.
Resources:
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: 2012-10-17
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda.handler
Role: !GetAtt IAMRole.Arn
Runtime: ruby2.5
CodeUri: "./src/"
MemorySize: 512
Timeout: 30
Events:
MyAppApi:
Type: Api
Properties:
Path: /
Method: ANY
RestApiId: !Ref MyAppAPI
MyAppAPI:
Type: AWS::Serverless::Api
Properties:
Name: MyAppAPI
StageName: Prod
DefinitionBody:
swagger: '2.0'
basePath: '/'
info:
title: !Ref AWS::StackName
paths:
/{proxy+}:
x-amazon-apigateway-any-method:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations'
passthroughBehavior: "when_no_match"
httpMethod: POST
type: "aws_proxy"
/:
post:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations'
passthroughBehavior: "when_no_match"
httpMethod: POST
type: "aws_proxy"
ConfigLambdaPermission:
Type: "AWS::Lambda::Permission"
DependsOn:
- MyFunction
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref MyFunction
Principal: apigateway.amazonaws.com
ConfigLambdaPermission:
Type: "AWS::Lambda::Permission"
DependsOn:
- MyFunction
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref MyFunction
Principal: !GetAtt IAMRole.Arn
Outputs:
MyFunction:
Description: Lambda Function for interacting with Slack
Value:
Fn::GetAtt:
- MyFunction
- Arn
MyAppAppUrl:
Description: App endpoint URL
Value: !Sub "https://${MyAppAPI}.execute-api.${AWS::Region}.amazonaws.com/"
Any idea how I can get things working right?
Yes, you need to add the other resource as well as a event trigger. And you lambda permissions have no use in the above template. Here's how it worked for me.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: 2012-10-17
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: roleTest.roleTest
Role: !GetAtt IAMRole.Arn
Runtime: nodejs8.10
CodeUri: s3://apicfn/roleTest.zip
MemorySize: 512
Timeout: 30
Events:
MyAppApi1:
Type: Api
Properties:
Path: /
Method: ANY
MyAppApi2:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
Outputs:
MyFunction:
Description: Lambda Function for interacting with Slack
Value:
Fn::GetAtt:
- MyFunction
- Arn
PS: do not even need the Serverless::Api section for this template.

How set Proxy on AWS API Gateway using Cloudformation

I have a lambda function that will handle PUT and GET requests using Amazon API Gateway {proxy+}.
It is working correctly when all the settings are set manually by the Amazon Console. but I want to automate it using AWS Cloudformation.
To inform you, I will write steps to set {proxy+}:
1) create a simple Lambda function and paste this lines of code inside it:
import boto3
def lambda_handler(event, context):
return {
"statusCode": 200,
"headers": {
"Content-Type": 'text/html',
"Access-Control-Allow-Origin": "*"
},
"body": "Hello Reza Amya, Your Lambda is working..!"
}
2) goto Amazon API Gateway and click on Create API.
3) choose New API, fill API name, select Edge optimized from the list for Endpoint Type then click on Create API
4) then your API is created and you should be on it's Resources page, if you are not, go to the Resources page for the created API.
5) from Actions select Create Resource
6) Select Configure as proxy resource (then it should change other fields automatically, if it doesn't, type proxy for Resource Name and {proxy+} for Resource Path) then click on Create Resource
7) Select Lambda Function Proxy for Integration type and select your lambda function from Lambda Function and click on Save
8) on the Add Permission to Lambda Function popup, click on Ok
9) from Actions click on Deploy API
10) Select New Stage from the list for Deployment stage then type a name for Stage name (for me, I have typed 'api') and click on Deploy
11) on the stage on the root page for your deployed API, you can see Invoke URL. click on it, and it will open new tab linked to somewhere like this: https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/
12) add a simple segment to end of your URL like this:
https://xxxxxxxxx.execute-api.us-east-1.amazonaws.com/api/test
now you should see bellow message in your browser page:
Hello Reza Amya, Your Lambda is working..!
Now the problem is I have written all these steps inside a Yaml file:
AWSTemplateFormatVersion: 2010-09-09
Description: My Lambda Function
Parameters:
S3Bucket:
Description: S3 Bucket where the Lambda code is
Type: String
S3Key:
Description: S3 Key where the Lambda code is
Type: String
S3ObjectVersion:
Description: Version of the S3 Key to use
Type: String
Resources:
apiGateway:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: "my-api"
Description: "My API"
EndpointConfiguration:
Types:
- EDGE
Resource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId:
Ref: "apiGateway"
ParentId:
Fn::GetAtt:
- "apiGateway"
- "RootResourceId"
PathPart: "{proxy+}"
ProxyMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: ANY
ResourceId: !Ref Resource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
RequestParameters:
method.request.path.proxy: true
Integration:
CacheKeyParameters:
- 'method.request.path.proxy'
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
Type: AWS_PROXY
IntegrationHttpMethod: ANY
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Arn}/invocations
- Arn:
Fn::GetAtt:
- LambdaFunction
- Arn
PassthroughBehavior: WHEN_NO_MATCH
IntegrationResponses:
- StatusCode: 200
apiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
DependsOn:
- "ProxyMethod"
Properties:
RestApiId: !Ref "apiGateway"
StageName: "dev"
IAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: Logging
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: 'arn:aws:logs:*:*:*'
- PolicyName: AccessToDynamoDB
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'dynamodb:CreateTable'
- 'dynamodb:DeleteItem'
- 'dynamodb:DeleteTable'
- 'dynamodb:GetItem'
- 'dynamodb:GetRecords'
- 'dynamodb:UpdateItem'
- 'dynamodb:UpdateTable'
- 'dynamodb:PutItem'
- 'dynamodb:UpdateTable'
Resource: 'arn:aws:dynamodb:*:*:*'
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: {Ref: S3Bucket}
S3Key: {Ref: S3Key}
S3ObjectVersion: {Ref: S3ObjectVersion}
Handler: main.lambda_handler
MemorySize: 128
Role: {'Fn::GetAtt': [IAMRole, Arn]}
Runtime: python3.6
Timeout: 300
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt
- LambdaFunction
- Arn
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Sub arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/*
Outputs:
apiGatewayInvokeURL:
Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGateway}"
lambdaArn:
Value: !GetAtt "LambdaFunction.Arn"
The above Yaml file will create the Lambda function and will deploy the API, but it will show bellow error when I am trying to test the API:
{"message": "Internal server error"}
Can you please guide me what is wrong and how I can solve the problem?
The issue is related to you IntegrationHttpMethod setting. Although your APIGateway method is ANY, the IntegrationHttpMethod must always be POST for AWS Lambda.
This would lead to the following method declaration.
ProxyMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
HttpMethod: ANY
ResourceId: !Ref Resource
RestApiId: !Ref apiGateway
AuthorizationType: NONE
RequestParameters:
method.request.path.proxy: true
Integration:
CacheKeyParameters:
- 'method.request.path.proxy'
RequestParameters:
integration.request.path.proxy: 'method.request.path.proxy'
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Arn}/invocations
- Arn:
Fn::GetAtt:
- LambdaFunction
- Arn
PassthroughBehavior: WHEN_NO_MATCH
IntegrationResponses:
- StatusCode: 200