screwdriver-api 8.0.10 → 8.0.11

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": "8.0.10",
3
+ "version": "8.0.11",
4
4
  "description": "API server for the Screwdriver.cd service",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -135,18 +135,26 @@ async function getStage({ stageFactory, workflowGraph, jobName, pipelineId }) {
135
135
  }
136
136
 
137
137
  /**
138
- * Checks if all builds in stage are done running
138
+ * Get all builds in stage
139
139
  *
140
140
  * @param {Stage} stage Stage
141
141
  * @param {Event} event Event
142
- * @return {Boolean} Flag if stage is done
142
+ * @return {Promise<Build[]>} Builds in stage
143
143
  */
144
- async function isStageDone({ stage, event }) {
144
+ async function getStageJobBuilds({ stage, event }) {
145
145
  // Get all jobIds for jobs in the stage
146
146
  const stageJobIds = [...stage.jobIds, stage.setup];
147
147
 
148
148
  // Get all builds in a stage for this event
149
- const stageJobBuilds = await event.getBuilds({ params: { jobId: stageJobIds } });
149
+ return event.getBuilds({ params: { jobId: stageJobIds } });
150
+ }
151
+
152
+ /**
153
+ * Checks if all builds in stage are done running
154
+ * @param {Build[]} stageJobBuilds Builds in stage
155
+ * @returns {Boolean} Flag if stage is done
156
+ */
157
+ function isStageDone(stageJobBuilds) {
150
158
  let stageIsDone = false;
151
159
 
152
160
  if (stageJobBuilds && stageJobBuilds.length !== 0) {
@@ -292,10 +300,13 @@ async function updateBuildAndTriggerDownstreamJobs(config, build, server, userna
292
300
 
293
301
  // Start stage teardown build if stage is done
294
302
  if (stageTeardownBuild && stageTeardownBuild.status === 'CREATED') {
295
- const stageIsDone = await isStageDone({ stage, event: newEvent });
303
+ const stageJobBuilds = await getStageJobBuilds({ stage, event: newEvent });
304
+ const stageIsDone = isStageDone(stageJobBuilds);
296
305
 
297
306
  if (stageIsDone) {
298
307
  stageTeardownBuild.status = 'QUEUED';
308
+ stageTeardownBuild.parentBuildId = stageJobBuilds.map(b => b.id);
309
+
299
310
  await stageTeardownBuild.update();
300
311
  await stageTeardownBuild.start();
301
312
  }
@@ -98,7 +98,6 @@ class AndTrigger extends JoinBase {
98
98
  nextBuild,
99
99
  nextJob,
100
100
  parentBuilds: newParentBuilds,
101
- parentBuildId: this.currentBuild.id,
102
101
  joinListNames,
103
102
  isNextJobVirtual,
104
103
  nextJobStageName
@@ -534,10 +534,9 @@ async function getBuildsForGroupEvent(groupEventId, buildFactory) {
534
534
  * @param {Object} arg
535
535
  * @param {ParentBuilds} arg.joinParentBuilds Parent builds object for join job
536
536
  * @param {Build} arg.nextBuild Next build
537
- * @param {Build} arg.build Build for current completed job
538
537
  * @returns {Promise<Build>} Updated next build
539
538
  */
540
- async function updateParentBuilds({ joinParentBuilds, nextBuild, build }) {
539
+ async function updateParentBuilds({ joinParentBuilds, nextBuild }) {
541
540
  // Override old parentBuilds info
542
541
  const newParentBuilds = merge({}, joinParentBuilds, nextBuild.parentBuilds, (objVal, srcVal) =>
543
542
  // passthrough objects, else mergeWith mutates source
@@ -545,59 +544,69 @@ async function updateParentBuilds({ joinParentBuilds, nextBuild, build }) {
545
544
  );
546
545
 
547
546
  nextBuild.parentBuilds = newParentBuilds;
548
- // nextBuild.parentBuildId may be int or Array, so it needs to be flattened
549
- nextBuild.parentBuildId = Array.from(new Set([build.id, nextBuild.parentBuildId || []].flat()));
550
547
 
551
548
  return nextBuild.update();
552
549
  }
553
550
 
554
551
  /**
555
- * Check if all parent builds of the new build are done
556
- * @param {Object} arg
557
- * @param {Build} arg.newBuild Updated build
552
+ * Get builds in join list from parent builds
553
+ * @param {newBuild} arg.newBuild Updated build
558
554
  * @param {String[]} arg.joinListNames Join list names
559
555
  * @param {Number} arg.pipelineId Pipeline ID
560
556
  * @param {BuildFactory} arg.buildFactory Build factory
561
- * @returns {Promise<{hasFailure: Boolean, done: Boolean}>} Object with done and hasFailure statuses
557
+ * @returns {Promise<Map<String, Build>>} Join builds
562
558
  */
563
- async function getParentBuildStatus({ newBuild, joinListNames, pipelineId, buildFactory }) {
559
+ async function getJoinBuilds({ newBuild, joinListNames, pipelineId, buildFactory }) {
564
560
  const upstream = newBuild.parentBuilds || {};
561
+ const joinBuilds = {};
565
562
 
566
- // Get buildId
567
- const joinBuildIds = joinListNames.map(name => {
563
+ for (const jobName of joinListNames) {
568
564
  let upstreamPipelineId = pipelineId;
569
- let upstreamJobName = name;
565
+ let upstreamJobName = jobName;
570
566
 
571
- if (isExternalTrigger(name)) {
572
- const { externalPipelineId, externalJobName } = getExternalPipelineAndJob(name);
567
+ if (isExternalTrigger(upstreamJobName)) {
568
+ const { externalPipelineId, externalJobName } = getExternalPipelineAndJob(jobName);
573
569
 
574
570
  upstreamPipelineId = externalPipelineId;
575
571
  upstreamJobName = externalJobName;
576
572
  }
577
573
 
578
574
  if (upstream[upstreamPipelineId] && upstream[upstreamPipelineId].jobs[upstreamJobName]) {
579
- return upstream[upstreamPipelineId].jobs[upstreamJobName];
575
+ const buildId = upstream[upstreamPipelineId].jobs[upstreamJobName];
576
+
577
+ const build = await buildFactory.get(buildId);
578
+
579
+ if (typeof build.endTime === 'string') {
580
+ build.endTime = new Date(build.endTime);
581
+ }
582
+
583
+ joinBuilds[jobName] = build;
580
584
  }
585
+ }
581
586
 
582
- return undefined;
583
- });
587
+ return joinBuilds;
588
+ }
584
589
 
590
+ /**
591
+ * Check if all parent builds of the new build are done
592
+ * @param {Object} arg
593
+ * @param {String[]} arg.joinListNames Join list names
594
+ * @param {String[]} arg.joinBuilds Join builds
595
+ * @returns {Promise<{hasFailure: Boolean, done: Boolean}>} Object with done and hasFailure statuses
596
+ */
597
+ async function getParentBuildStatus({ joinListNames, joinBuilds }) {
585
598
  // If buildId is empty, the job hasn't executed yet and the join is not done
586
- const isExecuted = !joinBuildIds.includes(undefined);
587
-
588
- // Get the status of the builds
589
- const buildIds = joinBuildIds.filter(buildId => buildId !== undefined);
590
- const promisesToAwait = buildIds.map(buildId => buildFactory.get(buildId));
591
- const joinedBuilds = await Promise.all(promisesToAwait);
599
+ const isExecuted = joinListNames.every(name => joinBuilds[name] !== undefined);
600
+ const parentBuilds = Object.values(joinBuilds);
592
601
 
593
- const hasFailure = joinedBuilds
602
+ const hasFailure = parentBuilds
594
603
  .map(build => {
595
604
  // Do not need to run the next build; terminal status
596
605
  return [Status.FAILURE, Status.ABORTED, Status.COLLAPSED, Status.UNSTABLE].includes(build.status);
597
606
  })
598
607
  .includes(true);
599
608
 
600
- const isDoneStatus = joinedBuilds.every(build => {
609
+ const isDoneStatus = parentBuilds.every(build => {
601
610
  // All builds are done
602
611
  return [Status.FAILURE, Status.SUCCESS, Status.ABORTED, Status.UNSTABLE, Status.COLLAPSED].includes(
603
612
  build.status
@@ -616,40 +625,40 @@ async function getParentBuildStatus({ newBuild, joinListNames, pipelineId, build
616
625
  * if no failure, start new build
617
626
  * Otherwise, do nothing
618
627
  * @param {Object} arg If the build is done or not
619
- * @param {Boolean} arg.done If the build is done or not
620
- * @param {Boolean} arg.hasFailure If the build has a failure or not
628
+ * @param {String[]} arg.joinListNames Join list names
621
629
  * @param {Build} arg.newBuild Next build
622
630
  * @param {Job} arg.job Next job
623
631
  * @param {String|undefined} arg.pipelineId Pipeline ID
624
632
  * @param {String|undefined} arg.stageName Stage name
625
633
  * @param {Boolean} arg.isVirtualJob If the job is virtual or not
626
634
  * @param {Event} arg.event Event
627
- * @param {Build} arg.currentBuild Current build
635
+ * @param {BuildFactory} arg.buildFactory Build factory
628
636
  * @returns {Promise<Build|null>} The newly updated/created build
629
637
  */
630
638
  async function handleNewBuild({
631
- done,
632
- hasFailure,
639
+ joinListNames,
633
640
  newBuild,
634
641
  job,
635
642
  pipelineId,
636
643
  stageName,
637
644
  isVirtualJob,
638
645
  event,
639
- currentBuild
646
+ buildFactory
640
647
  }) {
641
- if (!done || Status.isStarted(newBuild.status)) {
642
- // The virtual job does not inherit metadata because the Launcher is not executed.
643
- // Therefore, it is necessary to take over the metadata from the previous build.
644
-
645
- // TODO There is a bug when virtual job requires [a, b] and if "a" and "b" are completed simultaneously.
646
- // https://github.com/screwdriver-cd/screwdriver/issues/3287
647
- if (isVirtualJob && !hasFreezeWindows(job)) {
648
- newBuild.meta = merge({}, newBuild.meta, currentBuild.meta);
648
+ const joinBuilds = await getJoinBuilds({
649
+ newBuild,
650
+ joinListNames,
651
+ pipelineId,
652
+ buildFactory
653
+ });
649
654
 
650
- await newBuild.update();
651
- }
655
+ /* CHECK IF ALL PARENT BUILDS OF NEW BUILD ARE DONE */
656
+ const { hasFailure, done } = await getParentBuildStatus({
657
+ joinBuilds,
658
+ joinListNames
659
+ });
652
660
 
661
+ if (!done || Status.isStarted(newBuild.status)) {
653
662
  return null;
654
663
  }
655
664
 
@@ -668,14 +677,28 @@ async function handleNewBuild({
668
677
  return null;
669
678
  }
670
679
 
680
+ /* Prepare to execute the build */
681
+ const parentBuilds = Object.values(joinBuilds);
682
+
683
+ parentBuilds.sort((l, r) => {
684
+ if (l.endTime && r.endTime) {
685
+ return l.endTime.getTime() - r.endTime.getTime();
686
+ }
687
+
688
+ // Move to tail if endTime is not set
689
+ return (l.endTime ? 0 : 1) - (r.endTime ? 0 : 1);
690
+ });
691
+ newBuild.parentBuildId = parentBuilds.map(build => build.id);
692
+
671
693
  // Bypass execution of the build if the job is virtual
672
694
  if (isVirtualJob && !hasFreezeWindows(job)) {
673
695
  newBuild.status = Status.SUCCESS;
674
696
  newBuild.statusMessage = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessage;
675
697
  newBuild.statusMessageType = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessageType;
698
+
676
699
  // The virtual job does not inherit metadata because the Launcher is not executed.
677
700
  // Therefore, it is necessary to take over the metadata from the previous build.
678
- newBuild.meta = merge({}, newBuild.meta, currentBuild.meta);
701
+ newBuild.meta = parentBuilds.reduce((acc, build) => merge(acc, build.meta), {});
679
702
 
680
703
  return newBuild.update();
681
704
  }
@@ -1015,38 +1038,6 @@ async function ensureStageTeardownBuildExists({
1015
1038
  }
1016
1039
  }
1017
1040
 
1018
- /**
1019
- * Get parentBuildId from parentBuilds object
1020
- * @param {Object} arg
1021
- * @param {ParentBuilds} arg.parentBuilds Builds that triggered this build
1022
- * @param {String[]} arg.joinListNames Array of join job name
1023
- * @param {Number} arg.pipelineId Pipeline ID
1024
- * @returns {String[]} Array of parentBuildId
1025
- */
1026
- function getParentBuildIds({ currentBuildId, parentBuilds, joinListNames, pipelineId }) {
1027
- const parentBuildIds = joinListNames
1028
- .map(name => {
1029
- let parentBuildPipelineId = pipelineId;
1030
- let parentBuildJobName = name;
1031
-
1032
- if (isExternalTrigger(name)) {
1033
- const { externalPipelineId, externalJobName } = getExternalPipelineAndJob(name);
1034
-
1035
- parentBuildPipelineId = externalPipelineId;
1036
- parentBuildJobName = externalJobName;
1037
- }
1038
-
1039
- if (parentBuilds[parentBuildPipelineId] && parentBuilds[parentBuildPipelineId].jobs[parentBuildJobName]) {
1040
- return parentBuilds[parentBuildPipelineId].jobs[parentBuildJobName];
1041
- }
1042
-
1043
- return null;
1044
- })
1045
- .filter(Boolean); // Remove undefined or null values
1046
-
1047
- return Array.from(new Set([currentBuildId, ...parentBuildIds]));
1048
- }
1049
-
1050
1041
  /**
1051
1042
  * Extract a current pipeline's next jobs from pipeline join data
1052
1043
  * (Next jobs triggered as external are not included)
@@ -1206,13 +1197,13 @@ module.exports = {
1206
1197
  getSameParentEvents,
1207
1198
  mergeParentBuilds,
1208
1199
  updateParentBuilds,
1200
+ getJoinBuilds,
1209
1201
  getParentBuildStatus,
1210
1202
  handleNewBuild,
1211
1203
  ensureStageTeardownBuildExists,
1212
1204
  getBuildsForGroupEvent,
1213
1205
  createJoinObject,
1214
1206
  createExternalEvent,
1215
- getParentBuildIds,
1216
1207
  strToInt,
1217
1208
  createEvent,
1218
1209
  deleteBuild,
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const logger = require('screwdriver-logger');
4
- const { createInternalBuild, updateParentBuilds, getParentBuildStatus, handleNewBuild } = require('./helpers');
4
+ const { createInternalBuild, updateParentBuilds, handleNewBuild } = require('./helpers');
5
5
 
6
6
  /**
7
7
  * @typedef {import('screwdriver-models').EventFactory} EventFactory
@@ -41,7 +41,6 @@ class JoinBase {
41
41
  * @param {Build} nextBuild
42
42
  * @param {Job} nextJob
43
43
  * @param {import('./helpers').ParentBuilds} parentBuilds
44
- * @param {String} parentBuildId
45
44
  * @param {String[]} joinListNames
46
45
  * @param {Boolean} isNextJobVirtual
47
46
  * @param {String} nextJobStageName
@@ -53,7 +52,6 @@ class JoinBase {
53
52
  nextBuild,
54
53
  nextJob,
55
54
  parentBuilds,
56
- parentBuildId,
57
55
  joinListNames,
58
56
  isNextJobVirtual,
59
57
  nextJobStageName
@@ -73,7 +71,7 @@ class JoinBase {
73
71
  event, // this is the parentBuild for the next build
74
72
  baseBranch: event.baseBranch || null,
75
73
  parentBuilds,
76
- parentBuildId,
74
+ parentBuildId: this.currentBuild.id,
77
75
  start: false
78
76
  });
79
77
  } else {
@@ -90,24 +88,15 @@ class JoinBase {
90
88
  return null;
91
89
  }
92
90
 
93
- /* CHECK IF ALL PARENT BUILDS OF NEW BUILD ARE DONE */
94
- const { hasFailure, done } = await getParentBuildStatus({
95
- newBuild,
96
- joinListNames,
97
- pipelineId,
98
- buildFactory: this.buildFactory
99
- });
100
-
101
91
  return handleNewBuild({
102
- done,
103
- hasFailure,
92
+ joinListNames,
104
93
  newBuild,
105
94
  job: nextJob,
106
95
  pipelineId,
107
96
  isVirtualJob: isNextJobVirtual,
108
97
  stageName: nextJobStageName,
109
98
  event,
110
- currentBuild: this.currentBuild
99
+ buildFactory: this.buildFactory
111
100
  });
112
101
  }
113
102
  }
@@ -56,12 +56,16 @@ class OrBase {
56
56
  return nextBuild;
57
57
  }
58
58
 
59
+ nextBuild.parentBuildId = [this.currentBuild.id];
60
+
59
61
  // Bypass execution of the build if the job is virtual
60
62
  if (isNextJobVirtual && !hasWindows) {
61
63
  nextBuild.status = Status.SUCCESS;
62
64
  nextBuild.statusMessage = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessage;
63
65
  nextBuild.statusMessageType = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessageType;
64
- nextBuild.meta = merge({}, nextBuild.meta, this.currentBuild.meta);
66
+
67
+ // Overwrite metadata by current build's
68
+ nextBuild.meta = merge({}, this.currentBuild.meta);
65
69
 
66
70
  return nextBuild.update();
67
71
  }
@@ -93,7 +97,9 @@ class OrBase {
93
97
  nextBuild.status = Status.SUCCESS;
94
98
  nextBuild.statusMessage = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessage;
95
99
  nextBuild.statusMessageType = BUILD_STATUS_MESSAGES.SKIP_VIRTUAL_JOB.statusMessageType;
96
- nextBuild.meta = merge({}, nextBuild.meta, this.currentBuild.meta);
100
+
101
+ // Overwrite metadata by current build's
102
+ nextBuild.meta = merge({}, this.currentBuild.meta);
97
103
 
98
104
  await nextBuild.update();
99
105
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { mergeParentBuilds, getParentBuildIds } = require('./helpers');
3
+ const { mergeParentBuilds } = require('./helpers');
4
4
  const { JoinBase } = require('./joinBase');
5
5
 
6
6
  /**
@@ -45,20 +45,12 @@ class RemoteJoin extends JoinBase {
45
45
 
46
46
  const newParentBuilds = mergeParentBuilds(parentBuilds, groupEventBuilds, this.currentEvent, externalEvent);
47
47
 
48
- const parentBuildId = getParentBuildIds({
49
- currentBuildId: this.currentBuild.id,
50
- parentBuilds: newParentBuilds,
51
- joinListNames,
52
- pipelineId: externalEvent.pipelineId
53
- });
54
-
55
48
  return this.processNextBuild({
56
49
  pipelineId: externalEvent.pipelineId,
57
50
  event: externalEvent,
58
51
  nextBuild,
59
52
  nextJob,
60
53
  parentBuilds: newParentBuilds,
61
- parentBuildId,
62
54
  joinListNames,
63
55
  isNextJobVirtual,
64
56
  nextJobStageName
@@ -74,7 +74,7 @@ module.exports = () => ({
74
74
 
75
75
  permutation.annotations = permutation.annotations || {};
76
76
 
77
- const annotations = permutation.annotations;
77
+ const { annotations } = permutation;
78
78
 
79
79
  if (annotations[adminAnnotation]) {
80
80
  logger.info(