I first encountered this issue when working on a change in terraform
to add the ability to list functions keysand wrote it up here with a manual change to the Swagger which resolves the issue for the endpoint I used WebApps_ListFunctionSecrets
.
Here is a quick write up of the issue as it looks like it affects a number of the endpoints in the REST specs for both 2018-02-01
and 2018-08-01
. We'll focus on 2018-02-01
below to simplify things.
For details on how to reproduce these findings see
- ./repro.md for doing this in the CLI
- ./dotnetsample showing issues when using the .NET SDK (also affects Golang as generated from same Spec)
The spec defines the call as follows:
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/functions/{functionName}/listsecrets": {
"post": {
"tags": [
"WebApps"
],
"summary": "Get function secrets for a function in a web site, or a deployment slot.",
"description": "Get function secrets for a function in a web site, or a deployment slot.",
"operationId": "WebApps_ListFunctionSecrets",
"parameters": [
{
"$ref": "#/parameters/resourceGroupNameParameter"
},
{
"name": "name",
"in": "path",
"description": "Site name.",
"required": true,
"type": "string"
},
{
"name": "functionName",
"in": "path",
"description": "Function name.",
"required": true,
"type": "string"
},
{
"$ref": "#/parameters/subscriptionIdParameter"
},
{
"$ref": "#/parameters/apiVersionParameter"
}
],
"responses": {
"200": {
"description": "Function secrets returned.",
"schema": {
"$ref": "#/definitions/FunctionSecrets"
}
},
"default": {
"description": "App Service error response.",
"schema": {
"$ref": "./CommonDefinitions.json#/definitions/DefaultErrorResponse"
}
}
}
}
}
The response type referenced #/definitions/FunctionSecrets
is as follows:
"FunctionSecrets": {
"description": "Function secrets.",
"type": "object",
"allOf": [
{
"$ref": "./CommonDefinitions.json#/definitions/ProxyOnlyResource"
}
],
"properties": {
"properties": {
"description": "FunctionSecrets resource specific properties",
"properties": {
"key": {
"description": "Secret key.",
"type": "string"
},
"trigger_url": {
"description": "Trigger URL.",
"type": "string"
}
},
"x-ms-client-flatten": true
}
}
},
If you use az rest
to make a request to this API the response is:
az rest --method post
-u /subscriptions/5774ad8f-0000-0000-a72e-0447910568d3/resourceGroups/funcRestSpecTest/providers/Microsoft.Web/sites/tesSOEMSITE8b7a/functions/testfunc/listsecrets?api-version=2018-02-01
{
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
}
In the generated SDKs (tested Go and .NET) the API is unusable as the result cannot be deserialized correctly.
The defined return type for WebApps_ListFunctionSecrets
in the REST Spec has a body definition of "#/definitions/FunctionSecrets"
. This is the following JSON:
"properties": {
"properties": {
"description": "FunctionSecrets resource specific properties",
"properties": {
"key": {
"description": "Secret key.",
"type": "string"
},
"trigger_url": {
"description": "Trigger URL.",
"type": "string"
}
},
"x-ms-client-flatten": true
}
}
Translated to the response this defines an EXPECTED
response from the API to be the following json
:
{
"properties": {
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
}
}
The ACTUAL
response body is:
{
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
}
The DIFF
is that the spec incorrectly expects the key
and trigger
properties to be under a parent properties
object which they aren't.
{
- "properties": {
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
- }
}
I went to look at the code for the func cli to look at how it gets keys back.
You can see from the code that it also looks for a top level value of keys
in the response and not properties.keys
as defined in the spec.
internal static async Task<string> GetFunctionKey(string functionName, string appId, string accessToken, string managementURL)
{
// If anything goes wrong anywhere, simply return null and let the caller take care of it.
if (string.IsNullOrEmpty(functionName) || string.IsNullOrEmpty(accessToken))
{
return null;
}
var url = new Uri($"{managementURL}{appId}/hostruntime/admin/functions/{functionName}/keys?api-version={ArmUriTemplates.WebsitesApiVersion}");
var key = string.Empty;
try
{
var keysJson = await ArmHttpAsync<JToken>(HttpMethod.Get, url, accessToken);
key = (string)(keysJson["keys"] as JArray).First()["value"];
}
catch (Exception)
{
return null;
}
return key;
}
In the generated SDKs (tested Go and .NET) the API has additional confusing properties on request that don't make sense and are never deserialised to.
The defined return type for WebApps_ListFunctionSecrets
in the REST Spec has a body definition of "#/definitions/FunctionSecrets"
.
Before the return body definition it has a reference to allOf
these add additional properties from another definition onto the object.
"allOf": [
{
"$ref": "./CommonDefinitions.json#/definitions/ProxyOnlyResource"
}
],
The "./CommonDefinitions.json#/definitions/ProxyOnlyResource"
is as follows:
"ProxyOnlyResource": {
"description": "Azure proxy only resource. This resource is not tracked by Azure Resource Manager.",
"properties": {
"id": {
"description": "Resource Id.",
"type": "string",
"readOnly": true
},
"name": {
"description": "Resource Name.",
"type": "string",
"readOnly": true
},
"kind": {
"description": "Kind of resource.",
"type": "string"
},
"type": {
"description": "Resource type.",
"type": "string",
"readOnly": true
}
},
"x-ms-azure-resource": true
},
Translated to the response this defines an EXPECTED
response from the API to be the following json
(including body defined and discussed in Issue 1):
{
"id": "someid",
"name": "somename",
"kind": "somekind",
"type": "sometype",
"properties": {
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
}
}
The ACTUAL
response body is:
{
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
}
The DIFF
is that the allOf
statement has incorrectly included 4 additional
{
- "id": "someid",
- "name": "somename",
- "kind": "somekind",
- "type": "sometype",
- "properties": { // Ignore: Introduce by Issue 1
"key": "LZro9c3Aq2HxICVG/somekey",
"trigger_url": "https://something.azurewebsites.net/api/testfunc?code=LZro9ICVG/Wdbsomekey"
- } // Ignore: Introduce by Issue 1
}
While investigating the issue I was pointed to a newer set of APIs here: Azure/azure-rest-api-specs#7174 (comment)
This includes a repeat of the above issue on a number of new APIS:
- POST api/sites/{name}[/slots/{slot}]/functions/{functionName}/listkeys
- PUT api/sites/{name}[/slots/{slot}]/functions/{functionName}/keys/{keyName}
- DELETE api/sites/{name}[/slots/{slot}]/functions/{functionName}/keys/{keyName}
- POST api/sites/{name}[/slots/{slot}]/host/default/listkeys
- PUT api/sites/{name}[/slots/{slot}]/host/default/{functionkeys|systemkeys}/{keyName}
- DELETE api/sites/{name}[/slots/{slot}]/host/default/{functionkeys|systemkeys}/{keyName}
- POST api/sites/{name}[/slots/{slot}]/host/default/sync
- POST api/sites/{name}[/slots/{slot}]/host/default/listsyncstatus
- GET api/sites/{name}[/slots/{slot}]/functions/{functionName}/properties/state
- PUT api/sites/{name}[/slots/{slot}]/functions/{functionName}/properties/state
- GET api/sites/{name}[/slots/{slot}]/functions/{functionName}/properties/status
- GET api/sites/{name}[/slots/{slot}]/host/default/properties/config
- GET api/sites/{name}[/slots/{slot}]/host/default/properties/status
Local copy of changes in ./testdata/2091OR7174WebApps.json
The defined return type for WebApps_ListHostKeys
in the REST Spec has a definition of "#/definitions/HostKeys"
. This is the following JSON
:
{
"description": "Functions host level keys.",
"type": "object",
"allOf": [
{
"$ref": "./CommonDefinitions.json#/definitions/ProxyOnlyResource"
}
],
"properties": {
"properties": {
"description": "HostKeys resource specific properties",
"properties": {
"masterKey": {
"description": "Secret key.",
"type": "string"
},
"functionKeys": {
"description": "Host level function keys.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"systemKeys": {
"description": "System keys.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"x-ms-client-flatten": true
}
}
}
Translated to the response this defines an EXPECTED
response from the API to be the following json
:
{
"id": "someid",
"name": "somename",
"kind": "somekind",
"type": "sometype",
"properties": {
"masterkey": "LZro9c3Aq2HxICVG/somekey",
"functionKeys": {},
"systemKeys": {}
}
}
Making a request with az rest --method post -u /subscriptions/somesub-d51e-4456-a72e-0447910568d3/resourceGroups/funcRestSpecTest/providers/Microsoft.Web/sites/somesite/host/default/listkeys?api-version=2018-02-01
.
The ACTUAL
response body is:
{
"functionKeys": {
"default": "KJvuWBMHd8O0sXuB5qFkKCPUuMR//VCPcvoyXsVNnnxWsR9lfqIqHA=="
},
"masterKey": "8mHmW6CjbiFdt1pJrnxJ3bty5zWNScw6qPFEvaFUl20cpSm4pCeHhg==",
"systemKeys": {
"eventgridextensionconfig_extension": "/QMEve5NWSdZ89Or2kf/ET56HmPXmoDe1E6WoRuM0cSN240i6GhIqA=="
}
}
Other issues Seen in PR: Azure/azure-rest-api-specs#7174 (comment)
DEFINITION
: POST api/sites/{name}[/slots/{slot}]/host/default/listsyncstatus
This shows that the responses will be statusCode of 204
with No Content
{
"tags": [
"WebApps"
],
"summary": "This is to allow calling via powershell and ARM template.",
"description": "This is to allow calling via powershell and ARM template.",
"operationId": "WebApps_ListSyncStatus",
"parameters": [
{
"$ref": "#/parameters/resourceGroupNameParameter"
},
{
"name": "name",
"in": "path",
"description": "Name of the app.",
"required": true,
"type": "string"
},
{
"$ref": "#/parameters/subscriptionIdParameter"
},
{
"$ref": "#/parameters/apiVersionParameter"
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
Actual
Response looks returns a 200
with a body of:
{
"status": "success"
}
Making a request with az rest --method post -u /subscriptions/somesub-d51e-4456-a72e-0447910568d3/resourceGroups/funcRestSpecTest/providers/Microsoft.Web/sites/somesite/host/default/listsyncstatus?api-version=2018-02-01 --debug
Raw:
urllib3.connectionpool : https://management.azure.com:443 "POST /subscriptions/somesub-d51e-4456-a72e-0447910568d3/resourceGroups/funcRestSpecTest/providers/Microsoft.Web/sites/somesite/host/default/listsyncstatus?api-version=2018-02-01 HTTP/1.1" 200 None
Event: CommandInvoker.OnTransformResult [<function _resource_group_transform at 0x7fdf26468488>, <function _x509_from_base64_to_hex_transform at 0x7fdf26468510>]
Event: CommandInvoker.OnFilterResult []
{
"status": "success"
}