Ocelot Swagger MMLib.SwaggerForOcelot showing "No operations defined in spec!" - .net-core

I am using Ocelot gateway and for swagger document using "MMLib.SwaggerForOcelot" library.
For some swagger Key, swagger UI is showing "No operations defined in spec!" and swagger JSON is coming without paths like
{
"openapi": "3.0.1",
"info": {
"title": "Admin API",
"version": "v1"
},
"paths": {},
"components": {
"schemas": {}
}
}
Ocelot Configuration Route is
{
"DownstreamPathTemplate": "/api/admin/v{version}/{everything} ",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5000
}
],
"UpstreamPathTemplate": "/api/admin/v{version}/{everything}",
"UpstreamHttpMethod": [],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 1000,
"TimeoutValue": 900000
},
"SwaggerKey": "AdminAPI"
}
and Swagger Configuration is
"SwaggerEndPoints": [
{
"Key": "AdminAPI",
"Config": [
{
"Name": "Admin API",
"Version": "v1",
"Url": "http://localhost:5000/swagger/v1/swagger.json"
}
]
}
]
after reviewing the MMLib.SwaggerForOcelot source code, it looks like something to do with the version in the downstream path, any clue on how this can be fixed?

The issue is that MMLib.SwaggerForOcelot is not considering {version} while doing Ocelot transformation.
RouteOptions has a property TransformByOcelotConfig which is set to true by default, so once swagger JSON is obtained from downstream, the transformation will be done.
here, it will try to find the route from the route configuration like below and if not found it will delete the route from swagger JSON
private static RouteOptions FindRoute(IEnumerable<RouteOptions> routes, string downstreamPath, string basePath)
{
string downstreamPathWithBasePath = PathHelper.BuildPath(basePath, downstreamPath);
return routes.FirstOrDefault(p
=> p.CanCatchAll
? downstreamPathWithBasePath.StartsWith(p.DownstreamPathWithSlash, StringComparison.CurrentCultureIgnoreCase)
: p.DownstreamPathWithSlash.Equals(downstreamPathWithBasePath, StringComparison.CurrentCultureIgnoreCase));
}
The problem is StartsWith will return false since swagger JSON path will be like
/api/admin/v{version}/Connections
and route config is like
/api/admin/v{version}/{everything}
and version will replace with the version given in swagger options so that it will become
/api/admin/v1/{everything}
Fix to this problem will be
Either set "TransformByOcelotConfig":false in swagger option
"SwaggerEndPoints": [
{
"Key": "AdminAPI",
"TransformByOcelotConfig":false,
"Config": [
{
"Name": "Admin API",
"Version": "v1",
"Url": "http://localhost:5000/swagger/v1/swagger.json"
}
]
}
]
Or Change the route, just to have {everything} keyword
{
"DownstreamPathTemplate": "/api/admin/{everything} ",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5000
}
],
"UpstreamPathTemplate": "/api/admin/{everything}",
"UpstreamHttpMethod": [],
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3,
"DurationOfBreak": 1000,
"TimeoutValue": 900000
},
"SwaggerKey": "AdminAPI"
}

Related

How to create api in wso2am by payload

