Skip to content

Commit

Permalink
feat(DockterExecutor): mount project folder and set user and group ids
Browse files Browse the repository at this point in the history
  • Loading branch information
nokome committed Nov 6, 2018
1 parent e30ef8c commit a67b3b6
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 24 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -328,13 +328,19 @@ If you want to build your image with bare Docker rename `.Dockerfile` to `Docker

### Execute a Docker image

You can use Docker to run the created image. Or use Dockter's `execute` command to compile, build and run your image in one step:
You can use Docker to run the created image. Or use Dockter's `execute` command to compile, build and run your image in one:

```bash
> dockter execute
2018-10-23 00:58:39
```

Dockter's `execute` also mounts the folder into the container and sets the users and group ids. This allows you to read and write files into the project folder from within the container. It's equivaluent to running Docker with these arguments:

```bash
docker run --rm --volume $(pwd):/work --workdir=/work --user=$(id -u):$(id -g) <image>
```

## Contribute

We 💕 contributions! All contributions: ideas 💡, bug reports 🐛, documentation 🗎, code 💾. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
Expand Down
13 changes: 9 additions & 4 deletions src/DockerCompiler.ts
Expand Up @@ -72,16 +72,21 @@ export default class DockerCompiler {
return environ
}

async execute (source: string): Promise<string> {
async execute (source: string) {
let folder
if (source.substring(0, 7) === 'file://') {
folder = source.substring(7)
} else {
folder = source
}

// Compile the environment first
let environ = await this.compile(source)
if (!environ) throw new Error('Environment not created')
if (!environ.name) throw new Error('Environment does not have a name')

// Execute the environment's image (which is built in compile())
const executor = new DockerExecutor()
const result = await executor.execute(environ.name)

return result
return executor.execute(environ.name, folder)
}
}
76 changes: 57 additions & 19 deletions src/DockerExecutor.ts
@@ -1,37 +1,75 @@
import Docker from 'dockerode'
import os from 'os'
import path from 'path'
import stream from 'stream'
import { resolve } from 'dns'

/**
* Executes a Docker image (ie. starts a container from the image)
* Executes a Docker environment.
*
* Currently, this class simply runs the image and captures any
* output. In the future it may do more advanced things like mounting data and output
* directories into the image.
* This class has a single method, `execute`, which starts a container from an
* image and runs the command specified in the Dockerfile `CMD` instruction.
*
* It mounts the project's directory into the container a `/work` and uses it
* as the working directory.
*
* It also sets the current user and group as the
* user and group in the container. This means that within the container the
* command that runs has the same permissions as the current user does in the
* `/work` directory.
*
* Finally, it removes the container (but not the image).
*
* This then is the equivalent of running the container with Docker from within
* the project directory using,
*
* docker run --rm --volume $(pwd):/work --workdir=/work --user=$(id -u):$(id -g) <image>
*/
export default class DockerExecutor {

async execute (name: string) {
const docker = new Docker()
async execute (name: string, folder: string) {
// Capture stdout so we can attempt to parse it
// to JSON
let out = ''
let stdout = new stream.Writable({
write (chunk, encoding, callback) {
out += chunk.toString()
callback()
}
})

// Capture output stream
let output = ''
let outputStream = new stream.Writable()
outputStream._write = (chunk) => {
output += chunk
}
// Just write errors through to local console error
let stderr = new stream.Writable({
write (chunk, encoding, callback) {
console.error(chunk.toString())
callback()
}
})

const container = await docker.run(name, [], outputStream, {
// Options from https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
// Get and set user:group
const userInfo = os.userInfo()
const user = `${userInfo.uid}:${userInfo.gid}`

// Run the container!
// Options from https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
const docker = new Docker()
const container = await docker.run(name, [], [stdout, stderr], {
Tty: false,
HostConfig: {
Binds: [
`${path.resolve(folder)}:/work`
]
},
WorkingDir: '/work',
User: user
})
container.remove()

let value
// Attempt to parse output as JSON
try {
value = JSON.parse(output)
return JSON.parse(out)
} catch {
value = output.trim()
return out.trim()
}

return value
}
}

0 comments on commit a67b3b6

Please sign in to comment.