UNPKG

2.49 kBPlain TextView Raw
1import os from 'os'
2import path from 'path'
3import stream from 'stream'
4
5import Docker from 'dockerode'
6
7/**
8 * Executes a Docker environment.
9 *
10 * This class has a single method, `execute`, which starts a container from an
11 * image and runs the command specified in the Dockerfile `CMD` instruction.
12 *
13 * It mounts the project's directory into the container a `/work` and uses it
14 * as the working directory.
15 *
16 * It also sets the current user and group as the
17 * user and group in the container. This means that within the container the
18 * command that runs has the same permissions as the current user does in the
19 * `/work` directory.
20 *
21 * Finally, it removes the container (but not the image).
22 *
23 * This then is the equivalent of running the container with Docker from within
24 * the project directory using,
25 *
26 * docker run --rm --volume $(pwd):/work --workdir=/work --user=$(id -u):$(id -g) <image>
27 */
28export default class DockerExecutor {
29
30 /**
31 * Run a Docker container
32 *
33 * @param name Name of the Docker image to use
34 * @param folder Path of the project folder which will be mounted into the image
35 */
36 async execute (name: string, folder: string, command: string = '') {
37 // Capture stdout so we can attempt to parse it
38 // to JSON
39 let out = ''
40 let stdout = new stream.Writable({
41 write (chunk, encoding, callback) {
42 out += chunk.toString()
43 callback()
44 }
45 })
46
47 // Just write errors through to local console error
48 let stderr = new stream.Writable({
49 write (chunk, encoding, callback) {
50 console.error(chunk.toString())
51 callback()
52 }
53 })
54
55 // Get and set user:group
56 const userInfo = os.userInfo()
57 const user = `${userInfo.uid}:${userInfo.gid}`
58
59 // Run the container!
60 // Options from https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
61 const docker = new Docker()
62 // If the user has specified a command thaen use that, otherwise fallback to the
63 // CMD in the Dockerfile
64 let cmd
65 if (command) cmd = command.split(' ')
66 const container = await docker.run(name, [], [stdout, stderr], {
67 Cmd: cmd,
68 HostConfig: {
69 Binds: [
70 `${path.resolve(folder)}:/work`
71 ]
72 },
73 Tty: false,
74 User: user,
75 WorkingDir: '/work'
76 })
77 container.remove()
78
79 // Attempt to parse output as JSON
80 try {
81 return JSON.parse(out)
82 } catch {
83 return out.trim()
84 }
85 }
86}