screwdriver-api 7.0.130 → 7.0.132

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.
@@ -2,8 +2,6 @@
2
2
 
3
3
  const logger = require('screwdriver-logger');
4
4
  const workflowParser = require('screwdriver-workflow-parser');
5
- const merge = require('lodash.mergewith');
6
- const schema = require('screwdriver-data-schema');
7
5
  const hoek = require('@hapi/hoek');
8
6
  const getRoute = require('./get');
9
7
  const getBuildStatusesRoute = require('./getBuildStatuses');
@@ -18,59 +16,29 @@ const stepLogsRoute = require('./steps/logs');
18
16
  const listSecretsRoute = require('./listSecrets');
19
17
  const tokenRoute = require('./token');
20
18
  const metricsRoute = require('./metrics');
21
- const { EXTERNAL_TRIGGER_ALL } = schema.config.regex;
22
19
  const locker = require('../lock');
23
- const { getFullStageJobName } = require('../helper');
24
-
25
- /**
26
- * Checks if job is external trigger
27
- * @param {String} jobName Job name
28
- * @return {Boolean} If job name is external trigger or not
29
- */
30
- function isExternalTrigger(jobName) {
31
- return EXTERNAL_TRIGGER_ALL.test(jobName);
32
- }
33
-
34
- /**
35
- * Get pipelineId and job name from the `name`
36
- * If internal, pipelineId will be the current pipelineId
37
- * @param {String} name Job name
38
- * @param {String} pipelineId Pipeline ID
39
- * @return {Object} With pipeline id, job name and isExternal flag
40
- */
41
- function getPipelineAndJob(name, pipelineId) {
42
- let externalJobName = name;
43
- let externalPipelineId = pipelineId;
44
- let isExternal = false;
45
-
46
- if (isExternalTrigger(name)) {
47
- isExternal = true;
48
- [, externalPipelineId, externalJobName] = EXTERNAL_TRIGGER_ALL.exec(name);
49
- }
50
-
51
- return { externalPipelineId, externalJobName, isExternal };
52
- }
53
-
54
- /**
55
- * Helper function to fetch external event from parentBuilds
56
- * @param {Object} currentBuild Build for current completed job
57
- * @param {String} pipelineId Pipeline ID for next job to be triggered.
58
- * @param {Object} eventFactory Factory for querying event data store.
59
- * @return {Object} External Event Event where the next job to be triggered belongs to.
60
- */
61
- function getExternalEvent(currentBuild, pipelineId, eventFactory) {
62
- if (!currentBuild.parentBuilds || !currentBuild.parentBuilds[pipelineId]) {
63
- return null;
64
- }
65
-
66
- const { eventId } = currentBuild.parentBuilds[pipelineId];
67
-
68
- return eventFactory.get(eventId);
69
- }
20
+ const { OrTrigger } = require('./triggers/or');
21
+ const { AndTrigger } = require('./triggers/and');
22
+ const { RemoteTrigger } = require('./triggers/remoteTrigger');
23
+ const { RemoteJoin } = require('./triggers/remoteJoin');
24
+ const {
25
+ strToInt,
26
+ createJoinObject,
27
+ createEvent,
28
+ parseJobInfo,
29
+ handleStageFailure,
30
+ getJobId,
31
+ isOrTrigger,
32
+ extractExternalJoinData,
33
+ extractCurrentPipelineJoinData,
34
+ createExternalEvent,
35
+ getBuildsForGroupEvent,
36
+ buildsToRestartFilter,
37
+ trimJobName
38
+ } = require('./triggers/helpers');
70
39
 
