Skip to content

Commit a75c41b

Browse files
author
David Staheli
committed
JenkinsQueueJob: Port recent fixes to releases/m105
1 parent 81647da commit a75c41b

File tree

9 files changed

+251
-25
lines changed

9 files changed

+251
-25
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"archivePackages": [
3+
{
4+
"archiveName": "7zip.zip",
5+
"url": "https://vstsagenttools.blob.core.windows.net/tools/7zip/1/7zip.zip",
6+
"dest": "./"
7+
}
8+
]
9+
}

Tasks/JenkinsQueueJob/jenkinsqueuejobtask.ts

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,38 +20,72 @@ import JobQueue = jobqueue.JobQueue;
2020
import util = require('./util');
2121

2222
export class TaskOptions {
23-
serverEndpoint: string = tl.getInput('serverEndpoint', true);
24-
serverEndpointUrl: string = tl.getEndpointUrl(this.serverEndpoint, false);
23+
serverEndpoint: string;
24+
serverEndpointUrl: string;
2525

26-
serverEndpointAuth: tl.EndpointAuthorization = tl.getEndpointAuthorization(this.serverEndpoint, false);
27-
username: string = this.serverEndpointAuth['parameters']['username'];
28-
password: string = this.serverEndpointAuth['parameters']['password'];
26+
serverEndpointAuth: tl.EndpointAuthorization;
27+
username: string;
28+
password: string;
2929

30-
jobName: string = tl.getInput('jobName', true);
30+
jobName: string;
3131

32-
captureConsole: boolean = tl.getBoolInput('captureConsole', true);
32+
captureConsole: boolean;
3333
// capturePipeline is only possible if captureConsole mode is enabled
34-
capturePipeline: boolean = this.captureConsole ? tl.getBoolInput('capturePipeline', true) : false;
34+
capturePipeline: boolean;
3535

36-
pollIntervalMillis: number = 5000; // five seconds is what the Jenkins Web UI uses
36+
pollIntervalMillis: number;
3737

38-
parameterizedJob: boolean = tl.getBoolInput('parameterizedJob', true);
38+
parameterizedJob: boolean;
3939
// jobParameters are only possible if parameterizedJob is enabled
40-
jobParameters: string[] = this.parameterizedJob ? tl.getDelimitedInput('jobParameters', '\n', false) : [];
40+
jobParameters: string[];
4141

42-
jobQueueUrl: string = util.addUrlSegment(this.serverEndpointUrl, util.convertJobName(this.jobName)) + ((this.parameterizedJob) ? '/buildWithParameters?delay=0sec' : '/build?delay=0sec');
43-
teamJobQueueUrl: string = util.addUrlSegment(this.serverEndpointUrl, '/team-build/build/' + this.jobName + '?delay=0sec');
44-
teamPluginUrl: string = util.addUrlSegment(this.serverEndpointUrl, '/pluginManager/available');
42+
jobQueueUrl: string;
43+
teamJobQueueUrl: string;
44+
teamPluginUrl: string;
4545

46-
strictSSL: boolean = ("true" !== tl.getEndpointDataParameter(this.serverEndpoint, "acceptUntrustedCerts", true));
46+
teamBuildPluginAvailable: boolean;
47+
saveResultsTo: string;
4748

48-
NO_CRUMB: string = 'NO_CRUMB';
49-
crumb: string = this.NO_CRUMB;
49+
strictSSL: boolean;
50+
51+
NO_CRUMB: string;
52+
crumb: string;
5053

5154
constructor() {
52-
tl.debug('strictSSL=' + this.strictSSL);
55+
this.serverEndpoint = tl.getInput('serverEndpoint', true);
56+
this.serverEndpointUrl = tl.getEndpointUrl(this.serverEndpoint, false);
5357
tl.debug('serverEndpointUrl=' + this.serverEndpointUrl);
58+
this.serverEndpointAuth = tl.getEndpointAuthorization(this.serverEndpoint, false);
59+
this.username = this.serverEndpointAuth['parameters']['username'];
60+
this.password = this.serverEndpointAuth['parameters']['password'];
61+
62+
this.jobName = tl.getInput('jobName', true);
63+
64+
this.captureConsole = tl.getBoolInput('captureConsole', true);
65+
// capturePipeline is only possible if captureConsole mode is enabled
66+
this.capturePipeline = this.captureConsole ? tl.getBoolInput('capturePipeline', true) : false;
67+
68+
this.pollIntervalMillis = 5000; // five seconds is what the Jenkins Web UI uses
69+
70+
this.parameterizedJob = tl.getBoolInput('parameterizedJob', true);
71+
// jobParameters are only possible if parameterizedJob is enabled
72+
this.jobParameters = this.parameterizedJob ? tl.getDelimitedInput('jobParameters', '\n', false) : [];
73+
74+
this.jobQueueUrl = util.addUrlSegment(this.serverEndpointUrl, util.convertJobName(this.jobName)) + ((this.parameterizedJob) ? '/buildWithParameters?delay=0sec' : '/build?delay=0sec');
5475
tl.debug('jobQueueUrl=' + this.jobQueueUrl);
76+
this.teamJobQueueUrl = util.addUrlSegment(this.serverEndpointUrl, '/team-build/build/' + this.jobName + '?delay=0sec');
77+
tl.debug('teamJobQueueUrl=' + this.teamJobQueueUrl);
78+
this.teamPluginUrl = util.addUrlSegment(this.serverEndpointUrl, '/pluginManager/available');
79+
tl.debug('teamPluginUrl=' + this.teamPluginUrl);
80+
81+
this.teamBuildPluginAvailable = false;
82+
this.saveResultsTo = path.join(tl.getVariable('Build.StagingDirectory'), 'jenkinsResults');
83+
84+
this.strictSSL = ("true" !== tl.getEndpointDataParameter(this.serverEndpoint, "acceptUntrustedCerts", true));
85+
tl.debug('strictSSL=' + this.strictSSL);
86+
87+
this.NO_CRUMB = 'NO_CRUMB';
88+
this.crumb = this.NO_CRUMB;
5589
}
5690
}
5791

