-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
203 lines (170 loc) · 6.97 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
'use strict';
const ANNOTATION_EXECUTOR_TYPE = 'executor'; // Key in annotations object that maps to an executor NPM module
const Executor = require('screwdriver-executor-base');
const logger = require('screwdriver-logger');
class ExecutorRouter extends Executor {
/**
* Constructs a router for different Executor strategies.
* @method constructor
* @param {Object} config Object with executor and ecosystem
* @param {String} [config.defaultPlugin] Optional default executor
* @param {Object} [config.ecosystem] Optional object with ecosystem values
* @param {Array} config.executor Array of executors to load
* @param {String} config.executor[x].name Name of the executor NPM module to load
* @param {String} config.executor[x].options Configuration to construct the module with
*/
constructor(config = {}) {
const ecosystem = config.ecosystem || {};
const { executor, defaultPlugin } = config;
if (!executor || !Array.isArray(executor) || executor.length === 0) {
throw new Error('No executor config passed in.');
}
super();
let ExecutorPlugin;
this._executors = [];
executor.forEach(plugin => {
try {
const pluginName = plugin.pluginName || plugin.name;
// eslint-disable-next-line global-require, import/no-dynamic-require
ExecutorPlugin = require(`screwdriver-executor-${pluginName}`);
this._executors.push(plugin);
} catch (err) {
logger.error(err.message);
return;
}
// Add ecosystem to executor options
const options = Object.assign({ ecosystem }, plugin.options);
this[plugin.name] = new ExecutorPlugin(options);
});
// executor rules chain
// order-> annotated > weighted > default
this._executorRules = [
{
name: 'annotated',
check: buildConfig => {
const annotations = this.parseAnnotations(buildConfig.annotations || {});
return annotations[ANNOTATION_EXECUTOR_TYPE];
}
},
{
name: 'weighted',
check: buildConfig => {
const allowedExecutors = this.checkExclusions(this._executors, buildConfig.container);
return this.getWeightedExecutor(allowedExecutors);
}
},
{
name: 'default',
check: () => defaultPlugin || (this._executors[0] && this._executors[0].name)
}
];
if (!this._executorRules.find(a => a.name === 'default').check()) {
throw new Error('No default executor set.');
}
}
/**
* Returns the executor based on a random selection optimized on weightage
* @param {Array} executors
* @return {String} executor name
*/
getWeightedExecutor(executors) {
const totalWeight = executors.reduce((prev, curr) => prev + (+curr.weightage || 0), 0);
if (totalWeight === 0) {
return undefined;
}
const number = Math.floor(Math.random() * totalWeight);
let sum = 0;
for (let i = 0; i < executors.length; i += 1) {
sum += parseInt(executors[i].weightage, 10) || 0;
if (number < sum) return executors[i].name;
}
return executors[0].name;
}
/**
* Checks if executor is excluded for a container.
* @method checkExclusions
* @param {Array} executors
* @param {String} container
*/
checkExclusions(executors, container) {
return executors.filter(executor => {
const { exclusions } = executor;
if (!exclusions) return true;
return !exclusions.some(item => {
const regEx = new RegExp(item, 'gi');
return container.match(regEx);
});
});
}
/**
* Evaluates the executor rules by priority and returns the first matching executor
* @method getExecutor
* @param {Object} config Configuration
* @param {Object} [config.annotations] Optional key/value object
* @param {String} config.apiUri Screwdriver's API
* @param {Object} [config.build] Build object
* @param {String} config.buildId Unique ID for a build
* @param {String} config.container Container for the build to run in
* @param {String} config.token JWT to act on behalf of the build
* @return {Object} executor object
*/
getExecutor(config) {
let executorName;
for (const rule of this._executorRules) {
try {
executorName = rule.check(config);
if (executorName && this[executorName]) {
break;
}
} catch (err) {
logger.error(`Failed to validate executor rule ${rule.name}`, err);
}
}
return this[executorName];
}
/**
* Starts a new build in an executor
* @method _start
* @param {Object} config Configuration
* @param {Object} [config.annotations] Optional key/value object
* @param {String} config.apiUri Screwdriver's API
* @param {Object} [config.build] Build object
* @param {String} config.buildId Unique ID for a build
* @param {String} config.container Container for the build to run in
* @param {String} config.token JWT to act on behalf of the build
* @return {Promise}
*/
_start(config) {
const executor = this.getExecutor(config);
return executor.start(config);
}
/**
* Stop a running or finished build
* @method _stop
* @param {Object} config Configuration
* @param {Object} [config.annotations] Optional key/value object
* @param {String} config.buildId Unique ID for a build
* @return {Promise}
*/
_stop(config) {
const executor = this.getExecutor(config);
return executor.stop(config);
}
/**
* Verifies the status of a build in an executor
* @method _start
* @param {Object} config Configuration
* @param {Object} [config.annotations] Optional key/value object
* @param {String} config.apiUri Screwdriver's API
* @param {Object} [config.build] Build object
* @param {String} config.buildId Unique ID for a build
* @param {String} config.container Container for the build to run in
* @param {String} config.token JWT to act on behalf of the build
* @return {Promise}
*/
_verify(config) {
const executor = this.getExecutor(config);
return executor.verify(config);
}
}
module.exports = ExecutorRouter;