-
Notifications
You must be signed in to change notification settings - Fork 1
/
runInDocker.js
125 lines (110 loc) · 3.7 KB
/
runInDocker.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
const util = require('util');
const childProcess = require('child_process');
const exec = util.promisify(childProcess.exec);
const CONTAINER_NAME = 'my-running-app';
const escapeQuotes = (str = '') => ('' + str).replace(/"/g, '\\"');
const displayStackUntilError = str => {
const lines = str.trim().split(/[\r\n]+/);
return lines
.slice(0, lines.findIndex(line => /error/i.test(line)) + 1)
.join('\n');
};
const bareRunInDocker = command =>
exec(`docker exec ${CONTAINER_NAME} sh -c "${escapeQuotes(command)}"`);
const runInDocker = (command, log) =>
bareRunInDocker(command).then(({ stderr, stdout }) => {
if (log) {
log(stdout);
log(stderr);
} else if (stderr) {
console.error(stderr);
}
return stdout;
});
// Run a command from a separate directory, to prevent "npm install" commands
// from failing because of an invalid student-provided package.json file.
const runInDockerSeparate = (command, log) =>
exec(
`docker exec -w /usr/src ${CONTAINER_NAME} sh -c "${escapeQuotes(command)}"`
).then(({ stderr, stdout }) => {
if (log) {
log(stdout);
log(stderr);
} else if (stderr) {
console.error(stderr);
}
return stdout;
});
const runInDockerBg = (command, debug = () => {}) =>
new Promise((resolve, reject) => {
const logs = [];
let log = str => logs.push(str);
const serverProcess = childProcess.spawn('docker', [
`exec`,
`${CONTAINER_NAME}`,
`sh`,
`-c`,
`${command}`
]);
serverProcess.stdout.on('data', data => {
const str = data.toString('utf8');
log(str);
if (/error/i.test(str)) {
console.error(displayStackUntilError(str));
}
});
serverProcess.stderr.on('data', data =>
console.error(data.toString('utf8'))
);
serverProcess.on('exit', data => {
logs.forEach(str => console.error(str));
reject(new Error('runInDockerBg process exited with ' + data));
});
setTimeout(() => {
logs.forEach(str => debug(str));
log = debug;
resolve();
}, 2000); // resolve by default, after 2 seconds of runtime
});
const waitUntilServerRunning = port =>
exec(`PORT=${port} ./wait-for-student-server.sh`);
const getServerFileName = () =>
runInDocker(
`node -e "console.log(require('./package.json').main)"`,
log
).then(filename => filename.trim);
async function startServer(envVars = {}) {
const log = envVars.log || console.warn;
log(`\nInstall project dependencies in container...`);
try {
await bareRunInDocker(`npm install --no-audit`);
} catch (err) {
let code, file;
err
.toString()
.split(/[\r\n]+/)
.forEach(line => {
code = code || (line.match(/npm ERR\! code (.*)/) || [])[1];
file = file || (line.match(/npm ERR\! file (.*)/) || [])[1];
});
console.error(`🔶 can't npm install, code: ${code}, file: ${file}`);
}
const serverFile = 'server.js'; // or await getServerFileName()
log(`\nStart ${serverFile} in container...`);
const vars = Object.keys(envVars)
.map(key => `${key}="${escapeQuotes(envVars[key])}"`)
.join(' ');
return await runInDockerBg(`${vars} node ${serverFile} 2>&1`, log);
}
async function startServerAndWaitUntilRunning(port, serverEnvVars = {}) {
await startServer(serverEnvVars);
await waitUntilServerRunning(port);
}
exports.CONTAINER_NAME = CONTAINER_NAME;
exports.runInDocker = runInDocker;
exports.runInDockerSeparate = runInDockerSeparate;
exports.startServer = startServer;
exports.waitUntilServerRunning = waitUntilServerRunning;
exports.startServerAndWaitUntilRunning = startServerAndWaitUntilRunning;
exports.killSync = pid =>
childProcess.execSync(`docker exec ${CONTAINER_NAME} kill ${pid}`);