From 8480f3b26fe99796a9df3d2f338af6eaeee0bb8f Mon Sep 17 00:00:00 2001 From: NShitrit Date: Tue, 1 Mar 2022 09:49:45 +0200 Subject: [PATCH] user story #1514237:Update CI job root when necessary from Azure devops extension, user story #1515241:Add plugin version for Azure devops CI server and add experiment check service for Azure extension --- src/BaseTask.ts | 153 ++++++++++++++++++-- src/ExtensionConstants.ts | 9 +- src/debug/debug-conf.ts | 2 + templates/private/Tasks/EndTask/task.json | 4 +- templates/private/Tasks/StartTask/task.json | 4 +- templates/private/overview.md | 3 + templates/private/vss-extension.json | 2 +- templates/public/Tasks/EndTask/task.json | 4 +- templates/public/Tasks/StartTask/task.json | 4 +- templates/public/overview.md | 3 + templates/public/vss-extension.json | 2 +- 11 files changed, 164 insertions(+), 26 deletions(-) diff --git a/src/BaseTask.ts b/src/BaseTask.ts index a350004..e254027 100644 --- a/src/BaseTask.ts +++ b/src/BaseTask.ts @@ -15,6 +15,8 @@ import {AuthenticationService} from "./services/security/AuthenticationService"; import {NodeUtils} from "./NodeUtils"; import {SharedSpaceUtils} from "./SharedSpaceUtils"; import {UrlUtils} from "./UrlUtils"; +import {WebApi} from "azure-devops-node-api"; +import {ConnectionUtils} from "./ConnectionUtils"; const Query = require('@microfocus/alm-octane-js-rest-sdk/lib/query'); @@ -44,12 +46,16 @@ export class BaseTask { protected sourceBranchName: string; protected customWebContext: string; protected authenticationService: AuthenticationService; + protected definitionId: number; + protected sourceBranch: string; private octaneServiceConnectionData: any; private url: URL; private sharedSpaceId: string; private workspaces: any; private analyticsCiInternalApiUrlPart: string; + private ciInternalAzureApiUrlPart: string; + private isRunPipelineFromOctaneEnable: boolean; protected constructor(tl: any) { this.tl = tl; @@ -126,6 +132,7 @@ export class BaseTask { private buildAnalyticsCiInternalApiUrlPart() { this.analyticsCiInternalApiUrlPart = '/internal-api/shared_spaces/' + this.sharedSpaceId + '/analytics/ci'; + this.ciInternalAzureApiUrlPart = '/internal-api/shared_spaces/' + this.sharedSpaceId + '/workspaces/{workspace_id}/analytics/ci'; } private setAgentJobName(agentJobName: string) { @@ -168,12 +175,16 @@ export class BaseTask { this.buildId = this.tl.getVariable('Build.BuildId'); this.sourceBranchName = this.tl.getVariable('Build.SourceBranchName'); this.jobStatus = this.convertJobStatus(this.tl.getVariable('AGENT_JOBSTATUS')); + this.definitionId = this.tl.getVariable("System.DefinitionId"); + this.sourceBranch = this.tl.getVariable("Build.SourceBranch"); this.logger.info('collectionUri = ' + this.collectionUri); this.logger.info('projectId = ' + this.projectId); this.logger.info('projectName = ' + this.projectName); this.logger.info('buildDefinitionName = ' + this.buildDefinitionName); this.logger.info('sourceBranchName = ' + this.sourceBranchName); + this.logger.info('definitionId = ' + this.definitionId); + this.logger.info('sourceBranch = ' + this.sourceBranch); } protected convertJobStatus(nativeStatus: string): Result { @@ -221,6 +232,16 @@ export class BaseTask { this.workspaces = this.workspaces.split(',').map(s => s.trim()); } + private async initializeExperiments(octaneSDKConnection,ws):Promise{ + const currentVersion = await this.getOctaneVersion(octaneSDKConnection); + this.logger.info("Octane current version: " + currentVersion); + this.isRunPipelineFromOctaneEnable = this.isVersionGreaterOrEquals(currentVersion,'16.0.316') && await this.isExperimentEnable(octaneSDKConnection,ws); + if(this.isRunPipelineFromOctaneEnable){ + await this.updatePluginVersion(octaneSDKConnection); + this.logger.info("Send plugin details to Octane."); + } + } + private async createOctaneConnectionsAndRetrieveCiServersAndPipelines() { let clientId: string = this.authenticationService.getOctaneClientId(); let clientSecret: string = this.authenticationService.getOctaneClientSecret(); @@ -235,11 +256,11 @@ export class BaseTask { await octaneSDKConnection._requestHandler.authenticate(); } - + await this.initializeExperiments(octaneSDKConnection,ws); let ciServer = await this.getCiServer(octaneSDKConnection, this.agentJobName === BaseTask.ALM_OCTANE_PIPELINE_START); await this.getPipeline(octaneSDKConnection, this.buildDefinitionName, this.pipelineFullName, ciServer, - this.agentJobName === BaseTask.ALM_OCTANE_PIPELINE_START); + this.agentJobName === BaseTask.ALM_OCTANE_PIPELINE_START,ws); this.octaneSDKConnections[ws] = octaneSDKConnection; })(ws); @@ -273,7 +294,7 @@ export class BaseTask { } else { throw new Error('CI Server \'' + this.projectFullName + '(instanceId=\'' + this.instanceId + '\')\' not found.'); } - } else { + } else if(!this.isRunPipelineFromOctaneEnable){ let ciServer = { 'name': this.projectFullName, 'url': serverUrl, @@ -311,21 +332,127 @@ export class BaseTask { return ciServers; } - protected async getPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer, createOnAbsence) { - let pipelineQuery = Query.field('name').equal(BaseTask.escapeOctaneQueryValue(pipelineName)) + protected async getPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer, createOnAbsence,workspaceId) { + let pipelines; + if(this.isRunPipelineFromOctaneEnable){ + const pipelineQuery = Query.field('ci_id').equal(BaseTask.escapeOctaneQueryValue(this.projectFullName + '.' + this.buildDefinitionName)) + .and(Query.field(EntityTypeConstants.CI_SERVER_ENTITY_TYPE).equal(Query.field('id').equal(ciServer.id))).build(); + const ciJobs = await octaneSDKConnection.get(EntityTypeRestEndpointConstants.CI_JOB_REST_API_NAME) + .fields('pipeline,definition_id') + .query(pipelineQuery).execute(); + + if(ciJobs && ciJobs.total_count > 0 && ciJobs.data) { + pipelines = ciJobs.data.filter(ciJob => ciJob.pipeline).map(ciJob => ciJob.pipeline); + } + if (!pipelines || pipelines.length == 0) { + if(createOnAbsence) { + rootJobName = rootJobName + '@@@' + this.definitionId + '@@@' + this.sourceBranch; + let result = await this.createPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer); + return result[0].data[0]; + } else { + throw new Error('Pipeline \'' + pipelineName + '\' not found.') + } + } else { + this.logger.debug('Checking if have CI jobs to update Octane'); + const ciJobsToUpdate = ciJobs.data.filter(ciJob => ciJob.pipeline && ciJob?.definition_id !== this.definitionId); + if(ciJobsToUpdate?.length > 0) { + this.logger.info('Updating ' + ciJobsToUpdate.length +' CI jobs of Octane'); + await this.updateExistCIJobs(ciJobsToUpdate,ciServer.id,workspaceId,octaneSDKConnection); + } + } + return pipelines[0]; + } else { + const pipelineQuery = Query.field('name').equal(BaseTask.escapeOctaneQueryValue(pipelineName)) .and(Query.field(EntityTypeConstants.CI_SERVER_ENTITY_TYPE).equal(Query.field('id').equal(ciServer.id))).build(); + pipelines = await octaneSDKConnection.get(EntityTypeRestEndpointConstants.PIPELINES_REST_API_NAME).query(pipelineQuery).execute(); + if (!pipelines || pipelines.total_count == 0 || pipelines.data.length == 0) { + if (createOnAbsence) { + let result = await this.createPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer); + return result[0].data[0]; + } else { + throw new Error('Pipeline \'' + pipelineName + '\' not found.') + } + } + return pipelines.data[0]; + } - let pipelines = await octaneSDKConnection.get(EntityTypeRestEndpointConstants.PIPELINES_REST_API_NAME).query(pipelineQuery).execute(); - if (!pipelines || pipelines.total_count == 0 || pipelines.data.length == 0) { - if (createOnAbsence) { - let result = await this.createPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer); - return result[0].data[0]; - } else { - throw new Error('Pipeline \'' + pipelineName + '\' not found.') + + } + + private async getOctaneVersion(octaneSDKConnection): Promise{ + const urlStatus = this.analyticsCiInternalApiUrlPart +'/servers/connectivity/status' + const response = await octaneSDKConnection._requestHandler._requestor.get(urlStatus); + this.logger.debug("Octane connectivity status response: " + response); + return response.octaneVersion; + } + private async updatePluginVersion(octaneSDKConnection): Promise{ + const querystring = require('querystring'); + const sdk = ""; + const plugin = await this.getPluginVersion(); + const client_id = this.authenticationService.getOctaneClientId(); + const self_url = querystring.escape(this.collectionUri + this.projectName); + const instance_id = this.instanceId; + + const urlConnectivity = this.analyticsCiInternalApiUrlPart + + `/servers/${instance_id}/tasks?self-type=azure_devops&api-version=1&sdk-version=${sdk}&plugin-version=${plugin}&self-url=${self_url}&client-id=${client_id}&client-server-user=`; + await octaneSDKConnection._requestHandler._requestor.get(urlConnectivity); + } + + + private async getPluginVersion():Promise{ + const api: WebApi = ConnectionUtils.getWebApiWithProxy(this.collectionUri, this.authenticationService.getAzureAccessToken()); + const extApi = await api.getExtensionManagementApi(this.collectionUri) + const extension = await extApi.getInstalledExtensionByName("almoctane","alm-octane-integration-public"); + this.logger.info("extenstion version: " + extension?.version); + return extension?.version; + } + + private isVersionGreaterOrEquals(version1: string,version2: string): boolean{ + if(!version1 || !version2){ + return false; + } + const version1Spl = version1.split('.'); + const version2Spl = version2.split('.'); + for(let i = 0;i < version1Spl.length || i < version2Spl.length;i++){ + const decrement = parseInt(version1Spl[i]) - parseInt(version2Spl[i]); + if(decrement !== 0){ + return decrement > 0 } } + return version1Spl.length >= version2Spl.length; + } + + private async updateExistCIJobs(ciJobs,ciServerId,workspaceId,octaneSDKConnection): Promise { + let ciJobsToUpdate = []; + ciJobs.forEach(ciJob => ciJobsToUpdate.push(this.createCiJobBody(ciJob))); + this.logger.debug('CI Jobs update body:' + ciJobs); + const url = this.ciInternalAzureApiUrlPart.replace('{workspace_id}',workspaceId) + '/ci_job_update?ci-server-id=' + ciServerId; + await octaneSDKConnection._requestHandler.update(url,ciJobsToUpdate); + } + + private createCiJobBody(ciJob){ + return { + 'id': ciJob.id, + 'definitionId': this.definitionId, + 'jobCiId': this.projectFullName + '.' + this.buildDefinitionName, + 'parameters': [ + { + 'name': 'branch', + 'type': 'string', + 'description': 'Branch to execute pipeline', + 'defaultValue': this.sourceBranch, + 'choices': [], + } + ] + + } + } - return pipelines.data[0]; + private async isExperimentEnable(octaneSDKConnection,workspaceId): Promise{ + const experimentUrl = this.ciInternalAzureApiUrlPart.replace('{workspace_id}',workspaceId) + '/experiment_run_pipeline'; + const response = await octaneSDKConnection._requestHandler._requestor.get(experimentUrl); + this.logger.info("Octane experiment 'run_azure_pipeline' enabled: " + response); + return response; } private async createPipeline(octaneSDKConnection, pipelineName, rootJobName, ciServer) { diff --git a/src/ExtensionConstants.ts b/src/ExtensionConstants.ts index 52cc7b1..c658f05 100644 --- a/src/ExtensionConstants.ts +++ b/src/ExtensionConstants.ts @@ -21,7 +21,8 @@ export enum SystemVariablesConstants { BUILD_BUILD_ID = 'Build.BuildId', AGENT_JOB_NAME = 'Agent.JobName', AGENT_JOB_STATUS = 'AGENT_JOBSTATUS', - BUILD_DEFINITION_ID = 'System.DefinitionId' + BUILD_DEFINITION_ID = 'System.DefinitionId', + BUILD_SOURCE_BRANCH = "Build.SourceBranch" } export enum EndpointDataConstants { @@ -32,12 +33,14 @@ export enum EndpointDataConstants { export enum EntityTypeConstants { CI_SERVER_ENTITY_TYPE = 'ci_server', - PIPELINE_ENTITY_TYPE = 'pipeline' + PIPELINE_ENTITY_TYPE = 'pipeline', + CI_JOB_ENTITY_TYPE = 'ci_job' } export enum EntityTypeRestEndpointConstants { CI_SERVERS_REST_API_NAME = EntityTypeConstants.CI_SERVER_ENTITY_TYPE + 's', - PIPELINES_REST_API_NAME = EntityTypeConstants.PIPELINE_ENTITY_TYPE + 's' + PIPELINES_REST_API_NAME = EntityTypeConstants.PIPELINE_ENTITY_TYPE + 's', + CI_JOB_REST_API_NAME = EntityTypeConstants.CI_JOB_ENTITY_TYPE + 's' } export enum OctaneTaskConstants { diff --git a/src/debug/debug-conf.ts b/src/debug/debug-conf.ts index 3aefd53..f69a0ff 100644 --- a/src/debug/debug-conf.ts +++ b/src/debug/debug-conf.ts @@ -20,6 +20,7 @@ interface Build { sourceBranchName: string; definitionName: string; buildId: string; + sourceBranch: string; } interface Octane { @@ -119,6 +120,7 @@ export class DebugConfToDebugMapsConverter { map.set(SystemVariablesConstants.SYSTEM_TEAM_PROJECT_ID, conf.system.teamProjectId); map.set(SystemVariablesConstants.BUILD_SOURCE_BRANCH_NAME, conf.build.sourceBranchName); map.set(SystemVariablesConstants.BUILD_DEFINITION_ID,conf.system.definitionId); + map.set(SystemVariablesConstants.BUILD_SOURCE_BRANCH,conf.build.sourceBranch); map.set(EndpointDataConstants.ENDPOINT_DATA_OCTANE_AZURE_PERSONAL_ACCESS_TOKEN, conf.endpoint.azurePersonalAccessToken); map.set(EndpointDataConstants.ENDPOINT_DATA_OCTANE_INSTANCE_ID, conf.endpoint.octaneInstanceId); diff --git a/templates/private/Tasks/EndTask/task.json b/templates/private/Tasks/EndTask/task.json index e822a1b..228f686 100644 --- a/templates/private/Tasks/EndTask/task.json +++ b/templates/private/Tasks/EndTask/task.json @@ -12,8 +12,8 @@ "demands": [], "version": { "Major": "1", - "Minor": "18", - "Patch": "16" + "Minor": "19", + "Patch": "2" }, "minimumAgentVersion": "2.170.1", "instanceNameFormat": "ALM Octane Job End", diff --git a/templates/private/Tasks/StartTask/task.json b/templates/private/Tasks/StartTask/task.json index 6e66188..ec244da 100644 --- a/templates/private/Tasks/StartTask/task.json +++ b/templates/private/Tasks/StartTask/task.json @@ -12,8 +12,8 @@ "demands": [], "version": { "Major": "1", - "Minor": "18", - "Patch": "16" + "Minor": "19", + "Patch": "2" }, "minimumAgentVersion": "2.170.1", "instanceNameFormat": "ALM Octane Job Start", diff --git a/templates/private/overview.md b/templates/private/overview.md index c715adf..b9cbec1 100644 --- a/templates/private/overview.md +++ b/templates/private/overview.md @@ -1,4 +1,7 @@ # ALM Octane Integration with Azure DevOps Services +## 0.4.2.2 version Release notes +* Update Octane CI Server plugin version +* Update Octane pipeline to will be able to execute from Octane side ## 0.4.1.16 version Release notes * Report to Octane duration of the pipeline run ## 0.4.1.13 version Release notes diff --git a/templates/private/vss-extension.json b/templates/private/vss-extension.json index 4e6d9c3..f01d160 100644 --- a/templates/private/vss-extension.json +++ b/templates/private/vss-extension.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "id": "alm-octane-integration", - "version": "0.4.1.16", + "version": "0.4.2.2", "name": "ALM Octane Integration Private Extension", "description": "Beta version. An Azure DevOps extension that supplies the required entities for establishing an integration with ALM Octane.", "publisher": "almoctane", diff --git a/templates/public/Tasks/EndTask/task.json b/templates/public/Tasks/EndTask/task.json index 14aace3..6f5374f 100644 --- a/templates/public/Tasks/EndTask/task.json +++ b/templates/public/Tasks/EndTask/task.json @@ -12,8 +12,8 @@ "demands": [], "version": { "Major": "1", - "Minor": "8", - "Patch": "6" + "Minor": "9", + "Patch": "1" }, "minimumAgentVersion": "2.170.1", "instanceNameFormat": "ALM Octane Job End", diff --git a/templates/public/Tasks/StartTask/task.json b/templates/public/Tasks/StartTask/task.json index 5796e47..2691b50 100644 --- a/templates/public/Tasks/StartTask/task.json +++ b/templates/public/Tasks/StartTask/task.json @@ -12,8 +12,8 @@ "demands": [], "version": { "Major": "1", - "Minor": "8", - "Patch": "6" + "Minor": "9", + "Patch": "1" }, "minimumAgentVersion": "2.170.1", "instanceNameFormat": "ALM Octane Job Start", diff --git a/templates/public/overview.md b/templates/public/overview.md index fc234bb..9855074 100644 --- a/templates/public/overview.md +++ b/templates/public/overview.md @@ -1,4 +1,7 @@ # ALM Octane Integration with Azure DevOps Services +## 0.2.6.1 version Release notes +* Update Octane CI Server plugin version +* Update Octane pipeline to will be able to execute from Octane side ## 0.2.5.7 version Release notes * Report to Octane duration of the pipeline run ## 0.2.5.6 version Release notes diff --git a/templates/public/vss-extension.json b/templates/public/vss-extension.json index c867469..551d553 100644 --- a/templates/public/vss-extension.json +++ b/templates/public/vss-extension.json @@ -1,7 +1,7 @@ { "manifestVersion": 1, "id": "alm-octane-integration-public", - "version": "0.2.5.7", + "version": "0.2.6.0", "name": "ALM Octane Integration Extension", "description": "Tech Preview. An Azure DevOps extension that supplies the required entities for establishing an integration with ALM Octane.", "publisher": "almoctane",