screwdriver-queue-service 2.0.17 → 2.0.21
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/config/custom-environment-variables.yaml +3 -1
- package/config/default.yaml +3 -1
- package/config/kafka.js +19 -0
- package/lib/queue.js +0 -14
- package/package.json +8 -8
- package/plugins/helper.js +54 -1
- package/plugins/queue/scheduler.js +11 -5
- package/plugins/queue/utils/freezeWindows.js +5 -6
- package/plugins/worker/lib/jobs.js +54 -5
- package/plugins/worker/worker.js +1 -1
package/config/default.yaml
CHANGED
package/config/kafka.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = require('config');
|
|
4
|
+
const kafkaConfig = config.get('kafka');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* get config
|
|
8
|
+
* @returns Object containing kafka config values
|
|
9
|
+
*/
|
|
10
|
+
function get() {
|
|
11
|
+
return {
|
|
12
|
+
kafkaEnabled: kafkaConfig.enabled === 'true',
|
|
13
|
+
useShortRegionName: kafkaConfig.shortRegion === 'true'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
get
|
|
19
|
+
};
|
package/lib/queue.js
CHANGED
|
@@ -58,20 +58,6 @@ module.exports = class ExecutorQueue {
|
|
|
58
58
|
this.redis[funcName](...args),
|
|
59
59
|
breakerOptions
|
|
60
60
|
);
|
|
61
|
-
this.requestRetryStrategy = response => {
|
|
62
|
-
if (Math.floor(response.statusCode / 100) !== 2) {
|
|
63
|
-
throw new Error('Retry limit reached');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return response;
|
|
67
|
-
};
|
|
68
|
-
this.requestRetryStrategyPostEvent = response => {
|
|
69
|
-
if (Math.floor(response.statusCode / 100) !== 2 && response.statusCode !== 404) {
|
|
70
|
-
throw new Error('Retry limit reached');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return response;
|
|
74
|
-
};
|
|
75
61
|
this.fuseBox = new FuseBox();
|
|
76
62
|
this.fuseBox.addFuse(this.queueBreaker);
|
|
77
63
|
this.fuseBox.addFuse(this.redisBreaker);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screwdriver-queue-service",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.21",
|
|
4
4
|
"description": "Screwdriver Queue Service API",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"directories": {
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"@hapi/good": "^9.0.1",
|
|
12
12
|
"@hapi/good-console": "^9.0.1",
|
|
13
13
|
"@hapi/good-squeeze": "^6.0.0",
|
|
14
|
-
"@hapi/hapi": "^20.1
|
|
14
|
+
"@hapi/hapi": "^20.2.1",
|
|
15
15
|
"@hapi/hoek": "^9.1.1",
|
|
16
|
-
"amqp-connection-manager": "^3.
|
|
16
|
+
"amqp-connection-manager": "^3.8.1",
|
|
17
17
|
"amqplib": "^0.8.0",
|
|
18
18
|
"blipp": "^4.0.2",
|
|
19
19
|
"circuit-fuses": "^4.0.5",
|
|
20
20
|
"config": "^1.31.0",
|
|
21
|
-
"cron-parser": "^2.
|
|
21
|
+
"cron-parser": "^4.2.1",
|
|
22
22
|
"hapi-auth-jwt2": "^10.2.0",
|
|
23
23
|
"ioredis": "^3.2.2",
|
|
24
|
-
"joi": "^17.
|
|
24
|
+
"joi": "^17.5.0",
|
|
25
25
|
"js-yaml": "^3.14.1",
|
|
26
26
|
"jsonwebtoken": "^8.5.1",
|
|
27
27
|
"laabr": "^6.1.3",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"npm-auto-version": "^1.0.0",
|
|
30
30
|
"redlock": "^4.2.0",
|
|
31
31
|
"screwdriver-aws-producer-service": "^1.1.0",
|
|
32
|
-
"screwdriver-data-schema": "^21.
|
|
32
|
+
"screwdriver-data-schema": "^21.16.0",
|
|
33
33
|
"screwdriver-executor-docker": "^5.0.2",
|
|
34
34
|
"screwdriver-executor-jenkins": "^5.0.1",
|
|
35
|
-
"screwdriver-executor-k8s": "^14.
|
|
35
|
+
"screwdriver-executor-k8s": "^14.16.0",
|
|
36
36
|
"screwdriver-executor-k8s-vm": "^4.3.2",
|
|
37
37
|
"screwdriver-executor-router": "^2.1.2",
|
|
38
38
|
"screwdriver-logger": "^1.0.2",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"mockery": "^2.1.0",
|
|
54
54
|
"nyc": "^15.1.0",
|
|
55
55
|
"sinon": "^9.2.4",
|
|
56
|
-
"snyk": "^1.
|
|
56
|
+
"snyk": "^1.814.0",
|
|
57
57
|
"util": "^0.12.2"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
package/plugins/helper.js
CHANGED
|
@@ -7,6 +7,34 @@ const { queuePrefix } = require('../config/redis');
|
|
|
7
7
|
const RETRY_LIMIT = 3;
|
|
8
8
|
const RETRY_DELAY = 5;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Callback function to retry when HTTP status code is not 2xx
|
|
12
|
+
* @param {Object} response
|
|
13
|
+
* @param {Function} retryWithMergedOptions
|
|
14
|
+
* @return {Object} response
|
|
15
|
+
*/
|
|
16
|
+
function requestRetryStrategy(response) {
|
|
17
|
+
if (Math.floor(response.statusCode / 100) !== 2) {
|
|
18
|
+
throw new Error('Retry limit reached');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Callback function to retry when HTTP status code is not 2xx and 404
|
|
26
|
+
* @param {Object} response
|
|
27
|
+
* @param {Function} retryWithMergedOptions
|
|
28
|
+
* @return {Object} response
|
|
29
|
+
*/
|
|
30
|
+
function requestRetryStrategyPostEvent(response) {
|
|
31
|
+
if (Math.floor(response.statusCode / 100) !== 2 && response.statusCode !== 404) {
|
|
32
|
+
throw new Error('Retry limit reached');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
/**
|
|
11
39
|
*
|
|
12
40
|
* @param {String} method
|
|
@@ -218,11 +246,36 @@ async function updateBuild(updateConfig, retryStrategyFn) {
|
|
|
218
246
|
);
|
|
219
247
|
}
|
|
220
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Post the webhooks process
|
|
251
|
+
* @method processHooks
|
|
252
|
+
* @param {String} apiUri
|
|
253
|
+
* @param {String} token
|
|
254
|
+
* @param {String} webhookConfig as JSON format
|
|
255
|
+
* @param {Function} retryStrategyFn
|
|
256
|
+
* @return {Promise} response or error
|
|
257
|
+
*/
|
|
258
|
+
async function processHooks(apiUri, token, webhookConfig, retryStrategyFn) {
|
|
259
|
+
return request(formatOptions('POST', `${apiUri}/v4/processHooks`, token, webhookConfig, retryStrategyFn)).then(
|
|
260
|
+
res => {
|
|
261
|
+
logger.info(`POST /v4/processHooks completed, ${res.statusCode}, ${JSON.stringify(res.body)}`);
|
|
262
|
+
if ([200, 201, 204].includes(res.statusCode)) {
|
|
263
|
+
return res;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
throw new Error(`Failed to process webhook with ${res.statusCode} code and ${res.body}`);
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
221
271
|
module.exports = {
|
|
272
|
+
requestRetryStrategy,
|
|
273
|
+
requestRetryStrategyPostEvent,
|
|
222
274
|
updateBuildStatus,
|
|
223
275
|
updateStepStop,
|
|
224
276
|
getCurrentStep,
|
|
225
277
|
createBuildEvent,
|
|
226
278
|
getPipelineAdmin,
|
|
227
|
-
updateBuild
|
|
279
|
+
updateBuild,
|
|
280
|
+
processHooks
|
|
228
281
|
};
|
|
@@ -38,7 +38,7 @@ async function postBuildEvent(executor, eventConfig) {
|
|
|
38
38
|
scope: ['user']
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
const admin = await helper.getPipelineAdmin(token, apiUri, pipelineId,
|
|
41
|
+
const admin = await helper.getPipelineAdmin(token, apiUri, pipelineId, helper.requestRetryStrategy);
|
|
42
42
|
|
|
43
43
|
if (admin) {
|
|
44
44
|
logger.info(
|
|
@@ -64,7 +64,7 @@ async function postBuildEvent(executor, eventConfig) {
|
|
|
64
64
|
buildEvent.buildId = buildId;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
await helper.createBuildEvent(apiUri, jwt, buildEvent,
|
|
67
|
+
await helper.createBuildEvent(apiUri, jwt, buildEvent, helper.requestRetryStrategyPostEvent);
|
|
68
68
|
} else {
|
|
69
69
|
logger.error(
|
|
70
70
|
`POST event for pipeline failed as no admin found: ${pipelineId}:${job.name}:${job.id}:${buildId}`
|
|
@@ -337,7 +337,7 @@ async function start(executor, config) {
|
|
|
337
337
|
apiUri,
|
|
338
338
|
payload
|
|
339
339
|
},
|
|
340
|
-
|
|
340
|
+
helper.requestRetryStrategy
|
|
341
341
|
)
|
|
342
342
|
.catch(err => {
|
|
343
343
|
logger.error(`frozenBuilds: failed to update build status for build ${buildId}: ${err}`);
|
|
@@ -399,7 +399,7 @@ async function start(executor, config) {
|
|
|
399
399
|
apiUri,
|
|
400
400
|
payload: { stats: build.stats, status: 'QUEUED' }
|
|
401
401
|
},
|
|
402
|
-
|
|
402
|
+
helper.requestRetryStrategy
|
|
403
403
|
);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
@@ -730,7 +730,13 @@ async function queueWebhook(executor, webhookConfig) {
|
|
|
730
730
|
'enqueue',
|
|
731
731
|
executor.webhookQueue,
|
|
732
732
|
'sendWebhook',
|
|
733
|
-
JSON.stringify(
|
|
733
|
+
JSON.stringify({
|
|
734
|
+
webhookConfig,
|
|
735
|
+
token: executor.tokenGen({
|
|
736
|
+
service: 'queue',
|
|
737
|
+
scope: ['webhook_worker']
|
|
738
|
+
})
|
|
739
|
+
})
|
|
734
740
|
);
|
|
735
741
|
}
|
|
736
742
|
|
|
@@ -74,12 +74,11 @@ const timeOutOfWindow = (cronExp, timeToCheck) => {
|
|
|
74
74
|
const utcDayOfWeek = timeToCheck.getUTCDay();
|
|
75
75
|
const utcMonth = timeToCheck.getUTCMonth() + 1;
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const monthField = cronObj._fields.month;
|
|
77
|
+
const minuteField = cronObj.fields.minute;
|
|
78
|
+
const hourField = cronObj.fields.hour;
|
|
79
|
+
const dayOfMonthField = cronObj.fields.dayOfMonth;
|
|
80
|
+
const dayOfWeekField = cronObj.fields.dayOfWeek;
|
|
81
|
+
const monthField = cronObj.fields.month;
|
|
83
82
|
|
|
84
83
|
const includesMinute = minuteField.includes(utcMinutes);
|
|
85
84
|
const includesHour = hourField.includes(utcHours);
|
|
@@ -7,6 +7,7 @@ const hoek = require('@hapi/hoek');
|
|
|
7
7
|
const ExecutorRouter = require('screwdriver-executor-router');
|
|
8
8
|
const logger = require('screwdriver-logger');
|
|
9
9
|
const AWSProducer = require('screwdriver-aws-producer-service');
|
|
10
|
+
const helper = require('../../helper');
|
|
10
11
|
const { BlockedBy } = require('./BlockedBy');
|
|
11
12
|
const { Filter } = require('./Filter');
|
|
12
13
|
const { CacheFilter } = require('./CacheFilter');
|
|
@@ -14,10 +15,10 @@ const blockedByConfig = config.get('plugins').blockedBy;
|
|
|
14
15
|
const { connectionDetails, queuePrefix, runningJobsPrefix, waitingJobsPrefix } = require('../../../config/redis');
|
|
15
16
|
const rabbitmqConf = require('../../../config/rabbitmq');
|
|
16
17
|
const { amqpURI, exchange, connectOptions } = rabbitmqConf.getConfig();
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const kafkaConfig = require('../../../config/kafka');
|
|
19
|
+
const { kafkaEnabled, useShortRegionName } = kafkaConfig.get();
|
|
19
20
|
const RETRY_LIMIT = 3;
|
|
20
|
-
// This is in milliseconds, reference: https://github.com/
|
|
21
|
+
// This is in milliseconds, reference: https://github.com/actionhero/node-resque/blob/2ffdf0/lib/plugins/Retry.js#L12
|
|
21
22
|
const RETRY_DELAY = 5 * 1000;
|
|
22
23
|
const redis = new Redis(connectionDetails.port, connectionDetails.host, connectionDetails.options);
|
|
23
24
|
|
|
@@ -56,6 +57,17 @@ const blockedByOptions = {
|
|
|
56
57
|
|
|
57
58
|
collapse: blockedByConfig.collapse
|
|
58
59
|
};
|
|
60
|
+
// AWS region map
|
|
61
|
+
const AWS_REGION_MAP = {
|
|
62
|
+
north: 'n',
|
|
63
|
+
west: 'w',
|
|
64
|
+
northeast: 'nw',
|
|
65
|
+
east: 'e',
|
|
66
|
+
south: 's',
|
|
67
|
+
central: 'c',
|
|
68
|
+
southeast: 'se'
|
|
69
|
+
};
|
|
70
|
+
|
|
59
71
|
let rabbitmqConn;
|
|
60
72
|
|
|
61
73
|
/**
|
|
@@ -133,6 +145,23 @@ async function pushToKafka(message, topic) {
|
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {*String} accountId The AWS accountId
|
|
151
|
+
* @param {*String} region The region name
|
|
152
|
+
* @returns String topicName
|
|
153
|
+
*/
|
|
154
|
+
function getTopicName(accountId, region) {
|
|
155
|
+
const items = region.split('-');
|
|
156
|
+
|
|
157
|
+
if (items.length < 3 || !useShortRegionName) {
|
|
158
|
+
return `builds-${accountId}-${region}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const shortRegion = ''.concat(items[0], AWS_REGION_MAP[items[1]], items[2]);
|
|
162
|
+
|
|
163
|
+
return `builds-${accountId}-${shortRegion}`;
|
|
164
|
+
}
|
|
136
165
|
/**
|
|
137
166
|
* Schedule a job based on mode
|
|
138
167
|
* @method schedule
|
|
@@ -152,9 +181,8 @@ async function schedule(job, buildConfig) {
|
|
|
152
181
|
|
|
153
182
|
if (kafkaEnabled && buildConfig.provider) {
|
|
154
183
|
const { accountId, region } = buildConfig.provider;
|
|
155
|
-
const topic = `builds-${accountId}-${region}`;
|
|
156
184
|
|
|
157
|
-
return pushToKafka(msg,
|
|
185
|
+
return pushToKafka(msg, getTopicName(accountId, region));
|
|
158
186
|
}
|
|
159
187
|
|
|
160
188
|
if (rabbitmqConf.getConfig().schedulerMode) {
|
|
@@ -273,6 +301,20 @@ async function clear(cacheConfig) {
|
|
|
273
301
|
return null;
|
|
274
302
|
}
|
|
275
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Send message to processHooks API
|
|
306
|
+
* @param {String} configs as String
|
|
307
|
+
*/
|
|
308
|
+
async function sendWebhook(configs) {
|
|
309
|
+
const parsedConfig = JSON.parse(configs);
|
|
310
|
+
const { webhookConfig, token } = parsedConfig;
|
|
311
|
+
const apiUri = ecosystem.api;
|
|
312
|
+
|
|
313
|
+
await helper.processHooks(apiUri, token, webhookConfig, helper.requestRetryStrategyPostEvent);
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
276
318
|
module.exports = {
|
|
277
319
|
start: {
|
|
278
320
|
plugins: [Filter, 'Retry', BlockedBy],
|
|
@@ -295,5 +337,12 @@ module.exports = {
|
|
|
295
337
|
Retry: retryOptions
|
|
296
338
|
},
|
|
297
339
|
perform: clear
|
|
340
|
+
},
|
|
341
|
+
sendWebhook: {
|
|
342
|
+
plugins: ['Retry'],
|
|
343
|
+
pluginOptions: {
|
|
344
|
+
Retry: retryOptions
|
|
345
|
+
},
|
|
346
|
+
perform: sendWebhook
|
|
298
347
|
}
|
|
299
348
|
};
|
package/plugins/worker/worker.js
CHANGED
|
@@ -42,7 +42,7 @@ async function shutDownAll(worker, scheduler) {
|
|
|
42
42
|
const multiWorker = new NodeResque.MultiWorker(
|
|
43
43
|
{
|
|
44
44
|
connection: connectionDetails,
|
|
45
|
-
queues: [`${queuePrefix}builds`, `${queuePrefix}cache`],
|
|
45
|
+
queues: [`${queuePrefix}builds`, `${queuePrefix}cache`, `${queuePrefix}webhooks`],
|
|
46
46
|
minTaskProcessors: workerConfig.minTaskProcessors,
|
|
47
47
|
maxTaskProcessors: workerConfig.maxTaskProcessors,
|
|
48
48
|
checkTimeout: workerConfig.checkTimeout,
|