I deployed wso2am-4.0 on my vm, I tried to create api by curl. I'm so confused I don't know how to add params in paylod. I guess that params could be added to payload in operations section. but there are no attribute about params. How to add body or query params in payload?
{
"name": "PizzaShackAPI",
"description": "This is a simple API for Pizza Shack online pizza delivery store.",
"context": "pizza",
"version": "1.0.0",
"provider": "admin",
"lifeCycleStatus": "CREATED",
"responseCachingEnabled": false,
"hasThumbnail": false,
"isDefaultVersion": false,
"enableSchemaValidation": false,
"type": "HTTP",
"transport": [
"http",
"https"
],
"tags": [
"substract",
"add"
],
"policies": [
"Unlimited"
],
"apiThrottlingPolicy": "Unlimited",
"securityScheme": ["oauth2"],
"maxTps": {
"production": 1000,
"sandbox": 1000
},
"visibility": "PUBLIC",
"visibleRoles": [],
"visibleTenants": [],
"subscriptionAvailability": "CURRENT_TENANT",
"additionalProperties": [
{
"name" : "AdditionalProperty",
"value" : "PropertyValue",
"display" : true
}
],
"accessControl": "NONE",
"businessInformation": {
"businessOwner": "John Doe",
"businessOwnerEmail": "johndoe#wso2.com",
"technicalOwner": "Jane Roe",
"technicalOwnerEmail": "janeroe#wso2.com"
},
"endpointConfig": {
"endpoint_type": "http",
"sandbox_endpoints": {
"url": "https://localhost:9443/am/sample/pizzashack/v1/api/"
},
"production_endpoints": {
"url": "https://localhost:9443/am/sample/pizzashack/v1/api/"
}
},
"operations": [
{
"target": "/order/{orderId}",
"verb": "POST",
"throttlingPolicy": "Unlimited"
"uriMapping": "uriMapping-test1",
"payloadSchema": "payloadSchema-test1"
},
{
"target": "/menu",
"verb": "GET",
"throttlingPolicy": "Unlimited",
"uriMapping": "uriMapping-test2",
"payloadSchema": "payloadSchema-test1"
}
]
}
In ui, there would be added like below:
For this you need to do a Swagger PUT - https://apim.docs.wso2.com/en/4.0.0/reference/product-apis/publisher-apis/publisher-v2/publisher-v2/#tag/APIs/operation/getAPISwagger
The API payload does not support adding these Swagger specific stuff.
Here is a sample definition for the resource check - https://gist.github.com/pubudu538/8a8eec6663cc0bbf97302bf50a7f2194#file-swagger-put-L354

The language expression property '0' can't be evaluated, property name must be a string - ARM Template error while adding Key Vault access policy

I've been working on an issue and seem to be stuck, so asking on so in case anyone can help.
To describe the issue, I've got an existing Azure Key Vault setup, and wish to add a number of access policies to this resource group. It needs to be conditional as if the function name is "false" then that function should not be added to key vault access policy.
variable section:
"variables": {
"functionAccess": {
"value": [
{
"name": "[parameters('Function_1')]"
},
{
"name": "[parameters('Function_2')]"
},
{
"name": "[parameters('Function_3')]"
}
]
}
}
My Template :
{
"apiVersion": "2016-10-01",
"condition": "[not(equals(variables('functionAccess')[CopyIndex()].name, 'false'))]",
"copy": {
"batchSize": 1,
"count": "[length(variables('functionAccess'))]",
"mode": "Serial",
"name": "accessPolicies"
},
"name": "[concat(parameters('KeyVault_Name'), '/add')]",
"properties": {
"accessPolicies": [
{
"tenantId": "[subscription().tenantId]",
"objectId": "[if(not(equals(variables('functionAccess')[CopyIndex()].name, 'false')), reference(concat('Microsoft.Web/sites/', variables('functionAccess')[CopyIndex()].name), '2016-08-01', 'Full').identity.principalId, json('null'))]",
"permissions": {
"keys": [
"get",
"list"
],
"secrets": [
"get",
"list"
],
"certificates": [
"get",
"list"
]
}
}
]
},
"type": "Microsoft.KeyVault/vaults/accessPolicies"
}
When I deploy my ARM template for the azure key vault I got this error message:
The language expression property '0' can't be evaluated, property name must be a string.
also tried below, but same error:
{
"apiVersion": "2018-02-14",
"name": "[concat(parameters('KeyVault_Name'), '/add')]",
"properties": {
"copy": [
{
"batchSize": 1,
"count": "[length(variables('functionAccess'))]",
"mode": "serial",
"name": "accessPolicies",
"input": {
"condition": "[not(equals(variables('functionAccess')[copyIndex('accessPolicies')].name, 'false'))]",
"tenantId": "[subscription().tenantId]",
"objectId": "[if(not(equals(variables('functionAccess')[copyIndex('accessPolicies')].name, 'false')), reference(concat('Microsoft.Web/sites/', variables('functionAccess')[copyIndex('accessPolicies')].name), '2016-08-01', 'Full').identity.principalId, json('null'))]",
"permissions": {
"keys": [
"get",
"list"
],
"secrets": [
"get",
"list"
],
"certificates": [
"get",
"list"
]
}
}
}
]
},
"type": "Microsoft.KeyVault/vaults/accessPolicies"
}
There are a few options for dealing with filtering an array for copy operation. I deploy my ARM templates from PowerShell scripts and use PowerShell to setup parameter values. When I need special logic handle different inputs for different environments, I let PowerShell handle it.
If you must handle the filtering in ARM and you have the option to input a CSV list of functions, then perhaps the following will work. You can then use the functionAccessArray to iterate over in the copy operation.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
},
"variables": {
"functionAccessCsv": "Function-0,Function-1,false,Function-4,false,Function-6,Function-7",
"functionAccessFiltered": "[replace(replace(variables('functionAccessCsv'), 'false', ''), ',,', ',')]",
"functionAccessArray": "[split(variables('functionAccessFiltered'), ',')]"
},
"resources": [
],
"outputs": {
"functionAccessCsvFiltered": {
"type": "string",
"value": "[variables('functionAccessFiltered')]"
},
"functionAccessArray": {
"type": "array",
"value": "[variables('functionAccessArray')]"
}
}
}
The result:
I just had the same issue. By using an array parameter with a default value instead of a variable, I got it to work.
"parameters": {
"functionAccess": {
"type": "array",
"defaultValue": [
"value1",
"value2",
"value3"
]
}
}