71
40
  /**
72
41
  * Delete a build
73
- * @method delBuild
74
42
  * @param {Object} buildConfig build object to delete
75
43
  * @param {Object} buildFactory build factory
76
44
  * @return {Promise}
@@ -86,722 +54,188 @@ async function deleteBuild(buildConfig, buildFactory) {
86
54
  }
87
55
 
88
56
  /**
89
- * Create event for downstream pipeline that need to be rebuilt
90
- * @method createEvent
91
- * @param {Object} config Configuration object
92
- * @param {Factory} config.pipelineFactory Pipeline Factory
93
- * @param {Factory} config.eventFactory Event Factory
94
- * @param {Number} config.pipelineId Pipeline to be rebuilt
95
- * @param {String} config.startFrom Job to be rebuilt
96
- * @param {String} config.causeMessage Caused message, e.g. triggered by 1234(buildId)
97
- * @param {String} config.parentBuildId ID of the build that triggers this event
98
- * @param {Object} [config.parentBuilds] Builds that triggered this build
99
- * @param {Number} [config.parentEventId] Parent event ID
100
- * @param {Number} [config.groupEventId] Group parent event ID
101
- * @return {Promise} Resolves to the newly created event
102
- */
103
- async function createEvent(config) {
104
- const {
105
- pipelineFactory,
106
- eventFactory,
107
- pipelineId,
108
- startFrom,
109
- causeMessage,
110
- parentBuildId,
111
- parentBuilds,
112
- parentEventId,
113
- groupEventId
114
- } = config;
115
- const { scm } = eventFactory;
116
- const payload = {
117
- pipelineId,
118
- startFrom,
119
- type: 'pipeline',
120
- causeMessage,
121
- parentBuildId
122
- };
123
-
124
- if (parentEventId) {
125
- payload.parentEventId = parentEventId;
126
- }
127
-
128
- // for backward compatibility, this field is optional
129
- if (parentBuilds) {
130
- payload.parentBuilds = parentBuilds;
131
- }
132
-
133
- if (groupEventId) {
134
- payload.groupEventId = groupEventId;
135
- }
136
-
137
- const pipeline = await pipelineFactory.get(pipelineId);
138
- const realAdmin = await pipeline.admin;
139
- const { scmContext, scmUri } = pipeline;
140
-
141
- payload.scmContext = scmContext;
142
- payload.username = realAdmin.username;
143
-
144
- // get pipeline admin's token
145
- const token = await realAdmin.unsealToken();
146
- const scmConfig = {
147
- scmContext,
148
- scmUri,
149
- token
150
- };
151
-
152
- // Get commit sha
153
- const sha = await scm.getCommitSha(scmConfig);
154
-
155
- payload.sha = sha;
156
-
157
- // Set configPipelineSha for child pipeline
158
- if (pipeline.configPipelineId) {
159
- const configPipeline = await pipelineFactory.get(pipeline.configPipelineId);
160
- const configAdmin = await configPipeline.admin;
161
- const configToken = await configAdmin.unsealToken();
162
- const configScmConfig = {
163
- scmContext: configPipeline.scmContext,
164
- scmUri: configPipeline.scmUri,
165
- token: configToken
166
- };
167
-
168
- payload.configPipelineSha = await scm.getCommitSha(configScmConfig);
169
- }
170
-
171
- return eventFactory.create(payload);
172
- }
173
-
174
- /**
175
- * Create external build (returns event with `builds` field)
176
- * @method createExternalBuild
177
- * @param {Object} config Configuration object
178
- * @param {Factory} config.pipelineFactory Pipeline Factory
179
- * @param {Factory} config.eventFactory Event Factory
180
- * @param {Number} config.externalPipelineId External pipeline ID
181
- * @param {String} config.startFrom External trigger to start from
182
- * @param {Number} config.parentBuildId Parent Build ID
183
- * @param {Object} config.parentBuilds Builds that triggered this build
184
- * @param {String} config.causeMessage Cause message of this event
185
- * @param {Number} [config.parentEventId] Parent event ID
186
- * @param {Number} [config.groupEventId] Group parent event ID
187
- * @return {Promise}
188
- */
189
- async function createExternalBuild(config) {
190
- const {
191
- pipelineFactory,
192
- eventFactory,
193
- externalPipelineId,
194
- startFrom,
195
- parentBuildId,
196
- parentBuilds,
197
- causeMessage,
198
- parentEventId,
199
- groupEventId
200
- } = config;
201
-
202
- const createEventConfig = {
203
- pipelineFactory,
204
- eventFactory,
205
- pipelineId: externalPipelineId,
206
- startFrom,
207
- parentBuildId, // current build
208
- causeMessage,
209
- parentBuilds
210
- };
211
-
212
- if (parentEventId) {
213
- createEventConfig.parentEventId = parentEventId;
214
- }
215
-
216
- if (groupEventId) {
217
- createEventConfig.groupEventId = groupEventId;
218
- }
219
-
220
- return createEvent(createEventConfig);
221
- }
222
-
223
- /**
224
- * Create internal build. If config.start is false or not passed in then do not start the job
225
- * Need to pass in (jobName and pipelineId) or (jobId) to get job data
226
- * @method createInternalBuild
227
- * @param {Object} config Configuration object
228
- * @param {Factory} config.jobFactory Job Factory
229
- * @param {Factory} config.buildFactory Build Factory
230
- * @param {Number} [config.pipelineId] Pipeline Id
231
- * @param {String} [config.jobName] Job name
232
- * @param {String} config.username Username of build
233
- * @param {String} config.scmContext SCM context
234
- * @param {Object} [config.parentBuilds] Builds that triggered this build
235
- * @param {String} config.baseBranch Branch name
236
- * @param {Number} [config.parentBuildId] Parent build ID
237
- * @param {Boolean} [config.start] Whether to start the build or not
238
- * @param {Number} [config.jobId] Job ID
239
- * @param {Object} [config.event] Event build belongs to
240
- * @return {Promise}
241
- */
242
- async function createInternalBuild(config) {
243
- const {
244
- jobFactory,
245
- buildFactory,
246
- pipelineId,
247
- jobName,
248
- username,
249
- scmContext,
250
- event,
251
- parentBuilds,
252
- start,
253
- baseBranch,
254
- parentBuildId,
255
- jobId
256
- } = config;
257
- const prRef = event.pr.ref ? event.pr.ref : '';
258
- const prSource = event.pr.prSource || '';
259
- const prInfo = event.pr.prBranchName
260
- ? {
261
- url: event.pr.url || '',
262
- prBranchName: event.pr.prBranchName || ''
263
- }
264
- : '';
265
-
266
- let job = {};
267
-
268
- if (!jobId) {
269
- job = await jobFactory.get({
270
- name: jobName,
271
- pipelineId
272
- });
273
- } else {
274
- job = await jobFactory.get(jobId);
275
- }
276
-
277
- const internalBuildConfig = {
278
- jobId: job.id,
279
- sha: event.sha,
280
- parentBuildId,
281
- parentBuilds: parentBuilds || {},
282
- eventId: event.id,
283
- username,
284
- configPipelineSha: event.configPipelineSha,
285
- scmContext,
286
- prRef,
287
- prSource,
288
- prInfo,
289
- start: start !== false,
290
- baseBranch
291
- };
292
-
293
- let jobState = job.state;
294
-
295
- if (prRef) {
296
- // Whether a job is enabled is determined by the state of the original job.
297
- // If the original job does not exist, it will be enabled.
298
- const originalJobName = job.parsePRJobName('job');
299
- const originalJob = await jobFactory.get({
300
- name: originalJobName,
301
- pipelineId
302
- });
303
-
304
- jobState = originalJob ? originalJob.state : 'ENABLED';
305
- }
306
-
307
- if (jobState === 'ENABLED') {
308
- return buildFactory.create(internalBuildConfig);
309
- }
310
-
311
- return null;
312
- }
313
-
314
- /**
315
- * Return PR job or not
316
- * PR job name certainly has ":". e.g. "PR-1:jobName"
317
- * @method isPR
318
- * @param {String} destJobName
319
- * @return {Boolean}
320
- */
321
- function isPR(jobName) {
322
- return jobName.startsWith('PR-');
323
- }
324
-
325
- /**
326
- * Trim Job name to follow data-schema
327
- * @method trimJobName
328
- * @param {String} jobName
329
- * @return {String} trimmed jobName
330
- */
331
- function trimJobName(jobName) {
332
- if (isPR(jobName)) {
333
- return jobName.split(':')[1];
334
- }
335
-
336
- return jobName;
337
- }
338
-
339
- /**
340
- * Generates a parent builds object
341
- * @param {Number} config.buildId Build ID
342
- * @param {Number} config.eventId Event ID
343
- * @param {Number} config.pipelineId Pipeline ID
344
- * @param {String} config.jobName Job name
345
- * @param {Array} [config.joinListNames] Job names in join list
346
- * @return {Object} Returns parent builds object
57
+ * Trigger the next jobs of the current job
58
+ * @param { import('./types/index').ServerConfig } config Configuration object
59
+ * @param { import('./types/index').ServerApp } app Server app object
60
+ * @return {Promise<null>} Resolves to the newly created build or null
347
61
  */
