diff --git a/lib/build.js b/lib/build.js index 7ca9bf65..5e56c922 100644 --- a/lib/build.js +++ b/lib/build.js @@ -526,56 +526,55 @@ class BuildModel extends BaseModel { return this.job.then(job => Promise.all([job.pipeline, job.prNum]) .then(([pipeline, prNum]) => - getBlockedByIds(pipeline, job).then(blockedBy => { - const config = { - build: this, - buildId: this.id, - eventId: this.eventId, - jobId: job.id, - jobState: job.state, - jobArchived: job.archived, - jobName: job.name, - annotations: hoek.reach(job.permutations[0], 'annotations', { default: {} }), - blockedBy, - pipelineId: pipeline.id, - pipeline: { - id: pipeline.id, - name: pipeline.name, - scmContext: pipeline.scmContext, - configPipelineId: pipeline.configPipelineId - }, - causeMessage: causeMessage || '', - freezeWindows: hoek.reach(job.permutations[0], 'freezeWindows', { default: [] }), - apiUri: this[apiUri], - container: this.container, - tokenGen: this[tokenGen], - token: getToken(this, job, pipeline), - isPR: job.isPR(), - prParentJobId: job.prParentJobId - }; - - if (template && !hoek.deepEqual(template, {})) { - config.template = template; - } + getBlockedByIds(pipeline, job) + .then(blockedBy => { + const config = { + build: this, + buildId: this.id, + eventId: this.eventId, + jobId: job.id, + jobState: job.state, + jobArchived: job.archived, + jobName: job.name, + annotations: hoek.reach(job.permutations[0], 'annotations', { default: {} }), + blockedBy, + pipelineId: pipeline.id, + pipeline: { + id: pipeline.id, + name: pipeline.name, + scmContext: pipeline.scmContext, + configPipelineId: pipeline.configPipelineId + }, + causeMessage: causeMessage || '', + freezeWindows: hoek.reach(job.permutations[0], 'freezeWindows', { default: [] }), + apiUri: this[apiUri], + container: this.container, + tokenGen: this[tokenGen], + token: getToken(this, job, pipeline), + isPR: job.isPR(), + prParentJobId: job.prParentJobId + }; + + if (template && !hoek.deepEqual(template, {})) { + config.template = template; + } - if (prNum) { - config.prNum = prNum; - } + if (prNum) { + config.prNum = prNum; + } - if (this.buildClusterName) { - config.buildClusterName = this.buildClusterName; - } + if (this.buildClusterName) { + config.buildClusterName = this.buildClusterName; + } - if (hoek.reach(job.permutations[0], 'provider')) { - config.provider = job.permutations[0].provider; - } + if (hoek.reach(job.permutations[0], 'provider')) { + config.provider = job.permutations[0].provider; + } - return Promise.all([ - this[executor].start(config), - this.updateCommitStatus(pipeline) // update github - ]); - }) - ) + return this[executor].start(config); + }) + .then(() => this.updateCommitStatus(pipeline)) + ) // update github .then(() => this) ); } diff --git a/lib/eventFactory.js b/lib/eventFactory.js index 491ddc73..78ac58c9 100644 --- a/lib/eventFactory.js +++ b/lib/eventFactory.js @@ -406,6 +406,13 @@ function createBuilds(config) { buildConfig.configPipelineSha = pipelineConfig.configPipelineSha; + const { annotations, freezeWindows } = j.permutations[0]; + const isVirtualJob = annotations ? annotations['screwdriver.cd/virtualJob'] === true : false; + const hasFreezeWindows = freezeWindows ? freezeWindows.length > 0 : false; + + // Bypass execution of the build if the job is virtual + buildConfig.start = !isVirtualJob || (isVirtualJob && hasFreezeWindows); + return startBuild({ decoratedCommit, subscribedConfigSha, diff --git a/package.json b/package.json index 1bbdbee2..b38a93f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "screwdriver-models", - "version": "29.0.0", + "version": "30.0.0", "description": "Screwdriver models", "main": "index.js", "scripts": { diff --git a/test/lib/eventFactory.test.js b/test/lib/eventFactory.test.js index 8d488038..a06c2e5e 100644 --- a/test/lib/eventFactory.test.js +++ b/test/lib/eventFactory.test.js @@ -179,6 +179,8 @@ describe('Event Factory', () => { { name: '~pr' }, { name: '~commit' }, { name: 'main' }, + { name: 'firstVirtual' }, + { name: 'secondVirtual' }, { name: 'disabledJob' }, { name: 'stage@integration:setup', stageName: 'integration' }, { name: 'int-deploy', stageName: 'integration' }, @@ -198,6 +200,8 @@ describe('Event Factory', () => { { src: '~sd@123:main', dest: 'main' }, { src: '~pr', dest: 'main' }, { src: '~commit', dest: 'main' }, + { src: '~commit', dest: 'firstVirtual' }, + { src: '~commit', dest: 'secondVirtual' }, { src: 'main', dest: 'disabledJob' }, { src: 'main', dest: 'stage@integration:setup' }, { src: 'stage@integration:setup', dest: 'int-deploy' }, @@ -236,6 +240,8 @@ describe('Event Factory', () => { { name: '~pr' }, { name: '~commit' }, { name: 'main' }, + { name: 'firstVirtual' }, + { name: 'secondVirtual' }, { name: 'disabledJob' }, { name: 'stage@integration:setup', stageName: 'integration' }, { name: 'int-deploy', stageName: 'integration' }, @@ -255,6 +261,8 @@ describe('Event Factory', () => { { src: '~sd@123:main', dest: 'main' }, { src: '~pr', dest: 'main' }, { src: '~commit', dest: 'main' }, + { src: '~commit', dest: 'firstVirtual' }, + { src: '~commit', dest: 'secondVirtual' }, { src: 'main', dest: 'disabledJob' }, { src: 'main', dest: 'stage@integration:setup' }, { src: 'stage@integration:setup', dest: 'int-deploy' }, @@ -471,6 +479,38 @@ describe('Event Factory', () => { ], state: 'ENABLED', isPR: sinon.stub().returns(false) + }, + + { + id: 16, + pipelineId: 8765, + name: 'firstVirtual', + permutations: [ + { + requires: ['~commit', '~pr'], + annotations: { + 'screwdriver.cd/virtualJob': true + } + } + ], + state: 'ENABLED', + isPR: sinon.stub().returns(false) + }, + { + id: 17, + pipelineId: 8765, + name: 'secondVirtual', + permutations: [ + { + requires: ['~commit', '~pr'], + freezeWindows: ['* 10-21 ? * *'], + annotations: { + 'screwdriver.cd/virtualJob': true + } + } + ], + state: 'ENABLED', + isPR: sinon.stub().returns(false) } ]; @@ -901,12 +941,59 @@ describe('Event Factory', () => { return eventFactory.create(config).then(model => { assert.instanceOf(model, Event); assert.notCalled(jobFactoryMock.create); + assert.callCount(buildFactoryMock.create, 6); assert.calledWith( buildFactoryMock.create, sinon.match({ parentBuildId: 12345, eventId: model.id, - jobId: 1 + jobId: 1, + start: true + }) + ); + assert.calledWith( + buildFactoryMock.create, + sinon.match({ + parentBuildId: 12345, + eventId: model.id, + jobId: 5, + start: true + }) + ); + assert.calledWith( + buildFactoryMock.create, + sinon.match({ + parentBuildId: 12345, + eventId: model.id, + jobId: 6, + start: true + }) + ); + assert.calledWith( + buildFactoryMock.create, + sinon.match({ + parentBuildId: 12345, + eventId: model.id, + jobId: 7, + start: true + }) + ); + assert.calledWith( + buildFactoryMock.create, + sinon.match({ + parentBuildId: 12345, + eventId: model.id, + jobId: 16, + start: false // should skip execution of virtual job without freeze windows + }) + ); + assert.calledWith( + buildFactoryMock.create, + sinon.match({ + parentBuildId: 12345, + eventId: model.id, + jobId: 17, + start: true }) ); assert.calledOnce(pipelineMock.sync);