UNPKG

apim-developer-portal1

Version:

API management developer portal

189 lines (172 loc) 7.44 kB
/** * This script automates deployments between developer portal instances. * In order to run it, you need to: * * 1) Clone the api-management-developer-portal repository * 2) `npm install` in the root of the project * 3) Run this script with a valid combination of arguments * * Managed portal command example: * node migrate --sourceEndpoint from.management.azure-api.net --destEndpoint to.management.azure-api.net --publishEndpoint to.developer.azure-api.net --sourceToken "SharedAccessSignature integration&2020..." --destToken "SharedAccessSignature integration&2020..." * * Auto-publishing is not supported for self-hosted versions, so make sure you publish the portal (for example, locally) and upload the generated static files to your hosting after the migration is completed. * * You can specify the SAS tokens directly (via sourceToken and destToken), or you can supply an identifier and key, * and the script will generate tokens that expire in 1 hour. (via sourceId, sourceKey, destId, destKey) */ const moment = require('moment'); const crypto = require('crypto'); const execSync = require('child_process').execSync; const { request } = require('./utils.js'); const yargs = require('yargs') .example('$0 \ --publishEndpoint <name.developer.azure-api.net> \ --sourceEndpoint <name.management.azure-api.net> \ --sourceToken <token> \ --destEndpoint <name.management.azure-api.net> \ --destToken <token>\n', 'Managed') .example('$0 --selfHosted \ --sourceEndpoint <name.management.azure-api.net> \ --sourceToken <token> \ --destEndpoint <name.management.azure-api.net> \ --destToken <token>') /*.option('interactive', { alias: 'i', type: 'boolean', description: 'Whether to use interactive login', conflicts: ['sourceToken', 'sourceId', 'sourceKey', 'destToken', 'destId', 'destKey'] })*/ .option('selfHosted', { alias: 'h', type: 'boolean', description: 'If the portal is self-hosted' }) .option('publishEndpoint', { alias: 'p', type: 'string', description: 'Endpoint of the destination managed developer portal; if empty, destination portal will not be published; unsupported in self-hosted scenario', example: '<name.developer.azure-api.net>' }) .option('sourceEndpoint', { type: 'string', description: 'The hostname of the management endpoint of the source API Management service', example: '<name.management.azure-api.net>', demandOption: true }) .option('sourceId', { type: 'string', description: 'The management API identifier', implies: 'sourceKey', conflicts: 'sourceToken' }) .option('sourceKey', { type: 'string', description: 'The management API key (primary or secondary)', implies: 'sourceId', conflicts: 'sourceToken' }) .option('sourceToken', { type: 'string', description: 'A SAS token for the source portal', example: 'SharedAccessSignature…', conflicts: ['sourceId, sourceToken'] }) .option('destEndpoint', { type: 'string', description: 'The hostname of the management endpoint of the destination API Management service', example: '<name.management.azure-api.net>', demandOption: true }) .option('destId', { type: 'string', description: 'The management API identifier', implies: 'destKey', conflicts: 'destToken' }) .option('destKey', { type: 'string', description: 'The management API key (primary or secondary)', implies: 'destId', conflicts: 'destToken' }) .option('destToken', { type: 'string', example: 'SharedAccessSignature…', description: 'A SAS token for the destination portal', conflicts: ['destId, destToken'] }) .argv; async function run() { const sourceManagementApiEndpoint = yargs.sourceEndpoint; const sourceManagementApiAccessToken = await getTokenOrThrow(yargs.sourceToken, yargs.sourceId, yargs.sourceKey); const destManagementApiEndpoint = yargs.destEndpoint; const destManagementApiAccessToken = await getTokenOrThrow(yargs.destToken, yargs.destId, yargs.destKey); const publishEndpoint = yargs.publishEndpoint; // the rest of this mirrors migrate.bat, but since we're JS, we're platform-agnostic. const snapshotFolder = '../dist/snapshot'; // capture the content of the source portal execSync(`node ./capture ${sourceManagementApiEndpoint} "${sourceManagementApiAccessToken}" "${snapshotFolder}"`); // remove all content of the target portal execSync(`node ./cleanup ${destManagementApiEndpoint} "${destManagementApiAccessToken}"`); // upload the content of the source portal execSync(`node ./generate ${destManagementApiEndpoint} "${destManagementApiAccessToken}" "${snapshotFolder}"`); if (publishEndpoint && !yargs.selfHosted) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; await publish(publishEndpoint, destManagementApiAccessToken); } else if (publishEndpoint) { console.warn("Auto-publishing self-hosted portal is not supported."); } } /** * Attempts to get a SAS token in two ways: * 1) if the token is explicitly set by the user, use that token. * 2) if the id and key are specified, manually generate a SAS token. * @param {string} token an optionally specified token * @param {string} id the Management API identifier * @param {string} key the Management API key */ async function getTokenOrThrow(token, id, key) { if (token) { return token; } if (id && key) { return await generateSASToken(id, key); } throw Error('You need to specify either: token or id AND key'); } /** * Generates a SAS token from the specified Management API id and key. Optionally * specify the expiry time, in seconds. * * See https://docs.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication#ManuallyCreateToken * @param {string} id The Management API identifier. * @param {string} key The Management API key (primary or secondary) * @param {number} expiresIn The number of seconds in which the token should expire. */ async function generateSASToken(id, key, expiresIn = 3600) { const now = moment.utc(moment()); const expiry = now.clone().add(expiresIn, 'seconds'); const expiryString = expiry.format(`YYYY-MM-DD[T]HH:mm:ss.SSSSSSS[Z]`); const dataToSign = `${id}\n${expiryString}`; const signedData = crypto.createHmac('sha512', key).update(dataToSign).digest('base64'); return `SharedAccessSignature uid=${id}&ex=${expiryString}&sn=${signedData}`; } /** * Publishes the content of the specified APIM instance using a SAS token. * @param {string} endpoint the publishing endpoint of the destination developer portal instance * @param {string} token the SAS token */ async function publish(endpoint, token) { const url = `https://${endpoint}/publish`; // returns with literal OK (missing quotes), which is invalid json. await request("POST", url, token); } run() .then(() => { console.log("DONE"); }) .catch(error => { console.error(error); process.exitCode = 1; });