forked from taskcluster/taskcluster
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstates.js
162 lines (134 loc) · 4.65 KB
/
states.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
let assert = require('assert');
let debug = require('debug')('docker-worker:states');
let _ = require('lodash');
function hasMethod(method, input) {
return typeof input[method] === 'function';
}
/**
The state handler is a FSM-ish handler for the various points in a single task's
lifetime. The hooks provided by this module are used to extend the basic
behaviour of task handling (and hand in hand with feature flags allow granular
selection of actions which have various performance impacts on the task being
run).
Each "state" in the lifetime is a well defined function:
- link : prior to creating the task container returning containers to be
linked in.
- created : After the container has been created but prior to running the
container. Useful for loggers, etc... No value is returned.
- stopped : After the container has completely run (not run in cases where the
container has not run).
- killed: After the task container has been entirely removed. The intention
is for this to be used to cleanup any remaining linked containers.
@constructor
@param {Array[Object]} hooks for handling states in the task lifecycle.
*/
class States {
constructor(hooks) {
assert.ok(Array.isArray(hooks), 'hooks is an array');
this.hooks = hooks;
}
/**
Invoke all hooks with a particular method.
@param {Task} task handler.
*/
_invoke(method, task) {
debug('taskId: %s at state: %s', task.status.taskId, method);
let hooks = this.hooks.filter(hasMethod.bind(this, method));
let errors = [];
return Promise.all(
hooks.map(hook => {
return hook[method](task)
.then(info => { return info; })
.catch(err => {
errors.push(new Error(`Error calling '${method}' for ${hook.featureName} : ${err.stack}`));
});
}),
).then(results => {
if (errors.length > 0) {
throw new Error(errors.map(e => e).join(' | '));
}
return results;
});
}
/**
The "link" state is responsible for creating any dependent containers and
returning the name of the container to be "linked" with the task container,
additionally it may also return over-writeable environment variables to
give to the task.
Each "hook" which contains a link method or wants to declare an env variable
_must_ return an object in the following format:
```js
{
links: [
{name: 'container name', alias: 'alias in task container'}
]
env: {
'name-of-env-var': 'value of variable'
},
binds: [
{source: '/path/on/host', target: '/path/in/container', readOnly: true}
]
}
```
All links are run in parallel then merged.
Note, that environment variables can be overwritten by task-specific
environment variables, or environment variables from other hooks.
@param {Task} task handler.
@return {Object} object on the same form as returned by hook, see above.
*/
async link(task) {
// Build the list of linked containers...
let results = await this._invoke('link', task);
// List of lists of links
let listsOfLinks = results.map(_.property('links')).filter(_.isArray);
// List of env objects
let listsOfEnvs = results.map(_.property('env')).filter(_.isObject);
// List of lists of binds
let listsOfBinds = results.map(_.property('binds')).filter(_.isArray);
// Merge env objects and flatten lists of links
return {
links: _.flatten(listsOfLinks),
env: _.defaults.apply(_, listsOfEnvs),
binds: _.flatten(listsOfBinds),
};
}
/**
Invoke the `create hook no value is expected to be returned this is
effectively for "side effects" like logging which must start prior to running
the container.
@param {Task} task handler.
@return void.
*/
created(task) {
return this._invoke('created', task);
}
/**
Invoke the `started hook no value is expected to be returned this is
effectively for "side effects" monitoring the container or executing
scripts inside it while it's running.
@param {Task} task handler.
@return void.
*/
started(task) {
return this._invoke('started', task);
}
/**
Invoke the `stop` hook intended to be used to upload any artifacts created
during the task container run (which is in a stopped state at this point).
@param {Task} task handler.
@return void.
*/
stopped(task) {
return this._invoke('stopped', task);
}
/**
The `kill hook is intended to be used to cleanup any remaining containers and
finalize any log artifacts.
@param {Task} task handler
@return void.
*/
killed(task) {
return this._invoke('killed', task);
}
}
module.exports = States;