UNPKG

tfsrestservice

Version:

Service which abstracts calls to the TFS/VSTS REST API and exposes the objects to the clients for further usage.

476 lines 24.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TfsRestService = exports.AuthenticationMethodPersonalAccessToken = exports.AuthenticationMethodBasicAuthentication = exports.AuthenticationMethodOAuthToken = exports.TfsRepositoryType = exports.RepositoryType = exports.OAuthAccessToken = exports.CurrentBuildDefinition = exports.SourceBranch = exports.SourceVersion = exports.ReleaseRequestedForId = exports.ReleaseRequestedForUsername = exports.RequestedForUserId = exports.RequestedForUsername = exports.TeamProjectId = exports.TeamProject = exports.TeamFoundationCollectionUri = void 0; const fs = require("fs"); const url = require("url"); const linqts_1 = require("linqts"); const path = require("path"); const vsts = require("azure-devops-node-api"); const buildInterfaces = require("azure-devops-node-api/interfaces/BuildInterfaces"); const testInterfaces = require("azure-devops-node-api/interfaces/TestInterfaces"); const ts_data_stack_1 = require("ts-data.stack"); const common = require("./generalfunctions"); exports.TeamFoundationCollectionUri = "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"; exports.TeamProject = "SYSTEM_TEAMPROJECT"; exports.TeamProjectId = "SYSTEM_TEAMPROJECTID"; exports.RequestedForUsername = "BUILD_REQUESTEDFOR"; exports.RequestedForUserId = "BUILD_REQUESTEDFORID"; exports.ReleaseRequestedForUsername = "RELEASE_REQUESTEDFOR"; exports.ReleaseRequestedForId = "RELEASE_REQUESTEDFORID"; exports.SourceVersion = "BUILD_SOURCEVERSION"; exports.SourceBranch = "BUILD_SOURCEBRANCH"; exports.CurrentBuildDefinition = "BUILD_DEFINITIONNAME"; exports.OAuthAccessToken = "SYSTEM_ACCESSTOKEN"; exports.RepositoryType = "BUILD_REPOSITORY_PROVIDER"; exports.TfsRepositoryType = "TfsVersionControl"; exports.AuthenticationMethodOAuthToken = "OAuth Token"; exports.AuthenticationMethodBasicAuthentication = "Basic Authentication"; exports.AuthenticationMethodPersonalAccessToken = "Personal Access Token"; class AzureDevOpsWebApi { constructor() { this.connection = null; } initializeConnection(tfsServer, authHandler, requestOptions) { this.connection = new vsts.WebApi(tfsServer, authHandler, requestOptions); } getBuildApi() { return __awaiter(this, void 0, void 0, function* () { this.verifyConnection(); return yield this.connection.getBuildApi(); }); } getTestApi() { return __awaiter(this, void 0, void 0, function* () { this.verifyConnection(); return yield this.connection.getTestApi(); }); } getTaskAgentApi() { return __awaiter(this, void 0, void 0, function* () { this.verifyConnection(); return yield this.connection.getTaskAgentApi(); }); } getCoreApi() { return __awaiter(this, void 0, void 0, function* () { this.verifyConnection(); return yield this.connection.getCoreApi(); }); } getBearerHandler(bearerToken) { return vsts.getBearerHandler(bearerToken); } getBasicHandler(username, password) { return vsts.getBasicHandler(username, password); } getHandlerFromToken(token) { return vsts.getHandlerFromToken(token); } verifyConnection() { if (this.connection === null) { throw new Error("Initialize must be called before api's can be fetched!"); } } } class TfsRestService { constructor(azureDevOpsWebApi, generalFunctions) { this.vstsBuildApi = null; this.vstsTestApi = null; this.taskAgentApi = null; this.teamProjectId = ""; this.azureDevOpsWebApi = null; this.genralFunctions = null; if (azureDevOpsWebApi === undefined) { azureDevOpsWebApi = new AzureDevOpsWebApi(); } if (generalFunctions === undefined) { generalFunctions = new common.GeneralFunctions(); } this.azureDevOpsWebApi = azureDevOpsWebApi; this.genralFunctions = generalFunctions; } initialize(authenticationMethod, username, password, tfsServer, teamProject, ignoreSslError) { return __awaiter(this, void 0, void 0, function* () { if (teamProject === "" || teamProject === undefined) { throw new Error("Team Project has to be defined!"); } this.verifyAuthenticationMethod(authenticationMethod, username, password); let authHandler; switch (authenticationMethod) { case exports.AuthenticationMethodOAuthToken: console.log("Using OAuth Access Token"); authHandler = this.azureDevOpsWebApi.getBearerHandler(password); break; case exports.AuthenticationMethodBasicAuthentication: console.log("Using Basic Authentication"); authHandler = this.azureDevOpsWebApi.getBasicHandler(username, password); break; case exports.AuthenticationMethodPersonalAccessToken: console.log("Using Personal Access Token"); authHandler = this.azureDevOpsWebApi.getHandlerFromToken(password); break; default: throw new Error("Cannot handle authentication method " + authenticationMethod); } let requestOptions = { ignoreSslError: ignoreSslError }; this.azureDevOpsWebApi.initializeConnection(tfsServer, authHandler, requestOptions); this.vstsBuildApi = yield this.azureDevOpsWebApi.getBuildApi(); this.vstsTestApi = yield this.azureDevOpsWebApi.getTestApi(); this.taskAgentApi = yield this.azureDevOpsWebApi.getTaskAgentApi(); yield this.setTeamProjectId(this.azureDevOpsWebApi, teamProject); }); } getBuildsByStatus(buildDefinitionName, statusFilter) { return __awaiter(this, void 0, void 0, function* () { var buildDefinitionID = yield this.getBuildDefinitionId(buildDefinitionName); var result = yield this.vstsBuildApi.getBuilds(this.teamProjectId, [buildDefinitionID], null, null, null, null, null, null, statusFilter); return result; }); } triggerBuild(buildDefinitionName, branch, requestedForUserID, sourceVersion, demands, queueId, buildParameters, templateParameters) { return __awaiter(this, void 0, void 0, function* () { var buildId = yield this.getBuildDefinitionId(buildDefinitionName); var buildToTrigger = { definition: { id: buildId }, parameters: this.createBuildParameterObject(buildParameters) }; var tokenizedTemmplateParameters = this.parseParameterString(templateParameters); if (tokenizedTemmplateParameters !== null) { buildToTrigger.templateParameters = tokenizedTemmplateParameters; } if (branch !== null) { buildToTrigger.sourceBranch = branch; } if (requestedForUserID !== undefined && requestedForUserID !== "") { buildToTrigger.requestedFor = { id: requestedForUserID }; } if (sourceVersion !== undefined && sourceVersion !== "") { buildToTrigger.sourceVersion = sourceVersion; } if (queueId !== null && queueId !== undefined) { buildToTrigger.queue = { id: queueId }; } if (demands !== null && demands.length > 0) { buildToTrigger.demands = []; demands.forEach(demand => { var demandSplit = demand.split('='); var demandName = demandSplit[0].trim(); var demandValue = null; if (demandSplit.length > 1) { demandValue = demandSplit[1].trim(); } buildToTrigger.demands.push({ name: demandName, value: demandValue }); }); } var result = yield this.makeRequest(() => this.vstsBuildApi.queueBuild(buildToTrigger, this.teamProjectId, true)); return result; }); } areBuildsFinished(triggeredBuilds, failIfNotSuccessful, treatPartiallySucceededBuildAsSuccessful) { return __awaiter(this, void 0, void 0, function* () { var result = true; for (let queuedBuildId of triggeredBuilds) { var buildInfo = yield this.getBuildInfo(queuedBuildId); var buildFinished = buildInfo.status === buildInterfaces.BuildStatus.Completed; if (!buildFinished) { result = false; } else { result = result && true; var buildSuccessful = buildInfo.result === buildInterfaces.BuildResult.Succeeded; if (!buildSuccessful && treatPartiallySucceededBuildAsSuccessful) { buildSuccessful = buildInfo.result === buildInterfaces.BuildResult.PartiallySucceeded; } if (failIfNotSuccessful && !buildSuccessful) { throw new Error(`Build ${queuedBuildId} (${buildInfo.definition.name}) was not successful. See following link for more info: ${buildInfo._links.web.href}`); } } } return result; }); } cancelBuild(buildId) { return __awaiter(this, void 0, void 0, function* () { var buildInfo = yield this.getBuildInfo(buildId); if (buildInfo.status === buildInterfaces.BuildStatus.Completed) { console.log(`Build ${buildId} has already finished.`); return; } var requestBody = { status: buildInterfaces.BuildStatus.Cancelling }; yield this.makeRequest(() => this.vstsBuildApi.updateBuild(requestBody, this.teamProjectId, buildId)); }); } downloadArtifacts(buildId, downloadDirectory) { return __awaiter(this, void 0, void 0, function* () { console.log(`Downloading artifacts for Build ${buildId}`); if (!fs.existsSync(downloadDirectory)) { console.log(`Directory ${downloadDirectory} does not exist - will be created`); fs.mkdirSync(downloadDirectory); } var result = yield this.makeRequest(() => this.vstsBuildApi.getArtifacts(this.teamProjectId, buildId)); if (result.length === 0) { console.log(`No artifacts found for build ${buildId} - skipping...`); return; } console.log(`Found ${result.length} artifact(s)`); for (let artifact of result) { if (artifact.resource.type.toLowerCase() !== "container") { console.log(`Cannot download artifact ${artifact.name}. Only Containers are supported (type is \"${artifact.resource.type}"\)`); continue; } console.log(`Downloading artifact ${artifact.name}...`); var fileFormat = url.parse(artifact.resource.downloadUrl, true).query.$format; if (fileFormat === null || fileFormat === undefined) { fileFormat = "zip"; } var fileName = `${artifact.name}.${fileFormat}`; var index = 1; var filePath = path.join(downloadDirectory, fileName); while (fs.existsSync(filePath)) { console.log(`${fileName} already exists...`); fileName = `${artifact.name}${index}.${fileFormat}`; filePath = path.join(downloadDirectory, fileName); index++; } const artifactStream = yield this.vstsBuildApi.getArtifactContentZip(this.teamProjectId, buildId, artifact.name); const fileStream = fs.createWriteStream(filePath); artifactStream.pipe(fileStream); fileStream.on("close", () => { console.log(`Stored artifact here: ${filePath}`); }); } }); } getTestRuns(testRunName, numberOfRunsToFetch) { return __awaiter(this, void 0, void 0, function* () { var testRunSummaries = yield this.makeRequest(() => this.vstsTestApi.getTestRuns(this.teamProjectId)); let testRuns = new linqts_1.List(testRunSummaries) .Reverse() .Where(x => x !== undefined && x.state === testInterfaces.TestRunState.Completed.toString() && x.name === testRunName) .Take(numberOfRunsToFetch) .ToArray(); return testRuns.reverse(); }); } getQueueIdByName(buildQueue) { return __awaiter(this, void 0, void 0, function* () { var agentQueues = yield this.makeRequest(() => this.taskAgentApi.getAgentQueues(this.teamProjectId, buildQueue)); if (agentQueues.length === 1) { var agentQueue = agentQueues[0]; return agentQueue.id; } console.error(`No queue found with the name: ${buildQueue}. Following Queues were found (Name (id)):`); for (let queue of agentQueues) { console.error(`${queue.name} (${queue.id})`); } throw new Error(`Could not find any Queue with the name ${buildQueue}`); }); } isBuildFinished(buildId) { return __awaiter(this, void 0, void 0, function* () { var result = yield this.getBuildInfo(buildId); return result.status === buildInterfaces.BuildStatus.Completed; }); } wasBuildSuccessful(buildId) { return __awaiter(this, void 0, void 0, function* () { var result = yield this.getBuildInfo(buildId); return result.result === buildInterfaces.BuildResult.Succeeded; }); } getBuildDefinitionId(buildDefinitionName) { return __awaiter(this, void 0, void 0, function* () { var result = yield this.makeRequest(() => this.vstsBuildApi.getDefinitions(this.teamProjectId, buildDefinitionName)); if (result.length === 0) { console.log(`No build definition with name ${buildDefinitionName} found...`); var buildId = parseInt(buildDefinitionName); if (isNaN(buildId)) { throw new Error(`Did not find any build definition with this name: ${buildDefinitionName}`); } console.log(`Specified build name is a number - will treat as build id...`); return buildId; } return result[0].id; }); } getAssociatedChanges(build) { return __awaiter(this, void 0, void 0, function* () { var result = yield this.makeRequest(() => this.vstsBuildApi.getBuildChanges(this.teamProjectId, build.id)); return result; }); } getBuildInfo(buildId) { return __awaiter(this, void 0, void 0, function* () { var build = yield this.makeRequest(() => this.vstsBuildApi.getBuild(this.teamProjectId, buildId)); return build; }); } parseParameterString(parameterString) { var parameterDictionary = {}; if (parameterString === null || parameterString === undefined || parameterString === "") { return null; } parameterString = parameterString.trim(); var keyValuePairs = parameterString.split(","); for (var index = 0; index < keyValuePairs.length; index++) { var kvp = keyValuePairs[index]; var splittedKvp = kvp.split(/:(.+)/); if (splittedKvp[0] === undefined || splittedKvp[1] === undefined) { var errorMessage = `Parameters were not in expected format. Please verify that parameters are in the following format: \"VariableName: Value\"`; console.error(errorMessage); console.error(`Specified build parameters: ${parameterString}`); throw new Error(errorMessage); } var key = this.cleanValue(splittedKvp[0].trim()); var value = this.cleanValue(splittedKvp[1].trim()); var checkNextValues = true; var openingCurlyBracesStack = new ts_data_stack_1.default(); if (value.startsWith("{")) { console.log(`Identified value as Json Object - will use as is`); this.updateCurlyBracesStack(openingCurlyBracesStack, value); } while (index < keyValuePairs.length - 1 && checkNextValues) { var nextKvp = keyValuePairs[index + 1]; var nextValue = `${this.cleanValue(nextKvp)}`; if (!openingCurlyBracesStack.isEmpty()) { value += `,${nextValue}`; index++; this.updateCurlyBracesStack(openingCurlyBracesStack, nextValue); if (openingCurlyBracesStack.isEmpty()) { checkNextValues = false; } } else if (nextKvp.indexOf(":") === -1) { value += `,${nextValue}`; index++; } else { checkNextValues = false; } } console.log(`Found parameter ${key} with value: ${value}`); parameterDictionary[key] = value; } return parameterDictionary; } createBuildParameterObject(buildParameters) { if (buildParameters === null || buildParameters === undefined) { return ""; } buildParameters = buildParameters.trim(); if (buildParameters.startsWith("{") && buildParameters.endsWith("}")) { console.log(`Specified Build Parameters are a json object - will be treated as is. Please make sure you handled any kind of escaping etc. yourself.`); console.log(`Parameters: ${buildParameters}`); return buildParameters; } return JSON.stringify(this.parseParameterString(buildParameters)); } updateCurlyBracesStack(openingCurlyBracesStack, value) { var openingCurlyBracesInValue = (value.match(/{/g) || []).length; for (var index = 0; index < openingCurlyBracesInValue; index++) { openingCurlyBracesStack.push("{"); } var closingCurlyBracesInValue = (value.match(/}/g) || []).length; for (var index = 0; index < closingCurlyBracesInValue; index++) { openingCurlyBracesStack.pop(); } } cleanValue(value) { if (value.startsWith("\\\"") && value.endsWith("\\\"")) { value = value.substr(2, value.length - 4); } return value; } setTeamProjectId(connection, teamProject) { return __awaiter(this, void 0, void 0, function* () { try { var guidCheckRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; if (guidCheckRegex.test(teamProject)) { console.log("Provided team project was guid."); this.teamProjectId = teamProject; return; } var numberOfProjects = 300; if (process.env.AzureNodeAPI_GetProjects_Top) { numberOfProjects = Number.parseInt(process.env.AzureNodeAPI_GetProjects_Top); } console.log("Provided team project was no guid, trying to resolve ID via API..."); var coreAgentApi = yield connection.getCoreApi(); var projects = yield coreAgentApi.getProjects(null, numberOfProjects); for (let project of projects) { if (project.name === teamProject) { this.teamProjectId = project.id; console.log(`Found id for team project ${teamProject}: ${this.teamProjectId}`); break; } } } catch (err) { throw new Error("Could not access projects - you're version of TFS might be too old, please check online for help."); } if (this.teamProjectId === "") { throw new Error(`Could not find any Team Project with name ${teamProject}`); } }); } verifyAuthenticationMethod(authenticationMethod, username, password) { switch (authenticationMethod) { case exports.AuthenticationMethodOAuthToken: if (password === undefined || password === null || password === "") { throw new Error("No valid OAuth token provided. Please check that you have the OAuth Token Access allowed for your builds."); } break; case exports.AuthenticationMethodBasicAuthentication: if (username === undefined || username === null || username === "") { throw new Error("No Username provided. Please check that you specified the user correctly in the configuration."); } if (password === undefined || password === null || password === "") { throw new Error("No password provided. Please check that you specified the password correctly in the configuration."); } break; case exports.AuthenticationMethodPersonalAccessToken: if (password === undefined || password === null || password === "") { throw new Error("No valid Personal Access Token provided. Please check that you specified the token to be used correctly in the configuration."); } break; } } makeRequest(requestFunction) { return __awaiter(this, void 0, void 0, function* () { var maxRequestTryCount = 5; var maxWaitingTime = 64; for (var requestCount = 0; requestCount < maxRequestTryCount; requestCount++) { try { return yield requestFunction(); } catch (error) { console.log(`Error during request (${requestCount + 1}/${maxRequestTryCount})`); console.log(`Error message: ${error}`); if (requestCount < maxRequestTryCount - 1) { var waitTimeInSeconds = Math.pow(2, requestCount); if (waitTimeInSeconds > maxWaitingTime) { waitTimeInSeconds = maxWaitingTime; } console.log(`Will wait ${waitTimeInSeconds} seconds before retrying request...`); yield this.genralFunctions.sleep(waitTimeInSeconds * 1000); } } } throw new Error(`Request failed after ${maxRequestTryCount} tries - see error messages in the log`); }); } } exports.TfsRestService = TfsRestService; //# sourceMappingURL=index.js.map