Wildcard path proxy to Google Cloud Run

I have two cloud run services (Next.js and API server) and I want to serve them through a single endpoint.
I want requests to /api to be forwarded to API service and all other other requests (/*) to be forwarded to Next.js server.
Cloud Run documentation suggests that I use Endpoint but it does not seem to support wildcard paths.
What are the possible alternatives?
Google API Gateway supports different wildcards, I'using Google Functions as backend, but that shouldn't make a different when using the GW with Google Run.
My scenario:
/ should route to the /index.html
/assets should route to the /assets/any-file-here.png
/logo-256.png should route to the /logo-256.png
/endpoint-1 should route only to the API, but hosted on another function
/endpoint-2/some-param should route only to the API, hosted on the same function as the assets
With this configuration everything get's routed liked wanted, using Double Wildcard Matching.
It doesn't matter that the wildcard is before specific routes, this is handled correctly by the gateway.
{
"swagger": "2.0",
"info": {
"version": "0.0.1",
"title": "Some API w/ Assets"
},
"paths": {
"/": {
"get": {
"summary": "home",
"operationId": "home",
"parameters": [],
"x-google-backend": {
"address": "https://THE-GOOGLE-RUN-OR-FUNCTION",
"path_translation": "CONSTANT_ADDRESS"
},
"responses": {
"200": {
"description": "Home"
}
}
}
},
"/{files=**}": {
"get": {
"summary": "assets",
"operationId": "assets",
"parameters": [
{
"in": "path",
"name": "files",
"type": "string",
"required": true
}
],
"x-google-backend": {
"address": "https://THE-GOOGLE-RUN-OR-FUNCTION",
"path_translation": "APPEND_PATH_TO_ADDRESS"
},
"responses": {
"200": {
"description": "assets"
}
}
}
},
"/endpoint-1": {
"get": {
"summary": "Some pure backend api",
"operationId": "ep1",
"x-google-backend": {
"address": "https://SOME-OTHER-GOOGLE-RUN-OR-FUNCTION",
"path_translation": "APPEND_PATH_TO_ADDRESS"
},
"parameters": [],
"responses": {
"200": {
"description": "result values"
}
}
}
},
"/endpoint-2/{some_param}": {
"get": {
"summary": "Some pure backend API with path param",
"operationId": "ep2",
"parameters": [
{
"in": "path",
"name": "some_param",
"type": "string",
"required": true
}
],
"x-google-backend": {
"address": "https://THE-GOOGLE-RUN-OR-FUNCTION",
"path_translation": "APPEND_PATH_TO_ADDRESS"
},
"responses": {
"200": {
"description": "result values"
}
}
}
}
}
}
But with this setup, your page won't be that fast, I recommend adding Google Load Balancer with Google CDN before your API gateway when you are serving files.
This is best addressed through the usage of Firebase hosting since they have a tutorial to do just this.
Hope you find this useful

Path conflict in Ocelot routing configuration

How would you set re-reoute following two set of similar routes to different machines. Please advise.
Problem/Question: The conflicting situation is between /customers/1 & /customers/1/products where each one is going to a different machine.
- machine name: customer
GET /customers
GET /customers/1
POST /customers
PUT /customers1
DELETE /customers/1
- machine name: customerproduct
GET /customers/1/products
PUT /customers/1/products
ocelot.json
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/customers/{id}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "customer",
"Port": 80
}
],
"UpstreamPathTemplate": "/customers/{id}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ]
},
{
"DownstreamPathTemplate": "/customers/{id}/products",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "customerproduct",
"Port": 80
}
],
"UpstreamPathTemplate": "/customers/{id}/products",
"UpstreamHttpMethod": [ "Get", "Put" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:80"
}
}
Just adding an answer based on the OP's own solution in the comments.
The reason this was not working is because of order of evaluation. Ocelot will evaluate paths in the order they are specified, unless the priority property is specified on the routes (see docs https://ocelot.readthedocs.io/en/latest/features/routing.html#priority)
So the in the following routing config:
{
"ReRoutes":[
{
"UpstreamPathTemplate":"/customers/{id}", // <-- will be evaluated first
...
},
{
"UpstreamPathTemplate":"/customers/{id}/products",
...
},
...
],
...
}
the first path will be evaluated and Ocelot will match to this path even if the upstream call specifies the /products sub-path.
To fix this, either change the ordering so that the more specific path is evaluated first:
{
"ReRoutes":[
{
"UpstreamPathTemplate":"/customers/{id}/products", // <-- will be evaluated first
...
},
{
"UpstreamPathTemplate":"/customers/{id}",
...
},
...
],
...
}
or use the priority property, with the priority being used to indicate the desired order of evaluation:
{
"ReRoutes":[
{
"UpstreamPathTemplate":"/customers/{id}",
"Priority": 0,
...
},
{
"UpstreamPathTemplate":"/customers/{id}/products", // <-- will be evaluated first
"Priority": 1,
...
},
...
],
...
}

How to deploy swagger from Arm Template to Azure API managment using use "contentFormat": "swagger-json"

How do I deploy swagger from ARM Template to Azure API Management using the option "contentFormat": "swagger-json"
https://learn.microsoft.com/en-us/azure/templates/microsoft.apimanagement/2018-06-01-preview/service/apis
"contentFormat": "swagger-json",
"contentValue": "D:\\tempvsts\\APISwagger\\APISwagger\\swagger.json",
When I run this I get the following error
"message": "Parsing error(s): Unexpected character encountered while
parsing value: D. Path '', line 0, position 0."
Please note I have used the below option and it works
"contentFormat": "swagger-link-json",
Full ARM template
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"ApimServiceName": {
"type": "string"
}
},
"variables": {
},
"resources": [
{
"type": "Microsoft.ApiManagement/service/apis",
"name": "[concat(parameters('ApimServiceName'), '/animalAPI4')]",
"apiVersion": "2018-06-01-preview",
"scale": null,
"properties": {
"displayName": "HTTP animal API",
"apiRevision": "1",
"description": "API Management facade for a very handy and free online HTTP toolsds",
"serviceUrl": "https://animailhttpbin.org",
"path": "animals4",
"contentFormat": "swagger-json",
"contentValue": "D:\\tempvsts\\APISwagger\\APISwagger\\swagger.json",
"apiVersionSet": {
"id": "[concat(resourceId('Microsoft.ApiManagement/service', parameters('ApimServiceName')), '/api-version-sets/versionset-animal-api4')]"
},
"protocols": [
"https"
],
"authenticationSettings": null,
"subscriptionKeyParameterNames": null,
"apiVersion": "v1"
},
"dependsOn": [
]
}
]
}
Like 4c74356b41 mentioned, the JSON content value as an escaped string has to be used.
Your swagger JSON would be something like this
{ "swagger":"2.0", ... }
So, it would go into your ARM template like this
{
...
"contentFormat": "swagger-json",
"contentValue": "{ \"swagger\":\"2.0\", ... }",
...
}

Resources