diff --git a/ci/ci-test-tasks/test-and-verify-v2/package-lock.json b/ci/ci-test-tasks/test-and-verify-v2/package-lock.json index 49713f3b4f86..2e3a209d2a1d 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/package-lock.json +++ b/ci/ci-test-tasks/test-and-verify-v2/package-lock.json @@ -1,137 +1,162 @@ { - "name": "test-and-verify-v2", - "lockfileVersion": 3, "requires": true, - "packages": { - "": { - "dependencies": { - "axios": "^1.6.2" - }, - "devDependencies": { - "@tsconfig/node20": "^20.1.2", - "@types/node": "^20.9.1", - "typescript": "^5.2.2" - } - }, - "node_modules/@tsconfig/node20": { + "lockfileVersion": 1, + "dependencies": { + "@tsconfig/node20": { "version": "20.1.2", "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.2.tgz", "integrity": "sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==", "dev": true }, - "node_modules/@types/node": { - "version": "20.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz", - "integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==", + "@types/node": { + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dev": true, - "dependencies": { + "requires": { "undici-types": "~5.26.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "azure-devops-node-api": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.1.0.tgz", + "integrity": "sha512-VY+G45eNKVJfMIO0uyZfbi4PzUR8JHEfsHQjEUAXUGRkYhhBbhGHjy8cpiyYFxLXc3a4PL5cqgqqV/YD1SaCXg==", + "requires": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" } }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "requires": { + "get-intrinsic": "^1.2.2" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, - "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" } }, - "node_modules/undici-types": { + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, + "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", diff --git a/ci/ci-test-tasks/test-and-verify-v2/package.json b/ci/ci-test-tasks/test-and-verify-v2/package.json index 670d3f005ac6..a71a82c386ff 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/package.json +++ b/ci/ci-test-tasks/test-and-verify-v2/package.json @@ -4,7 +4,7 @@ "build": "tsc" }, "dependencies": { - "axios": "^1.6.2" + "azure-devops-node-api": "^12.1.0" }, "devDependencies": { "@tsconfig/node20": "^20.1.2", diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/config.ts b/ci/ci-test-tasks/test-and-verify-v2/src/config.ts index e61ead3ea1c8..c1980c224bb9 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/src/config.ts +++ b/ci/ci-test-tasks/test-and-verify-v2/src/config.ts @@ -1,11 +1,11 @@ +import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api'; + class Config { public readonly AuthToken: string; public readonly AdoUrl: string; public readonly ProjectName: string; public readonly TaskArg: string; - public readonly ApiUrl: string; - // TODO: Replace axios usage to node-api. Need to add support of Pipelines API. - public readonly AxiosAuth: { auth: { username: string, password: string } }; + public readonly webApi: WebApi; constructor(argv: string[]) { this.AuthToken = argv[2]; @@ -25,14 +25,28 @@ class Config { throw new Error('Task list is not provided'); } - this.ApiUrl = `${this.AdoUrl}/${this.ProjectName}/_apis`; + const authHandler = getPersonalAccessTokenHandler(this.AuthToken); + this.webApi = new WebApi(this.AdoUrl, authHandler); + } - this.AxiosAuth = { - auth: { - username: 'Basic', - password: this.AuthToken - } - } + public async getDefinitions () { + const api = await this.webApi.getBuildApi(); + return await api.getDefinitions(this.ProjectName); + } + + public async queueBuild (definitionId: number, parameters = {}) { + const api = await this.webApi.getBuildApi(); + return await api.queueBuild({ definition: { id: definitionId }, parameters: JSON.stringify(parameters) }, this.ProjectName); + } + + public async getBuild (buildId: number) { + const api = await this.webApi.getBuildApi(); + return api.getBuild(this.ProjectName, buildId); + } + + public async updateBuild (buildId: number) { + const api = await this.webApi.getBuildApi(); + return api.updateBuild({}, this.ProjectName, buildId, true); } } diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/constants.ts b/ci/ci-test-tasks/test-and-verify-v2/src/constants.ts deleted file mode 100644 index 07e638e53206..000000000000 --- a/ci/ci-test-tasks/test-and-verify-v2/src/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const API_VERSION = 'api-version=7.0'; diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Build.ts b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Build.ts index 22577553cbe3..4ca625ee98ae 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Build.ts +++ b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Build.ts @@ -1,33 +1,32 @@ -import axios from "axios"; +import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { configInstance } from './config'; -import { PipelineBuild } from "./interfaces"; -import { API_VERSION } from "./constants"; -import { configInstance } from "./config"; +const buildLogData = (build: Build) => `[Definition Name: ${build.definition?.name}, Build Number: ${build.buildNumber}, Build Id: ${build.id}]`; -export async function fetchBuildStatus(pipelineBuild: PipelineBuild): Promise { +export async function fetchBuildStatus(pipelineBuild: Build): Promise { const intervalInSeconds = 20; const maxRetries = 10; let retryCount = 0; - const getBuildPromise = new Promise((resolve, reject) => { + const getBuildPromise = new Promise((resolve, reject) => { const interval = setInterval( async () => { try { - const res = await axios.get(pipelineBuild.url, configInstance.AxiosAuth); + const res = await configInstance.getBuild(pipelineBuild.id!); clearInterval(interval); - resolve(res.data); + resolve(res); } catch (err: any) { if (['ETIMEDOUT', 'ECONNRESET'].includes(err.code) || err.response?.status >= 500) { if (retryCount < maxRetries) { retryCount++; - console.log(`Error verifying state of the [${pipelineBuild.name} ${pipelineBuild.id}] build, retry request. Retry count: ${retryCount} out of ${maxRetries}. Error message: ${err.message}`); + console.log(`Error verifying state of the ${buildLogData(pipelineBuild)} build, retry request. Retry count: ${retryCount} out of ${maxRetries}. Error message: ${err.message}`); return; } else { - console.error(`Error verifying state of the [${pipelineBuild.name} ${pipelineBuild.id}], retries limit reached. Cancel retries. Error message: ${err.message}`); + console.error(`Error verifying state of the ${buildLogData(pipelineBuild)} build, retries limit reached. Cancel retries. Error message: ${err.message}`); } } @@ -49,21 +48,12 @@ export async function fetchBuildStatus(pipelineBuild: PipelineBuild): Promise { - const buildLogData = `(Name: ${pipelineBuild.name}, Id: ${pipelineBuild.id})`; - +export async function retryFailedJobsInBuild(pipelineBuild: Build): Promise { try { - await axios.patch( - `${configInstance.ApiUrl}/build/builds/${pipelineBuild.id}?retry=true&${API_VERSION}`, - undefined, - { - ...configInstance.AxiosAuth, - headers: { 'Content-Type': 'application/json' } - } - ) + await configInstance.updateBuild(pipelineBuild.id!); } catch (err: any) { - err.stack = `Error retrying failed jobs in build ${buildLogData}. Error: ${err.stack}`; + err.stack = `Error retrying failed jobs in build ${buildLogData(pipelineBuild)}. Error: ${err.stack}`; console.error(err.stack); if (err.response?.data) { console.error(err.response.data); diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Pipeline.ts b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Pipeline.ts index 44738a211383..d90139208a62 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Pipeline.ts +++ b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.Pipeline.ts @@ -1,20 +1,16 @@ -import axios from "axios"; -import { configInstance } from "./config"; -import { API_VERSION } from "./constants"; -import { PipelineBuild } from "./interfaces"; +import { BuildDefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { configInstance } from './config'; // We're caching pipelines since we assume they will not change during the plan execution. export function fetchPipelines() { - let cachedPipelines: PipelineBuild[] = []; + let cachedPipelines: BuildDefinitionReference[] = []; - return async (): Promise => { + return async (): Promise => { if (cachedPipelines.length > 0) { return new Promise((resolve) => resolve(cachedPipelines)); } try { - const res = await axios - .get(`${configInstance.ApiUrl}/pipelines?${API_VERSION}`, configInstance.AxiosAuth); - cachedPipelines = res.data.value; + cachedPipelines = await configInstance.getDefinitions(); return cachedPipelines; } catch (err: any) { diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.ts b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.ts index 2f7ca6393554..1e959027ec5d 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/src/helpers.ts +++ b/ci/ci-test-tasks/test-and-verify-v2/src/helpers.ts @@ -29,5 +29,3 @@ export function getBuildConfigs(task: string): string[] { return [task]; } } - -export const pipelineVariable = (key: string, value: string) => ({ [key]: { value, isSecret: false } }); diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/index.ts b/ci/ci-test-tasks/test-and-verify-v2/src/index.ts index 52836e91d23a..e7025fbfb5f7 100644 --- a/ci/ci-test-tasks/test-and-verify-v2/src/index.ts +++ b/ci/ci-test-tasks/test-and-verify-v2/src/index.ts @@ -1,35 +1,52 @@ +import { BuildDefinitionReference, Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; -import axios from 'axios'; - -import { PipelineBuild } from './interfaces'; -import { getBuildConfigs, pipelineVariable } from './helpers'; import { configInstance } from './config'; -import { API_VERSION } from './constants'; import { fetchBuildStatus, retryFailedJobsInBuild } from './helpers.Build'; import { fetchPipelines } from './helpers.Pipeline'; +import { getBuildConfigs } from './helpers'; + +const DISABLED = 'disabled'; async function main() { const tasks = configInstance.TaskArg.split(','); + const disabledPipelines: string[] = []; const runningTestBuilds: Promise[] = []; for (const task of tasks) { console.log(`starting tests for ${task} task`); - runningTestBuilds.push(...await runTaskPipelines(task)); + const runResult = await runTaskPipelines(task); + + if (runResult == DISABLED) { + disabledPipelines.push(task); + } else { + runningTestBuilds.push(...runResult); + } } Promise.all(runningTestBuilds).then(results => { - console.log(results); + console.log('\nResults:'); + results.map(result => console.log(result)); }).catch(error => { console.error(error); + }).finally(() => { + if (disabledPipelines.length > 0) console.log(` +##vso[task.issue type=warning]Disabled pipelines: +${disabledPipelines.join('\n')} +`); }); } // Running test pipelines for task by build configs -async function runTaskPipelines(taskName: string): Promise[]> { +async function runTaskPipelines(taskName: string): Promise[] | typeof DISABLED> { const pipelines = await fetchPipelines()(); const pipeline = pipelines.find(pipeline => pipeline.name === taskName); if (pipeline) { + if (pipeline.queueStatus === 2) { // disabled + console.log(`Build pipeline "${pipeline.name}" is disabled for project "${configInstance.ProjectName}".`); + return DISABLED; + } + const configs = getBuildConfigs(taskName); console.log(`Detected buildconfigs ${JSON.stringify(configs)}`); @@ -38,7 +55,7 @@ async function runTaskPipelines(taskName: string): Promise[]> { console.log(`Running tests for "${taskName}" task with config "${config}" for pipeline "${pipeline.name}"`) const pipelineBuild = await startTestPipeline(pipeline, config); - const buildPromise = new Promise((resolve, reject) => completeBuild(taskName, pipelineBuild, resolve, reject)) + const buildPromise = new Promise(resolve => completeBuild(taskName, pipelineBuild, resolve)) runningBuilds.push(buildPromise); } @@ -50,7 +67,7 @@ async function runTaskPipelines(taskName: string): Promise[]> { return []; } -async function startTestPipeline(pipeline: PipelineBuild, config = ''): Promise { +async function startTestPipeline(pipeline: BuildDefinitionReference, config = ''): Promise { console.log(`Run ${pipeline.name} pipeline, pipelineId: ${pipeline.id}`); const { BUILD_SOURCEVERSION: branch, CANARY_TEST_NODE_VERSION: nodeVersion } = process.env; @@ -59,21 +76,12 @@ async function startTestPipeline(pipeline: PipelineBuild, config = ''): Promise< } try { - const res = await axios - .post( - `${configInstance.ApiUrl}/pipelines/${pipeline.id}/runs?${API_VERSION}`, - { - variables: { - ...pipelineVariable('CANARY_TEST_TASKNAME', pipeline.name), - ...pipelineVariable('CANARY_TEST_BRANCH', branch), - ...pipelineVariable('CANARY_TEST_CONFIG', config), - ...pipelineVariable('CANARY_TEST_NODE_VERSION', nodeVersion) - }, - }, - configInstance.AxiosAuth - ); - - return res.data; + return await configInstance.queueBuild(pipeline.id!, { + CANARY_TEST_TASKNAME: pipeline.name, + CANARY_TEST_BRANCH: branch, + CANARY_TEST_CONFIG: config, + CANARY_TEST_NODE_VERSION: nodeVersion + }); } catch (err: any) { err.stack = `Error running ${pipeline.name} pipeline. Stack: ${err.stack}`; console.error(err.stack); @@ -87,11 +95,10 @@ async function startTestPipeline(pipeline: PipelineBuild, config = ''): Promise< async function completeBuild( pipelineName: string, - pipelineBuild: PipelineBuild, - resolve: (value: string) => void, - reject: (reason?: any) => void + pipelineBuild: Build, + resolve: (value: string) => void ): Promise { - const maxRetries = 10; + const maxRetries = 3; const buildTimeoutInSeconds = 300 * 60; const intervalInSeconds = 20; @@ -103,23 +110,22 @@ async function completeBuild( const interval = setInterval( async () => { const buildStatus = await fetchBuildStatus(pipelineBuild); - console.log(`State of the ${stringifiedBuild}: "${buildStatus.state}"`); + console.log(`State of the ${stringifiedBuild}: "${buildStatus.status}"`); - if (buildStatus.state !== 'completed') { + if (buildStatus.status !== 2) { // completed if (++intervalAmount * intervalInSeconds >= buildTimeoutInSeconds) { clearInterval(interval); - reject(new Error(`Timeout to complete the ${stringifiedBuild} exceeded`)); + resolve(`##vso[task.issue type=error]Timeout to complete the ${stringifiedBuild} exceeded`); } return; } - if (buildStatus.result === 'succeeded') { + if (buildStatus.result === 2) { // succeeded clearInterval(interval); - const result = `The ${stringifiedBuild} completed with result "${buildStatus.result}"`; - resolve(result); + resolve(`The ${stringifiedBuild} completed with result "${buildStatus.result}"`); } else if (retryCount < maxRetries) { console.log(`Retrying failed jobs in ${stringifiedBuild}. Retry count: ${++retryCount} out of ${maxRetries}`); await retryFailedJobsInBuild(pipelineBuild); @@ -127,8 +133,7 @@ async function completeBuild( else { clearInterval(interval); - const result = `The ${stringifiedBuild} completed with result "${buildStatus.result}"`; - reject(new Error(result)); + resolve(`##vso[task.issue type=error]The ${stringifiedBuild} completed with result "${buildStatus.result}"`); } }, intervalInSeconds * 1000 diff --git a/ci/ci-test-tasks/test-and-verify-v2/src/interfaces.ts b/ci/ci-test-tasks/test-and-verify-v2/src/interfaces.ts deleted file mode 100644 index 09ba87aa6ee1..000000000000 --- a/ci/ci-test-tasks/test-and-verify-v2/src/interfaces.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface PipelineBuild { - _links: Links; - url: string; - id: number; - name: string; - result?: string, - state: "completed" | "inProgress" | "notStarted" | "canceling"; -} - -interface Links { - self: Link; - web: Link; -} - -interface Link { - href: string; -}