Tasks/JenkinsQueueJob/job.ts

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import JobSearch = jobsearch.JobSearch;
1616
import jobqueue = require('./jobqueue');
1717
import JobQueue = jobqueue.JobQueue;
1818

19+
import unzip = require('./unzip');
20+
1921
import * as Util from './util';
2022

2123
export enum JobState {
@@ -26,7 +28,8 @@ export enum JobState {
2628
Done, // 4
2729
Joined, // 5
2830
Queued, // 6
29-
Cut // 7
31+
Cut, // 7
32+
Downloading// 8
3033
}
3134

3235
export class Job {
@@ -98,6 +101,8 @@ export class Job {
98101
} else if (oldState == JobState.Streaming) {
99102
validStateChange = (newState == JobState.Finishing);
100103
} else if (oldState == JobState.Finishing) {
104+
validStateChange = (newState == JobState.Downloading || newState == JobState.Done);
105+
} else if (oldState == JobState.Downloading) {
101106
validStateChange = (newState == JobState.Done);
102107
} else if (oldState == JobState.Done || oldState == JobState.Joined || oldState == JobState.Cut) {
103108
validStateChange = false; // these are terminal states
@@ -118,6 +123,8 @@ export class Job {
118123
this.initialize();
119124
} else if (this.state == JobState.Streaming) {
120125
this.streamConsole();
126+
} else if (this.state == JobState.Downloading) {
127+
this.downloadResults();
121128
} else if (this.state == JobState.Finishing) {
122129
this.finish();
123130
} else {
@@ -143,6 +150,7 @@ export class Job {
143150
return this.state == JobState.New ||
144151
this.state == JobState.Locating ||
145152
this.state == JobState.Streaming ||
153+
this.state == JobState.Downloading ||
146154
this.state == JobState.Finishing
147155
}
148156

@@ -299,7 +307,11 @@ export class Job {
299307
thisJob.debug("parsedBody for: " + resultUrl + ": " + JSON.stringify(parsedBody));
300308
if (parsedBody.result) {
301309
thisJob.setParsedExecutionResult(parsedBody);
302-
thisJob.stopWork(0, JobState.Done);
310+
if (thisJob.queue.taskOptions.teamBuildPluginAvailable) {
311+
thisJob.stopWork(0, JobState.Downloading);
312+
} else {
313+
thisJob.stopWork(0, JobState.Done);
314+
}
303315
} else {
304316
// result not updated yet -- keep trying
305317
thisJob.stopWork(thisJob.queue.taskOptions.pollIntervalMillis, thisJob.state);
@@ -308,6 +320,81 @@ export class Job {
308320
}).auth(thisJob.queue.taskOptions.username, thisJob.queue.taskOptions.password, true);
309321
}
310322
}
323+
324+
downloadResults(): void {
325+
var thisJob: Job = this;
326+
var downloadUrl: string = Util.addUrlSegment(thisJob.executableUrl, 'team-results/zip');
327+
tl.debug('downloadResults(), url:' + downloadUrl);
328+
329+
var downloadRequest = request.get({ url: downloadUrl, strictSSL: thisJob.queue.taskOptions.strictSSL })
330+
.auth(thisJob.queue.taskOptions.username, thisJob.queue.taskOptions.password, true)
331+
.on("error", err => {
332+
Util.handleConnectionResetError(err); // something went bad
333+
thisJob.stopWork(thisJob.queue.taskOptions.pollIntervalMillis, thisJob.state);
334+
})
335+
.on("response", response => {
336+
tl.debug('downloadResults(), url:' + downloadUrl + ' , response.statusCode: ' + response.statusCode + ', response.statusMessage: ' + response.statusMessage);
337+
if (response.statusCode == 404) { // expected if there are no results
338+
tl.debug('no results to download');
339+
thisJob.stopWork(0, JobState.Done);
340+
} else if (response.statusCode == 200) { // successfully found results
341+
var destinationFolder: string = path.join(thisJob.queue.taskOptions.saveResultsTo, thisJob.name + '/')
342+
var fileName = path.join(destinationFolder, 'team-results.zip');
343+
344+
try {
345+
// Create the destination folder if it doesn't exist
346+
if (!tl.exist(destinationFolder)) {
347+
tl.debug('creating results destination folder: ' + destinationFolder);
348+
tl.mkdirP(destinationFolder);
349+
}
350+
351+
tl.debug('downloading results file: ' + fileName);
352+
353+
let file = fs.createWriteStream(fileName);
354+
downloadRequest.pipe(file)
355+
.on("error", err => { throw err; })
356+
.on("finish", function fileFinished() {
357+
tl.debug('successfully downloaded results to: ' + fileName);
358+
try {
359+
unzip.unzip(fileName, destinationFolder);
360+
thisJob.stopWork(0, JobState.Done);
361+
} catch (e) {
362+
tl.warning('unable to extract results file')
363+
tl.debug(e.message);
364+
tl._writeError(e);
365+
thisJob.stopWork(0, JobState.Done);
366+
}
367+
});
368+
} catch (e) {
369+
// don't fail the job if the results can not be downloaded successfully
370+
tl.warning('unable to download results to file: ' + fileName + ' for Jenkins Job: ' + thisJob.executableUrl);
371+
tl.warning(e.message);
372+
tl._writeError(e);
373+
thisJob.stopWork(0, JobState.Done);
374+
}
375+
} else { // an unexepected error with results
376+
try {
377+
var warningMessage: string = (response.statusCode >= 500) ?
378+
'A Jenkins error occurred while retrieving results. Results could not be downloaded.' : // Jenkins server error
379+
'Jenkins results could not be downloaded.'; // Any other error
380+
tl.warning(warningMessage);
381+
var warningStream: any = new Util.StringWritable({ decodeStrings: false });
382+
downloadRequest.pipe(warningStream)
383+
.on("error", err => { throw err; })
384+
.on("finish", function finsished() {
385+
tl.warning(warningStream);
386+
thisJob.stopWork(0, JobState.Done);
387+
});
388+
} catch (e) {
389+
// don't fail the job if the results can not be downloaded successfully
390+
tl.warning(e.message);
391+
tl._writeError(e);
392+
thisJob.stopWork(0, JobState.Done);
393+
}
394+
}
395+
});
396+
}
397+
311398
/**
312399
* Streams the Jenkins console.
313400
*

Tasks/JenkinsQueueJob/jobqueue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class JobQueue {
8383
var running = [];
8484
for (var i in this.allJobs) {
8585
var job = this.allJobs[i];
86-
if (job.state == JobState.Streaming || job.state == JobState.Finishing) {
86+
if (job.state == JobState.Streaming || job.state==JobState.Downloading || job.state == JobState.Finishing) {
8787
running.push(job);
8888
}
8989
}

Tasks/JenkinsQueueJob/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"name": "vsts-tasks-jenkinsqueuejob",
33
"dependencies": {
44
"request": "^2.65.0",
5-
"vsts-task-lib": "0.8.3"
5+
"vsts-task-lib": "0.8.5"
66
}
77
}

Tasks/JenkinsQueueJob/task.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"version": {
1515
"Major": 1,
1616
"Minor": 0,
17-
"Patch": 8
17+
"Patch": 9
1818
},
1919
"groups": [
2020
{

Tasks/JenkinsQueueJob/task.loc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"version": {
1515
"Major": 1,
1616
"Minor": 0,
17-
"Patch": 8
17+
"Patch": 9
1818
},
1919
"groups": [
2020
{

Tasks/JenkinsQueueJob/unzip.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
import tl = require('vsts-task-lib/task');
3+
import tr = require('vsts-task-lib/toolrunner');
4+
5+
import path = require('path');
6+
7+
import unzip = require('./unzip');
8+
9+
import * as Util from './util';
10+
11+
12+
var win = tl.osType().match(/^Win/);
13+
tl.debug('win: ' + win);
14+
15+
// extractors
16+
var xpUnzipLocation: string = win ? null : xpUnzipLocation = tl.which('unzip', false);
17+
var winSevenZipLocation: string = path.join(__dirname, '7zip/7z.exe');
18+
19+
export function unzip(file: string, destinationFolder: string) {
20+
if (win) {
21+
sevenZipExtract(file, destinationFolder);
22+
} else {
23+
unzipExtract(file, destinationFolder);
24+
}
25+
}
26+
27+
function unzipExtract(file: string, destinationFolder: string) {
28+
tl.debug('Extracting file: ' + file);
29+
if (typeof xpUnzipLocation == "undefined") {
30+
xpUnzipLocation = tl.which('unzip', true);
31+
}
32+
var unzip = tl.createToolRunner(xpUnzipLocation);
33+
unzip.arg(file);
34+
unzip.arg('-d');
35+
unzip.arg(destinationFolder);
36+
37+
return handleExecResult(unzip.execSync(getOptions()), file);
38+
}
39+
40+
function sevenZipExtract(file: string, destinationFolder: string) {
41+
tl.debug('Extracting file: ' + file);
42+
var sevenZip = tl.createToolRunner(winSevenZipLocation);
43+
sevenZip.arg('x');
44+
sevenZip.arg('-o' + destinationFolder);
45+
sevenZip.arg(file);
46+
return handleExecResult(sevenZip.execSync(getOptions()), file);
47+
}
48+
49+
function handleExecResult(execResult: tr.IExecResult, file: string) {
50+
if (execResult.code != tl.TaskResult.Succeeded) {
51+
tl.debug('execResult: ' + JSON.stringify(execResult));
52+
var message = 'Extraction failed for file: ' + file +
53+
'\ncode: ' + execResult.code +
54+
'\nstdout: ' + execResult.stdout +
55+
'\nstderr: ' + execResult.stderr +
56+
'\nerror: ' + execResult.error;
57+
throw new UnzipError(message);
58+
}
59+
}
60+
61+
function getOptions(): tr.IExecOptions {
62+
var execOptions: tr.IExecOptions = {
63+
silent: true,
64+
outStream: new Util.StringWritable({ decodeStrings: false }),
65+
errStream: new Util.StringWritable({ decodeStrings: false }),
66+
};
67+
return execOptions;
68+
}
69+
70+
export class UnzipError extends Error {
71+
}

0 commit comments

Comments
 (0)