azure
Version:
Microsoft Azure Client Library for node
195 lines (179 loc) • 8.2 kB
JavaScript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
;
var request = require('request');
var utils = require('../utils');
exports.retryTimeout = 30;
/**
* Mostly all the filters are in the generic runtime 'ms-rest'. However this one ends up here because
* it is Azure specific. This filter is responsible for autoregistering an RP if the initial request failed.
* After successful registration of the RP it retries the initial request. Other filters play with other aspects
* of the request/response but the actual request on the wire is made from the sink. However, this filter
* makes actual requests on the wire. It does not depend on the sink. This is done because it is tasked to register
* the RP, poll the registration status and the retry the initial request.
*/
/**
* Creates a filter that verifies whether the request failed due to RP not being registered.
* It tries to register the RP and after successful registration it retries the original request.
* @param {number} retryTimeoutInSec The retry timeout in seconds. Default is 30 seconds.
*/
exports.create = function create(retryTimeoutInSec) {
if (retryTimeoutInSec !== null && retryTimeoutInSec !== undefined) {
exports.retryTimeout = retryTimeoutInSec;
}
return function handle(options, next, callback) {
return next(options, function (err, response, body) {
if (err) {
return callback(err);
}
let rpName, urlPrefix;
if (response.statusCode === 409) {
rpName = exports.checkRPNotRegisteredError(body);
if (rpName) {
urlPrefix = exports.extractSubscriptionUrl(options.url);
return exports.registerRP(urlPrefix, rpName, options, (registrationErr, result) => {
if (registrationErr) {
//Autoregistration of ${provider} failed for some reason. We will not return this error
//instead will return the initial response with 409 status code back to the user.
return callback(err, response, body);
}
if (result) {
//Retry the original request. We have to change the x-ms-client-request-id
//otherwise Azure endpoint will return the initial 409 (cached) response.
options.headers['x-ms-client-request-id'] = utils.generateUuid();
return request(options.url, options, (error, response) => {
return callback(error, response, response.body);
});
} else {
return callback(err, response, body);
}
});
}
}
return callback(err, response, body);
});
};
};
/**
* Reuses the headers of the original request and url (if specified).
* @param {object} originalRequest The original request
* @param {boolean} reuseUrlToo Should the url from the original request be reused as well. Default false.
* @returns {object} reqOptions - A new request object with desired headers.
*/
function getRequestEssentials(originalRequest, reuseUrlToo) {
let reqOptions = {
headers: {}
};
if (reuseUrlToo) {
reqOptions.url = originalRequest.url;
}
//Copy over the original request headers. This will get us the auth token and other useful stuff from
//the original request header. Thus making it easier to make requests from this filter.
for (let h in originalRequest.headers) {
reqOptions.headers[h] = originalRequest.headers[h];
}
//We have to change the x-ms-client-request-id otherwise Azure endpoint
//will return the initial 409 (cached) response.
reqOptions.headers['x-ms-client-request-id'] = utils.generateUuid();
//Set content-type to application/json
reqOptions.headers['Content-Type'] = 'application/json; charset=utf-8';
return reqOptions;
}
/**
* Validates the error code and message associated with 409 response status code. If it matches to that of
* RP not registered then it returns the name of the RP else returns undefined.
* @param {string} body - The response body received after making the original request.
* @returns {string} result The name of the RP if condition is satisfied else undefined.
*/
exports.checkRPNotRegisteredError = function checkRPNotRegisteredError(body) {
let result, responseBody;
if (body) {
try {
responseBody = JSON.parse(body);
} catch (err) {
//do nothing;
}
if (responseBody && responseBody.error && responseBody.error.message &&
responseBody.error.code && responseBody.error.code === 'MissingSubscriptionRegistration') {
let matchRes = responseBody.error.message.match(/.*'(.*)'/i);
if (matchRes) {
result = matchRes.pop();
}
}
}
return result;
};
/**
* Extracts the first part of the URL, just after subscription:
* https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/
* @param {string} url - The original request url
* @returns {string} urlPrefix The url prefix as explained above.
*/
exports.extractSubscriptionUrl = function extractSubscriptionUrl(url) {
let result;
let matchRes = url.match(/.*\/subscriptions\/[a-f0-9-]+\//ig);
if (matchRes && matchRes[0]) {
result = matchRes[0];
} else {
throw new Error(`Unable to extract subscriptionId from the given url - ${url}.`);
}
return result;
};
/**
* Registers the given provider.
* @param {string} urlPrefix - https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/
* @param {string} provider - The provider name to be registered.
* @param {object} originalRequest - The original request sent by the user that returned a 409 response
* with a message that the provider is not registered.
* @param {registrationCallback} callback - The callback that handles the RP registration
*/
exports.registerRP = function registerRP(urlPrefix, provider, originalRequest, callback) {
let postUrl = `${urlPrefix}providers/${provider}/register?api-version=2016-02-01`;
let getUrl = `${urlPrefix}providers/${provider}?api-version=2016-02-01`;
let reqOptions = getRequestEssentials(originalRequest);
return request.post(postUrl, reqOptions, (err, response) => {
if (response.statusCode !== 200) {
return callback(new Error(`Autoregistration of ${provider} failed. Please try registering manually.`));
}
return exports.getRegistrationStatus(getUrl, originalRequest, (err, result) => {
if (err) {
return callback(err, result);
}
return callback(null, result);
});
});
};
/**
* Polls the registration status of the provider that was registered. Polling happens at an interval of 30 seconds.
* Polling will happen till the registrationState property of the response body is 'Registered'.
* @param {string} url - The request url for polling
* @param {object} originalRequest - The original request sent by the user that returned a 409 response
* with a message that the provider is not registered.
* @param {registrationCallback} callback - The callback that handles the RP registration.
*/
exports.getRegistrationStatus = function getRegistrationStatus(url, originalRequest, callback) {
let reqOptions = getRequestEssentials(originalRequest);
return request.get(url, reqOptions, (err, response, body) => {
if (err) return callback(err);
let responseBody = {};
try {
responseBody = JSON.parse(body);
} catch (error) {
return callback(new Error(`An error occurred while parsing the response body for ` +
`getting registration status ${url}. The response body was ${body}.`));
}
if (responseBody.registrationState && responseBody.registrationState === 'Registered') {
return callback(null, true);
} else {
setTimeout(() => {
return exports.getRegistrationStatus(url, originalRequest, callback);
}, exports.retryTimeout * 1000);
}
});
};
/**
* This callback handles the RP registration
* @callback registrationCallback
* @param {object} error - The Error object
* @param {boolean} result - true - provider is registered; false - provider is not registered.
*/