generator-csebot
Version:
Generates a bot (Microsoft Bot Framework) with CI/CD in Team Services
276 lines (247 loc) • 9.41 kB
JavaScript
// This is the code that deals with TFS
const fs = require('fs');
const async = require('async');
const uuidV4 = require('uuid/v4');
const request = require('request');
const util = require('../app/utility');
function run(args, gen, done) {
'use strict';
var build = {};
var approverId;
var queueId = 0;
var azureSub = {
name: args.azureSub
};
var teamProject = {};
var azureEndpoint = {};
var approverUniqueName;
var approverDisplayName;
var dockerEndpoint = {};
var dockerRegistryEndpoint = {};
var token = util.encodePat(args.pat);
async.series([
function (mainSeries) {
util.findProject(args.tfs, args.project, token, gen, function (err, tp) {
teamProject = tp;
mainSeries(err, tp);
});
},
function (mainSeries) {
async.parallel([
function (inParallel) {
util.findQueue(args.queue, args.tfs, teamProject, token, function (err, id) {
queueId = id;
inParallel(err, id);
});
},
function (inParallel) {
if (util.needsDockerHost({}, args)) {
util.findDockerServiceEndpoint(args.tfs, teamProject.id, args.dockerHost, token, gen, function (err, ep) {
dockerEndpoint = ep;
inParallel(err, dockerEndpoint);
});
} else {
inParallel(null, undefined);
}
},
function (inParallel) {
if (util.needsRegistry({}, args)) {
util.findDockerRegistryServiceEndpoint(args.tfs, teamProject.id, args.dockerRegistry, token, function (err, ep) {
dockerRegistryEndpoint = ep;
inParallel(err, dockerRegistryEndpoint);
});
} else {
inParallel(null, undefined);
}
},
function (inParallel) {
util.findBuild(args.tfs, teamProject, token, args.target, function (err, bld) {
build = bld;
approverId = bld.authoredBy.id;
approverUniqueName = bld.authoredBy.uniqueName;
approverDisplayName = bld.authoredBy.displayName;
inParallel(err, bld);
});
},
function (inParallel) {
if (util.isPaaS(args)) {
util.findAzureServiceEndpoint(args.tfs, teamProject.id, azureSub, token, gen, function (err, ep) {
azureEndpoint = ep;
inParallel(err, azureEndpoint);
});
} else {
azureEndpoint = undefined;
inParallel(null, undefined);
}
}
], mainSeries);
},
function (mainSeries) {
var relArgs = {
token: token,
build: build,
type: args.type,
queueId: queueId,
account: args.tfs,
target: args.target,
botName: args.botName,
msAppId: args.msAppId,
msAppPasswd: args.msAppPasswd,
location: args.location,
approverId: approverId,
teamProject: teamProject,
template: args.releaseJson,
dockerPorts: args.dockerPorts,
dockerHostEndpoint: dockerEndpoint,
dockerRegistry: args.dockerRegistry,
approverUniqueName: approverUniqueName,
dockerRegistryId: args.dockerRegistryId,
approverDisplayName: approverDisplayName,
dockerRegistryEndpoint: dockerRegistryEndpoint,
endpoint: azureEndpoint ? azureEndpoint.id : null,
dockerRegistryPassword: args.dockerRegistryPassword
};
findOrCreateRelease(relArgs, gen, function (err, rel) {
mainSeries(err, rel);
});
}
], function (err, results) {
// This is just for test and will be undefined during normal use
if (done) {
done(err);
}
if (err) {
// To get the stacktrace run with the --debug built-in option when
// running the generator.
gen.env.error(err.message);
}
});
}
function getRelease(args, callback) {
var release = ``;
let pat = util.encodePat(args.pat);
if (util.isDocker(args.target)) {
// see if they support load tests or not
if (args.removeloadTest && args.target === `dockerpaas`) {
release = `vsts_bot_release_${args.target}_noloadtest.json`;
} else {
release = `vsts_bot_release_${args.target}.json`;
}
callback(release);
} else {
if (args.target === `paasslots`) {
release = `vsts_bot_release_slots.json`;
} else {
// see if they support load tests or not
if (args.removeloadTest) {
release = `vsts_bot_release_noloadtest.json`;
} else {
isCsharp = args.type == 'csharp' ? args.type + '_' : "";
release = `vsts_bot_${isCsharp}release.json`;
}
}
callback(release);
}
}
function findOrCreateRelease(args, gen, callback) {
'use strict';
util.tryFindRelease(args, function (e, rel) {
if (e) {
callback(e);
}
if (!rel) {
createRelease(args, gen, callback);
} else {
gen.log(`+ Found release definition`);
callback(e, rel);
}
});
}
function createRelease(args, gen, callback) {
'use strict';
let releaseDefName = util.isDocker(args.target) ? `${args.teamProject.name}-Docker-CD` : `${args.teamProject.name}-CD`;
gen.log(`+ Creating ${releaseDefName} release definition`);
// Qualify the image name with the dockerRegistryId for docker hub
// or the server name for other registries.
let dockerNamespace = util.getImageNamespace(args.dockerRegistryId, args.dockerRegistryEndpoint);
// Azure website names have to be unique. So we gen a GUID and addUserAgent
// a portion to the site name to help with that.
let uuid = uuidV4();
// Load the template and replace values.
var tokens = {
'{{BuildId}}': args.build.id,
'"{{QueueId}}"': args.queueId,
'{{WebAppName}}': args.botName,
'{{MsAppId}}': args.msAppId,
'{{MsAppPasswd}}': args.msAppPasswd,
'{{azLocation}}': args.location,
'{{uuid}}': uuid.substring(0, 8),
'{{BuildName}}': args.build.name,
'{{ApproverId}}': args.approverId,
'{{ProjectId}}': args.teamProject.id,
'{{ConnectedServiceID}}': args.endpoint,
'{{ProjectName}}': args.teamProject.name,
'{{ApproverDisplayName}}': args.approverDisplayName,
'{{ProjectLowerCase}}': args.teamProject.name.toLowerCase(),
'{{dockerPorts}}': args.dockerPorts ? args.dockerPorts : null,
'{{ApproverUniqueName}}': args.approverUniqueName.replace("\\", "\\\\"),
'{{dockerHostEndpoint}}': args.dockerHostEndpoint ? args.dockerHostEndpoint.id : null,
'{{TemplateFolder}}': args.type === 'csharp' ? `${args.botName}.IaC` : `templates`,
'{{dockerRegistryId}}': dockerNamespace,
'{{containerregistry}}': args.dockerRegistry,
'{{containerregistry_noprotocol}}': args.dockerRegistry ? util.getDockerRegistryServer(args.dockerRegistry) : null,
'{{containerregistry_username}}': args.dockerRegistryId,
'{{containerregistry_password}}': args.dockerRegistryPassword,
'{{dockerRegistryEndpoint}}': args.dockerRegistryEndpoint ? args.dockerRegistryEndpoint.id : null,
'{{ReleaseDefName}}': releaseDefName
};
var contents = fs.readFileSync(args.template, 'utf8');
var options = util.addUserAgent({
method: 'POST',
headers: {
'cache-control': 'no-cache',
'content-type': 'application/json',
'authorization': `Basic ${args.token}`
},
json: true,
url: `${util.getFullURL(args.account, true, util.RELEASE_MANAGEMENT_SUB_DOMAIN)}/${args.teamProject.name}/_apis/release/definitions`,
qs: {
'api-version': util.RELEASE_API_VERSION
},
body: JSON.parse(util.tokenize(contents, tokens))
});
// I have witnessed the release returning a 403 if you try
// to create it too quickly. The release REST API appears
// to return 403 for several reasons and could cause an
// infinite loop on this code waiting on RM to become ready.
var status = '';
async.whilst(
function () {
return status !== 'failed' && status !== 'succeeded';
},
function (finished) {
request(options, function (err, resp, body) {
if (resp.statusCode == 400) {
status = "failed";
finished(new Error("x " + resp.body.message), null);
} else if (resp.statusCode >= 300) {
status = "in progress";
finished(err, null);
} else {
status = "succeeded";
finished(err, body);
}
});
},
function (err, results) {
callback(err, results);
}
);
}
module.exports = {
// Exports the portions of the file we want to share with files that require
// it.
run: run,
getRelease: getRelease,
findOrCreateRelease: findOrCreateRelease
};