I'm using Ansible to run few test cases against our web services. Following is my playbook:
- hosts: localhost
connection: local
tasks:
- name: Testing the API...
uri:
url: https://api.example.com/v1/
method: GET
user: username
password: password
status_code: 200
return_content: yes
force_basic_auth: yes
register: results
- debug: var=results
Above playbook works fine and returns the following output:
ok: [localhost] => {
"results": {
"access_control_allow_origin": "*",
"cache_control": "max-age=864000, private",
"changed": false,
"connection": "close",
"content": "\r\n{\"id\":1,\"deleted\":false,\"first-name\":\"xxx\",\"last-name\":\"xxx\",\"name\":\"xxx\",\"title\":\"xxx\",\"background\":\"\",\"company-id\":xx,\"company-name\":\"example\",\"company-type-id\":2,\"company-type-name\":\"Customer\",\"email-address-work\":\"kk#example.info\",\"email-address-personal\":\"\",\"email-address-alternate\":\"\",\"email-address-other\":\"\",\"phone-number-work\":\1234567890\",\"phone-number-home\":\"\",\"phone-number-mobile\":\"252654789\",\"phone-number-alternate\":\"256471235\",\"business-street\":\"526574, usa\",\"business-city\":\"San Antonio\",\"business-state\":\"TX\",\"business-zip\":\"1234607\",\"business-country\":\"United States\",\"home-street\":\"\",\"home-city\":\"\",\"home-state\":\"\",\"home-zip\":\"\",\"home-country\":\"\",\"created-time\":\"2015-11-03T20:56:33.000Z\",\"last-modified-time\":\"2017-11-21T06:27:55.000Z\",\"tags\":[]}",
"content_length": "857",
"content_type": "application/json",
"date": "Tue, 21 Nov 2017 09:59:34 GMT",
"expires": "Fri, 01 Dec 2017 09:59:34 GMT",
Now, I want to run another task if there is any data outside the flower brackets of the 'content' section. In the above output, it starts with 'content": "\r\n{\"id\":1,\"deleted...'. Any idea how I can achieve this?
This condition should suffice:
when: "(results.content | regex_replace('{.*}', '')) != ''"
Just check if the string is empty after removing everything between { and }.
Related
I want to use Ansible to backup license files from Swichten. But I have a problem. The idea is to write the output into a variable with ls *.lic. With this variable I want to copy a copy from the switch via tftp server to a computer. Everything should run fully automated so I try to do the same. I post below the playbook and the output. Then it will probably be clearer. Many thanks in advance.
I want to be clarify that i only want the output: (D3456234.lic) and not (
[
"D3456234.lic.lic"
]
as you can see below in the logs.
Playbook:
- name: copy license in home dir
os10_command:
commands:
- system "cp /mnt/license/*.lic /home/admin"
- name: create var
os10_command:
commands:
- "system ls"
register: licensevar
- debug:
var: licensevar
- name: backup license
os10_command:
commands:
- copy home://{{ licensevar.stdout }} tftp://10.x.x.xx/Sicherung/lizenz/{{
licensevar.stdout }}
-vvv ansible playbook run:
TASK [debug] *******************************************************************
ok: [hostname] => {
"licensevar.stdout": [
"DTH67C3.lic"
]
}
redirecting (type: action) dellemc.os10.os10_command to dellemc.os10.os10
TASK [backup license] **********************************************************
<10.0.0.81> ANSIBLE_NETWORK_IMPORT_MODULES: Result: {'changed': False, 'stdout': ["copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.li\x1bEc']\nFailed parsing URI filename"], 'stdout_lines': [["copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.li\x1bEc']", 'Failed parsing URI filename']], 'invocation': {'module_args': {'commands': ["copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.lic']"], 'match': 'all', 'retries': 10, 'interval': 1, 'wait_for': None, 'provider': None}}, '_ansible_parsed': True}
ok: [hostname] => {
"changed": false,
"invocation": {
"module_args": {
"commands": [
"copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.lic']"
],
"interval": 1,
"match": "all",
"provider": null,
"retries": 10,
"wait_for": null
}
},
"stdout": [
"copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.li\u001bEc']\nFailed parsing URI filename"
],
"stdout_lines": [
[
"copy home://['D3456234.lic'] tftp://10.0.0.43/Sicherung/lizenz/['D3456234.li\u001bEc']",
"Failed parsing URI filename"
]
]
}
META: ran handlers
META: ran handlers
I think it can be solve with regex.
I am migrating a Ruby 2.7 Lambda from zip file deployment to container image deployment.
When I deploy my container to AWS Lambda, the container behaves as I hope that it would.
When I attempt to test the same image locally using docker run {my-image-name}, I am encountering 2 issues
The parameters are not accessible from the event object in the same manner
The return headers and status code are not honored in the same way as they are handled from Lambda
My Questions
Do I need to bundle something else into my dockerfile to assist with Lambda simulation?
Do I need to use a different entrypoint in my dockerfile?
I found documentation for the "AWS Lambda Runtime Interface Emulator", but I am unclear if it is designed to assist with the problems I am encountering.
Here is a simplified version of what I am attempting to test.
Dockerfile
FROM public.ecr.aws/lambda/ruby:2.7
COPY * ./
RUN bundle install
CMD [ "lambda_function.LambdaFunctions::Handler.process" ]
Gemfile
source "https://rubygems.org"
lambda_function.rb
require 'json'
module LambdaFunctions
class Handler
def self.process(event:,context:)
begin
json = {
message: 'This is a placeholder for your lambda code',
event: event
}.to_json
{
headers: {
'Access-Control-Allow-Origin': '*'
},
statusCode: 200,
body: json
}
rescue => e
{
headers: {
'Access-Control-Allow-Origin': '*'
},
statusCode: 500,
body: { error: e.message }.to_json
}
end
end
end
end
Running with AWS Lambda
We have a load balancer making this Lambda available
curl -v https://{my-load-balancer-url}/owners?path=owners
Response - note that the load balancer has converted my GET request to a POST request
{
"message": "This is a placeholder for your lambda code",
"event": {
"requestContext": {
"elb": {...}
},
"httpMethod": "POST",
"path": "/owners",
"queryStringParameters": {
"path": "owners"
},
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br",
"cache-control": "no-cache",
"connection": "keep-alive",
"content-length": "0",
...
},
"body": "",
"isBase64Encoded": false
}
}
Running in docker
docker run --rm --name lsample -p 9000:8080 -d {my-image-name}
POST request to local docker
curl -v http://localhost:9000/2015-03-31/functions/function/invocations -d '{"path": "owners"}'
Note that the headers are returned as part of the respose
Response Headers
< HTTP/1.1 200 OK
< Date: Fri, 18 Dec 2020 19:06:56 GMT
< Content-Length: 166
< Content-Type: text/plain; charset=utf-8
Response Body (formatted)
{
"headers": {
"Access-Control-Allow-Origin": "*"
},
"statusCode": 200,
"body": "{\"message\":\"This is a placeholder for your lambda code\",\"event\":{\"path\":\"owners\"}}"
}
GET request to local docker
curl -v http://localhost:9000/2015-03-31/functions/function/invocations?path=owners
No content is returned
< HTTP/1.1 200 OK
< Date: Fri, 18 Dec 2020 19:10:08 GMT
< Content-Length: 0
Create a Sinatra App to simulate the actions of the Application Load Balancer
Based on the following document, I believe that I need to simulate the actions performed by the AWS Application Load Balancer that fronts my lambda code. See https://docs.aws.amazon.com/lambda/latest/dg/services-alb.html
simulate-lambda-alb/alb_simulate.rb
require 'rubygems'
require 'bundler/setup'
require 'sinatra'
require 'sinatra/base'
require 'json'
require 'httpclient'
# ruby app.rb -o 0.0.0.0
set :port, 8091
set :bind, '0.0.0.0'
get '/web' do
send_file "../web/index.html"
end
get '/web/' do
send_file "../web/index.html"
end
get '/web/:filename' do |filename|
send_file "../web/#{filename}"
end
get '/lambda*' do
path = params['splat'][0]
path=path.gsub(/^lambda\//,'')
event = {path: path, queryStringParameters: params}.to_json
cli = HTTPClient.new
url = "#{ENV['LAMBDA_DOCKER_HOST']}/2015-03-31/functions/function/invocations"
resp = cli.post(url, event)
body = JSON.parse(resp.body)
status body['statusCode']
headers body['headers']
body['body']
end
simulate-lambda-alb/Dockerfile
FROM ruby:2.7
RUN gem install bundler
COPY Gemfile Gemfile
RUN bundle install
COPY . .
EXPOSE 8091
CMD ["ruby", "alb_simulate.rb"]
docker-compose.yml
version: '3.7'
networks:
mynet:
services:
lambda-container:
container_name: lambda-container
# your container image goes here, or you can test with the following
image: cdluc3/mysql-ruby-lambda
stdin_open: true
tty: true
ports:
- published: 8090
target: 8080
networks:
mynet:
alb-simulate:
container_name: alb-simulate
# this image contains the code in this answer
image: cdluc3/simulate-lambda-alb
networks:
mynet:
environment:
LAMBDA_DOCKER_HOST: http://lambda-container:8080
ports:
- published: 8091
target: 8091
depends_on:
- lambda-container
Run Sinatra to simulate Application Load Balancer
docker-compose up
Output
curl "http://localhost:8091/lambda?path=test&foo=bar"
{"message":"This is a placeholder for your lambda code","event":{"path":"","queryStringParameters":{"path":"test","foo":"bar","splat":[""]}}}
I added a Model to my API Gateway. When I send a POST request with the body containing a string (instead of valid JSON) the Model validates the request. I was expecting it to be rejected.
I've tried these two cases showing the problem:
The Model will validate "A_STRING" (incorrect behaviour)
The Model will validate "A_STRING"{} (incorrect behaviour)
The Model will fail {"A_STRING"} (correct behaviour)
I am testing the API Gateway directly from the AWS console (see attached screenshot), here are the logs for one example:
Execution log for request b2b825b4-4fb2-43e6-935b-0781264eb5df
Mon Aug 17 21:45:42 UTC 2020 : Starting execution for request: b2b825b4-4fb2-43e6-935b-0781264eb5df
Mon Aug 17 21:45:42 UTC 2020 : HTTP Method: POST, Resource Path: /TrainingNotif
Mon Aug 17 21:45:42 UTC 2020 : Method request path: {}
Mon Aug 17 21:45:42 UTC 2020 : Method request query string: {}
Mon Aug 17 21:45:42 UTC 2020 : Method request headers: {}
Mon Aug 17 21:45:42 UTC 2020 : Method request body before transformations: "A_STRING"
Mon Aug 17 21:45:42 UTC 2020 : Request validation succeeded for content type application/json
If it helps here is my model:
{
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-04/schema#",
"required":
[
"trainingSiteRequester",
"employeeTrainingList"
],
"properties":
{
"trainingSiteRequester":
{
"type": "string"
},
"employeeTrainingList":
{
"type": "array",
"items":
{
"additionalProperties": false,
"type": "object",
"required":
[
"id",
"trainingURL",
"dueDate"
],
"properties":
{
"trainingURL":
{
"type": "string"
},
"dueDate":
{
"type": "string"
},
"id":
{
"type": "integer"
}
}
}
}
}
}
And to be complete here is my CloudFormation code to attach the model to the API Gateay:
PostMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: AWS_IAM
HttpMethod: POST
RequestValidatorId: !Ref TrainingNotificationRequestValidator
RequestModels:
application/json: !Ref TrainingNotificationRequestModel
Integration:
Credentials: !GetAtt TrainingNotificationsAPIGatewayRole.Arn
IntegrationHttpMethod: POST
IntegrationResponses:
- SelectionPattern: 200
StatusCode: 200
- SelectionPattern: 429
StatusCode: 429
PassthroughBehavior: NEVER
RequestParameters:
integration.request.querystring.Action: "'SendMessage'"
integration.request.querystring.TopicArn: !Sub "'${ReceivedRequestsSNS}'"
integration.request.querystring.Message: "method.request.body"
RequestTemplates:
application/json: "{\"statusCode\": 200}"
Type: AWS
Uri:
Fn::Join:
- ""
- - "arn:aws:apigateway:"
- Ref: AWS::Region
- ":sns:action/Publish"
MethodResponses:
- StatusCode: 200
- StatusCode: 429
- StatusCode: 500
ResourceId: !Ref TrainingNotificationsAPIGatewayResources
RestApiId: !Ref TrainingNotificationsAPIGateway
# Request Model
TrainingNotificationRequestModel:
Type: AWS::ApiGateway::Model
Properties:
RestApiId: !Ref TrainingNotificationsAPIGateway
ContentType: application/json
Name: TrainingNotificationRequestModel
Schema:
$schema: 'http://json-schema.org/draft-04/schema#'
additionalProperties: false
required:
- trainingSiteRequester
- employeeTrainingList
properties:
trainingSiteRequester:
type: string
employeeTrainingList:
type: array
items:
type: object
additionalProperties: false
properties:
id:
type: integer
trainingURL:
type: string
dueDate:
type: string
required:
- id
- trainingURL
- dueDate
TrainingNotificationRequestValidator:
Type: AWS::ApiGateway::RequestValidator
Properties:
RestApiId: !Ref TrainingNotificationsAPIGateway
Name: TrainingNotificationRequestValidator
ValidateRequestBody: true
Found the answer, the Model's schema didn't have a Type property.
Adding type : object makes the model reject anything that isn't json (as expected) and makes the model run validation against anything that is JSON.
To be honest I found this through trial and error, so I'm not 100% sure as to the why of my answer, but it works.
Hi together,
I need to shut down RDS instances. However, when the RDS instance has a Multi-AZ deployment it is not possible to stop them. Hence, it is necessary to modify deployment to a none Multi-AZ deyploment. Then, I thought, I should be able to stop the instance.
When finally starting the instance again, after it is available it should modified to a Multi-AZ deployment.
However, I struggle with this very ansible playbook which is executed within a Jenkins Pipeline since it does not "wait" until the modification has been successfully conducted and RDS state is "available".
Here are the files
### vars/rds.yml
my_rds_state:
running:
name: started
description: Starting
multiZone: true
stopping:
name: stopped
description: Stopping
multiZone: false
### manage_rds.yml
---
- hosts: localhost
vars:
rdsState: "{{instanceState}}"
rdsIdentifier: "{{identifier}}"
tasks:
- name: Include vars
include_vars: rds.yml
- import_tasks: tasks/task_modify_rds.yml
when: rdsState == "stopping"
- debug:
var: my_rds_state
- import_tasks: tasks/task_state_rds.yml
- import_tasks: tasks/task_modify_rds.yml
when: rdsState == "running
### tasks/task_modify_rds.yml
- name: Modify RDS deployment
rds_instance:
db_instance_identifier: "{{rdsIdentifier}}"
apply_immediately: yes
multi_az: "{{my_rds_state[rdsState].multiZone | bool}}"
state: "{{my_rds_state[rdsState].name}}"
The my_rds_state value is:
my_rds_state:
ok: [localhost] => {
"my_rds_state": {
"running": {
"description": "Starting",
"multiZone": false,
"name": "started"
},
"stopping": {
"description": "Stopping",
"multiZone": true,
"name": "stopped"
}
}
}
Furthermore, console output looks like:
TASK [Modify RDS deployment] **********************************************
changed: [localhost]
TASK [Stopping RDS instances] **************************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: InvalidDBInstanceStateFault: An error occurred (InvalidDBInstanceState) when calling the StopDBInstance operation: Cannot stop or start a SQLServer MultiAz database instance
fatal: [localhost]: FAILED! => {"boto3_version": "1.11.8", "botocore_version": "1.14.8", "changed": false, "error": {"code": "InvalidDBInstanceState", "message": "Cannot stop or start a SQLServer MultiAz database instance", "type": "Sender"}, "msg": "Unable to stop DB instance: An error occurred (InvalidDBInstanceState) when calling the StopDBInstance operation: Cannot stop or start a SQLServer MultiAz database instance", "response_metadata": {"http_headers": {"connection": "close", "content-length": "311", "content-type": "text/xml", "date": "Tue, 25 Feb 2020 00:01:26 GMT", "x-amzn-requestid": "215571e3-12b6-4b1f-b640-587f3e1686fe"}, "http_status_code": 400, "request_id": "215571e3-12b6-4b1f-b640-587f3e1686fe", "retry_attempts": 0}}
Any ideas what the problem might be why ansible does not wait?
I have found the solution by myself.
Since triggering an action that causes the state to change into "modifying" is an asynchronous operation I had to use a kind of waiter.
- name: Wait until the DB instance status changes to 'modifying'
rds_instance_info:
db_instance_identifier: "{{rdsIdentifier}}"
register: database_info
until: database_info.instances[0].db_instance_status == "modifying"
retries: 18
delay: 10
when:
- rds_instance_info.db_instance_status != "modifying"
I'm trying to use CloudFormation to create an API Gateway but I have CORS issue with it.
Error on the front-end:
POST https://<>.execute-api.us-east-1.amazonaws.com/prod/<> 500
new:1 Access to XMLHttpRequest at '<>' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The API is created without any issue and I even double check every single page on the console against the working API and find no differences in their Method Request, Integration Request, Integration Response and Method Response for all the methods (including the OPTIONS).
If I remove the resources created by the template and create them manually in the same API gateway then my code works as expected. I've tested with the localhost, front-end code in S3 bucket and PostMan, so I can verify that my front-end code, lambda functions and database are working correctly.
I understand that people have had this issue before but I haven't been able to find any answer that solves my issue.
Here's my template.
Please note that the "method.response.header.Access-Control-Allow-Origin": false actually creates the API with the same settings as the working one.
I also use the code from the correct answer for this question.
Yes, my OPTIONS request has the "Access-Control-Allow-Origin" header.
Update
Following dannymac's answer below. I got these:
I added console.log(event.requestContext); to my Lambda function (written in Node.js).
There are logs for Lambda when I test the function.
2019-06-27T20:07:03.118Z 462b93b2-9d4b-4ed3-bc04-f966fcd034cf Debug CORS issue. Request ID:
2019-06-27T20:07:03.118Z 462b93b2-9d4b-4ed3-bc04-f966fcd034cf undefined
It looks like there is no event.requestContext.
I selected Enable CloudWatch Logs-INFO and Enable Detailed CloudWatch Metrics with CloudWatch log role ARN*:arn:aws:iam::<ID>:role/ApiGatewayCloudWatchLogsRole (it's a role created by AWS) in the API Gateway settings.
However, there is no CloudWatch log for the API Gateway. There's a default log in CloudWatch - Log Groups: /aws/apigateway/welcome
Time (UTC +00:00)
2019-06-27
19:50:55
Cloudwatch logs enabled for API Gateway
It looks like the CloudWatch log didn't pick up the test from API Gateway.
This is what I got from testing the GET method in my API Gateway:
Response Body
{
"message": "Internal server error"
}
Response Headers
{}
Logs
Execution log for request 10d90173-9919-11e9-82e1-dd33dda3b9df
Thu Jun 27 20:20:54 UTC 2019 : Starting execution for request: 10d90173-9919-11e9-82e1-dd33dda3b9df
Thu Jun 27 20:20:54 UTC 2019 : HTTP Method: GET, Resource Path: /notes
Thu Jun 27 20:20:54 UTC 2019 : Method request path: {}
Thu Jun 27 20:20:54 UTC 2019 : Method request query string: {userid=<ID>}
Thu Jun 27 20:20:54 UTC 2019 : Method request headers: {}
Thu Jun 27 20:20:54 UTC 2019 : Method request body before transformations:
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request URI: https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:770402430649:function:test-api-gateway-2-LambdaFunction-1XDONAN3QIY9I/invocations
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request headers: {x-amzn-lambda-integration-tag=... [TRUNCATED]
Thu Jun 27 20:20:54 UTC 2019 : Endpoint request body after transformations: {"resource":"/notes","path":"/notes","httpMethod":"GET","headers":null,"multiValueHeaders":null,"queryStringParameters":{"userid":"<USERID>"},"multiValueQueryStringParameters":{"userid":["<USERID>"]},"pathParameters":null,"stageVariables":null,"requestContext":{"path":"/notes","accountId":"<ID>"...,"identity":{"cognitoIdentityPoolId":null,"cognitoIdentityId":null,"apiKey":"test-invoke-api-key","principalOrgId":null,"cognitoAuthenticationType":null,"userArn":"<ARN>","apiKeyId":"test-invoke-api-key-id","userAgent":..."test [TRUNCATED]
Thu Jun 27 20:20:54 UTC 2019 : Sending request to https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:<ID>:function:test-api-gateway-2-LambdaFunction-<STRING>/invocations
Thu Jun 27 20:20:54 UTC 2019 : Received response. Status: 403, Integration latency: 6 ms
Thu Jun 27 20:20:54 UTC 2019 : Endpoint response headers: {Date=Thu, 27 Jun 2019 20:20:54 GMT, Content-Length=130, Connection=keep-alive, x-amzn-RequestId=<ID>}
Thu Jun 27 20:20:54 UTC 2019 : Endpoint response body before transformations: <AccessDeniedException>
<Message>Unable to determine service/operation name to be authorized</Message>
</AccessDeniedException>
Thu Jun 27 20:20:54 UTC 2019 : Lambda invocation failed with status: 403. Lambda request id: feb22917-0dea-4f91-a274-fb6b85a69121
Thu Jun 27 20:20:54 UTC 2019 : Execution failed due to configuration error:
Thu Jun 27 20:20:54 UTC 2019 : Method completed with status: 500
I've also exported both the working and not working API Gateway in Swagger 2. The only difference is:
// working one:
"x-amazon-apigateway-any-method": {
"produces": [
"application/json"
],
"parameters": [
{
"name": "noteid",
"in": "path",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "200 response",
"schema": {
"$ref": "#/definitions/Empty"
}
}
},
"security": [
{
"mobile-notes-api-authorizer": []
}
]
}
// not working one:
"x-amazon-apigateway-any-method": {
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "200 response",
"schema": {
"$ref": "#/definitions/Empty"
}
}
},
"security": [
{
"test-api-gateway-2-authorizer": []
}
]
}
They both have:
"headers": {
"Access-Control-Allow-Origin": {
"type": "string"
},
"Access-Control-Allow-Methods": {
"type": "string"
},
"Access-Control-Allow-Headers": {
"type": "string"
}
}
I've tried to use the Swagger template in the Body of my API Gateway before but unable to solve the invalid authorizer issue.
I've figured out the issue. There are 2 main things:
The IntegrationHttpMethod for Lambda must be POST. I found the answer here.
The template didn't have AWS::Lambda::Permission that allows API Gateway to invoke Lambda function.
With the template, when you use AWS::Lambda::Permission, it will show the API as a trigger of your Lambda function.
However, if you manually create the API Gateway and link it with your Lambda function, it won't show API Gateway as a trigger but it still works.
So for the template I posted above, I needed to add these for it to work:
"LambdaPermission": {
"Type": "AWS::Lambda::Permission",
"Description": "Permission for API GateWay to invoke Lambda.",
"Properties": {
"Action": "lambda:invokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Join": [
"",
[
"arn:aws:execute-api:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":",
{
"Ref": "ApiGateway"
},
"/*"
]
]
}
}
},
And edit method ANY to look like this
"methodNotesANY": {
"Type": "AWS::ApiGateway::Method",
"DependsOn": "LambdaPermission",
"Properties": {
"AuthorizationType": "COGNITO_USER_POOLS",
"AuthorizerId": {
"Ref": "GatewayAuthorizer"
},
"RestApiId": {
"Ref": "ApiGateway"
},
"ResourceId": {
"Ref": "resourceNotes"
},
"HttpMethod": "ANY",
"Integration": {
"Type": "AWS_PROXY",
"IntegrationHttpMethod": "POST",
"Uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations"
},
"IntegrationResponses": [{
"StatusCode": "200"
}]
},
"MethodResponses": [{
"ResponseModels": {
"application/json": "Empty"
},
"StatusCode": "200"
}]
}
},
My Best Guess: The POST to your ANY lambda function is failing during execution, and not setting the Access-Control-Allow-Origin header to * (or your domain). Anytime I get a 5XX error and a CORS error at the same time from a non-OPTIONS request, this is almost always the case for me.
Recommended Next Steps: Reproduce the error situation after adding debug logging to your Lambda source code, and turning on CloudWatch Logs in your API Gateway Rest API. You can do this by going to the API Gateway console, clicking on Stages > Prod > Logs/Tracing, then checking these two: Enable CloudWatch Logs (Log level: INFO), and Enable Detailed CloudWatch Metrics. Then you must "deploy" the changes in order for them to take effect. Do this by clicking the Actions button from your Rest API's Resources menu, and choosing Deploy API. I also recommend logging the extendedRequestId (an event property passed to your handler) from your Lambda function in order to tie the Lambda request to the API Gateway request: event.requestContext.extendedRequestId.
Example API Gateway logs:
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Extended Request Id: b5zpBGS3IAMFvqw=
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Verifying Usage Plan for request: b66b3876-984b-11e9-95eb-dd93c7e40ca0. API Key: API Stage: 1234567890/Prod
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) API Key authorized because method 'ANY /forms' does not require API Key. Request will not contribute to throttle or quota limits
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Usage Plan check succeeded for API Key and API Stage 1234567890/Prod
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Starting execution for request: b66b3876-984b-11e9-95eb-dd93c7e40ca0
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) HTTP Method: GET, Resource Path: /forms
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Lambda execution failed with status 200 due to customer function error: select count(*) AS `count(*)` from (select `user`.* from `user` where (id IN ('some_id_123'))) as `temp` - Cannot enqueue Query after fatal error.. Lambda request id: 1ae2bb06-5347-4775-9277-caccc42f18f2
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) Method completed with status: 502
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) AWS Integration Endpoint RequestId : 1ae2bb06-5347-4775-9277-caccc42f18f2
(b66b3876-984b-11e9-95eb-dd93c7e40ca0) X-ray Tracing ID : 1-5d13cca0-3be96a1ab93a877edc70577c
Example correlated Lambda execution logs:
START RequestId: 1ae2bb06-5347-4775-9277-caccc42f18f2 Version: $LATEST
2019-06-26T19:50:56.391Z 1ae2bb06-5347-4775-9277-caccc42f18f2 { "extendedRequestId": "b5zpBGS3IAMFvqw=", ... }
2019-06-26T19:50:57.853Z 1ae2bb06-5347-4775-9277-caccc42f18f2 { "errorMessage": "select count(*) AS `count(*)` from (select `user`.* from `user` where (id IN ('some_id_123'))) as `temp` - Cannot enqueue Query after fatal error.", ... }
END RequestId: 1ae2bb06-5347-4775-9277-caccc42f18f2
REPORT RequestId: 1ae2bb06-5347-4775-9277-caccc42f18f2 Duration: 1660.45 ms Billed Duration: 1700 ms Memory Size: 256 MB Max Memory Used: 57 MB
Other Thoughts: Export the Swagger definitions of both the broken API and the working API. Compare and see what is different. Do this from the console by going to Stages > Prod > Export > Export as Swagger + API Gateway Extensions. It may not be exactly the same as the CloudFormation template, but it's pretty close.
At the time of this post, Lambda Proxy Integration (AWS_PROXY) and CORS (Access-Control-Allow-Origin) don't work very well together. My approach -inspired on this explanation- was to use AWS instead of AWS_PROXY and manually provide Mapping templates for both request and response as follows:
MyApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref Stage
Cors:
AllowMethods: "'POST,OPTIONS'"
AllowHeaders: "'Access-Control-Allow-Origin,Content-Type,X-Amz-Date,Authorization,X-Api-Key,x-requested-with,x-requested-for'"
AllowOrigin: "'*'"
DefinitionBody:
swagger: 2.0
info:
version: 1.1
title: !Ref AWS::StackName
paths:
/mypath:
get:
responses:
"200":
schema:
$ref: "#/definitions/Empty"
headers:
Access-Control-Allow-Origin:
type: string
x-amazon-apigateway-integration:
httpMethod: POST # must be POST even for GET
type: AWS # must be AWS to allow cors headers
uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations
requestTemplates:
application/json: |
{
#set($params = $input.params().querystring)
"queryStringParameters" : {
#foreach($param in $params.keySet())
"$param" : "$util.escapeJavaScript($params.get($param))" #if($foreach.hasNext),#end
#end
},
#set($params = $input.params().path)
"pathParameters" : {
#foreach($param in $params.keySet())
"$param" : "$util.escapeJavaScript($params.get($param))" #if($foreach.hasNext),#end
#end
}
}
responses:
default:
statusCode: 200
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: |
#set($payload = $util.parseJson($input.json('$')))
#set($context.responseOverride.status = $payload.statusCode)
$payload.body