Deploy api gateway with terraform based on a swagger file - swagger

I want to deploy my api gateway with terraform using a swagger file to describe my api. The swagger.yaml looks like this:
swagger: '2.0'
info:
version: '1.0'
title: "CodingTips"
schemes:
- https
paths:
"/api":
get:
description: "Get coding tips"
produces:
- application/json
x-amazon-apigateway-integration: ${apiIntegration}
responses:
'200':
description: "Codingtips were successfully requested"
Terraform is giving me a BadRequestException saying that The REST API doesn't contain any methods.
Because of this I am thinking that it is trying to deploy the REST api without waiting for the methods and integrations of this api to be created.
This made me think in the direction of having to add DEPENDS_ON to the aws_api_gateway_deployment. However I do not know what to depend on since I don't define the method and integration resource using swagger. They should be automatically deducted from the swagger definition.
Am I thinking in the right direction and if so, what do I have to make my aws_api_gateway_deployment depend on? Or is something else wrong with the way I am trying to deploy this api.
My apigateway.tf file looks like this:
resource "aws_api_gateway_rest_api" "codingtips-api-gateway" {
name = "ServerlessExample"
description = "Terraform Serverless Application Example"
body = "${data.template_file.codingtips_api_swagger.rendered}"
}
locals{
"get_codingtips_arn" = "${aws_lambda_function.get-tips-lambda.invoke_arn}"
"x-amazon-coding-tips-apigateway-integration" = <<EOF
#
uri = "${local.get_codingtips_arn}"
passthroughBehavior: when_no_match
httpMethod: POST
type: aws_proxy
credentials: "${aws_iam_role.api_gateway_role.arn}"
EOF
}
data "template_file" codingtips_api_swagger{
template = "${file("./swagger.yaml")}"
vars {
apiIntegration = "${indent(8, local.x-amazon-coding-tips-apigateway-integration)}"
}
}
resource "aws_api_gateway_deployment" "codingtips-api-gateway-deployment" {
rest_api_id = "${aws_api_gateway_rest_api.codingtips-api-gateway.id}"
stage_name = "test"
}
How can I fix the BadRequestException: The REST API doesn't contain any methods ?

I found out what was wrong. It is a syntactical error in the locals{} block.
uri = should be uri: . Using a colon instead of an equal sign. The block then looks like this:
locals{
"get_codingtips_arn" = "${aws_lambda_function.get-tips-lambda.invoke_arn}"
"x-amazon-codingtips-get-apigateway-integration" = <<EOF
# comment for new line
uri: "${aws_lambda_function.get-tips-lambda.invoke_arn}"
passthroughBehavior: when_no_match
httpMethod: POST
type: aws_proxy
EOF
}
Researching this I found that it reads easier when you specify the x-amazon-apigateway-integration in the swagger.yaml like this:
swagger: '2.0'
info:
version: '1.0'
title: "CodingTips"
schemes:
- https
paths:
"/api":
get:
description: "Get coding tips"
produces:
- application/json
responses:
'200':
description: "The codingtips request was successful."
x-amazon-apigateway-integration:
uri: ${uri_arn}
passthroughBehavior: "when_no_match"
httpMethod: "POST"
type: "aws_proxy"
The data{} and locals{} blocks in your terraform then look like:
data "template_file" codingtips_api_swagger{
template = "${file("swagger.yaml")}"
vars {
uri_arn = "${local.get_codingtips_arn}"
}
}
locals {
"get_codingtips_arn" = "${aws_lambda_function.get-tips-lambda.invoke_arn}"
}

Related

how to send post requests to AWS Lambda function created with serverless

