screwdriver-api 8.0.66 → 8.0.68

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.
@@ -361,32 +361,32 @@ scms:
361
361
  # # SCM clone type (https or ssh)
362
362
  # cloneType: SCM_GITLAB_RO_CLONE_TYPE
363
363
  webhooks:
364
- # Object keyed by scm name with value webhook settings.
365
- # Value of webhook settings is an object with the following properties:
366
- # Example:
367
- # {
368
- # "github": {
369
- # # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
370
- # "username": "sd-buildbot",
371
- # # Ignore commits made by these users
372
- # "ignoreCommitsBy": [],
373
- # # Restrict PR: all, none, branch, or fork
374
- # "restrictPR": "none",
375
- # # Chain PR: true or false
376
- # "chainPR": false,
377
- # # Upper limit on incoming uploads to builds
378
- # "maxBytes": 1048576 #1MB
379
- # },
380
- # "github.example.com": {
381
- # "username": "someuser",
382
- # "ignoreCommitsBy": ["someuser", "anotheruser"],
383
- # "restrictPR": "branch",
384
- # "chainPR": true,
385
- # "maxBytes": 2097152 #2MB
386
- # }
387
- # }
388
- __name: WEBHOOK_SETTINGS
389
- __format: json
364
+ scms:
365
+ # Object keyed by scm name with value webhook settings.
366
+ # Value of webhook settings is an object with the following properties:
367
+ # Example:
368
+ # {
369
+ # "github": {
370
+ # # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
371
+ # "username": "sd-buildbot",
372
+ # # Ignore commits made by these users
373
+ # "ignoreCommitsBy": [],
374
+ # # Restrict PR: all, none, branch, or fork
375
+ # "restrictPR": "none",
376
+ # # Chain PR: true or false
377
+ # "chainPR": false
378
+ # },
379
+ # "github.example.com": {
380
+ # "username": "someuser",
381
+ # "ignoreCommitsBy": ["someuser", "anotheruser"],
382
+ # "restrictPR": "branch",
383
+ # "chainPR": true
384
+ # }
385
+ # }
386
+ __name: WEBHOOK_SETTINGS
387
+ __format: json
388
+ maxBytes: WEBHOOK_MAX_BYTES
389
+
390
390
 
391
391
  bookends:
392
392
  # Object keyed by cluster name with value setup/teardown bookend.
@@ -277,42 +277,40 @@ scms: {}
277
277
  # accessToken: headlesstoken
278
278
  # # SCM clone type (https or ssh)
279
279
  # cloneType: https
280
- webhooks:
281
- github:
282
- # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
283
- username: sd-buildbot
284
- # Ignore commits made by these users
285
- ignoreCommitsBy: []
286
- # Restrict PR: all, none, branch, or fork
287
- restrictPR: none
288
- # Chain PR: true or false
289
- chainPR: false
290
- # Upper limit on incoming uploads to builds
291
- maxBytes: 1048576 #1MB
292
- # Object keyed by scm name with value webhook settings.
293
- # Value of webhook settings is an object with the following properties:
294
- # Example:
295
- # {
296
- # "github:github.com": {
297
- # # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
298
- # "username": "sd-buildbot",
299
- # # Ignore commits made by these users
300
- # "ignoreCommitsBy": [],
301
- # # Restrict PR: all, none, branch, or fork
302
- # "restrictPR": "none",
303
- # # Chain PR: true or false
304
- # "chainPR": false,
305
- # # Upper limit on incoming uploads to builds
306
- # "maxBytes": 1048576 #1MB
307
- # },
308
- # "github.example.com": {
309
- # "username": "someuser",
310
- # "ignoreCommitsBy": ["someuser", "anotheruser"],
311
- # "restrictPR": "branch",
312
- # "chainPR": true,
313
- # "maxBytes": 2097152 #2MB
314
- # }
315
- # }
280
+ webhooks:
281
+ scms:
282
+ github:
283
+ # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
284
+ username: sd-buildbot
285
+ # Ignore commits made by these users
286
+ ignoreCommitsBy: []
287
+ # Restrict PR: all, none, branch, or fork
288
+ restrictPR: none
289
+ # Chain PR: true or false
290
+ chainPR: false
291
+ # Object keyed by scm name with value webhook settings.
292
+ # Value of webhook settings is an object with the following properties:
293
+ # Example:
294
+ # {
295
+ # "github:github.com": {
296
+ # # Obtains the SCM token for a given user. If a user does not have a valid SCM token registered with Screwdriver, it will use this user's token instead.
297
+ # "username": "sd-buildbot",
298
+ # # Ignore commits made by these users
299
+ # "ignoreCommitsBy": [],
300
+ # # Restrict PR: all, none, branch, or fork
301
+ # "restrictPR": "none",
302
+ # # Chain PR: true or false
303
+ # "chainPR": false
304
+ # },
305
+ # "github.example.com": {
306
+ # "username": "someuser",
307
+ # "ignoreCommitsBy": ["someuser", "anotheruser"],
308
+ # "restrictPR": "branch",
309
+ # "chainPR": true
310
+ # }
311
+ # }
312
+ # Upper limit on incoming uploads to builds
313
+ maxBytes: 1048576 # 1MB
316
314
  coverage:
