screwdriver-api 7.0.234 → 7.0.236

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screwdriver-api",
3
- "version": "7.0.234",
3
+ "version": "7.0.236",
4
4
  "description": "API server for the Screwdriver.cd service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -118,7 +118,7 @@
118
118
  "screwdriver-executor-queue": "^5.0.0",
119
119
  "screwdriver-executor-router": "^4.0.0",
120
120
  "screwdriver-logger": "^2.0.0",
121
- "screwdriver-models": "^30.2.0",
121
+ "screwdriver-models": "^31.0.0",
122
122
  "screwdriver-notifications-email": "^4.0.0",
123
123
  "screwdriver-notifications-slack": "^6.0.0",
124
124
  "screwdriver-request": "^2.0.1",
@@ -0,0 +1,310 @@
1
+ 'use strict';
2
+
3
+ const boom = require('@hapi/boom');
4
+ const hoek = require('@hapi/hoek');
5
+ const merge = require('lodash.mergewith');
6
+ const { getFullStageJobName } = require('../../helper');
7
+ const STAGE_TEARDOWN_PATTERN = /^stage@([\w-]+)(?::teardown)$/;
8
+ const TERMINAL_STATUSES = ['FAILURE', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
9
+ const FINISHED_STATUSES = ['FAILURE', 'SUCCESS', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
10
+
11
+ /**
12
+ * @typedef {import('screwdriver-models/lib/build')} Build
13
+ * @typedef {import('screwdriver-models/lib/event')} Event
14
+ * @typedef {import('screwdriver-models/lib/step')} Step
15
+ */
16
+
17
+ /**
18
+ * Identify whether this build resulted in a previously failed job to become successful.
19
+ *
20
+ * @method isFixedBuild
21
+ * @param {Build} build Build Object
22
+ * @param {JobFactory} jobFactory Job Factory instance
23
+ */
24
+ async function isFixedBuild(build, jobFactory) {
25
+ if (build.status !== 'SUCCESS') {
26
+ return false;
27
+ }
28
+
29
+ const job = await jobFactory.get(build.jobId);
30
+ const failureBuild = await job.getLatestBuild({ status: 'FAILURE' });
31
+ const successBuild = await job.getLatestBuild({ status: 'SUCCESS' });
32
+
33
+ return !!((failureBuild && !successBuild) || failureBuild.id > successBuild.id);
34
+ }
35
+
36
+ /**
37
+ * Stops a frozen build from executing
38
+ *
39
+ * @method stopFrozenBuild
40
+ * @param {Build} build Build Object
41
+ * @param {String} previousStatus Previous build status
42
+ */
43
+ async function stopFrozenBuild(build, previousStatus) {
44
+ if (previousStatus !== 'FROZEN') {
45
+ return Promise.resolve();
46
+ }
47
+
48
+ return build.stopFrozen(previousStatus);
49
+ }
50
+
51
+ /**
52
+ * Updates execution details for init step
53
+ *
54
+ * @method stopFrozenBuild
55
+ * @param {Build} build Build Object
56
+ * @param {Object} app Hapi app Object
57
+ * @returns {Promise<Step>} Updated step
58
+ */
59
+ async function updateInitStep(build, app) {
60
+ const step = await app.stepFactory.get({ buildId: build.id, name: 'sd-setup-init' });
61
+ // If there is no init step, do nothing
62
+
63
+ if (!step) {
64
+ return null;
65
+ }
66
+
67
+ step.endTime = build.startTime || new Date().toISOString();
68
+ step.code = 0;
69
+
70
+ return step.update();
71
+ }
72
+
73
+ /**
74
+ * Set build status to desired status, set build statusMessage
75
+ *
76
+ * @param {Build} build Build Model
77
+ * @param {String} desiredStatus New Status
78
+ * @param {String} statusMessage User passed status message
79
+ * @param {String} statusMessageType User passed severity of the status message
80
+ * @param {String} username User initiating status build update
81
+ */
82
+ function updateBuildStatus(build, desiredStatus, statusMessage, statusMessageType, username) {
83
+ const currentStatus = build.status;
84
+
85
+ // UNSTABLE -> SUCCESS needs to update meta and endtime.
86
+ // However, the status itself cannot be updated to SUCCESS
87
+ if (currentStatus === 'UNSTABLE') {
88
+ return;
89
+ }
90
+
91
+ if (desiredStatus !== undefined) {
92
+ build.status = desiredStatus;
93
+ }
94
+
95
+ switch (build.status) {
96
+ case 'ABORTED':
97
+ build.statusMessage =
98
+ currentStatus === 'FROZEN' ? `Frozen build aborted by ${username}` : `Aborted by ${username}`;
99
+ break;
100
+ case 'FAILURE':
101
+ case 'SUCCESS':
102
+ if (statusMessage) {
103
+ build.statusMessage = statusMessage;
104
+ build.statusMessageType = statusMessageType || null;
105
+ }
106
+ break;
107
+ default:
108
+ build.statusMessage = statusMessage || null;
109
+ build.statusMessageType = statusMessageType || null;
110
+ break;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Get stage for current node
116
+ *
117
+ * @param {StageFactory} stageFactory Stage factory
118
+ * @param {Object} workflowGraph Workflow graph
119
+ * @param {String} jobName Job name
120
+ * @param {Number} pipelineId Pipeline ID
121
+ * @return {Stage} Stage for node
122
+ */
123
+ async function getStage({ stageFactory, workflowGraph, jobName, pipelineId }) {
124
+ const currentNode = workflowGraph.nodes.find(node => node.name === jobName);
125
+ let stage = null;
126
+
127
+ if (currentNode && currentNode.stageName) {
128
+ stage = await stageFactory.get({
129
+ pipelineId,
130
+ name: currentNode.stageName
131
+ });
132
+ }
133
+
134
+ return Promise.resolve(stage);
135
+ }
136
+
137
+ /**
138
+ * Checks if all builds in stage are done running
139
+ *
140
+ * @param {Stage} stage Stage
141
+ * @param {Event} event Event
142
+ * @return {Boolean} Flag if stage is done
143
+ */
144
+ async function isStageDone({ stage, event }) {
145
+ // Get all jobIds for jobs in the stage
146
+ const stageJobIds = [...stage.jobIds, stage.setup];
147
+
148
+ // Get all builds in a stage for this event
149
+ const stageJobBuilds = await event.getBuilds({ params: { jobId: stageJobIds } });
150
+ let stageIsDone = false;
151
+
152
+ if (stageJobBuilds && stageJobBuilds.length !== 0) {
153
+ stageIsDone = !stageJobBuilds.some(b => !FINISHED_STATUSES.includes(b.status));
154
+ }
155
+
156
+ return stageIsDone;
157
+ }
158
+
159
+ /**
160
+ * Updates the build and trigger its downstream jobs in the workflow
161
+ *
162
+ * @method updateBuildAndTriggerDownstreamJobs
163
+ * @param {Object} config
164
+ * @param {Build} build
165
+ * @param {Object} server
166
+ * @param {String} username
167
+ * @param {Object} scmContext
168
+ * @returns {Promise<Build>} Updated build
169
+ */
170
+ async function updateBuildAndTriggerDownstreamJobs(config, build, server, username, scmContext) {
171
+ const { buildFactory, eventFactory, jobFactory, stageFactory, stageBuildFactory } = server.app;
172
+ const { statusMessage, statusMessageType, stats, status: desiredStatus, meta } = config;
173
+ const { triggerNextJobs, removeJoinBuilds, createOrUpdateStageTeardownBuild } = server.plugins.builds;
174
+
175
+ const currentStatus = build.status;
176
+
177
+ const event = await eventFactory.get(build.eventId);
178
+
179
+ if (stats) {
180
+ // need to do this so the field is dirty
181
+ build.stats = Object.assign(build.stats, stats);
182
+ }
183
+
184
+ // Short circuit for cases that don't need to update status
185
+ if (!desiredStatus) {
186
+ build.statusMessage = statusMessage || build.statusMessage;
187
+ build.statusMessageType = statusMessageType || build.statusMessageType;
188
+ } else if (['SUCCESS', 'FAILURE', 'ABORTED'].includes(desiredStatus)) {
189
+ build.meta = meta || {};
190
+ event.meta = merge({}, event.meta, build.meta);
191
+ build.endTime = new Date().toISOString();
192
+ } else if (desiredStatus === 'RUNNING') {
193
+ build.startTime = new Date().toISOString();
194
+ } else if (desiredStatus === 'BLOCKED' && !hoek.reach(build, 'stats.blockedStartTime')) {
195
+ build.stats = Object.assign(build.stats, {
196
+ blockedStartTime: new Date().toISOString()
197
+ });
198
+ } else if (desiredStatus === 'QUEUED' && currentStatus !== 'QUEUED') {
199
+ throw boom.badRequest(`Cannot update builds to ${desiredStatus}`);
200
+ } else if (desiredStatus === 'BLOCKED' && currentStatus === 'BLOCKED') {
201
+ // Queue-Service can call BLOCKED status update multiple times
202
+ throw boom.badRequest(`Cannot update builds to ${desiredStatus}`);
203
+ }
204
+
205
+ let isFixed = Promise.resolve(false);
206
+ let stopFrozen = null;
207
+
208
+ updateBuildStatus(build, desiredStatus, statusMessage, statusMessageType, username);
209
+
210
+ // If status got updated to RUNNING or COLLAPSED, update init endTime and code
211
+ if (['RUNNING', 'COLLAPSED', 'FROZEN'].includes(desiredStatus)) {
212
+ await updateInitStep(build, server.app);
213
+ } else {
214
+ stopFrozen = stopFrozenBuild(build, currentStatus);
215
+ isFixed = isFixedBuild(build, jobFactory);
216
+ }
217
+
218
+ const [newBuild, newEvent] = await Promise.all([build.update(), event.update(), stopFrozen]);
219
+ const job = await newBuild.job;
220
+ const pipeline = await job.pipeline;
221
+
222
+ if (desiredStatus) {
223
+ await server.events.emit('build_status', {
224
+ settings: job.permutations[0].settings,
225
+ status: newBuild.status,
226
+ event: newEvent.toJson(),
227
+ pipeline: pipeline.toJson(),
228
+ jobName: job.name,
229
+ build: newBuild.toJson(),
230
+ buildLink: `${buildFactory.uiUri}/pipelines/${pipeline.id}/builds/${build.id}`,
231
+ isFixed: await isFixed
232
+ });
233
+ }
234
+
235
+ const skipFurther = /\[(skip further)\]/.test(newEvent.causeMessage);
236
+
237
+ // Update stageBuild status if it has changed;
238
+ // if stageBuild status is currently terminal, do not update
239
+ const stage = await getStage({
240
+ stageFactory,
241
+ workflowGraph: newEvent.workflowGraph,
242
+ jobName: job.name,
243
+ pipelineId: pipeline.id
244
+ });
245
+ const isStageTeardown = STAGE_TEARDOWN_PATTERN.test(job.name);
246
+ let stageBuildHasFailure = false;
247
+
248
+ if (stage) {
249
+ const stageBuild = await stageBuildFactory.get({
250
+ stageId: stage.id,
251
+ eventId: newEvent.id
252
+ });
253
+
254
+ if (stageBuild.status !== newBuild.status) {
255
+ if (!TERMINAL_STATUSES.includes(stageBuild.status)) {
256
+ stageBuild.status = newBuild.status;
257
+ await stageBuild.update();
258
+ }
259
+ }
260
+
261
+ stageBuildHasFailure = TERMINAL_STATUSES.includes(stageBuild.status);
262
+ }
263
+
264
+ // Guard against triggering non-successful or unstable builds
265
+ // Don't further trigger pipeline if intend to skip further jobs
266
+ if (newBuild.status !== 'SUCCESS' || skipFurther) {
267
+ // Check for failed jobs and remove any child jobs in created state
268
+ if (newBuild.status === 'FAILURE') {
269
+ await removeJoinBuilds({ pipeline, job, build: newBuild, event: newEvent, stage }, server.app);
270
+
271
+ if (stage && !isStageTeardown) {
272
+ await createOrUpdateStageTeardownBuild(
273
+ { pipeline, job, build, username, scmContext, event, stage },
274
+ server.app
275
+ );
276
+ }
277
+ }
278
+ // Do not continue downstream is current job is stage teardown and statusBuild has failure
279
+ } else if (newBuild.status === 'SUCCESS' && isStageTeardown && stageBuildHasFailure) {
280
+ await removeJoinBuilds({ pipeline, job, build: newBuild, event: newEvent, stage }, server.app);
281
+ } else {
282
+ await triggerNextJobs({ pipeline, job, build: newBuild, username, scmContext, event: newEvent }, server.app);
283
+ }
284
+
285
+ // Determine if stage teardown build should start
286
+ // (if stage teardown build exists, and stageBuild.status is negative,
287
+ // and there are no active stage builds, and teardown build is not started)
288
+ if (stage && FINISHED_STATUSES.includes(newBuild.status)) {
289
+ const stageTeardownName = getFullStageJobName({ stageName: stage.name, jobName: 'teardown' });
290
+ const stageTeardownJob = await jobFactory.get({ pipelineId: pipeline.id, name: stageTeardownName });
291
+ const stageTeardownBuild = await buildFactory.get({ eventId: newEvent.id, jobId: stageTeardownJob.id });
292
+
293
+ // Start stage teardown build if stage is done
294
+ if (stageTeardownBuild && stageTeardownBuild.status === 'CREATED') {
295
+ const stageIsDone = await isStageDone({ stage, event: newEvent });
296
+
297
+ if (stageIsDone) {
298
+ stageTeardownBuild.status = 'QUEUED';
299
+ await stageTeardownBuild.update();
300
+ await stageTeardownBuild.start();
301
+ }
302
+ }
303
+ }
304
+
305
+ return newBuild;
306
+ }
307
+
308
+ module.exports = {
309
+ updateBuildAndTriggerDownstreamJobs
310
+ };
@@ -1,72 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const boom = require('@hapi/boom');
4
- const hoek = require('@hapi/hoek');
5
4
  const schema = require('screwdriver-data-schema');
6
5
  const joi = require('joi');
7
6
  const idSchema = schema.models.build.base.extract('id');
8
- const merge = require('lodash.mergewith');
9
- const { getScmUri, getUserPermissions, getFullStageJobName } = require('../helper');
10
- const STAGE_TEARDOWN_PATTERN = /^stage@([\w-]+)(?::teardown)$/;
11
- const TERMINAL_STATUSES = ['FAILURE', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
12
- const FINISHED_STATUSES = ['FAILURE', 'SUCCESS', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
13
-
14
- /**
15
- * Identify whether this build resulted in a previously failed job to become successful.
16
- *
17
- * @method isFixedBuild
18
- * @param build Build Object
19
- * @param jobFactory Job Factory instance
20
- */
21
- async function isFixedBuild(build, jobFactory) {
22
- if (build.status !== 'SUCCESS') {
23
- return false;
24
- }
25
-
26
- const job = await jobFactory.get(build.jobId);
27
- const failureBuild = await job.getLatestBuild({ status: 'FAILURE' });
28
- const successBuild = await job.getLatestBuild({ status: 'SUCCESS' });
29
-
30
- if ((failureBuild && !successBuild) || failureBuild.id > successBuild.id) {
31
- return true;
32
- }
33
-
34
- return false;
35
- }
36
-
37
- /**
38
- * Stops a frozen build from executing
39
- * @method stopFrozenBuild
40
- * @param {Object} build Build Object
41
- * @param {String} previousStatus Prevous build status
42
- */
43
- async function stopFrozenBuild(build, previousStatus) {
44
- if (previousStatus !== 'FROZEN') {
45
- return Promise.resolve();
46
- }
47
-
48
- return build.stopFrozen(previousStatus);
49
- }
50
-
51
- /**
52
- * Updates execution details for init step
53
- * @method stopFrozenBuild
54
- * @param {Object} build Build Object
55
- * @param {Object} app Hapi app Object
56
- */
57
- async function updateInitStep(build, app) {
58
- const step = await app.stepFactory.get({ buildId: build.id, name: 'sd-setup-init' });
59
- // If there is no init step, do nothing
60
-
61
- if (!step) {
62
- return null;
63
- }
64
-
65
- step.endTime = build.startTime || new Date().toISOString();
66
- step.code = 0;
67
-
68
- return step.update();
69
- }
7
+ const { getScmUri, getUserPermissions } = require('../helper');
8
+ const { updateBuildAndTriggerDownstreamJobs } = require('./helper/updateBuild');
70
9
 
71
10
  /**
72
11
  * Validate if build status can be updated
@@ -127,86 +66,6 @@ async function validateUserPermission(build, request) {
127
66
  await getUserPermissions({ user, scmUri, level: 'push', isAdmin: adminDetails.isAdmin });
128
67
  }
129
68
 
130
- /**
131
- * Set build status to desired status, set build statusMessage
132
- * @param {Object} build Build Model
133
- * @param {String} desiredStatus New Status
134
- * @param {String} statusMessage User passed status message
135
- * @param {String} statusMessageType User passed severity of the status message
136
- * @param {String} username User initiating status build update
137
- */
138
- function updateBuildStatus(build, desiredStatus, statusMessage, statusMessageType, username) {
139
- // UNSTABLE -> SUCCESS needs to update meta and endtime.
140
- // However, the status itself cannot be updated to SUCCESS
141
- const currentStatus = build.status;
142
-
143
- if (currentStatus !== 'UNSTABLE') {
144
- if (desiredStatus !== undefined) {
145
- build.status = desiredStatus;
146
- }
147
- if (build.status === 'ABORTED') {
148
- if (currentStatus === 'FROZEN') {
149
- build.statusMessage = `Frozen build aborted by ${username}`;
150
- } else {
151
- build.statusMessage = `Aborted by ${username}`;
152
- }
153
- } else if (build.status === 'FAILURE' || build.status === 'SUCCESS') {
154
- if (statusMessage) {
155
- build.statusMessage = statusMessage;
156
- build.statusMessageType = statusMessageType || null;
157
- }
158
- } else {
159
- build.statusMessage = statusMessage || null;
160
- build.statusMessageType = statusMessageType || null;
161
- }
162
- }
163
- }
164
-
165
- /**
166
- * Get stage for current node
167
- * @param {StageFactory} stageFactory Stage factory
168
- * @param {Object} workflowGraph Workflow graph
169
- * @param {String} jobName Job name
170
- * @param {Number} pipelineId Pipeline ID
171
- * @return {Stage} Stage for node
172
- */
173
- async function getStage({ stageFactory, workflowGraph, jobName, pipelineId }) {
174
- const currentNode = workflowGraph.nodes.find(node => node.name === jobName);
175
- let stage = null;
176
-
177
- if (currentNode && currentNode.stageName) {
178
- stage = await stageFactory.get({
179
- pipelineId,
180
- name: currentNode.stageName
181
- });
182
- }
183
-
184
- return Promise.resolve(stage);
185
- }
186
-
187
- /**
188
- * Checks if all builds in stage are done running
189
- * @param {Object} stage Stage
190
- * @param {Object} event Event
191
- * @return {Boolean} Flag if stage is done
192
- */
193
- async function isStageDone({ stage, event }) {
194
- // Get all jobIds for jobs in the stage
195
- const stageJobIds = stage.jobIds;
196
-
197
- stageJobIds.push(stage.setup);
198
-
199
- // Get all builds in a stage for this event
200
- const stageJobBuilds = await event.getBuilds({ params: { jobId: stageJobIds } });
201
- let stageIsDone = false;
202
-
203
- if (stageJobBuilds && stageJobBuilds.length !== 0) {
204
- stageIsDone = !stageJobBuilds.some(b => !FINISHED_STATUSES.includes(b.status));
205
- }
206
-
207
- return stageIsDone;
208
- }
209
-
210
69
  module.exports = () => ({
211
70
  method: 'PUT',
212
71
  path: '/builds/{id}',
@@ -220,13 +79,10 @@ module.exports = () => ({
220
79
  },
221
80
 
222
81
  handler: async (request, h) => {
223
- const { buildFactory, eventFactory, jobFactory, stageFactory, stageBuildFactory } = request.server.app;
82
+ const { buildFactory } = request.server.app;
224
83
  const { id } = request.params;
225
- const { statusMessage, statusMessageType, stats, status: desiredStatus } = request.payload;
226
84
  const { username, scmContext, scope } = request.auth.credentials;
227
85
  const isBuild = scope.includes('build') || scope.includes('temporal');
228
- const { triggerNextJobs, removeJoinBuilds, createOrUpdateStageTeardownBuild } =
229
- request.server.plugins.builds;
230
86
 
231
87
  // Check token permissions
232
88
  if (isBuild && username !== id) {
@@ -234,144 +90,18 @@ module.exports = () => ({
234
90
  }
235
91
 
236
92
  const build = await getBuildToUpdate(id, buildFactory);
237
- const currentStatus = build.status;
238
93
 
239
94
  if (!isBuild) {
240
95
  await validateUserPermission(build, request);
241
96
  }
242
- const event = await eventFactory.get(build.eventId);
243
-
244
- if (stats) {
245
- // need to do this so the field is dirty
246
- build.stats = Object.assign(build.stats, stats);
247
- }
248
-
249
- // Short circuit for cases that don't need to update status
250
- if (!desiredStatus) {
251
- build.statusMessage = statusMessage || build.statusMessage;
252
- build.statusMessageType = statusMessageType || build.statusMessageType;
253
- } else if (['SUCCESS', 'FAILURE', 'ABORTED'].includes(desiredStatus)) {
254
- build.meta = request.payload.meta || {};
255
- event.meta = merge({}, event.meta, build.meta);
256
- build.endTime = new Date().toISOString();
257
- } else if (desiredStatus === 'RUNNING') {
258
- build.startTime = new Date().toISOString();
259
- } else if (desiredStatus === 'BLOCKED' && !hoek.reach(build, 'stats.blockedStartTime')) {
260
- build.stats = Object.assign(build.stats, {
261
- blockedStartTime: new Date().toISOString()
262
- });
263
- } else if (desiredStatus === 'QUEUED' && currentStatus !== 'QUEUED') {
264
- throw boom.badRequest(`Cannot update builds to ${desiredStatus}`);
265
- } else if (desiredStatus === 'BLOCKED' && currentStatus === 'BLOCKED') {
266
- // Queue-Service can call BLOCKED status update multiple times
267
- throw boom.badRequest(`Cannot update builds to ${desiredStatus}`);
268
- }
269
-
270
- let isFixed = Promise.resolve(false);
271
- let stopFrozen = null;
272
-
273
- updateBuildStatus(build, desiredStatus, statusMessage, statusMessageType, username);
274
-
275
- // If status got updated to RUNNING or COLLAPSED, update init endTime and code
276
- if (['RUNNING', 'COLLAPSED', 'FROZEN'].includes(desiredStatus)) {
277
- await updateInitStep(build, request.server.app);
278
- } else {
279
- stopFrozen = stopFrozenBuild(build, currentStatus);
280
- isFixed = isFixedBuild(build, jobFactory);
281
- }
282
-
283
- const [newBuild, newEvent] = await Promise.all([build.update(), event.update(), stopFrozen]);
284
- const job = await newBuild.job;
285
- const pipeline = await job.pipeline;
286
-
287
- if (desiredStatus) {
288
- await request.server.events.emit('build_status', {
289
- settings: job.permutations[0].settings,
290
- status: newBuild.status,
291
- event: newEvent.toJson(),
292
- pipeline: pipeline.toJson(),
293
- jobName: job.name,
294
- build: newBuild.toJson(),
295
- buildLink: `${buildFactory.uiUri}/pipelines/${pipeline.id}/builds/${id}`,
296
- isFixed: await isFixed
297
- });
298
- }
299
-
300
- const skipFurther = /\[(skip further)\]/.test(newEvent.causeMessage);
301
-
302
- // Update stageBuild status if it has changed;
303
- // if stageBuild status is currently terminal, do not update
304
- const stage = await getStage({
305
- stageFactory,
306
- workflowGraph: newEvent.workflowGraph,
307
- jobName: job.name,
308
- pipelineId: pipeline.id
309
- });
310
- const isStageTeardown = STAGE_TEARDOWN_PATTERN.test(job.name);
311
- let stageBuildHasFailure = false;
312
-
313
- if (stage) {
314
- const stageBuild = await stageBuildFactory.get({
315
- stageId: stage.id,
316
- eventId: newEvent.id
317
- });
318
-
319
- if (stageBuild.status !== newBuild.status) {
320
- if (!TERMINAL_STATUSES.includes(stageBuild.status)) {
321
- stageBuild.status = newBuild.status;
322
- await stageBuild.update();
323
- }
324
- }
325
-
326
- stageBuildHasFailure = TERMINAL_STATUSES.includes(stageBuild.status);
327
- }
328
97
 
329
- // Guard against triggering non-successful or unstable builds
330
- // Don't further trigger pipeline if intend to skip further jobs
331
- if (newBuild.status !== 'SUCCESS' || skipFurther) {
332
- // Check for failed jobs and remove any child jobs in created state
333
- if (newBuild.status === 'FAILURE') {
334
- await removeJoinBuilds(
335
- { pipeline, job, build: newBuild, event: newEvent, stage },
336
- request.server.app
337
- );
338
-
339
- if (stage && !isStageTeardown) {
340
- await createOrUpdateStageTeardownBuild(
341
- { pipeline, job, build, username, scmContext, event, stage },
342
- request.server.app
343
- );
344
- }
345
- }
346
- // Do not continue downstream is current job is stage teardown and statusBuild has failure
347
- } else if (newBuild.status === 'SUCCESS' && isStageTeardown && stageBuildHasFailure) {
348
- await removeJoinBuilds({ pipeline, job, build: newBuild, event: newEvent, stage }, request.server.app);
349
- } else {
350
- await triggerNextJobs(
351
- { pipeline, job, build: newBuild, username, scmContext, event: newEvent },
352
- request.server.app
353
- );
354
- }
355
-
356
- // Determine if stage teardown build should start
357
- // (if stage teardown build exists, and stageBuild.status is negative,
358
- // and there are no active stage builds, and teardown build is not started)
359
- if (stage && FINISHED_STATUSES.includes(newBuild.status)) {
360
- const stageTeardownName = getFullStageJobName({ stageName: stage.name, jobName: 'teardown' });
361
- const stageTeardownJob = await jobFactory.get({ pipelineId: pipeline.id, name: stageTeardownName });
362
- const stageTeardownBuild = await buildFactory.get({ eventId: newEvent.id, jobId: stageTeardownJob.id });
363
-
364
- // Start stage teardown build if stage is done
365
- if (stageTeardownBuild && stageTeardownBuild.status === 'CREATED') {
366
- const stageIsDone = await isStageDone({ stage, event: newEvent });
367
-
368
- if (stageIsDone) {
369
- stageTeardownBuild.status = 'QUEUED';
370
- await stageTeardownBuild.update();
371
- await stageTeardownBuild.start();
372
- }
373
- }
374
- }
98
+ const newBuild = await updateBuildAndTriggerDownstreamJobs(
99
+ request.payload,
100
+ build,
101
+ request.server,
102
+ username,
103
+ scmContext
104
+ );
375
105
 
376
106
  return h.response(await newBuild.toJsonWithSteps()).code(200);
377
107
  },
@@ -5,6 +5,8 @@ const boom = require('@hapi/boom');
5
5
  const validationSchema = require('screwdriver-data-schema');
6
6
  const ANNOT_RESTRICT_PR = 'screwdriver.cd/restrictPR';
7
7
  const { getScmUri, isStageTeardown } = require('../helper');
8
+ const { updateBuildAndTriggerDownstreamJobs } = require('../builds/helper/updateBuild');
9
+ const { Status, BUILD_STATUS_MESSAGES } = require('../builds/triggers/helpers');
8
10
 
9
11
  module.exports = () => ({
10
12
  method: 'POST',
@@ -244,6 +246,23 @@ module.exports = () => ({
244
246
  if (event.builds === null) {
245
247
  return boom.notFound('No jobs to start.');
246
248
  }
249
+
250
+ const virtualJobBuilds = event.builds.filter(b => b.status === 'CREATED');
251
+
252
+ for (const build of virtualJobBuilds) {
253
+ await updateBuildAndTriggerDownstreamJobs(
254
+ {
255
+ status: Status.SUCCESS,
256
+ statusMessage: BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessage,
257
+ statusMessageType: BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessageType
258
+ },
259
+ build,
260
+ request.server,
261
+ username,
262
+ scmContext
263
+ );
264
+ }
265
+
247
266
  // everything succeeded, inform the user
248
267
  const location = urlLib.format({
249
268
  host: request.headers.host,