TLDR
I cant figure out how to format my post request for my AWS Lambda API created with serverless
Current Implementation
I have a Lambda function created using the serverless framework. I have two functions; a function which prints out all modules and their version, and another that does a prediction on data sent via a post request. It handles dependency storage by mounting an EFS volume, here's the serverless.yml
service: test3Predict
plugins:
- serverless-pseudo-parameters
custom:
efsAccessPoint: fsap-**********
LocalMountPath: /mnt/efs
subnetsId: subnet-**********
securityGroup: sg-**********
provider:
name: aws
runtime: python3.6
region: us-east-2
timeout: 20
package:
exclude:
- node_modules/**
- .vscode/**
- .serverless/**
- .pytest_cache/**
- __pychache__/**
functions:
ohPredict:
handler: handler.lambda_handler_OH
environment: # Service wide environment variables
MNT_DIR: ${self:custom.LocalMountPath}
vpc:
securityGroupIds:
- ${self:custom.securityGroup}
subnetIds:
- ${self:custom.subnetsId}
iamManagedPolicies:
- arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
events:
- http:
path: ohPredict
method: get
fileSystemConfig:
localMountPath: '${self:custom.LocalMountPath}'
arn: 'arn:aws:elasticfilesystem:${self:provider.region}:#{AWS::AccountId}:access-point/${self:custom.efsAccessPoint}'
test:
handler: handler.test
environment: # Service wide environment variables
MNT_DIR: ${self:custom.LocalMountPath}
vpc:
securityGroupIds:
- ${self:custom.securityGroup}
subnetIds:
- ${self:custom.subnetsId}
iamManagedPolicies:
- arn:aws:iam::aws:policy/AmazonElasticFileSystemClientReadWriteAccess
events:
- http:
path: test
method: get
fileSystemConfig:
localMountPath: '${self:custom.LocalMountPath}'
arn: 'arn:aws:elasticfilesystem:${self:provider.region}:#{AWS::AccountId}:access-point/${self:custom.efsAccessPoint}'
It seems like the test is working; If I run sls deploy and enter the URL for my test function in a browser, I get the expected output.
Problem
The ohPredict function is not working as well. When I send it a post request with the requests python module,
url = 'https://****.execute-api.****.amazonaws.com/dev/ohPredict'
myobj = {"data": data}
x = requests.post(url, json=myobj)
res = eval(x.text)
print(res)
I get the following error:
{'message': 'Missing Authentication Token'}
I ran a test with a simple lambda function and a post request without using serverless, and that was all the code I needed. I figured I would try experimenting and supplying a few values, and I ended up with this:
url = 'https://****.execute-api.****.amazonaws.com/dev/ohPredict'
myobj = {"data": data}
x = requests.post(url, headers={'Authorization': 'FOO'}, json=myobj)
res = eval(x.text)
print(res)
which resulted in:
{'message': "Authorization header requires 'Credential' parameter. Authorization header requires 'Signature' parameter. Authorization header requires 'SignedHeaders' parameter. Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header. Authorization=FOO"}
I tried playing around with supplying other values, and didn't get any change in the output. How can I get my post request to be recieved by my function?
As mentioned by yvesonline, the problem was the method. This was fixed by simply replacing method: get to method: post

Micronaut: Authentication Principal generated into request body

I'm trying to generate client code with swagger-codegen from a Micronaut server. The problem arises with authenticated POST and PUT routes (it works fine for GET an DELETE).
When I have a method like this in a controller:
#Override
#Post("/")
public Single<? extends HttpResponse> updateStatus(Authentication authentication, GameReference gameReference) {
// ...
}
The resulting swagger for that method looks like this:
post:
tags:
- /presence
description: Updates status
operationId: updateStatus
parameters: []
requestBody:
content:
application/json:
schema:
type: object
properties:
authentication:
$ref: '#/components/schemas/Authentication'
gameReference:
$ref: '#/components/schemas/GameReference'
required: true
responses:
default:
description: HTTP 204 for successful updates.
content:
application/json: {}
So the Authentication Principal has been built into the request body and in generated client code the parameters to that method would be an object with both Authentication and GameReference.
What I have tried to work around this problem:
Add #Parameter(hidden = true) before Authentication parameter. Did not change anything.
Playing around with swagger annotations for authentication, like #SecuritySchema and #SecurityDefention. Authentication Principle still gets generated into the swagger yaml.
Is this a bug in Micronauts Swagger implementation or is there a way to work-around this? Note that this works well for GET and DELETE. There the Authentication Principle is ignored.
It was accepted as a bug by the Micronaut team:
https://github.com/micronaut-projects/micronaut-core/issues/1155

Swagger generated Java Client has no overloaded calls, even though the query parameter is marked as optional

I'm introducing an optional "query parameter" (with default value) for one of my API endpoint ( i.e. /foo2) but hoping that existing API consumers don't have to make any changes in their application when they regenerate the java client stub from the new swagger file.
I was expecting that the generated java client will offer overloaded methods, to operate with and without a method parameter, so that my existing API consumers can work without any changes to their call. At the same time, they can switch to its more generic offering.
public void foo2Get() throws ApiException {
public void foo2Get(String type) throws ApiException {
But it generates ONLY the latter. I don't see the former.
At the same time, generated python client from same swagger throws a var arg and this way it more flexible and hence old and new offerings will work without any issues.
def foo2_get(self, **kwargs):
(swagger sample used: attached)
** Description**
Is there a way to overcome this limitation with java code generator.
swagger-generator version
swagger 2.0
Swagger declaration file content or url
swagger: '2.0'
info:
title: identity
version: Unknown
consumes:
- application/json
produces:
- application/json
parameters:
typeParam:
name: type
in: query
required: false
allowEmptyValue: true
default: basic
type: string
enum:
- basic
- advanced
paths:
/foo2:
get:
consumes:
- application/json
parameters:
- "$ref": "#/parameters/typeParam"
responses:
'200':
description: OK

Swagger YAML file add dynamic URL

How do I add a URL link to my swagger API, referencing localhost?. I dont want to add a hardcoded URL to the file, as the URL will differ on each enviornment. Trying the method below (specifying localhost) does not work.
externalDocs:
description: "View application metrics"
url: "http://localhost:3001/metrics"
You can follow the below steps:
Convert the yaml in json
Change the host and save it
Convert the saved json again to yaml
In swagger it takes json file for documentation so you might don't require the 3rd step.
const path = require("path");
const yaml = require("js-yaml");
const fs = require("fs");
const host = "127.0.0.1:3000"; // any host
const doc = yaml.safeLoad(fs.readFileSync("swagger.yaml"));
doc.host = host;
fs.writeFileSync(
path.join("swagger_new.json"),
JSON.stringify(doc, null, " ")
);
hope it helped!
Relative externalDocs URLs are supported in OpenAPI 3.0 (openapi: 3.0.0) but not in OpenAPI 2.0 (swagger: '2.0').
Assuming you use OpenAPI 3.0 and your OpenAPI file is hosted at http://localhost:3001/openapi.yaml, you can use:
openapi: 3.0.0
...
externalDocs:
description: View application metrics
url: /metrics # <--- http://localhost:3001/metrics
Note: If your API definition includes servers, relative URLs are resolved against servers rather than the location of the OpenAPI file.
openapi: 3.0.0
...
servers:
- url: 'https://api.example.com/v1'
externalDocs:
description: View application metrics
url: /metrics # <--- https://api.example.com/metrics
More info: API Host and Base Path

How to provide example value to a Response Body of content-type: text/html in Swagger (to test with dredd)

I have an API call which responds 200 OK and returns an HTML. I would like to add this to my API documentation
(especially since i validate it using dredd and unless i provide it with the expected response body the test fails). How
would i do this in Swagger?
--- More details ---
My Response to an API call is 200 OK and with a one line Response Body:
<html><body>You are being redirected.</body></html>
I can easily define the Response Body in Blueprint in the following form:
+ Response 302 (text/html; charset=utf-8)
+ Body
`<html><body>You are being redirected.</body></html>`
But i'm not sure how to do this in Swagger. Almost all examples i can find are for application/json responses (understandably) and i'm having trouble
guessing the correct syntax for this kind of response.
The relevant swagger text in my document is this (so far without specifying the response body, so with an empty body dredd
fails because the response body should be <html><body>You are being redirected.</body></html>):
# this is my API spec in YAML
swagger: '2.0'
info:
title: My API (Swagger)
description: blablabla
version: "1.0.0"
# the domain of the service
host: my.domain.com
# array of all schemes that your API supports
schemes:
- https
# will be prefixed to all paths
basePath: /
produces:
- application/json; charset=utf-8
paths:
/users/password:
post:
summary: Password Reset
description: |
Handles Reset password for existing user.
consumes:
- application/x-www-form-urlencoded
produces:
- text/html; charset=utf-8
parameters:
- name: "user[email]"
description: email
in: formData
required: true
type: string
default: "user#gmail.com"
tags:
- Reset Password
responses:
200:
description: Success
Please comment if you have any suggestions on this. Thanks!
Figured it out. the response object has a field called "examples" which allows to show examples to your API response.
The syntax is clearly shown in the specification and basically says you need to identify the MIME-type for the example response, in my case text/html and the value is the actual text.
so like this:
responses:
200:
description: success and returns some html text
examples:
text/html:
<html><body>Your HTML text</body></html>
That's it. Now whether or not dredd knows to take this value and compare it is another story. their docs state that any response other than JSON is validated as plain text so this should work.
Hope this helped.
In case someone needs this in the openapi version 3.0 you can do it this way:
Save your HTML. For example give this at the end of your file:
components:
schemas:
error401:
type: string
example: '<html>HTML text</html>'
Then you can reference this scheme in responses wherever you want. For example:
responses:
'401':
description: Error
content:
text/html:
schema:
$ref: '#/components/schemas/error401'

Resources