UNPKG

generator-csebot

Version:

Generates a bot (Microsoft Bot Framework) with CI/CD in Team Services

1,271 lines (1,072 loc) 38.9 kB
// Collection of utility functions used by other generators. const fs = require('fs'); const os = require('os'); const url = require('url'); const request = require(`request`); const package = require('../../package.json'); // The version of the API used to talk to TFS and VSTS. In future // versions this might be changed depending on the version of TFS/VSTS // we are talking too. However, the shape of the build and release // JSON changes from version to version. The versions used below are // supported in TFS 2017 U3, 2018 U1 and VSTS. const BUILD_API_VERSION = `2.0`; const PROJECT_API_VERSION = `1.0`; const RELEASE_API_VERSION = `3.0-preview`; const DISTRIBUTED_TASK_API_VERSION = `3.0-preview`; const SERVICE_ENDPOINTS_API_VERSION = `3.0-preview`; //4.1-preview.1 // This location is the same as the VSTeam PowerShell module. Therefore, // the profiles can be shared between Yo Team and the VSTeam PowerShell // module. const PROFILE_PATH = os.homedir() + '/vsteam_profiles.json'; const EXTENSIONS_SUB_DOMAIN = `extmgmt`; const RELEASE_MANAGEMENT_SUB_DOMAIN = `vsrm`; var profile = null; // var supportedTargets = ['docker', 'dockerpaas', 'paas', 'paasslots'] //'acilinux', var logMessage = function (msg) { if (process.env.LOGYO === `on`) { console.log(msg); } }; String.prototype.replaceAll = function (search, replacement) { var target = this; return target.split(search).join(replacement); }; function isDocker(value) { return value === `docker` || value === `dockerpaas` || value === `acilinux`; } function getDockerRegistryServer(server) { let parts = url.parse(server); return parts.host; } function getImageNamespace(registryId, endPoint) { let dockerNamespace = registryId ? registryId.toLowerCase() : null; if (endPoint && endPoint.authorization && !isDockerHub(endPoint.authorization.parameters.registry)) { dockerNamespace = getDockerRegistryServer(endPoint.authorization.parameters.registry); } return dockerNamespace; } function addUserAgent(options) { options.headers['user-agent'] = getUserAgent(); return options; } // Setting the User Agent allows these calls to be identified in the VSTS telemetry. function getUserAgent() { return `Yo Team/${package.version} (${process.platform}: ${process.arch}) Node.js/${process.version}`; } function reconcileValue(first, second, fallback) { // The only way I found to get the CLI to work when I need // to skip values is to enter " ". That space throws off // other portions of the code that test for the exisitence // of a value. Triming the strings will only leave non // whitespace. return first ? first.trim() : (second ? second.trim() : fallback); } // If the user has selected Java and Hosted Linux Preview you // can't select App Service because you have to use PowerShell // to convert the war file into a zip file. function getTargets(answers) { return new Promise(function (resolve, reject) { let targets = []; if (answers.type.includes(`csharp`)) { targets = [{ name: `Azure App Service`, value: `paas` }, { name: `Azure App Service (Deployment Slots)`, value: `paasslots` }]; } else { targets = [{ name: `Azure App Service`, value: `paas` }, { name: `Azure App Service (Deployment Slots)`, value: `paasslots` }, { name: `Azure App Service Docker (Linux)`, value: `dockerpaas` }, { name: `Docker Host`, value: `docker` }]; } resolve(targets); }); } function getBotTypes(answers) { // Default to languages that work on all agents let types = [{ name: `Node.js`, value: `node` }, { name: `Typescript`, value: `typescript` }]; // If this is not a Linux or Mac based agent also show // .NET Full if (answers.queue.indexOf(`Linux`) === -1 && answers.queue.indexOf(`macOS`) === -1) { types.splice(1, 0, { name: `.NET Framework`, value: `csharp` }); } return types; } function getBotBuilderVersions(answers) { let bbname = 'Bot Framework SDK'; // Default to languages that work on all agents let bbVersions = [{ name: `${bbname} v3`, value: `v3` }, { name: `${bbname} v4`, value: `v4` }]; return bbVersions; } function getPATPrompt(answers) { if (!answers.tfs) { // The user passed in the tfs value on the // command line but we still need to prompt // for the pat. answers will not have a tfs // value so send a generic response. return `What is your Personal Access Token?`; } if (isVSTS(answers.tfs)) { return `What is your VSTS Personal Access Token?`; } return `What is your TFS Personal Access Token?`; } function getInstancePrompt() { return `Enter VSTS account name\n ({account}.visualstudio.com)\n Or name of a stored Profile?`; } function getDefaultPortMapping(answers) { if (answers.target === `docker`) { if (answers.type === `java`) { return `8080:8080`; } else if (answers.type === `node`) { return `3000:3000`; } else { return `80:80`; } } else { if (answers.type === `java`) { return `8080`; } else if (answers.type === `node`) { return `3000`; } else { return `80`; } } } function validateRequired(input, msg) { return !input ? msg : true; } function validatePortMapping(input) { return validateRequired(input, `You must provide a Port Mapping`); } function validateProfileName(input) { return validateRequired(input, `You must provide a profile name`); } // function validateCustomFolder(input) { // return validateRequired(input, `You must provide a custom template path`); // } function validateBotName(input) { return validateRequired(input, `You must provide a name for your application`); } function validateAppId(input) { return validateRequired(input, `You must provide a Microsoft App ID (guid)`); } function validateAppPasswd(input) { return validateRequired(input, `You must provide a Microsoft App Password`); } function validateBotLocation(input) { return validateRequired(input, `You must provide a location for your bot`); } function validatePersonalAccessToken(input) { return validateRequired(input, `You must provide a Personal Access Token`); } function validateTFS(input) { return validateRequired(input, `You must provide your TFS URL or Team Service account name`); } function validateAzureSub(input) { return validateRequired(input, `You must provide an Azure Subscription Name`); } function validateDockerHost(input) { return validateRequired(input, `You must provide a Docker Host URL`); } function validateDockerCertificatePath(input) { return validateRequired(input, `You must provide a Docker Certificate Path`); } function validateDockerHubID(input) { return validateRequired(input, `You must provide a Docker Registry username`); } function validateDockerHubPassword(input) { return validateRequired(input, `You must provide a Docker Registry Password`); } function validateDockerRegistry(input) { if (!input) { return validateRequired(input, `You must provide a Docker Registry URL`); } return input.toLowerCase().match(/http/) === null ? `You must provide a Docker Registry URL including http(s)` : true; } function validateAzureSubID(input) { return validateRequired(input, `You must provide an Azure Subscription ID`); } function validateAzureTenantID(input) { return validateRequired(input, `You must provide an Azure Tenant ID`); } function validateServicePrincipalID(input) { return validateRequired(input, `You must provide a Service Principal ID`); } function validateServicePrincipalKey(input) { return validateRequired(input, `You must provide a Service Principal Key`); } function tokenize(input, nvp) { for (var key in nvp) { input = input.replaceAll(key, nvp[key]); } return input; } function encodePat(pat) { 'use strict'; // The personal access token must be 64 bit encoded to be used // with the REST API var b = new Buffer(`:` + pat); var s = b.toString(`base64`); return s; } function checkStatus(uri, token, gen, callback) { 'use strict'; // Simply issues a get to the provided URI and returns // the body as JSON. Call this when the action taken // requires time to process. var options = addUserAgent({ "method": `GET`, "headers": { "authorization": `Basic ${token}` }, "url": `${uri}` }); request(options, function (err, res, body) { let obj = {}; try { obj = JSON.parse(body); } catch (error) { // This a HTML page with an error message. err = error; console.log(body); } callback(err, obj); }); } function tryFindDockerRegistryServiceEndpoint(account, projectId, dockerRegistry, token, callback) { 'use strict'; // Will NOT throw an error if the endpoint is not found. This is used // by code that will create the endpoint if it is not found. findDockerRegistryServiceEndpoint(account, projectId, dockerRegistry, token, function (e, ep) { if (e && e.code === `NotFound`) { callback(null, undefined); } else { callback(e, ep); } }); } function findDockerRegistryServiceEndpoint(account, projectId, dockerRegistry, token, callback) { 'use strict'; // There is nothing to do if (!dockerRegistry) { callback(null, null); return; } var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${projectId}/_apis/distributedtask/serviceendpoints`, "qs": { "api-version": SERVICE_ENDPOINTS_API_VERSION } }); request(options, function (error, response, body) { var obj = JSON.parse(body); // TODO: Test that authorization.parameters.registry === dockerHost. But that requires // a second REST call once you know the ID of the dockerregistry type service endpoint. // For now assume any dockerregistry service endpoint is safe to use. var endpoint = obj.value.find(function (i) { return i.type.toLowerCase() === `dockerregistry`; }); if (endpoint === undefined) { callback({ "message": `x Could not find Docker Registry Service Endpoint`, "code": `NotFound` }, undefined); } else { // Down stream we need the full endpoint so call again with the ID. This will return more data getServiceEndpoint(account, projectId, endpoint.id, token, callback); } }); } function getServiceEndpoint(account, projectId, id, token, callback) { var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${projectId}/_apis/distributedtask/serviceendpoints/${id}`, "qs": { "api-version": SERVICE_ENDPOINTS_API_VERSION } }); request(options, function (error, response, body) { callback(error, JSON.parse(body)); }); } function tryFindDockerServiceEndpoint(account, projectId, dockerHost, token, gen, callback) { 'use strict'; // Will NOT throw an error if the endpoint is not found. This is used // by code that will create the endpoint if it is not found. findDockerServiceEndpoint(account, projectId, dockerHost, token, gen, function (e, ep) { if (e && e.code === `NotFound`) { callback(null, undefined); } else { callback(e, ep); } }); } function findDockerServiceEndpoint(account, projectId, dockerHost, token, gen, callback) { 'use strict'; // There is nothing to do // This will be true when the user selected Linux for the build queue. // Because we use ARM deployment with the Hosted VS2017 queue even if // the user selected Linux for build the call to needsDockerHost may // return true for build. However, if they select Linux for build they // would not be prompted for a docker host and dockerHost will be empty. // In that case just return. When user is calling from the command line // and they pass in Linux they have to leave dockerHost empty. If they // pass in Linux and a dockerHost they will get an error saying the // docker service endpoint could not be found. That is because we did // not create one because the Hosted Linux build has the docker tools // so we don't need an external host so no service endpoint was created. if (!dockerHost) { callback(null, null); return; } var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${projectId}/_apis/distributedtask/serviceendpoints`, "qs": { "api-version": SERVICE_ENDPOINTS_API_VERSION } }); request(options, function (error, response, body) { // Check the response statusCode first. If it is not a 200 // the body will be html and not JSON if (response.statusCode >= 400) { callback(`Error trying to find Docker Service Endpoint: ${response.statusMessage}`); return; } var obj = JSON.parse(body); // The i.url is returned with a trailing / so just use starts with just in case // the dockerHost is passed in without it var endpoint = obj.value.find(function (i) { return i.url.toLowerCase().startsWith(dockerHost.toLowerCase()); }); if (endpoint === undefined) { callback({ "message": `x Could not find Docker Service Endpoint`, "code": `NotFound` }, undefined); } else { callback(error, endpoint); } }); } function tryFindAzureServiceEndpoint(account, projectId, sub, token, gen, callback) { 'use strict'; // Will NOT throw an error if the endpoint is not found. This is used // by code that will create the endpoint if it is not found. findAzureServiceEndpoint(account, projectId, sub, token, gen, function (err, ep) { if (err && err.code === `NotFound`) { callback(null, undefined); } else { callback(err, ep); } }); } function findAzureServiceEndpoint(account, projectId, sub, token, gen, callback) { 'use strict'; // There is nothing to do if (!sub.name) { callback(null, null); return; } var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${projectId}/_apis/distributedtask/serviceendpoints`, "qs": { "api-version": SERVICE_ENDPOINTS_API_VERSION } }); request(options, function (error, response, body) { var obj = JSON.parse(body); var endpoint = obj.value.find(function (i) { return i.data.subscriptionName !== undefined && i.data.subscriptionName.toLowerCase() === sub.name.toLowerCase(); }); if (endpoint === undefined) { callback({ "message": `x Could not find Azure Service Endpoint`, "code": `NotFound` }, undefined); } else { // Down stream we need the full endpoint so call again with the ID. This will return more data getServiceEndpoint(account, projectId, endpoint.id, token, callback); } }); } function findAzureSub(account, subName, token, gen, callback) { "use strict"; var options = addUserAgent({ method: 'GET', headers: { 'cache-control': 'no-cache', 'content-type': 'application/json', 'authorization': `Basic ${token}` }, url: `${getFullURL(account)}/_apis/distributedtask/serviceendpointproxy/azurermsubscriptions` }); request(options, function (error, response, body) { var obj = JSON.parse(body); var sub = obj.value.find(function (i) { return i.displayName.toLowerCase() === subName.toLowerCase(); }); callback(error, sub); }); } function tryFindProject(account, project, token, gen, callback) { 'use strict'; // Will NOT throw an error if the project is not found. This is used // by code that will create the project if it is not found. findProject(account, project, token, gen, function (err, obj) { if (err && err.code === `NotFound`) { callback(null, undefined); } else { callback(err, obj); } }); } function findProject(account, project, token, gen, callback) { 'use strict'; // Will throw an error if the project is not found. This is used // by code that requires a project and will stop execution if the // project is not found. var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/_apis/projects/${project}`, "qs": { "api-version": PROJECT_API_VERSION } }); request(options, function (err, res, body) { if (err) { callback(err, null); return; } // Test for this before you try and parse the body. // When a 203 is returned the body is HTML instead of // JSON and will throw an exception if you try and parse. // I only test this here because the project is required for // all other items. if (res.statusCode === 203) { // You get this when the site tries to send you to the // login page. gen.log.error(`Unable to authenticate with Team Services. Check account name and personal access token.`); callback({ "message": `Unable to authenticate with Team Services. Check account name and personal access token.` }); return; } if (res.statusCode === 404) { // Returning a undefined project indicates it was not found callback({ "message": `x Project ${project} not found`, "code": `NotFound` }, undefined); } else { var obj = JSON.parse(body); // Return the team project we just found. callback(err, obj); } }); } function findQueue(name, account, teamProject, token, callback) { 'use strict'; logMessage(`findQueue params: ${name}, ${teamProject.id}`); var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${teamProject.id}/_apis/distributedtask/queues`, "qs": { "api-version": DISTRIBUTED_TASK_API_VERSION, "queueName": name } }); request(options, function (err, res, body) { var obj = JSON.parse(body); if (res.statusCode >= 400) { callback(new Error(res.statusMessage), null); } else if (res.statusCode >= 300) { // When it is a 300 the obj is a error // object from the server callback(obj); } else { // Setting to null is the all clear signal to the async // series to continue logMessage(`findQueue: ${res.statusCode}, ${obj}`); callback(null, obj.value[0].id); } }); } function tryFindBuild(account, teamProject, token, target, callback) { 'use strict'; findBuild(account, teamProject, token, target, function (e, bld) { if (e && e.code === `NotFound`) { callback(null, undefined); } else { callback(e, bld); } }); } function findBuild(account, teamProject, token, target, callback) { 'use strict'; var name = isDocker(target) ? `${teamProject.name}-Docker-CI` : `${teamProject.name}-CI`; var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/${teamProject.id}/_apis/build/definitions`, "qs": { "api-version": BUILD_API_VERSION } }); request(options, function (e, response, body) { var obj = JSON.parse(body); var bld = obj.value.find(function (i) { return i.name.toLowerCase() === name.toLowerCase(); }); if (!bld) { callback({ "message": `x Build ${name} not found`, "code": `NotFound` }, undefined); } else { callback(e, bld); } }); } function tryFindRelease(args, callback) { 'use strict'; findRelease(args, function (e, rel) { if (e && e.code === `NotFound`) { callback(null, undefined); } else { callback(e, rel); } }); } function findRelease(args, callback) { "use strict"; var name = isDocker(args.target) ? `${args.botName}-Docker-CD` : `${args.botName}-CD`; var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${args.token}` }, "url": `${getFullURL(args.account, true, RELEASE_MANAGEMENT_SUB_DOMAIN)}/${args.teamProject.name}/_apis/release/definitions`, "qs": { "api-version": RELEASE_API_VERSION } }); request(options, function (e, response, body) { var obj = JSON.parse(body); var rel = obj.value.find(function (i) { return i.name.toLowerCase() === name.toLowerCase(); }); if (!rel) { callback({ "message": `x Release ${name} not found`, "code": `NotFound` }, undefined); } else { callback(e, rel); } }); } function getAzureSubs(answers) { "use strict"; var token = encodePat(answers.pat); var options = addUserAgent({ method: 'GET', headers: { 'cache-control': 'no-cache', 'content-type': 'application/json', 'authorization': `Basic ${token}` }, url: `${getFullURL(answers.tfs)}/_apis/distributedtask/serviceendpointproxy/azurermsubscriptions` }); return new Promise(function (resolve, reject) { request(options, function (e, response, body) { if (e) { reject(e); return; } var obj = JSON.parse(body); var result = []; obj.value.forEach((sub) => { result.push({ name: sub.displayName }); }); resolve(result); }); }); } // TODO:Tried to collect Azure Regions from VSTS service endpoint, however may not created at this stage. // Other option would be to use Azure REST API, but it would involve Auth, breaking experience // Opted by hardcoding Azure Regions function getAzureLocations(answers) { return new Promise(function (resolve, reject) { let locations = [ {name: `East Asia`, value: `East Asia`}, {name: `Southeast Asia`, value: `Southeast Asia`}, {name: `Central US`, value: `Central US`}, {name: `East US`, value: `East US`}, {name: `East US 2`, value: `East US 2`}, {name: `West US`, value: `West US`}, {name: `North Central US`, value: `North Central US`}, {name: `South Central US`, value: `South Central US`}, {name: `North Europe`, value: `North Europe`}, {name: `West Europe`, value: `West Europe`}, {name: `Japan West`, value: `Japan West`}, {name: `Japan East`, value: `Japan East`}, {name: `Brazil South`, value: `Brazil South`}, {name: `Australia East`, value: `Australia East`}, {name: `Australia Southeast`, value: `Australia Southeast`}, {name: `South India`, value: `South India`}, {name: `Central India`, value: `Central India`}, {name: `West India`, value: `West India`}, {name: `Canada Central`, value: `Canada Central`}, {name: `Canada East`, value: `Canada East`}, {name: `UK South`, value: `UK South`}, {name: `UK West`, value: `UK West`}, {name: `West Central US`, value: `West Central US`}, {name: `West US 2`, value: `West US 2`}, {name: `Korea Central`, value: `Korea Central`}, {name: `Korea South`, value: `Korea South`}, {name: `France Central`, value: `France Central`}, {name: `France South`, value: `France South`}, {name: `Australia Central`, value: `Australia Central`}, {name: `Australia Central 2`, value: `Australia Central 2`}]; resolve(locations); }); } function getProfileCommands(answers) { "use strict"; return [{ name: `Add`, value: `add` }, { name: `List`, value: `list` }, { name: `Delete`, value: `delete` }]; } function getPools(answers) { "use strict"; var token = encodePat(answers.pat); var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(answers.tfs)}/_apis/distributedtask/pools`, "qs": { "api-version": DISTRIBUTED_TASK_API_VERSION } }); return new Promise(function (resolve, reject) { request(options, function (e, response, body) { if (e) { reject(e); return; } var obj = JSON.parse(body); resolve(obj.value); }); }); } function isDockerHub(dockerRegistry) { return dockerRegistry.toLowerCase().match(/index.docker.io/) !== null; } function loadProfiles() { let results = { error: null, profiles: null }; if (fs.existsSync(PROFILE_PATH)) { try { results.profiles = JSON.parse(fs.readFileSync(PROFILE_PATH, 'utf8')); } catch (error) { // The file is invalid results.error = `Invalid file.`; } } else { results.error = `No profiles file.`; } return results; } // Reads profiles created by the VSTeam PowerShell module. // Search ignores case. // Yo team stores the full URL of TFS and the account name // for VSTS. The profile name might not match the account // name or full URL. So when you run Yo Team again it will // default to the full URL or account name and fail to find // the profile and prompt for the PAT. Therefore, here you // need to seach the profile name and URL function searchProfiles(input) { let results = loadProfiles(); if (results.profiles !== null) { var found = results.profiles.filter(function (i) { return (i.Name.toLowerCase().includes(input.toLowerCase()) || i.URL.toLowerCase().includes(input.toLowerCase())) && i.Type === `Pat`; }); if (found.length > 1) { // Try to find the correct entry found = results.profiles.filter(function (i) { return (i.Name.toLowerCase() === input.toLowerCase() || i.URL.toLowerCase() === input.toLowerCase()); }); } if (found.length === 1) { return found[0]; } } return null; } // Used to determine if we need to prompt the user for a PAT // If the PAT was read from the profile this method returns // false letting the prompt know it does not need to ask for // a PAT. function readPatFromProfile(answers, obj) { if (profile) { // Profiles are stored 64 bit encoded let b = new Buffer(profile.Pat, 'base64'); // Skip the leading : obj.options.pat = b.toString().substring(1); } // If the value was passed on the command line it will // not be set in answers which other prompts expect. // So, place it in answers now. answers.pat = obj.options.pat; return obj.options.pat === undefined; } function extractInstance(input) { profile = searchProfiles(input); if (profile !== null) { input = profile.URL; } // When using VSTS we only want the account name but // people continue to give the entire url which will // cause issues later. So check to see if the value // provided contains visualstudio.com and if so extract // the account name and simply return that. // If you find visualstudio.com in the name the user most // likely entered the entire URL instead of just the account name // so lets extract it. if (input.toLowerCase().match(/visualstudio.com/) === null) { return input; } var myRegexp = /([^/]+)\.visualstudio.com/; var match = myRegexp.exec(input); return match[1]; } function needsRegistry(answers, options) { if (options !== undefined) { return (answers.target === `docker` || options.target === `docker` || answers.target === `acilinux` || options.target === `acilinux` || answers.target === `dockerpaas` || options.target === `dockerpaas`); } else { return (answers.target === `docker` || answers.target === `acilinux` || answers.target === `dockerpaas`); } } function needsDockerHost(answers, options) { let isDocker; let paasRequiresHost; if (options !== undefined) { // If you pass in the target on the command line // answers.target will be undefined so test options isDocker = (answers.target === `docker` || options.target === `docker`); // This will be true if the user did not select the Hosted Linux queue paasRequiresHost = (answers.target === `dockerpaas` || options.target === `dockerpaas` || answers.target === `acilinux` || options.target === `acilinux`) && ((answers.queue === undefined || answers.queue.indexOf(`Linux`) === -1) && (options.queue === undefined || options.queue.indexOf(`Linux`) === -1)); } else { // If you pass in the target on the command line // answers.target will be undefined so test options isDocker = answers.target === `docker`; // This will be true the user did not select the Hosted Linux queue paasRequiresHost = (answers.target === `dockerpaas` || answers.target === `acilinux`) && answers.queue.indexOf(`Linux`) === -1; } logMessage(`needsDockerHost returning = ${isDocker || paasRequiresHost}`); return (isDocker || paasRequiresHost); } function isPaaS(answers, cmdLnInput) { if (cmdLnInput !== undefined) { return (answers.target === `paas` || cmdLnInput.options.target === `paas` || answers.target === `paasslots` || cmdLnInput.options.target === `paasslots` || answers.target === `acilinux` || cmdLnInput.options.target === `acilinux` || answers.target === `dockerpaas` || cmdLnInput.options.target === `dockerpaas`); } else { return (answers.target === `paas` || answers.target === `paasslots` || answers.target === `acilinux` || answers.target === `dockerpaas`); } } function isVSTS(instance) { if (instance) { return instance.toLowerCase().match(/http/) === null || instance.toLowerCase().match(/visualstudio\.com/) !== null; } return false; } function supportsLoadTests(account, token, callback) { if (isVSTS(account)) { let pat = encodePat(token); // We can determine if this is 2017 or not by searching for a // specific docker task. var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${pat}` }, "url": `${getFullURL(account)}/_api/_account/GetAccountSettings?__v=5` }); request(options, function (error, response, body) { if (error) { callback(error, undefined); } else if (response.statusCode >= 200 && response.statusCode < 300) { let obj = {}; let result = true; try { obj = JSON.parse(body); if (obj.accountRegion === `West Central US`) { result = false; } callback(undefined, result); } catch (error) { // This a HTML page with an error message. err = error; console.log(body); callback(err, undefined); } } else { callback(undefined, undefined); } }); } else { callback(undefined, false); } } // // Returns true if the extension is installed. // Despite the resource name of installedextensionsbyname you actually have to pass in the ID. // https://{accountName}.extmgmt.visualstudio.com/_apis/extensionmanagement/installedextensionsbyname/{publisherId}/{extensionId} // function isExtensionInstalled(account, token, publisherId, extensionId, callback) { var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/_apis/extensionmanagement/installedextensionsbyname/${publisherId}/${extensionId}` }); request(options, function (error, response, body) { if (error) { callback(undefined, false); } else { callback(undefined, true); } }); } // // token must be encoded before calling this function. // function isTFSGreaterThan2017(account, token, callback) { if (isVSTS(account)) { callback(undefined, true); } else { // We can determine if this is 2017 or not by reviewing // the values from teh serviceLevel var options = addUserAgent({ "method": `GET`, "headers": { "cache-control": `no-cache`, "authorization": `Basic ${token}` }, "url": `${getFullURL(account)}/_apis/servicelevel` }); request(options, function (error, response, body) { if (error) { callback(error, undefined); } else if (response.statusCode === 200) { var obj = JSON.parse(body); if (obj.configurationDatabaseServiceLevel.startsWith(`Dev15`)) { callback(undefined, false); } else { callback(undefined, true); } } else { callback(undefined, true); } }); } } function getFullURL(instance, includeCollection, subDomain) { // The user MUST only enter the VSTS account name and not the full url. // This is how the system determines which system is being targeted. Some // URL for VSTS are not the same as they are for TFS for example Release // Management. // When used with VSTS all the user provides is the account name. But When // it is used with TFS they give the full url including the collection. This // functions makes sure any code needing the full URL gets that they need to // continue without worrying if it is TFS or VSTS. if (instance.toLowerCase().match(/http/) !== null) { return instance; } var vstsURL = `https://${instance}.visualstudio.com`; if (subDomain) { vstsURL = `https://${instance}.${subDomain}.visualstudio.com`; } if (includeCollection) { vstsURL += `/DefaultCollection`; } return vstsURL; } module.exports = { // Exports the portions of the file we want to share with files that require // it. PROFILE_PATH: PROFILE_PATH, BUILD_API_VERSION: BUILD_API_VERSION, PROJECT_API_VERSION: PROJECT_API_VERSION, RELEASE_API_VERSION: RELEASE_API_VERSION, EXTENSIONS_SUB_DOMAIN: EXTENSIONS_SUB_DOMAIN, DISTRIBUTED_TASK_API_VERSION: DISTRIBUTED_TASK_API_VERSION, SERVICE_ENDPOINTS_API_VERSION: SERVICE_ENDPOINTS_API_VERSION, RELEASE_MANAGEMENT_SUB_DOMAIN: RELEASE_MANAGEMENT_SUB_DOMAIN, isVSTS: isVSTS, isPaaS: isPaaS, getPools: getPools, tokenize: tokenize, isDocker: isDocker, encodePat: encodePat, findQueue: findQueue, findBuild: findBuild, getFullURL: getFullURL, logMessage: logMessage, getTargets: getTargets, getBotTypes: getBotTypes, getBotBuilderVersions: getBotBuilderVersions, checkStatus: checkStatus, findProject: findProject, findRelease: findRelease, validateTFS: validateTFS, isDockerHub: isDockerHub, getAzureSubs: getAzureSubs, findAzureSub: findAzureSub, loadProfiles: loadProfiles, getPATPrompt: getPATPrompt, tryFindBuild: tryFindBuild, addUserAgent: addUserAgent, getUserAgent: getUserAgent, needsRegistry: needsRegistry, tryFindRelease: tryFindRelease, reconcileValue: reconcileValue, searchProfiles: searchProfiles, tryFindProject: tryFindProject, extractInstance: extractInstance, needsDockerHost: needsDockerHost, validateAzureSub: validateAzureSub, getInstancePrompt: getInstancePrompt, getImageNamespace: getImageNamespace, supportsLoadTests: supportsLoadTests, getProfileCommands: getProfileCommands, readPatFromProfile: readPatFromProfile, validateDockerHost: validateDockerHost, validateAzureSubID: validateAzureSubID, validatePortMapping: validatePortMapping, validateProfileName: validateProfileName, validateDockerHubID: validateDockerHubID, isExtensionInstalled: isExtensionInstalled, isTFSGreaterThan2017: isTFSGreaterThan2017, // validateCustomFolder: validateCustomFolder, getDefaultPortMapping: getDefaultPortMapping, validateAzureTenantID: validateAzureTenantID, validateDockerRegistry: validateDockerRegistry, validateBotName: validateBotName, validateAppId: validateAppId, validateAppPasswd: validateAppPasswd, validateBotLocation: validateBotLocation, getAzureLocations: getAzureLocations, findAzureServiceEndpoint: findAzureServiceEndpoint, getDockerRegistryServer: getDockerRegistryServer, findDockerServiceEndpoint: findDockerServiceEndpoint, validateDockerHubPassword: validateDockerHubPassword, validateServicePrincipalID: validateServicePrincipalID, validateServicePrincipalKey: validateServicePrincipalKey, tryFindAzureServiceEndpoint: tryFindAzureServiceEndpoint, validatePersonalAccessToken: validatePersonalAccessToken, tryFindDockerServiceEndpoint: tryFindDockerServiceEndpoint, validateDockerCertificatePath: validateDockerCertificatePath, findDockerRegistryServiceEndpoint: findDockerRegistryServiceEndpoint, tryFindDockerRegistryServiceEndpoint: tryFindDockerRegistryServiceEndpoint };