I'm encountering an issue on SAM Local v0.2.6 that doesn't occur when deploying the SAM template to AWS.
Given the following template.yml:
---
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Parameters:
Resources:
Function:
Type: 'AWS::Serverless::Function'
Properties:
Handler: function.handler
Runtime: python3.6
CodeUri: './src'
Events:
RootRequest:
Type: Api
Properties:
Path: /
Method: ANY
RestApiId: !Ref FunctionApi
FunctionApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionBody:
swagger: "2.0"
info:
title: MyFunctionApi
paths:
/:
x-amazon-apigateway-any-method:
responses: {}
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri:
Fn::Sub:
- arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/${FunctionArn}/invocations
- { FunctionArn: !GetAtt Function.Arn }
The output from sam local start-api includes the following:
WARNING: Could not find function for [OPTIONS] to / resource
WARNING: Could not find function for [GET] to / resource
WARNING: Could not find function for [HEAD] to / resource
WARNING: Could not find function for [POST] to / resource
WARNING: Could not find function for [PUT] to / resource
WARNING: Could not find function for [DELETE] to / resource
WARNING: Could not find function for [PATCH] to / resource
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [OPTIONS GET HEAD POST PUT DELETE PATCH]
The response code from curl http://localhost:3000 is 502 Bad Gateway and the body is:
{ "message": "No function defined for resource method" }
I should add that the above works fine when deployed to a real Cloud Formation stack. It just behaves differently in SAM Local.
My current workaround to this issue is to add an API event to the function for each HTTP method, i.e.
Events:
RootRequestPost:
Type: Api
Properties:
Path: /
Method: POST
RestApiId: !Ref FunctionApi
RootRequestGet:
Type: Api
Properties:
Path: /
Method: GET
RestApiId: !Ref FunctionApi
RootRequestOptions:
Type: Api
Properties:
Path: /
Method: OPTIONS
RestApiId: !Ref FunctionApi
RootRequestHead:
Type: Api
Properties:
Path: /
Method: HEAD
RestApiId: !Ref FunctionApi
RootRequestDelete:
Type: Api
Properties:
Path: /
Method: DELETE
RestApiId: !Ref FunctionApi
RootRequestPatch:
Type: Api
Properties:
Path: /
Method: PATCH
RestApiId: !Ref FunctionApi
RootRequestPut:
Type: Api
Properties:
Path: /
Method: PUT
RestApiId: !Ref FunctionApi
Then the output of sam local start-api includes:
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [OPTIONS]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [GET]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [HEAD]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [POST]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [PUT]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [DELETE]
Mounting function.handler (python3.6) at http://127.0.0.1:3000/ [PATCH]
I can then curl the API with no problems.
Having looked at the SAM Local code, I can see that when a Swagger definition is parsed, the x-amazon-apigateway-any-method operation results in separate HTTP methods (GET, POST, etc) being added to the NewServerlessRouter. Conversely, when an API function event with an ANY method is parsed, only a single ANY method is added to NewServerlessRouter. The subsequent merge operation to consolidate mounts and handlers therefore fails to map the multiple Swagger methods to the single function method handler.
Could this be fixed by changing the merge logic in NewServerlessRouter to be more intelligent about which mounts should be merged when an 'ANY' mount is encountered?
An additional complication - the workaround mentioned above only works locally. Deploying it, Cloud Formation creates permissions in the Lambda function policy which are specific to each HTTP verb, e.g.:
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:invokeFunction",
"Resource": "arn:aws:lambda:us-east-1:**********:function:MyFunction
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:**********:**********/MyStage/GET/"
}
}
}
Since an ANY method is still created in API Gateway and that method handles all requests, API requests fail due to API Gateway not having the right permissions to invoke the Lambda function. An additional permission is therefore required on the function policy, e.g.:
{
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "lambda:invokeFunction",
"Resource": "arn:aws:lambda:us-east-1:**********:function:MyFunction
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:us-east-1:**********:**********/MyStage/ANY/"
}
}
}
Currently, to ensure local and cloud can both work from a single template, I need to define a function API event per HTTP method and one for the ANY method, in other words:
Events:
RootRequestPost:
Type: Api
Properties:
Path: /
Method: POST
RestApiId: !Ref FunctionApi
RootRequestGet:
Type: Api
Properties:
Path: /
Method: GET
RestApiId: !Ref FunctionApi
RootRequestOptions:
Type: Api
Properties:
Path: /
Method: OPTIONS
RestApiId: !Ref FunctionApi
RootRequestHead:
Type: Api
Properties:
Path: /
Method: HEAD
RestApiId: !Ref FunctionApi
RootRequestDelete:
Type: Api
Properties:
Path: /
Method: DELETE
RestApiId: !Ref FunctionApi
RootRequestPatch:
Type: Api
Properties:
Path: /
Method: PATCH
RestApiId: !Ref FunctionApi
RootRequestPut:
Type: Api
Properties:
Path: /
Method: PUT
RestApiId: !Ref FunctionApi
RootRequestAny:
Type: Api
Properties:
Path: /
Method: ANY
RestApiId: !Ref FunctionApi
This is pretty ugly.
Hi,
Any updates on this?
Thanks!
+1 ... also having this exact same issue on 0.2.11.
I also got exactly the same problem.
@StefanSmith This was corrected in the 0.3.0 release. Closing since this has been addressed.
We even have integ tests for it: https://github.com/awslabs/aws-sam-cli/blob/develop/tests/integration/local/start_api/test_start_api.py#L180 :)
This is now working for me as expected @jfuss.
Thanks!
thanks @StefanSmith for documenting this issue. Searched high and low for how to set up the ANYmethod assigned to the /{proxy+} path pattern and your documentation of x-amazon-apigateway-any-method helped me.
I added the following to my SAM API resource
/{proxy+}:
x-amazon-apigateway-any-method:
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaResource.Arn}/invocations
passthroughBehavior: "when_no_match"
responses: {}
and added the following to my SAM Lambda Resource
AnyMethodForProxy:
Type: Api
Properties:
Path: /{proxy+}
Method: any
RestApiId: !Ref MyApiResource
Most helpful comment
Hi,
Any updates on this?
Thanks!