317
315
  default: "false"
318
316
  plugin: sonar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screwdriver-api",
3
- "version": "8.0.66",
3
+ "version": "8.0.68",
4
4
  "description": "API server for the Screwdriver.cd service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -7,7 +7,7 @@ const { PR_JOB_NAME, PR_STAGE_NAME, STAGE_TEARDOWN_PATTERN } = require('screwdri
7
7
  const { getFullStageJobName } = require('../../helper');
8
8
  const { updateVirtualBuildSuccess } = require('../triggers/helpers');
9
9
  const TERMINAL_STATUSES = ['FAILURE', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
10
- const FINISHED_STATUSES = ['FAILURE', 'SUCCESS', 'ABORTED', 'UNSTABLE', 'COLLAPSED'];
10
+ const FINISHED_STATUSES = ['SUCCESS', ...TERMINAL_STATUSES];
11
11
 
12
12
  /**
13
13
  * @typedef {import('screwdriver-models/lib/build')} Build
@@ -140,6 +140,35 @@ async function getStage({ stageFactory, workflowGraph, jobName, pipelineId }) {
140
140
  return Promise.resolve(stage);
141
141
  }
142
142
 
143
+ /**
144
+ * Get stage build for current event
145
+ * @param {StageFactory} stageFactory Stage factory
146
+ * @param {StageBuildFactory} stageBuildFactory stage build factory
147
+ * @param {Object} workflowGraph workflow graph
148
+ * @param {String} jobName job name
149
+ * @param {Number} pipelineId pipeline id
150
+ * @param {Number} eventId event id
151
+ *
152
+ * @returns {StageBuild} stage build for current event
153
+ */
154
+ async function getStageBuild({ stageFactory, stageBuildFactory, workflowGraph, jobName, pipelineId, eventId }) {
155
+ const stage = await getStage({
156
+ stageFactory,
157
+ workflowGraph,
158
+ jobName,
159
+ pipelineId
160
+ });
161
+
162
+ if (stage) {
163
+ return stageBuildFactory.get({
164
+ stageId: stage.id,
165
+ eventId
166
+ });
167
+ }
168
+
169
+ return null;
170
+ }
171
+
143
172
  /**
144
173
  * Get all builds in stage
145
174
  *
@@ -241,6 +270,50 @@ function deriveEventStatusFromBuildStatuses(builds) {
241
270
  return newEventStatus;
242
271
  }
243
272
 
273
+ /**
274
+ * Update stage build's status
275
+ * Stage build's status can transition as below
276
+ * CREATED -> RUNNING -when all builds are succeeded-> SUCCESS
277
+ * -when some builds are terminated-> FAILURE, ABORTED, UNSTABLE, COLLAPSED
278
+ *
279
+ * @param {Object} param
280
+ * @param {StageBuild} param.stageBuild
281
+ * @param {String} param.newStatus
282
+ * @param {Job} param.job
283
+ * @returns {StageBuild} new stage build's status
284
+ */
285
+ async function updateStageBuildStatus({ stageBuild, newStatus, job }) {
286
+ if (stageBuild.status === newStatus) {
287
+ return stageBuild;
288
+ }
289
+
290
+ // Not update when the stage build status is terminal
291
+ if (TERMINAL_STATUSES.includes(stageBuild.status)) {
292
+ return stageBuild;
293
+ }
294
+
295
+ const isStageTeardown = STAGE_TEARDOWN_PATTERN.test(job.name);
296
+ const intermediateStatuses = ['RUNNING', ...TERMINAL_STATUSES];
297
+ const teardownStatuses = ['RUNNING', ...FINISHED_STATUSES];
298
+
299
+ // It means all builds are succeeded if stage build status is not terminal when teardown build is done.
300
+ if (isStageTeardown && teardownStatuses.includes(newStatus)) {
301
+ stageBuild.status = newStatus;
302
+
303
+ return stageBuild.update();
304
+ }
305
+
306
+ // Stage build status can transition to terminated status in the intermediate builds.
307
+ // But not update to SUCCESS
308
+ if (!isStageTeardown && intermediateStatuses.includes(newStatus)) {
309
+ stageBuild.status = newStatus;
310
+
311
+ return stageBuild.update();
312
+ }
313
+
314
+ return stageBuild;
315
+ }
316
+
244
317
  /**
245
318
  * Updates the build and trigger its downstream jobs in the workflow
246
319
  *
@@ -336,14 +409,9 @@ async function updateBuildAndTriggerDownstreamJobs(config, build, server, userna
336
409
  eventId: newEvent.id
337
410
  });
338
411
 
339
- if (stageBuild.status !== newBuild.status) {
340
- if (!TERMINAL_STATUSES.includes(stageBuild.status)) {
341
- stageBuild.status = newBuild.status;
342
- await stageBuild.update();
343
- }
344
- }
412
+ const newStageBuild = await updateStageBuildStatus({ stageBuild, newStatus: newBuild.status, job });
345
413
 
346
- stageBuildHasFailure = TERMINAL_STATUSES.includes(stageBuild.status);
414
+ stageBuildHasFailure = TERMINAL_STATUSES.includes(newStageBuild.status);
347
415
  }
348
416
 
349
417
  // Guard against triggering non-successful or unstable builds
@@ -417,5 +485,7 @@ async function updateBuildAndTriggerDownstreamJobs(config, build, server, userna
417
485
 
418
486
  module.exports = {
419
487
  updateBuildAndTriggerDownstreamJobs,
420
- deriveEventStatusFromBuildStatuses
488
+ deriveEventStatusFromBuildStatuses,
489
+ getStageBuild,
490
+ updateStageBuildStatus
421
491
  };
@@ -44,6 +44,7 @@ const {
44
44
  getNextJobStageName
45
45
  } = require('./triggers/helpers');
46
46
  const { getFullStageJobName } = require('../helper');
47
+ const { updateStageBuildStatus, getStageBuild } = require('./helper/updateBuild');
47
48
 
48
49
  /**
49
50
  * Delete a build
@@ -71,7 +72,7 @@ async function triggerNextJobs(config, app) {
71
72
  const currentPipeline = config.pipeline;
72
73
  const currentJob = config.job;
73
74
  const currentBuild = config.build;
74
- const { jobFactory, buildFactory, eventFactory, pipelineFactory } = app;
75
+ const { jobFactory, buildFactory, eventFactory, pipelineFactory, stageFactory, stageBuildFactory } = app;
75
76
 
76
77
  /** @type {EventModel} */
77
78
  const currentEvent = await eventFactory.get({ id: currentBuild.eventId });
@@ -100,8 +101,8 @@ async function triggerNextJobs(config, app) {
100
101
  for (const [nextJobName] of Object.entries(currentPipelineNextJobs)) {
101
102
  const nextJob = await getJob(nextJobName, currentPipeline.id, jobFactory);
102
103
  const node = currentEvent.workflowGraph.nodes.find(n => n.name === trimJobName(nextJobName));
103
- const isNextJobVirtual = node.virtual || false;
104
- const nextJobStageName = getNextJobStageName({ stageName: node.stageName, nextJobName });
104
+ const isNextJobVirtual = node && node.virtual === true;
105
+ const nextJobStageName = node ? getNextJobStageName({ stageName: node.stageName, nextJobName }) : null;
105
106
  const resource = `pipeline:${currentPipeline.id}:groupEvent:${currentEvent.groupEventId}`;
106
107
  let lock;
107
108
  let nextBuild;
@@ -144,15 +145,31 @@ async function triggerNextJobs(config, app) {
144
145
  );
145
146
  }
146
147
 
147
- if (isNextJobVirtual && nextBuild && nextBuild.status === Status.SUCCESS) {
148
- downstreamOfNextJobsToBeProcessed.push({
149
- build: nextBuild,
150
- event: currentEvent,
151
- job: nextJob,
152
- pipeline: currentPipeline,
153
- scmContext: config.scmContext,
154
- username: config.username
148
+ if (isNextJobVirtual) {
149
+ const stageBuild = await getStageBuild({
150
+ stageFactory,
151
+ stageBuildFactory,
152
+ workflowGraph: currentEvent.workflowGraph,
153
+ jobName: nextJobName,
154
+ pipelineId: currentPipeline.id,
155
+ eventId: currentEvent.id
155
156
  });
157
+
158
+ if (stageBuild) {
159
+ await updateStageBuildStatus({ stageBuild, newStatus: nextBuild.status, job: nextJob });
160
+ }
161
+
162
+ // Trigger downstream jobs
163
+ if (nextBuild && nextBuild.status === Status.SUCCESS) {
164
+ downstreamOfNextJobsToBeProcessed.push({
165
+ build: nextBuild,
166
+ event: currentEvent,
167
+ job: nextJob,
168
+ pipeline: currentPipeline,
169
+ scmContext: config.scmContext,
170
+ username: config.username
171
+ });
172
+ }
156
173
  }
157
174
  } catch (err) {
158
175
  logger.error(
@@ -290,8 +307,8 @@ async function triggerNextJobs(config, app) {
290
307
  for (const [nextJobName, nextJobInfo] of Object.entries(joinedPipeline.jobs)) {
291
308
  const nextJob = await getJob(nextJobName, joinedPipelineId, jobFactory);
292
309
  const node = externalEvent.workflowGraph.nodes.find(n => n.name === trimJobName(nextJobName));
293
- const isNextJobVirtual = node.virtual || false;
294
- const nextJobStageName = getNextJobStageName({ stageName: node.stageName, nextJobName });
310
+ const isNextJobVirtual = node && node.virtual === true;
311
+ const nextJobStageName = node ? getNextJobStageName({ stageName: node.stageName, nextJobName }) : null;
295
312
 
296
313
  const { parentBuilds } = parseJobInfo({
297
314
  joinObj: joinedPipeline.jobs,
@@ -334,15 +351,30 @@ async function triggerNextJobs(config, app) {
334
351
  );
335
352
  }
336
353
 
337
- if (isNextJobVirtual && nextBuild && nextBuild.status === Status.SUCCESS) {
338
- downstreamOfNextJobsToBeProcessed.push({
339
- build: nextBuild,
340
- event: currentEvent,
341
- job: nextJob,
342
- pipeline: await nextJob.pipeline,
343
- scmContext: config.scmContext,
344
- username: config.username
354
+ if (isNextJobVirtual) {
355
+ const stageBuild = await getStageBuild({
356
+ stageFactory,
357
+ stageBuildFactory,
358
+ workflowGraph: currentEvent.workflowGraph,
359
+ jobName: nextJob.name,
360
+ pipelineId: currentPipeline.id,
361
+ eventId: currentEvent.id
345
362
  });
363
+
364
+ if (stageBuild) {
365
+ await updateStageBuildStatus({ stageBuild, newStatus: nextBuild.status, job: nextJob });
366
+ }
367
+
368
+ if (nextBuild && nextBuild.status === Status.SUCCESS) {
369
+ downstreamOfNextJobsToBeProcessed.push({
370
+ build: nextBuild,
371
+ event: currentEvent,
372
+ job: nextJob,
373
+ pipeline: await nextJob.pipeline,
374
+ scmContext: config.scmContext,
375
+ username: config.username
376
+ });
377
+ }
346
378
  }
347
379
  } catch (err) {
348
380
  logger.error(
@@ -8,6 +8,7 @@ const schema = require('screwdriver-data-schema');
8
8
  const getSchema = schema.models.event.get;
9
9
  const idSchema = schema.models.event.base.extract('id');
10
10
  const { deriveEventStatusFromBuildStatuses } = require('../builds/helper/updateBuild');
11
+ const nonTerminatedStatus = ['CREATED', 'RUNNING', 'QUEUED', 'BLOCKED', 'FROZEN'];
11
12
 
12
13
  module.exports = () => ({
13
14
  method: 'PUT',
@@ -78,25 +79,25 @@ module.exports = () => ({
78
79
 
79
80
  // User has good permissions, get event builds
80
81
  const builds = await event.getBuilds();
81
- const toUpdate = [];
82
+ const toUpdateBuilds = [];
82
83
  const updatedBuilds = [];
83
84
 
84
85
  // Update endtime and stop running builds
85
86
  // Note: COLLAPSED builds will never run
86
87
  builds.forEach(b => {
87
- if (['CREATED', 'RUNNING', 'QUEUED', 'BLOCKED', 'FROZEN'].includes(b.status)) {
88
+ if (nonTerminatedStatus.includes(b.status)) {
88
89
  if (b.status === 'RUNNING') {
89
90
  b.endTime = new Date().toISOString();
90
91
  }
91
92
  b.status = 'ABORTED';
92
93
  b.statusMessage = `Aborted by ${username}`;
93
94
 
94
- toUpdate.push(b.update());
95
+ toUpdateBuilds.push(b.update());
95
96
  } else {
96
97
  updatedBuilds.push(b);
97
98
  }
98
99
  });
99
- updatedBuilds.push(...(await Promise.all(toUpdate)));
100
+ updatedBuilds.push(...(await Promise.all(toUpdateBuilds)));
100
101
 
101
102
  const newEventStatus = deriveEventStatusFromBuildStatuses(updatedBuilds);
102
103
 
@@ -105,6 +106,19 @@ module.exports = () => ({
105
106
  await event.update();
106
107
  }
107
108
 
109
+ // Update stageBuild status to ABORTED
110
+ const stageBuilds = await event.getStageBuilds();
111
+ const toUpdateStageBuilds = [];
112
+
113
+ stageBuilds.forEach(sb => {
114
+ if (nonTerminatedStatus.includes(sb.status)) {
115
+ sb.status = 'ABORTED';
116
+ toUpdateStageBuilds.push(sb.update());
117
+ }
118
+ });
119
+
120
+ await Promise.all(toUpdateStageBuilds);
121
+
108
122
  // everything succeeded, inform the user
109
123
  const location = urlLib.format({
110
124
  host: request.headers.host,
@@ -20,7 +20,7 @@ module.exports = () => ({
20
20
  },
21
21
 
22
22
  handler: async (request, h) => {
23
- const pipelineFactory = request.server.app.pipelineFactory;
23
+ const { pipelineFactory } = request.server.app;
24
24
  const { scope } = request.auth.credentials;
25
25
  const { scmContext, includeUserToken } = request.query;
26
26
 
@@ -6,8 +6,7 @@ const boom = require('@hapi/boom');
6
6
  const { ValidationError } = require('joi');
7
7
  const { startHookEvent } = require('./helper');
8
8
 
9
- const DEFAULT_MAX_BYTES = 1048576;
10
- const MAX_BYTES_UPPER_BOUND = 5242880; // 5MB
9
+ const DEFAULT_MAX_BYTES = 1048576; // 1MB
11
10
  const providerSchema = joi
12
11
  .object({
13
12
  username: joi.string().required(),
@@ -41,7 +40,7 @@ const webhooksPlugin = {
41
40
  name: 'webhooks',
42
41
  async register(server, options) {
43
42
  const pluginOptions = joi.attempt(
44
- options,
43
+ options.scms,
45
44
  joi.object().pattern(joi.string(), providerSchema).min(1).required(),
46
45
  'Invalid config for plugin-webhooks'
47
46
  );
@@ -59,7 +58,7 @@ const webhooksPlugin = {
59
58
  }
60
59
  },
61
60
  payload: {
62
- maxBytes: MAX_BYTES_UPPER_BOUND,
61
+ maxBytes: parseInt(pluginOptions.maxBytes, 10) || DEFAULT_MAX_BYTES,
63
62
  parse: false,
64
63
  output: 'stream'
65
64
  },
@@ -71,17 +70,10 @@ const webhooksPlugin = {
71
70
  let hookId;
72
71
 
73
72
  try {
74
- let size = 0;
75
73
  const chunks = [];
76
74
 
77
75
  for await (const chunk of request.payload) {
78
- size += chunk.length;
79
76
  chunks.push(chunk);
80
- if (size > DEFAULT_MAX_BYTES) {
81
- throw boom.entityTooLarge(
82
- `Payload size exceeds the maximum limit of ${DEFAULT_MAX_BYTES} bytes`
83
- );
84
- }
85
77
  }
86
78
 
87
79
  const data = Buffer.concat(chunks).toString();