From 6b1a3973a18a31c049dc62b372183cf4b475d242 Mon Sep 17 00:00:00 2001 From: Atsuto Yamashita Date: Sat, 28 Jul 2018 03:19:14 +0900 Subject: [PATCH] fix(1159): Configure build timeout in Jenkins (#22) --- README.md | 2 ++ config/job.xml.tim | 5 ++++ index.js | 15 +++++++++- package.json | 1 + test/index.test.js | 70 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fb992ab..f9a6a27 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ The class provides a couple options that are configurable in the instantiation o | config.jenkins.host | String | | The hostname for the Jenkins cluster | | config.jenkins.port | Number | 8080 | The port number for the Jenkins cluster | | config.jenkins.nodeLabel | String | 'screwdriver' | Node labels of Jenkins slaves | +| config.jenkins.buildTimeout | Number | 90 | Number of minutes to allow a build to run before considering it is timed out | +| config.jenkins.maxBuildTimeout | Number | 120 | Max timeout user can configure up to | | config.docker.composeCommand | String | 'docker'-compose | The path to the docker-compose command | | config.docker.launchVersion | String | 'stable' | Launcher container version to use | | config.docker.prefix | String | '' | Prefix to container names | diff --git a/config/job.xml.tim b/config/job.xml.tim index 69867eb..fd3342a 100644 --- a/config/job.xml.tim +++ b/config/job.xml.tim @@ -30,6 +30,11 @@ + + SD_BUILD_TIMEOUT + + + diff --git a/index.js b/index.js index 107d483..90279af 100644 --- a/index.js +++ b/index.js @@ -6,10 +6,13 @@ const jenkins = require('jenkins'); const xmlescape = require('xml-escape'); const tinytim = require('tinytim'); const Breaker = require('circuit-fuses'); +const hoek = require('hoek'); const password = Symbol('password'); const baseUrl = Symbol('baseUrl'); +const ANNOTATE_BUILD_TIMEOUT = 'beta.screwdriver.cd/timeout'; + class JenkinsExecutor extends Executor { /** * JenkinsClient command to run @@ -211,6 +214,8 @@ class JenkinsExecutor extends Executor { * @param {String} [options.jenkins.username='screwdriver'] Jenkins username * @param {String} options.jenkins.password Jenkins password/token * @param {String} [options.jenkins.nodeLabel='screwdriver'] Node labels of Jenkins slaves + * @param {Number} [options.jenkins.buildTimeout=90] Number of minutes to allow a build to run before considering it is + * @param {Number} [options.jenkins.maxBuildTimeout=120] Max timeout user can configure up to * @param {String} [options.docker.composeCommand='docker-compose'] THe path to the docker-compose command * @param {String} [options.docker.launchVersion='stable'] Launcher container version to use * @param {String} [options.docker.prefix=''] Prefix to all container names @@ -230,6 +235,8 @@ class JenkinsExecutor extends Executor { this.username = options.jenkins.username || 'screwdriver'; this[password] = options.jenkins.password; this.nodeLabel = options.jenkins.nodeLabel || 'screwdriver'; + this.buildTimeout = options.jenkins.buildTimeout || 90; + this.maxBuildTimeout = options.jenkins.maxBuildTimeout || 120; this.composeCommand = (options.docker && options.docker.composeCommand) || 'docker-compose'; this.launchVersion = (options.docker && options.docker.launchVersion) || 'stable'; this.prefix = (options.docker && options.docker.prefix) || ''; @@ -264,6 +271,11 @@ class JenkinsExecutor extends Executor { async _start(config) { const jobName = this._jobName(config.buildId); const xml = this._loadJobXml(config); + const annotations = hoek.reach(config, 'annotations', { default: {} }); + + const buildTimeout = annotations[ANNOTATE_BUILD_TIMEOUT] + ? Math.min(annotations[ANNOTATE_BUILD_TIMEOUT], this.maxBuildTimeout) + : this.buildTimeout; await this._jenkinsJobCreateOrUpdate(jobName, xml); @@ -281,7 +293,8 @@ class JenkinsExecutor extends Executor { SD_TOKEN: config.token, SD_CONTAINER: config.container, SD_API: this.ecosystem.api, - SD_STORE: this.ecosystem.store + SD_STORE: this.ecosystem.store, + SD_BUILD_TIMEOUT: buildTimeout } }] }); diff --git a/package.json b/package.json index 575d9c2..1e7541c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "circuit-fuses": "^2.2.1", + "hoek": "^5.0.3", "jenkins": "^0.20.0", "request": "^2.72.0", "screwdriver-executor-base": "^6.1.0", diff --git a/test/index.test.js b/test/index.test.js index 24cf68b..9f3f264 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -25,6 +25,9 @@ const TEST_JOB_XML = ` `; +const CONFIGURED_BUILD_TIMEOUT = 45; +const DEFAULT_BUILD_TIMEOUT = 90; +const MAX_BUILD_TIMEOUT = 120; const TEST_COMPOSE_YAML = ` version: '2' services: @@ -74,7 +77,8 @@ describe('index', () => { buildId: 1993, container: 'node:4', apiUri: 'http://localhost:8080', - token: 'abcdefg' + token: 'abcdefg', + annotations: {} }; const jobName = `SD-${config.buildId}`; @@ -87,12 +91,23 @@ describe('index', () => { store: 'store' }; - const buildParameters = { + const buildParams = { SD_BUILD_ID: String(config.buildId), SD_TOKEN: 'someBuildToken', SD_CONTAINER: config.container, SD_API: ecosystem.api, - SD_STORE: ecosystem.store + SD_STORE: ecosystem.store, + SD_BUILD_TIMEOUT: DEFAULT_BUILD_TIMEOUT + }; + + const maxTimeoutBuildParams = { + ...buildParams, + SD_BUILD_TIMEOUT: MAX_BUILD_TIMEOUT + }; + + const configuredTimeoutBuildParams = { + ...buildParams, + SD_BUILD_TIMEOUT: CONFIGURED_BUILD_TIMEOUT }; const fakeJobInfo = { @@ -180,6 +195,8 @@ describe('index', () => { let configOpts; let existsOpts; let buildOpts; + let configuredBuildTimeoutOpts; + let maxBuildTimeoutOpts; let exchangeTokenStub; const fakeXml = 'fake_xml'; @@ -207,13 +224,32 @@ describe('index', () => { action: 'build', params: [{ name: jobName, - parameters: buildParameters + parameters: buildParams + }] + }; + + configuredBuildTimeoutOpts = { + module: 'job', + action: 'build', + params: [{ + name: jobName, + parameters: configuredTimeoutBuildParams + }] + }; + + maxBuildTimeoutOpts = { + module: 'job', + action: 'build', + params: [{ + name: jobName, + parameters: maxTimeoutBuildParams }] }; sinon.stub(executor, '_loadJobXml').returns(fakeXml); exchangeTokenStub = sinon.stub(executor, 'exchangeTokenForBuild'); exchangeTokenStub.resolves('someBuildToken'); + config.annotations = {}; }); it('return null when the job is successfully created', (done) => { @@ -238,6 +274,30 @@ describe('index', () => { }); }); + it('sets the build timeout if configured by user', (done) => { + breakerMock.runCommand.withArgs(existsOpts).resolves(false); + + config.annotations = { 'beta.screwdriver.cd/timeout': CONFIGURED_BUILD_TIMEOUT }; + executor.start(config).then(() => { + assert.calledWith(breakerMock.runCommand, existsOpts); + assert.calledWith(breakerMock.runCommand, createOpts); + assert.calledWith(breakerMock.runCommand, configuredBuildTimeoutOpts); + done(); + }); + }); + + it('sets the timeout to maxBuildTimeout if user specified a higher timeout', (done) => { + breakerMock.runCommand.withArgs(existsOpts).resolves(false); + + config.annotations = { 'beta.screwdriver.cd/timeout': 220 }; + executor.start(config).then(() => { + assert.calledWith(breakerMock.runCommand, existsOpts); + assert.calledWith(breakerMock.runCommand, createOpts); + assert.calledWith(breakerMock.runCommand, maxBuildTimeoutOpts); + done(); + }); + }); + it('return error when job.create is getting error', (done) => { const error = new Error('job.create error'); @@ -609,7 +669,7 @@ rm -f docker-compose.yml executor.start(config).then(() => { assert.calledWith(jenkinsMock.job.create, { name: jobName, xml: fakeXml }); assert.calledWith(jenkinsMock.job.build, - { name: jobName, parameters: buildParameters }); + { name: jobName, parameters: buildParams }); done(); }); });