348
- function createParentBuildsObj(config) {
349
- const { buildId, eventId, pipelineId, jobName, joinListNames } = config;
350
-
351
- // For getting multiple parent builds
352
- if (joinListNames) {
353
- const joinParentBuilds = {};
354
-
355
- joinListNames.forEach(name => {
356
- const joinInfo = getPipelineAndJob(name, pipelineId);
357
-
358
- if (!joinParentBuilds[joinInfo.externalPipelineId]) {
359
- joinParentBuilds[joinInfo.externalPipelineId] = {
360
- eventId: null,
361
- jobs: {}
362
- };
363
- }
364
-
365
- joinParentBuilds[joinInfo.externalPipelineId].jobs[joinInfo.externalJobName] = null;
366
- });
367
-
368
- return joinParentBuilds;
369
- }
370
-
371
- return {
372
- [pipelineId]: {
373
- eventId,
374
- jobs: { [jobName]: buildId }
375
- }
62
+ async function triggerNextJobs(config, app) {
63
+ const currentPipeline = config.pipeline;
64
+ const currentJob = config.job;
65
+ const currentBuild = config.build;
66
+ const { jobFactory, buildFactory, eventFactory, pipelineFactory } = app;
67
+
68
+ /** @type {EventModel} */
69
+ const currentEvent = await eventFactory.get({ id: currentBuild.eventId });
70
+ const current = {
71
+ pipeline: currentPipeline,
72
+ build: currentBuild,
73
+ event: currentEvent
376
74
  };
377
- }
378
-
379
- /**
380
- * Parse job info into important variables
381
- * - parentBuilds: parent build information
382
- * - joinListNames: array of join jobs
383
- * - joinParentBuilds: parent build information for join jobs
384
- * @param {Object} joinObj Join object
385
- * @param {Object} current Object holding current event, job & pipeline
386
- * @param {String} nextJobName Next job's name
387
- * @param {Number} nextPipelineId Next job's Pipeline Id
388
- * @return {Object} With above information
389
- */
390
- function parseJobInfo({ joinObj = {}, current, nextJobName, nextPipelineId }) {
391
- const joinList = joinObj[nextJobName] ? joinObj[nextJobName].join : [];
392
- const joinListNames = joinList.map(j => j.name);
393
-
394
- /* CONSTRUCT AN OBJ LIKE {111: {eventId: 2, D:987}}
395
- * FOR EASY LOOKUP OF BUILD STATUS */
396
- // current job's parentBuilds
397
- const currentJobParentBuilds = current.build.parentBuilds || {};
398
- // join jobs, with eventId and buildId empty
399
- const joinParentBuilds = createParentBuildsObj({
400
- pipelineId: nextPipelineId || current.pipeline.id,
401
- joinListNames
402
- });
403
- // override currentBuild in the joinParentBuilds
404
- const currentBuildInfo = createParentBuildsObj({
405
- buildId: current.build.id,
406
- eventId: current.build.eventId,
407
- pipelineId: current.pipeline.id,
408
- jobName: current.job.name
75
+ /** @type Array<string> */
76
+ const nextJobsTrigger = workflowParser.getNextJobs(currentEvent.workflowGraph, {
77
+ trigger: currentJob.name,
78
+ chainPR: currentPipeline.chainPR
409
79
  });
80
+ const pipelineJoinData = await createJoinObject(nextJobsTrigger, current, eventFactory);
81
+ const originalCurrentJobName = trimJobName(currentJob.name);
410
82
 
411
- // need to merge because it's possible same event has multiple builds
412
- const parentBuilds = merge({}, joinParentBuilds, currentJobParentBuilds, currentBuildInfo);
83
+ // Trigger OrTrigger and AndTrigger for current pipeline jobs.
84
+ // Helper function to handle triggering jobs in same pipeline
85
+ const orTrigger = new OrTrigger(app, config);
86
+ const andTrigger = new AndTrigger(app, config, currentEvent);
87
+ const currentPipelineNextJobs = extractCurrentPipelineJoinData(pipelineJoinData, currentPipeline.id);
413
88
 
414
- return {
415
- parentBuilds,
416
- joinListNames,
417
- joinParentBuilds
418
- };
419
- }
420
-
421
- /**
422
- * Get finished builds in all parent events
423
- * @param {Event} event Current event
424
- * @param {Number} [event.parentEventId] Parent event ID
425
- * @param {Number} [event.groupEventId] Group parent event ID
426
- * @param {Factory} buildFactory Build factory
427
- * @return {Promise} All finished builds
428
- */
429
- async function getFinishedBuilds(event, buildFactory) {
430
- // FIXME: buildFactory.getLatestBuilds doesn't return build model
431
- const builds = await buildFactory.getLatestBuilds({ groupEventId: event.groupEventId, readOnly: false });
89
+ for (const [nextJobName, nextJob] of Object.entries(currentPipelineNextJobs)) {
90
+ const nextJobId = nextJob.id || (await getJobId(nextJobName, currentPipeline.id, jobFactory));
91
+ const resource = `pipeline:${currentPipeline.id}:event:${currentEvent.id}`;
92
+ let lock;
432
93
 
433
- builds.forEach(b => {
434
94
  try {
435
- b.parentBuilds = JSON.parse(b.parentBuilds);
436
- } catch (err) {
437
- logger.error(`Failed to parse parentBuilds for ${b.id}`);
438
- }
439
- });
440
-
441
- return builds;
442
- }
443
-
444
- /**
445
- * Update parent builds info when next build already exists
446
- * @param {Object} joinParentBuilds Parent builds object for join job
447
- * @param {Build} nextBuild Next build
448
- * @param {Build} build Build for current completed job
449
- * @return {Promise} Updated next build
450
- */
451
- async function updateParentBuilds({ joinParentBuilds, nextBuild, build }) {
452
- // Override old parentBuilds info
453
- const newParentBuilds = merge({}, joinParentBuilds, nextBuild.parentBuilds, (objVal, srcVal) =>
454
- // passthrough objects, else mergeWith mutates source
455
- srcVal && typeof srcVal === 'object' ? undefined : objVal || srcVal
456
- );
457
-
458
- nextBuild.parentBuilds = newParentBuilds;
459
- // nextBuild.parentBuildId may be int or Array, so it needs to be flattened
460
- nextBuild.parentBuildId = Array.from(new Set([build.id, nextBuild.parentBuildId || []].flat()));
461
-
462
- // FIXME: Is this needed ? Why not update once in handleNewBuild()
463
- return nextBuild.update();
464
- }
465
-
466
- /**
467
- * Check if all parent builds of the new build are done
468
- * @param {Build} newBuild Updated build
469
- * @param {Array} joinListNames Join list names
470
- * @param {Number} pipelineId Pipeline ID
471
- * @param {Factory} buildFactory Build factory
472
- * @return {Promise} Object with done and hasFailure statuses
473
- */
474
- async function getParentBuildStatus({ newBuild, joinListNames, pipelineId, buildFactory }) {
475
- const upstream = newBuild.parentBuilds || {};
476
- let done = true;
477
- let hasFailure = false;
478
- const promisesToAwait = [];
479
-
480
- // Get buildId
481
- for (let i = 0; i < joinListNames.length; i += 1) {
482
- const name = joinListNames[i];
483
- const joinInfo = getPipelineAndJob(name, pipelineId);
484
-
485
- let bId;
486
-
487
- if (
488
- upstream[joinInfo.externalPipelineId] &&
489
- upstream[joinInfo.externalPipelineId].jobs[joinInfo.externalJobName]
490
- ) {
491
- bId = upstream[joinInfo.externalPipelineId].jobs[joinInfo.externalJobName];
492
- }
493
-
494
- // If buildId is empty, the job hasn't executed yet and the join is not done
495
- if (!bId) {
496
- done = false;
497
- // Otherwise, get the build to check the status
498
- } else {
499
- promisesToAwait.push(buildFactory.get(bId));
500
- }
501
- }
502
-
503
- // Get the status of the builds
504
- const joinedBuilds = await Promise.all(promisesToAwait);
95
+ lock = await locker.lock(resource);
96
+ const { parentBuilds, joinListNames } = parseJobInfo({
97
+ joinObj: currentPipelineNextJobs,
98
+ currentBuild,
99
+ currentPipeline,
100
+ currentJob,
101
+ nextJobName
102
+ });
505
103
 
506
- joinedBuilds.forEach(b => {
507
- // Do not need to run the next build; terminal status
508
- if (['FAILURE', 'ABORTED', 'COLLAPSED', 'UNSTABLE'].includes(b.status)) {
509
- hasFailure = true;
510
- }
511
- // Some builds are still going on
512
- if (!['FAILURE', 'SUCCESS', 'ABORTED', 'UNSTABLE', 'COLLAPSED'].includes(b.status)) {
513
- done = false;
104
+ // Handle no-join case. Sequential Workflow
105
+ // Note: current job can be "external" in nextJob's perspective
106
+ /* CREATE AND START NEXT BUILD IF ALL 2 SCENARIOS ARE TRUE
107
+ * 1. No join
108
+ * 2. ([~D,B,C]->A) currentJob=D, nextJob=A, joinList(A)=[B,C]
109
+ * joinList doesn't include D, so start A
110
+ */
111
+ if (isOrTrigger(currentEvent.workflowGraph, originalCurrentJobName, trimJobName(nextJobName))) {
112
+ await orTrigger.execute(currentEvent, currentPipeline.id, nextJobName, nextJobId, parentBuilds);
113
+ } else {
114
+ await andTrigger.execute(nextJobName, nextJobId, parentBuilds, joinListNames);
115
+ }
116
+ } catch (err) {
117
+ logger.error(
118
+ `Error in triggerNextJobInSamePipeline:${nextJobName} from pipeline:${currentPipeline.id}-${currentJob.name}-event:${currentEvent.id} `,
119
+ err
120
+ );
514
121
  }
515
- });
516
-
517
- return { hasFailure, done };
518
- }
519
-
520
- /**
521
- * Handle new build logic: update, start, or remove
522
- * If the build is done, check if it has a failure:
523
- * if failure, delete new build
524
- * if no failure, start new build
525
- * Otherwise, do nothing
526
- * @param {Boolean} done If the build is done or not
527
- * @param {Boolean} hasFailure If the build has a failure or not
528
- * @param {Build} newBuild Next build
529
- * @param {String} [jobName] Job name
530
- * @param {String} [pipelineId] Pipeline ID
531
- * @param {Object} [stage] Stage
532
- * @return {Promise} The newly updated/created build
533
- */
534
- async function handleNewBuild({ done, hasFailure, newBuild, jobName, pipelineId, stage }) {
535
- if (!done) {
536
- return null;
122
+ await locker.unlock(lock, resource);
537
123
  }
538
- if (!['CREATED', null, undefined].includes(newBuild.status)) {
539
- return null;
540
- }
541
-
542
- // Delete new build since previous build failed
543
- if (hasFailure) {
544
- let stageTeardownName = '';
545
124
 
546
- if (stage) {
547
- stageTeardownName = getFullStageJobName({ stageName: stage.name, jobName: 'teardown' });
125
+ // Trigger RemoteJoin and RemoteTrigger for current and external pipeline jobs.
126
+ // Helper function to handle triggering jobs in external pipeline
127
+ const remoteTrigger = new RemoteTrigger(app, config);
128
+ const remoteJoin = new RemoteJoin(app, config, currentEvent);
129
+ const externalPipelineJoinData = extractExternalJoinData(pipelineJoinData, currentPipeline.id);
130
+
131
+ for (const [joinedPipelineId, joinedPipeline] of Object.entries(externalPipelineJoinData)) {
132
+ const isCurrentPipeline = strToInt(joinedPipelineId) === currentPipeline.id;
133
+ const remoteJoinName = `sd@${currentPipeline.id}:${originalCurrentJobName}`;
134
+ const remoteTriggerName = `~${remoteJoinName}`;
135
+ let lock;
136
+ let resource;
137
+
138
+ let externalEvent = joinedPipeline.event;
139
+
140
+ // This includes CREATED builds too
141
+ const groupEventBuilds =
142
+ externalEvent !== undefined ? await getBuildsForGroupEvent(externalEvent.groupEventId, buildFactory) : [];
143
+ const buildsToRestart = buildsToRestartFilter(joinedPipeline, groupEventBuilds, currentEvent, currentBuild);
144
+ const isRestart = buildsToRestart.length > 0;
145
+
146
+ // If user used external trigger syntax, the jobs are triggered as external
147
+ if (isCurrentPipeline || isRestart) {
148
+ externalEvent = null;
548
149
  }
549
150
 
550
- // New build is not stage teardown job
551
- if (jobName !== stageTeardownName) {
552
- logger.info(
553
- `Failure occurred in upstream job, removing new build - build:${newBuild.id} pipeline:${pipelineId}-${jobName} event:${newBuild.eventId} `
554
- );
555
- await newBuild.remove();
151
+ // no need to lock if there is no external event
152
+ if (externalEvent) {
153
+ resource = `pipeline:${joinedPipelineId}:event:${externalEvent.id}`;
556
154
  }
557
155
 
558
- return null;
559
- }
156
+ // Create a new external event
157
+ // First downstream trigger, restart case, same pipeline trigger as external
158
+ if (!externalEvent) {
159
+ const { parentBuilds } = parseJobInfo({
160
+ currentBuild,
161
+ currentPipeline,
162
+ currentJob
163
+ });
560
164
 
561
- // All join builds finished successfully and it's clear that a new build has not been started before.
562
- // Start new build.
563
- newBuild.status = 'QUEUED';
564
- await newBuild.update();
165
+ const externalEventConfig = {
166
+ pipelineFactory,
167
+ eventFactory,
168
+ externalPipelineId: joinedPipelineId,
169
+ parentBuildId: currentBuild.id,
170
+ parentBuilds,
171
+ causeMessage: `Triggered by ${remoteJoinName}`,
172
+ parentEventId: currentEvent.id,
173
+ startFrom: remoteTriggerName,
174
+ skipMessage: 'Skip bulk external builds creation', // Don't start builds in eventFactory.
175
+ groupEventId: null
176
+ };
565
177
 
566
- return newBuild.start();
567
- }
178
+ // Restart case
179
+ if (isRestart) {
180
+ externalEventConfig.groupEventId = joinedPipeline.event.id;
181
+ externalEventConfig.parentBuilds = buildsToRestart[0].parentBuilds;
182
+ }
568
183
 
569
- /**
570
- * Get all builds with same parent event id
571
- * @param {Factory} eventFactory Event factory
572
- * @param {Number} parentEventId Parent event ID
573
- * @param {Number} pipelineId Pipeline ID
574
- * @return {Promise} Array of builds with same parent event ID
575
- */
576
- async function getParallelBuilds({ eventFactory, parentEventId, pipelineId }) {
577
- let parallelEvents = await eventFactory.list({
578
- params: {
579
- parentEventId
184
+ externalEvent = await createExternalEvent(externalEventConfig);
580
185
  }
581
- });
582
-
583
- // Remove previous events from same pipeline
584
- parallelEvents = parallelEvents.filter(pe => pe.pipelineId !== pipelineId);
585
-
586
- let parallelBuilds = [];
587
-
588
- await Promise.all(
589
- parallelEvents.map(async pe => {
590
- const parallelBuild = await pe.getBuilds();
591
-
592
- parallelBuilds = parallelBuilds.concat(parallelBuild);
593
- })
594
- );
595
-
596
- return parallelBuilds;
597
- }
598
186
 
599
- /**
600
- * Fills parentBuilds object with missing job information
601
- * @param {Array} parentBuilds
602
- * @param {Object} current Holds current build/event data
603
- * @param {Array} builds Completed builds which is used to fill parentBuilds data
604
- * @param {Object} [nextEvent] External event
605
- */
606
- function fillParentBuilds(parentBuilds, current, builds, nextEvent) {
607
- Object.keys(parentBuilds).forEach(pid => {
608
- Object.keys(parentBuilds[pid].jobs).forEach(jName => {
609
- let joinJob;
610
-
611
- if (parentBuilds[pid].jobs[jName] === null) {
612
- let workflowGraph;
613
- let searchJob = trimJobName(jName);
187
+ for (const [nextJobName, nextJob] of Object.entries(joinedPipeline.jobs)) {
188
+ const nextJobId = nextJob.id || (await getJobId(nextJobName, currentPipeline.id, jobFactory));
614
189
 
615
- // parentBuild is in current event
616
- if (+pid === current.pipeline.id) {
617
- workflowGraph = current.event.workflowGraph;
618
- } else if (nextEvent) {
619
- if (+pid !== nextEvent.pipelineId) {
620
- // parentBuild is remote triggered from external event
621
- // FIXME:: Will else condition ever be true ?
622
- searchJob = `sd@${pid}:${searchJob}`;
623
- }
624
- workflowGraph = nextEvent.workflowGraph;
625
- } else {
626
- // parentBuild is remote triggered from current Event
627
- searchJob = `sd@${pid}:${searchJob}`;
628
- workflowGraph = current.event.workflowGraph;
629
- }
630
- joinJob = workflowGraph.nodes.find(node => node.name === searchJob);
190
+ const { parentBuilds } = parseJobInfo({
191
+ joinObj: joinedPipeline.jobs,
192
+ currentBuild,
193
+ currentPipeline,
194
+ currentJob,
195
+ nextJobName,
196
+ nextPipelineId: joinedPipelineId
197
+ });
631
198
 
632
- if (!joinJob) {
633
- logger.warn(`Job ${jName}:${pid} not found in workflowGraph for event ${current.event.id}`);
199
+ try {
200
+ if (resource) lock = await locker.lock(resource);
201
+
202
+ if (isOrTrigger(externalEvent.workflowGraph, remoteTriggerName, nextJobName)) {
203
+ await remoteTrigger.execute(
204
+ externalEvent,
205
+ externalEvent.pipelineId,
206
+ nextJobName,
207
+ nextJobId,
208
+ parentBuilds
209
+ );
634
210
  } else {
635
- const targetBuild = builds.find(b => b.jobId === joinJob.id);
636
-
637
- if (targetBuild) {
638
- parentBuilds[pid].jobs[jName] = targetBuild.id;
639
- parentBuilds[pid].eventId = targetBuild.eventId;
640
- } else {
641
- logger.warn(`Job ${jName}:${pid} not found in builds`);
642
- }
211
+ // Re get join list when first time remote trigger since external event was empty and cannot get workflow graph then
212
+ const joinList =
213
+ nextJob.join.length > 0
214
+ ? nextJob.join
215
+ : workflowParser.getSrcForJoin(externalEvent.workflowGraph, { jobName: nextJobName });
216
+ const joinListNames = joinList.map(j => j.name);
217
+
218
+ await remoteJoin.execute(
219
+ externalEvent,
220
+ nextJobName,
221
+ nextJobId,
222
+ parentBuilds,
223
+ groupEventBuilds,
224
+ joinListNames
225
+ );
643
226
  }
227
+ } catch (err) {
228
+ logger.error(
229
+ `Error in triggerJobsInExternalPipeline:${joinedPipelineId} from pipeline:${currentPipeline.id}-${currentJob.name}-event:${currentEvent.id} `,
230
+ err
231
+ );
644
232
  }
645
- });
646
- });
647
- }
648
-
649
- /**
650
- * Create joinObject for nextJobs to trigger
651
- * For A & D in nextJobs for currentJobName B, create
652
- * {A:[B,C], D:[B,F], X: []} where [B,C] join on A,
653
- * [B,F] join on D and X has no join
654
- * This can include external jobs
655
- * @param {Array} nextJobs List of jobs to run next from workflow parser.
656
- * @param {Object} current Object holding current job's build, event data
657
- * @param {Object} eventFactory Object for querying DB for event data
658
- * @return {Object} Object representing join data for next jobs grouped by pipeline id
659
- * {"pipeineId" : {event: "externalEventId",
660
- * jobs: {"nextJobName": {"id": "jobId", join: ["a", "b"]
661
- * }
662
- * }
663
- */
664
- async function createJoinObject(nextJobs, current, eventFactory) {
665
- const { build, event } = current;
666
-
667
- const joinObj = {};
668
-
669
- for (const jobName of nextJobs) {
670
- const jobInfo = getPipelineAndJob(jobName, current.pipeline.id);
671
- const { externalPipelineId: pid, externalJobName: jName, isExternal } = jobInfo;
672
-
673
- const jId = event.workflowGraph.nodes.find(n => n.name === trimJobName(jobName)).id;
674
-
675
- if (!joinObj[pid]) joinObj[pid] = {};
676
- const pipelineObj = joinObj[pid];
677
- let jobs;
678
-
679
- if (pid !== current.pipeline.id) {
680
- jobs = [];
681
233
 
682
- const externalEvent = pipelineObj.event || (await getExternalEvent(build, pid, eventFactory));
683
-
684
- if (externalEvent) {
685
- pipelineObj.event = externalEvent;
686
- jobs = workflowParser.getSrcForJoin(externalEvent.workflowGraph, { jobName: jName });
687
- }
688
- } else {
689
- jobs = workflowParser.getSrcForJoin(event.workflowGraph, { jobName });
234
+ await locker.unlock(lock, resource);
690
235
  }
691
-
692
- if (!pipelineObj.jobs) pipelineObj.jobs = {};
693
- pipelineObj.jobs[jName] = { id: jId, join: jobs, isExternal };
694
236
  }
695
237
 
696
- return joinObj;
697
- }
698
-
699
- /**
700
- * Get parentBuildId from parentBuilds object
701
- * @param {Object} parentBuilds Builds that triggered this build
702
- * @param {Array} joinListNames Array of join job name
703
- * @param {Number} pipelineId Pipeline ID
704
- * @return {Array} Array of parentBuildId
705
- */
706
- function getParentBuildIds({ currentBuildId, parentBuilds, joinListNames, pipelineId }) {
707
- const parentBuildIds = [];
708
-
709
- for (let i = 0; i < joinListNames.length; i += 1) {
710
- const name = joinListNames[i];
711
- const joinInfo = getPipelineAndJob(name, pipelineId);
712
-
713
- if (
714
- parentBuilds[joinInfo.externalPipelineId] &&
715
- parentBuilds[joinInfo.externalPipelineId].jobs[joinInfo.externalJobName]
716
- ) {
717
- parentBuildIds.push(parentBuilds[joinInfo.externalPipelineId].jobs[joinInfo.externalJobName]);
718
- }
719
- }
720
-
721
- return Array.from(new Set([currentBuildId, ...parentBuildIds]));
722
- }
723
-
724
- /**
725
- * Create stage teardown build if it doesn't already exist
726
- * @param {Factory} jobFactory Job factory
727
- * @param {Factory} buildFactory Build factory
728
- * @param {Object} current Current object
729
- * @param {String} stageTeardownName Stage teardown name
730
- * @param {String} username Username
731
- * @param {String} scmContext SCM context
732
- */
733
- async function ensureStageTeardownBuildExists({
734
- jobFactory,
735
- buildFactory,
736
- current,
737
- stageTeardownName,
738
- username,
739
- scmContext
740
- }) {
741
- // Check if stage teardown build already exists
742
- const stageTeardownJob = await jobFactory.get({
743
- pipelineId: current.pipeline.id,
744
- name: stageTeardownName
745
- });
746
- const existingStageTeardownBuild = await buildFactory.get({
747
- eventId: current.event.id,
748
- jobId: stageTeardownJob.id
749
- });
750
-
751
- // Doesn't exist, create stage teardown job
752
- if (!existingStageTeardownBuild) {
753
- await createInternalBuild({
754
- jobFactory,
755
- buildFactory,
756
- pipelineId: current.pipeline.id,
757
- jobName: stageTeardownName,
758
- username,
759
- scmContext,
760
- event: current.event, // this is the parentBuild for the next build
761
- baseBranch: current.event.baseBranch || null,
762
- start: false
763
- });
764
- }
765
- }
766
-
767
- /**
768
- * Delete nextBuild, create teardown build if it doesn't exist, and return teardown build or return null
769
- * @param {String} nextJobName Next job name
770
- * @param {Object} current Object with stage, event, pipeline info
771
- * @param {Object} buildConfig Build config
772
- * @param {Factory} jobFactory Job factory
773
- * @param {Factory} buildFactory Build factory
774
- * @param {String} username Username
775
- * @param {String} scmContext Scm context
776
- * @return {Array} Array of promises
777
- */
778
- async function handleStageFailure({
779
- nextJobName,
780
- current,
781
- buildConfig,
782
- jobFactory,
783
- buildFactory,
784
- username,
785
- scmContext
786
- }) {
787
- const buildDeletePromises = [];
788
- const stageTeardownName = getFullStageJobName({ stageName: current.stage.name, jobName: 'teardown' });
789
-
790
- // Remove next build
791
- if (buildConfig.eventId && nextJobName !== stageTeardownName) {
792
- buildDeletePromises.push(deleteBuild(buildConfig, buildFactory));
793
- }
794
-
795
- await ensureStageTeardownBuildExists({
796
- jobFactory,
797
- buildFactory,
798
- current,
799
- stageTeardownName,
800
- username,
801
- scmContext
802
- });
803
-
804
- return buildDeletePromises;
238
+ return null;
805
239
  }
806
240
 
807
241
  /**
@@ -860,7 +294,7 @@ const buildsPlugin = {
860
294
 
861
295
  // if nextBuild is stage teardown, just return nextBuild
862
296
  if (current.stage) {
863
- const buildDeletePromises = await handleStageFailure({
297
+ const buildDeletePromises = handleStageFailure({
864
298
  nextJobName,
865
299
  current,
866
300
  buildConfig,
@@ -906,405 +340,8 @@ const buildsPlugin = {
906
340
 
907
341
  /**
908
342
  * Trigger the next jobs of the current job
909
- * @method triggerNextJobs
910
- * @param {Object} config Configuration object
911
- * @param {Pipeline} config.pipeline Current pipeline
912
- * @param {Job} config.job Current job
913
- * @param {Build} config.build Current build
914
- * @param {String} config.username Username
915
- * @param {String} config.scmContext Scm context
916
- * @param {String} app Server app object
917
- * @return {Promise} Resolves to the newly created build or null
918
343
  */
919
- server.expose('triggerNextJobs', async (config, app) => {
920
- const { pipeline, job, build, event, stage } = config;
921
- const { eventFactory, pipelineFactory, buildFactory, jobFactory } = app;
922
- const current = {
923
- pipeline,
924
- job,
925
- build,
926
- event,
927
- stage
928
- };
929
-
930
- const nextJobsTrigger = workflowParser.getNextJobs(current.event.workflowGraph, {
931
- trigger: current.job.name,
932
- chainPR: pipeline.chainPR
933
- });
934
- const pipelineJoinData = await createJoinObject(nextJobsTrigger, current, eventFactory);
935
-
936
- // Helper function to handle triggering jobs in same pipeline
937
- const triggerNextJobInSamePipeline = async (nextJobName, joinObj) => {
938
- const { username, scmContext } = config;
939
- const { parentBuilds, joinListNames } = parseJobInfo({
940
- joinObj,
941
- current,
942
- nextJobName
943
- });
944
-
945
- // Handle no-join case. Sequential Workflow
946
- // Note: current job can be "external" in nextJob's perspective
947
- /* CREATE AND START NEXT BUILD IF ALL 2 SCENARIOS ARE TRUE
948
- * 1. No join
949
- * 2. ([~D,B,C]->A) currentJob=D, nextJob=A, joinList(A)=[B,C]
950
- * joinList doesn't include D, so start A
951
- */
952
- const isORTrigger = !joinListNames.includes(current.job.name);
953
-
954
- if (isORTrigger) {
955
- const internalBuildConfig = {
956
- jobFactory,
957
- buildFactory,
958
- pipelineId: current.pipeline.id,
959
- jobName: nextJobName,
960
- username,
961
- scmContext,
962
- event: current.event, // this is the parentBuild for the next build
963
- baseBranch: current.event.baseBranch || null,
964
- parentBuilds,
965
- parentBuildId: current.build.id
966
- };
967
-
968
- const nextJob = await jobFactory.get({
969
- name: nextJobName,
970
- pipelineId: current.pipeline.id
971
- });
972
-
973
- const existNextBuild = await buildFactory.get({
974
- eventId: current.event.id,
975
- jobId: nextJob.id
976
- });
977
-
978
- if (existNextBuild === null) {
979
- return createInternalBuild(internalBuildConfig);
980
- }
981
-
982
- if (!['CREATED', null, undefined].includes(existNextBuild.status)) {
983
- return existNextBuild;
984
- }
985
-
986
- // Current build is not part of stage
987
- existNextBuild.status = 'QUEUED';
988
- await existNextBuild.update();
989
-
990
- return existNextBuild.start();
991
- }
992
-
993
- // Handle join case. Fan-out/fan-in Workflow
994
- logger.info(`Fetching finished builds for event ${event.id}`);
995
- let finishedInternalBuilds = await getFinishedBuilds(current.event, buildFactory);
996
-
997
- if (current.event.parentEventId) {
998
- // FIXME: On restart cases parentEventId should be fetched
999
- // from first event in the group
1000
- const parallelBuilds = await getParallelBuilds({
1001
- eventFactory,
1002
- parentEventId: current.event.parentEventId,
1003
- pipelineId: current.pipeline.id
1004
- });
1005
-
1006
- finishedInternalBuilds = finishedInternalBuilds.concat(parallelBuilds);
1007
- }
1008
-
1009
- const nextJobId = joinObj[nextJobName].id;
1010
-
1011
- let nextBuild;
1012
-
1013
- // If next build is internal, look at the finished builds for this event
1014
- nextBuild = finishedInternalBuilds.find(b => b.jobId === nextJobId && b.eventId === current.event.id);
1015
-
1016
- if (!nextBuild) {
1017
- // If the build to join fails and it succeeds on restart, depending on the timing, the latest build will be that of a child event.
1018
- // In that case, `nextBuild` will be null and will not be triggered even though there is a build that should be triggered.
1019
- // Now we need to check for the existence of a build that should be triggered in its own event.
1020
- nextBuild = await buildFactory.get({
1021
- eventId: current.event.id,
1022
- jobId: nextJobId
1023
- });
1024
-
1025
- if (nextBuild) {
1026
- finishedInternalBuilds = finishedInternalBuilds.concat(nextBuild);
1027
- }
1028
- }
1029
-
1030
- fillParentBuilds(parentBuilds, current, finishedInternalBuilds);
1031
-
1032
- let newBuild;
1033
-
1034
- // Create next build
1035
- if (!nextBuild) {
1036
- const internalBuildConfig = {
1037
- jobFactory,
1038
- buildFactory,
1039
- pipelineId: current.pipeline.id,
1040
- jobName: nextJobName,
1041
- start: false,
1042
- username,
1043
- scmContext,
1044
- event: current.event, // this is the parentBuild for the next build
1045
- baseBranch: current.event.baseBranch || null,
1046
- parentBuilds,
1047
- parentBuildId: current.build.id
1048
- };
1049
-
1050
- newBuild = await createInternalBuild(internalBuildConfig);
1051
- } else {
1052
- // nextBuild is not build model, so fetch proper build
1053
- newBuild = await updateParentBuilds({
1054
- joinParentBuilds: parentBuilds,
1055
- nextBuild: await buildFactory.get(nextBuild.id),
1056
- build: current.build
1057
- });
1058
- }
1059
-
1060
- if (!newBuild) {
1061
- logger.error(`No build found for ${current.pipeline.id}:${nextJobName}`);
1062
-
1063
- return null;
1064
- }
1065
- /* CHECK IF ALL PARENT BUILDS OF NEW BUILD ARE DONE */
1066
- const { hasFailure, done } = await getParentBuildStatus({
1067
- newBuild,
1068
- joinListNames,
1069
- pipelineId: current.pipeline.id,
1070
- buildFactory
1071
- });
1072
-
1073
- return handleNewBuild({
1074
- done,
1075
- hasFailure,
1076
- newBuild,
1077
- jobName: nextJobName,
1078
- pipelineId: current.pipeline.id,
1079
- stage: current.stage
1080
- });
1081
- };
1082
-
1083
- // Helper function to handle triggering jobs in external pipeline
1084
- const triggerJobsInExternalPipeline = async (externalPipelineId, joinObj) => {
1085
- let externalEvent = joinObj.event;
1086
- const nextJobs = joinObj.jobs;
1087
- let nextJobNames = Object.keys(nextJobs);
1088
- const triggerName = `sd@${current.pipeline.id}:${current.job.name}`;
1089
-
1090
- if (externalEvent) {
1091
- // Remote join case
1092
- // fetch builds created due to restart
1093
- const externalGroupBuilds = await getFinishedBuilds(externalEvent, buildFactory);
1094
-
1095
- const buildsToRestart = nextJobNames
1096
- .map(j => {
1097
- const existingBuild = externalGroupBuilds.find(b => b.jobId === nextJobs[j].id);
1098
-
1099
- return existingBuild &&
1100
- existingBuild.status !== 'CREATED' &&
1101
- !existingBuild.parentBuildId.includes(current.build.id) &&
1102
- existingBuild.eventId !== current.event.parentEventId
1103
- ? existingBuild
1104
- : null;
1105
- })
1106
- .filter(b => b !== null);
1107
-
1108
- // fetch builds created due to trigger
1109
- const parallelBuilds = await getParallelBuilds({
1110
- eventFactory,
1111
- parentEventId: externalEvent.id,
1112
- pipelineId: externalEvent.pipelineId
1113
- });
1114
-
1115
- externalGroupBuilds.push(...parallelBuilds);
1116
-
1117
- if (buildsToRestart.length) {
1118
- const { parentBuilds } = buildsToRestart[0];
1119
-
1120
- // If restart handle like a fresh trigger
1121
- // and start all jobs which are not join jobs
1122
- const externalBuildConfig = {
1123
- pipelineFactory,
1124
- eventFactory,
1125
- externalPipelineId,
1126
- startFrom: `~${triggerName}`,
1127
- parentBuildId: current.build.id,
1128
- parentBuilds,
1129
- causeMessage: `Triggered by ${triggerName}`,
1130
- parentEventId: current.event.id,
1131
- groupEventId: externalEvent.id
1132
- };
1133
-
1134
- // proceed with join jobs using new external event
1135
- nextJobNames = nextJobNames.filter(j => nextJobs[j].join.length);
1136
-
1137
- externalEvent = await createExternalBuild(externalBuildConfig);
1138
- }
1139
-
1140
- // create/start build for each of nextJobs
1141
- for (const nextJobName of nextJobNames) {
1142
- const { username, scmContext } = config;
1143
- const nextJob = nextJobs[nextJobName];
1144
- // create new build if restart case.
1145
- // externalGroupBuilds will contain previous externalEvent's builds
1146
- const nextBuild = buildsToRestart.length
1147
- ? null
1148
- : externalGroupBuilds.find(b => b.jobId === nextJob.id);
1149
- let newBuild;
1150
-
1151
- const { parentBuilds } = parseJobInfo({
1152
- joinObj: nextJobs,
1153
- current,
1154
- nextJobName,
1155
- nextPipelineId: externalPipelineId
1156
- });
1157
-
1158
- fillParentBuilds(parentBuilds, current, externalGroupBuilds, externalEvent);
1159
-
1160
- const joinList = nextJobs[nextJobName].join;
1161
- const joinListNames = joinList.map(j => j.name);
1162
- const isORTrigger = !joinListNames.includes(triggerName);
1163
-
1164
- if (nextBuild) {
1165
- // update current build info in parentBuilds
1166
- // nextBuild is not build model, so fetch proper build
1167
- newBuild = await updateParentBuilds({
1168
- joinParentBuilds: parentBuilds,
1169
- nextBuild: await buildFactory.get(nextBuild.id),
1170
- build: current.build
1171
- });
1172
- } else {
1173
- // no existing build, so first time processing this job
1174
- // in the external pipeline's event
1175
- const parentBuildId = getParentBuildIds({
1176
- currentBuildId: current.build.id,
1177
- parentBuilds,
1178
- joinListNames,
1179
- pipelineId: externalPipelineId
1180
- });
1181
-
1182
- newBuild = await createInternalBuild({
1183
- jobFactory,
1184
- buildFactory,
1185
- pipelineId: externalEvent.pipelineId,
1186
- jobName: nextJob.name,
1187
- jobId: nextJob.id,
1188
- username,
1189
- scmContext,
1190
- event: externalEvent, // this is the parentBuild for the next build
1191
- baseBranch: externalEvent.baseBranch || null,
1192
- parentBuilds,
1193
- parentBuildId,
1194
- start: false
1195
- });
1196
- }
1197
-
1198
- if (isORTrigger) {
1199
- if (['CREATED', null, undefined].includes(newBuild.status)) {
1200
- newBuild.status = 'QUEUED';
1201
- await newBuild.update();
1202
- await newBuild.start();
1203
- }
1204
- } else {
1205
- const { hasFailure, done } = await getParentBuildStatus({
1206
- newBuild,
1207
- joinListNames,
1208
- pipelineId: externalPipelineId,
1209
- buildFactory
1210
- });
1211
-
1212
- // Check if external pipeline has Join
1213
- // and join conditions are met
1214
- await handleNewBuild({
1215
- done,
1216
- hasFailure,
1217
- newBuild,
1218
- jobName: nextJobName,
1219
- pipelineId: externalPipelineId,
1220
- stage: current.stage
1221
- });
1222
- }
1223
- }
1224
-
1225
- return null;
1226
- }
1227
-
1228
- const { parentBuilds } = parseJobInfo({ current });
1229
-
1230
- // Simply create an external event if external job is not join job.
1231
- // Straight external trigger flow.
1232
- const externalBuildConfig = {
1233
- pipelineFactory,
1234
- eventFactory,
1235
- externalPipelineId,
1236
- startFrom: `~${triggerName}`,
1237
- parentBuildId: current.build.id,
1238
- parentBuilds,
1239
- causeMessage: `Triggered by ${triggerName}`,
1240
- parentEventId: current.event.id,
1241
- groupEventId: null
1242
- };
1243
-
1244
- return createExternalBuild(externalBuildConfig);
1245
- };
1246
-
1247
- for (const pid of Object.keys(pipelineJoinData)) {
1248
- // typecast pid to number
1249
- let triggerCurrentPipelineAsExternal = false;
1250
- const isCurrentPipeline = +pid === current.pipeline.id;
1251
-
1252
- if (isCurrentPipeline) {
1253
- for (const nextJobName of Object.keys(pipelineJoinData[pid].jobs)) {
1254
- const resource = `pipeline:${current.pipeline.id}:event:${current.event.id}`;
1255
- let lock;
1256
-
1257
- try {
1258
- const { isExternal } = pipelineJoinData[pid].jobs[nextJobName];
1259
-
1260
- triggerCurrentPipelineAsExternal = triggerCurrentPipelineAsExternal || isExternal;
1261
- if (!isExternal) {
1262
- lock = await locker.lock(resource);
1263
-
1264
- await triggerNextJobInSamePipeline(nextJobName, pipelineJoinData[pid].jobs);
1265
- }
1266
- } catch (err) {
1267
- logger.error(
1268
- `Error in triggerNextJobInSamePipeline:${nextJobName} from pipeline:${current.pipeline.id}-${current.job.name}-event:${current.event.id} `,
1269
- err
1270
- );
1271
- }
1272
-
1273
- await locker.unlock(lock, resource);
1274
- }
1275
- }
1276
-
1277
- if (triggerCurrentPipelineAsExternal || !isCurrentPipeline) {
1278
- let resource;
1279
- let lock;
1280
-
1281
- try {
1282
- if (isCurrentPipeline) {
1283
- // force external trigger for jobs in same pipeline if user used external trigger syntax
1284
- delete pipelineJoinData[pid].event;
1285
- }
1286
- const extEvent = pipelineJoinData[pid].event;
1287
-
1288
- // no need to lock if there is no external event
1289
- if (extEvent) {
1290
- resource = `pipeline:${pid}:event:${extEvent.id}`;
1291
- lock = await locker.lock(resource);
1292
- }
1293
-
1294
- await triggerJobsInExternalPipeline(pid, pipelineJoinData[pid]);
1295
- } catch (err) {
1296
- logger.error(
1297
- `Error in triggerJobsInExternalPipeline:${pid} from pipeline:${current.pipeline.id}-${current.job.name}-event:${current.event.id} `,
1298
- err
1299
- );
1300
- }
1301
-
1302
- await locker.unlock(lock, resource);
1303
- }
1304
- }
1305
-
1306
- return null;
1307
- });
344
+ server.expose('triggerNextJobs', triggerNextJobs);
1308
345
 
1309
346
  server.route([
1310
347
  getRoute(),