forked from taskcluster/taskcluster
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtaskcluster_proxy.js
145 lines (123 loc) · 4.72 KB
/
taskcluster_proxy.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
/**
This module handles the creation of the "taskcluster" proxy container which
allows tasks to talk directly to taskcluster services over a http proxy which
grants a particular permission level based on the task scopes.
*/
const promiseRetry = require('promise-retry');
const waitForPort = require('../wait_for_port');
const http = require('http');
const { version: workerVersion } = require('../../package.json');
// Alias used to link the proxy.
const ALIAS = 'taskcluster';
// Maximum time in MS to wait for the proxy socket to become available.
// Default timeout for heroku is 30 seconds, so we should at least wait enough
// time for some retries to happen before giving up.
const INIT_TIMEOUT = 90000;
class TaskclusterProxy {
constructor () {
this.featureName = 'taskclusterProxy';
/**
Docker container used in the linking process.
*/
this.container = null;
}
async link(task) {
let docker = task.runtime.docker;
// Use the taskcluster-proxy image built with this docker-worker release
// (and packaged with it, if done with monpacker)
let image = `taskcluster/taskcluster-proxy:v${workerVersion}`;
let imageId = await task.runtime.imageManager.ensureImage(image, process.stdout, task);
let cmd = [
'--client-id=' + task.claim.credentials.clientId,
'--access-token=' + task.claim.credentials.accessToken,
];
// only pass in a certificate if one has been set
if (task.claim.credentials.certificate) {
cmd.push('--certificate=' + task.claim.credentials.certificate);
}
cmd.push('--root-url=' + task.runtime.rootUrl);
// supply the task's scopes, limiting what can be done via the proxy
cmd = cmd.concat(task.task.scopes);
// ..and include the scope to create artifacts on this task, which cannot
// be represented in task.scopes (since it contains a taskId)
cmd.push(`queue:create-artifact:${task.status.taskId}/${task.runId}`);
// create the container.
this.container = await docker.createContainer({
Image: imageId,
Tty: true,
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
// The proxy image uses a delegating authentication pattern it accepts
// the primary workers authentication details and a task id (which is used
// to fetch the task and it's scopes to delegate). Note: passing the
// arguments via 'Cmd' may potentially be a security issue. Alternatives
// are: 1. Volume mounted files, 2. Docker secret (not landed yet).
Cmd: cmd,
});
// Terrible hack to get container promise proxy.
this.container = docker.getContainer(this.container.id);
// Stream output from the container to the worker logs.
let stream = await this.container.attach({ stream: true, stdout: true, stderr: true });
stream.pipe(process.stdout);
await this.container.start({});
let inspect = await this.container.inspect();
let name = inspect.Name.slice(1);
try {
// wait for the initial server response...
await waitForPort(inspect.NetworkSettings.IPAddress, '80', INIT_TIMEOUT);
} catch (e) {
throw new Error('Failed to initialize taskcluster proxy service.');
}
// Update credentials in proxy
task.on('credentials', async (credentials) => {
let creds = JSON.stringify(credentials);
await promiseRetry(retry => {
return new Promise((accept, reject) => {
let req = http.request({
hostname: inspect.NetworkSettings.IPAddress,
method: 'PUT',
path: '/credentials',
headers: {
'Content-Type': 'text/json',
'Content-Length': creds.length,
},
}, res => {
if (res.statusCode === 200) {
task.runtime.log('Credentials updated', {
taskId: task.status.taskId,
runId: task.runId,
clientId: creds.clientId,
});
accept();
} else {
let err = new Error(`Credentials update failed ${res.statusCode}`);
task.runtime.log(err, {
taskId: task.status.taskId,
runId: task.runId,
});
reject(err);
}
});
req.on('error', err => reject(err));
req.write(creds);
req.end();
}).catch(retry);
}, {
maxTimeout: 10000,
factor: 1.311,
randomize: true,
}).catch(err => task.runtime.log(err.stack));
});
return {
links: [{ name, alias: ALIAS }],
env: {
TASKCLUSTER_PROXY_URL: `http://${ALIAS}`,
},
};
}
async killed(task) {
task.runtime.gc.removeContainer(this.container.id);
}
}
module.exports = TaskclusterProxy;