├── .gitignore
├── .npmrc
├── .travis.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── cli.js
├── index.js
├── logo.png
├── package.json
└── test
├── docker
├── fixtures
├── inspect-empty.json
├── inspect-invalid.json
└── inspect-one-two.json
└── test-cli.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .nyc_output/
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | dist: bionic
3 | language: node_js
4 | node_js:
5 | - '10'
6 | - '12'
7 | - 'node'
8 | after_success: npm run coverage
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ## [3.0.0](https://github.com/nexdrew/rekcod/compare/v2.2.0...v3.0.0) (2020-05-21)
6 |
7 |
8 | ### Features
9 |
10 | * add --group-add, --pid, --security-opt ([#66](https://github.com/nexdrew/rekcod/issues/66)) ([d8e67d2](https://github.com/nexdrew/rekcod/commit/d8e67d2ff66f61b47523939c75f05e957b35eb6b))
11 | * add support for --privileged flag ([#52](https://github.com/nexdrew/rekcod/issues/52)) ([95b6dd4](https://github.com/nexdrew/rekcod/commit/95b6dd4364faad5252ee2d9583daf7f63155f5aa))
12 | * add support for labels ([#58](https://github.com/nexdrew/rekcod/issues/58)) ([f2fc146](https://github.com/nexdrew/rekcod/commit/f2fc146bbc54138f46322d51930c8c4137a80cc1))
13 | * handle special characters in cmd ([#54](https://github.com/nexdrew/rekcod/issues/54)) ([dded29a](https://github.com/nexdrew/rekcod/commit/dded29a06a46cb5388b0bc7d1d8fc17704444971))
14 | * support --uts and --domainname, smarter hostname handling ([#53](https://github.com/nexdrew/rekcod/issues/53)) ([03cebfe](https://github.com/nexdrew/rekcod/commit/03cebfec44e301658ca492d5d08c81ec0bceb3bf))
15 |
16 |
17 | # [2.2.0](https://github.com/nexdrew/rekcod/compare/v2.1.1...v2.2.0) (2019-02-04)
18 |
19 |
20 | ### Features
21 |
22 | * add support for --runtime flag ([#37](https://github.com/nexdrew/rekcod/issues/37)) ([6e6e9dd](https://github.com/nexdrew/rekcod/commit/6e6e9dd))
23 |
24 |
25 |
26 |
27 | ## [2.1.1](https://github.com/nexdrew/rekcod/compare/v2.1.0...v2.1.1) (2018-04-07)
28 |
29 |
30 |
31 |
32 | # [2.1.0](https://github.com/nexdrew/rekcod/compare/v2.0.1...v2.1.0) (2018-01-24)
33 |
34 |
35 | ### Features
36 |
37 | * handle special characters in env vars ([#24](https://github.com/nexdrew/rekcod/issues/24)) ([a61c999](https://github.com/nexdrew/rekcod/commit/a61c999))
38 |
39 |
40 |
41 |
42 | ## [2.0.1](https://github.com/nexdrew/rekcod/compare/v2.0.0...v2.0.1) (2016-12-22)
43 |
44 |
45 |
46 |
47 | # [2.0.0](https://github.com/nexdrew/rekcod/compare/v1.0.1...v2.0.0) (2016-11-07)
48 |
49 |
50 | ### Features
51 |
52 | * accept container, file, or json ([#14](https://github.com/nexdrew/rekcod/issues/14)) ([17264f0](https://github.com/nexdrew/rekcod/commit/17264f0))
53 |
54 |
55 | ### BREAKING CHANGES
56 |
57 | * no longer errors on empty arrays
58 |
59 | prefers file over container if identically named
60 |
61 | * moar tests, 100% coverage
62 |
63 | * moar docs
64 |
65 |
66 |
67 |
68 | ## [1.0.1](https://github.com/nexdrew/rekcod/compare/v1.0.0...v1.0.1) (2016-10-02)
69 |
70 |
71 |
72 |
73 | # [1.0.0](https://github.com/nexdrew/rekcod/compare/v0.2.1...v1.0.0) (2016-10-02)
74 |
75 |
76 | ### Chores
77 |
78 | * add tests ([#11](https://github.com/nexdrew/rekcod/issues/11)) ([77bdada](https://github.com/nexdrew/rekcod/commit/77bdada))
79 |
80 |
81 | ### Features
82 |
83 | * add support for ExtraHosts ([#10](https://github.com/nexdrew/rekcod/issues/10)) ([0e9c61e](https://github.com/nexdrew/rekcod/commit/0e9c61e))
84 |
85 |
86 | ### BREAKING CHANGES
87 |
88 | * move to 1.0.0 with 100% code coverage
89 |
90 | * add travis config and coveralls
91 |
92 |
93 |
94 |
95 | ## [0.2.1](https://github.com/nexdrew/rekcod/compare/v0.2.0...v0.2.1) (2016-04-15)
96 |
97 |
98 |
99 |
100 |
101 | # [0.2.0](https://github.com/nexdrew/rekcod/compare/v0.1.2...v0.2.0) (2016-04-15)
102 |
103 |
104 | ### Features
105 |
106 | * **index:** Support for more inspect-able params ([#4](https://github.com/nexdrew/rekcod/issues/4)) ([f2e1dc7](https://github.com/nexdrew/rekcod/commit/f2e1dc7))
107 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker:19
2 |
3 | RUN apk add --no-cache nodejs nodejs-npm
4 |
5 | RUN npm install -g rekcod
6 |
7 | ENTRYPOINT ["rekcod"]
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Contributors
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose
4 | with or without fee is hereby granted, provided that the above copyright notice
5 | and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
13 | THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | > docker inspect → docker run
4 |
5 | [](https://travis-ci.com/nexdrew/rekcod)
6 | [](https://coveralls.io/github/nexdrew/rekcod?branch=master)
7 | [](https://standardjs.com)
8 | [](https://github.com/conventional-changelog/standard-version)
9 | 
10 | [](https://hub.docker.com/r/nexdrew/rekcod)
11 | [](https://hub.docker.com/r/nexdrew/rekcod)
12 |
13 | Reverse engineer a `docker run` command from an existing container (via `docker inspect`).
14 |
15 | `rekcod` can turn any of the following into a `docker run` command:
16 |
17 | 1. container ids/names (`rekcod` will call `docker inspect`)
18 | 2. path to file containing `docker inspect` output
19 | 3. raw JSON (pass the `docker inspect` output directly)
20 |
21 | Each `docker run` command can be used to duplicate the containers.
22 |
23 | This is not super robust, but it should cover most arguments needed. See [Fields Supported](#fields-supported) below.
24 |
25 | When passing container ids/names, this module calls `docker inspect` directly, and the user running it should be able to as well.
26 |
27 | (If you didn't notice, the dumb name for this package is just "docker" in reverse.)
28 |
29 | ## Install and Usage
30 |
31 | ### CLI
32 |
33 | If you have Node installed:
34 |
35 | ```
36 | $ npm i -g rekcod
37 | ```
38 |
39 | If you only have Docker installed:
40 |
41 | ```
42 | $ docker pull nexdrew/rekcod
43 | $ alias rekcod="docker run --rm -i -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod"
44 | ```
45 |
46 | Or you can simply run this, no installation required:
47 |
48 | ```
49 | $ docker run --rm -i -v /var/run/docker.sock:/var/run/docker.sock nexdrew/rekcod
50 | ```
51 |
52 | #### Containers
53 |
54 | ```sh
55 | # containers as arguments
56 | $ rekcod container-one 6653931e39f2 happy_torvalds
57 |
58 | docker run --name container-one ...
59 |
60 | docker run --name stinky_jones ...
61 |
62 | docker run --name happy_torvalds ...
63 | ```
64 |
65 | ```sh
66 | # pipe in containers
67 | $ docker ps -aq | rekcod
68 |
69 | docker run --name container-one ...
70 |
71 | docker run --name stinky_jones ...
72 |
73 | docker run --name happy_torvalds ...
74 | ```
75 |
76 | #### Files
77 |
78 | If you are using the Node CLI - i.e. you installed `rekcod` via npm or yarn - you can pass file names or file contents to `rekcod` as is, since the Node CLI will have access to files on the host file system:
79 |
80 | ```sh
81 | # file names as arguments (Node CLI example)
82 | $ docker inspect container-one > one.json
83 | $ docker inspect 6653931e39f2 happy_torvalds > two.json
84 | $ rekcod one.json two.json
85 |
86 | docker run --name container-one ...
87 |
88 | docker run --name stinky_jones ...
89 |
90 | docker run --name happy_torvalds ...
91 | ```
92 |
93 | ```sh
94 | # pipe in file names (Node CLI example)
95 | $ docker inspect container-one > one.json
96 | $ docker inspect 6653931e39f2 happy_torvalds > two.json
97 | $ ls *.json | rekcod
98 | ```
99 |
100 | If you are using the Docker-only version of `rekcod` - i.e. you are using `docker run` to run the `nexdrew/rekcod` image - then note that **you'll need to bind mount files** from the host file system as volumes on the `rekcod` container in order for the containerized executable to read them:
101 |
102 | ```sh
103 | # file names as arguments (Docker-only example)
104 | $ docker inspect container-one > one.json
105 | $ docker run --rm -i -v /var/run/docker.sock:/var/run/docker.sock -v `pwd`/one.json:/one.json nexdrew/rekcod /one.json
106 |
107 | docker run --name container-one ...
108 | ```
109 |
110 | Otherwise, as long as you read the file from the host system, you can pipe the contents of a file to `rekcod` and either installation method will work:
111 |
112 | ```sh
113 | # pipe in file contents (works for Node CLI or Docker-only alias)
114 | $ cat one.json | rekcod
115 | ```
116 |
117 | #### JSON
118 |
119 | ```sh
120 | $ docker inspect container-one 6653931e39f2 | rekcod
121 |
122 | docker run --name container-one ...
123 |
124 | docker run --name stinky_jones ...
125 | ```
126 |
127 | ### Module
128 |
129 | ```
130 | $ npm i --save rekcod
131 | ```
132 |
133 | #### Containers via async `reckod()`
134 |
135 | ```js
136 | const rekcod = require('rekcod')
137 | // single container
138 | rekcod('container-name', (err, run) => {
139 | if (err) return console.error(err)
140 | console.log(run[0].command)
141 | })
142 | // multiple containers
143 | rekcod(['another-name', '6653931e39f2', 'happy_torvalds'], (err, run) => {
144 | if (err) return console.error(err)
145 | run.forEach((r) => {
146 | console.log('\n', r.command)
147 | })
148 | })
149 | ```
150 |
151 | #### File via async `rekcod.readFile()`
152 |
153 | ```js
154 | const rekcod = require('rekcod')
155 | rekcod.readFile('docker-inspect.json', (err, run) => {
156 | if (err) return console.error(err)
157 | run.forEach((r) => {
158 | console.log('\n', r.command)
159 | })
160 | })
161 | ```
162 |
163 | #### Parse a JSON string via sync `rekcod.parse()`
164 |
165 | ```js
166 | const fs = require('fs')
167 | const rekcod = require('rekcod')
168 | let array
169 | try {
170 | array = rekcod.parse(fs.readFileSync('docker-inspect.json', 'utf8'))
171 | } catch (err) {
172 | return console.error(err)
173 | }
174 | array.forEach((r) => {
175 | console.log('\n', r.command)
176 | })
177 | ```
178 |
179 | ## Fields Supported
180 |
181 | `rekcod` will translate the following `docker inspect` fields into the listed `docker run` arguments.
182 |
183 | | docker inspect | docker run |
184 | | ---------------------------- | ---------------- |
185 | | `Name` | `--name` |
186 | | `HostConfig.Privileged` | `--privileged` |
187 | | `HostConfig.Runtime` | `--runtime` |
188 | | `HostConfig.Binds` | `-v` |
189 | | `HostConfig.VolumesFrom` | `--volumes-from` |
190 | | `HostConfig.PortBindings` | `-p` |
191 | | `HostConfig.Links` | `--link` |
192 | | `HostConfig.PublishAllPorts` | `-P` |
193 | | `HostConfig.NetworkMode` | `--net` |
194 | | `HostConfig.UTSMode` | `--uts` |
195 | | `HostConfig.RestartPolicy` | `--restart` |
196 | | `HostConfig.ExtraHosts` | `--add-host` |
197 | | `HostConfig.GroupAdd` | `--group-add` |
198 | | `HostConfig.PidMode` | `--pid` |
199 | | `HostConfig.SecurityOpt` | `--security-opt` |
200 | | `Config.Hostname` | `-h` |
201 | | `Config.Domainname` | `--domainname` |
202 | | `Config.ExposedPorts` | `--expose` |
203 | | `Config.Labels` | `-l` |
204 | | `Config.Env` | `-e` |
205 | | `Config.Attach`* !== true | `-d` |
206 | | `Config.AttachStdin` | `-a stdin` |
207 | | `Config.AttachStdout` | `-a stdout` |
208 | | `Config.AttachStderr` | `-a stderr` |
209 | | `Config.Tty` | `-t` |
210 | | `Config.OpenStdin` | `-i` |
211 | | `Config.Entrypoint` | `--entrypoint` |
212 | | `Config.Image` || `Image` | image name or id |
213 | | `Config.Cmd` | command and args |
214 |
215 | Prior to version 0.2.0, `rekcod` always assumed `-d` for detached mode, but it now uses that only when all stdio options are not attached. I believe this is the correct behavior, but let me know if it causes you problems. A side effect of this is that the `-d` shows up much later in the `docker run` command than it used to, but it will still be there. ❤
216 |
217 | ## License
218 |
219 | ISC © Contributors
220 |
--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 |
4 | let args = process.argv.slice(2)
5 |
6 | if (!args.length) {
7 | // grab args from stdin
8 | let read = ''
9 | process.stdin.resume()
10 | process.stdin.setEncoding('utf8')
11 | process.stdin.on('data', function (chunk) {
12 | read += chunk
13 | })
14 | process.stdin.on('end', function () {
15 | try {
16 | args = [JSON.parse(read)] // either pipe json blob in from docker inspect
17 | } catch (_) {
18 | args = read.match(/\S+/g) || process.exit(0) // or pipe container ids from docker ps
19 | }
20 | processArgs(args)
21 | })
22 | } else {
23 | processArgs(args)
24 | }
25 |
26 | function processArgs (args) {
27 | const rekcod = require('./index')
28 | const fs = require('fs')
29 | // determine if each arg represents a container, a file, or json
30 | // collect containers so they can be processed together
31 | const containers = []
32 | const objects = args.map((arg, index) => {
33 | if (typeof arg === 'object') return { index, type: 'json', val: arg }
34 | try {
35 | return { index, type: 'json', val: JSON.parse(arg) }
36 | } catch (_) {}
37 | try {
38 | return { index, type: 'file', val: fs.statSync(arg) && arg }
39 | } catch (_) {}
40 | containers.push({ index, type: 'container', val: arg })
41 | return null
42 | })
43 |
44 | // asynchronously process each object and all containers
45 | let containersRunning = false
46 | if (containers.length) {
47 | containersRunning = true
48 | rekcod(containers.map((c) => c.val), (err, runObjects) => {
49 | handleError(err)
50 | for (let i = 0, len = runObjects.length, c; i < len; i++) {
51 | c = containers[i]
52 | c.run = [runObjects[i]]
53 | objects[c.index] = c
54 | }
55 | containersRunning = false
56 | })
57 | }
58 | let filesRunning = 0
59 | objects.filter(Boolean).forEach((o) => {
60 | if (o.type === 'json') {
61 | o.run = [].concat(rekcod.translate(o.val))
62 | } else if (o.type === 'file') {
63 | filesRunning++
64 | rekcod.readFile(o.val, (err, runObjects) => {
65 | handleError(err)
66 | o.run = runObjects
67 | filesRunning--
68 | })
69 | }
70 | })
71 |
72 | function checkCompletion () {
73 | if (!containersRunning && filesRunning === 0) {
74 | objects.forEach((o) => {
75 | if (!o || !Array.isArray(o.run) || o.run.length === 0) {
76 | console.log()
77 | return console.log('Nothing to translate')
78 | }
79 | o.run.forEach((r) => {
80 | console.log()
81 | console.log(r.command)
82 | })
83 | })
84 | return console.log()
85 | }
86 | setTimeout(checkCompletion, 20) // check up to 50 times a second
87 | }
88 | checkCompletion()
89 | }
90 |
91 | function handleError (err) {
92 | if (!err) return
93 | if (err.stdout) console.log(err.stdout)
94 | if (err.stderr) console.error(err.stderr)
95 | else console.error(err)
96 | process.exit((!isNaN(err.code) && err.code) || 1)
97 | }
98 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // call `docker inspect` for one or more containers
4 | // errback receives an array of "docker run objects"
5 | module.exports = function rekcod (containers, cb) {
6 | let cbCalled = false
7 | const stdout = []
8 | const stderr = []
9 | const child = require('child_process').spawn('docker', ['inspect'].concat(containers))
10 | child.stderr.on('data', data => {
11 | stderr.push(data)
12 | })
13 | child.stdout.on('data', data => {
14 | stdout.push(data)
15 | })
16 | child.on('error', err => {
17 | if (!cbCalled) {
18 | cbCalled = true
19 | cb(err)
20 | }
21 | })
22 | child.on('close', (code, signal) => {
23 | if (cbCalled) return
24 | if (code !== 0) {
25 | const err = new Error('docker inspect failed with code ' + code + ' from signal ' + signal)
26 | err.code = code
27 | err.signal = signal
28 | if (stderr.length) err.stderr = stderr.join('')
29 | if (stdout.length) err.stdout = stdout.join('')
30 | cbCalled = true
31 | return cb(err)
32 | }
33 | try {
34 | cb(null, parse(stdout.join('')))
35 | } catch (err) {
36 | cb(err)
37 | }
38 | })
39 | }
40 |
41 | // file should be path to a file containing `docker inspect` output
42 | // errback receives an array of "docker run objects"
43 | module.exports.readFile = function readFile (file, cb) {
44 | require('fs').readFile(file, { encoding: 'utf8' }, (err, fileContents) => {
45 | if (err) return cb(err)
46 | try {
47 | cb(null, parse(fileContents))
48 | } catch (err) {
49 | cb(err)
50 | }
51 | })
52 | }
53 |
54 | // json string could be an array from `docker inspect` or
55 | // a single inspected object; always returns an array
56 | const parse = module.exports.parse = function parse (jsonString) {
57 | return [].concat(translate(JSON.parse(jsonString)))
58 | }
59 |
60 | // translate a parsed array or object into "docker run objects"
61 | // returns an array if given an array, otherwise returns an object
62 | const translate = module.exports.translate = function translate (parsed) {
63 | return Array.isArray(parsed) ? parsed.map(o => toRunObject(o)) : toRunObject(parsed)
64 | }
65 |
66 | function toRunObject (inspectObj) {
67 | const run = {}
68 |
69 | run.image = shortHash(inspectObj.Image)
70 | run.id = shortHash(inspectObj.Id)
71 |
72 | run.name = inspectObj.Name
73 | if (run.name && run.name.indexOf('/') === 0) run.name = run.name.substring(1)
74 |
75 | run.command = toRunCommand(inspectObj, run.name)
76 |
77 | return run
78 | }
79 |
80 | function shortHash (hash) {
81 | if (hash && hash.length && hash.length > 12) return hash.substring(0, 12)
82 | return hash
83 | }
84 |
85 | function toRunCommand (inspectObj, name) {
86 | let rc = append('docker run', '--name', name)
87 |
88 | const hostcfg = inspectObj.HostConfig || {}
89 | const networkMode = hostcfg.NetworkMode
90 | const utsMode = hostcfg.UTSMode
91 | const modes = { networkMode, utsMode }
92 |
93 | rc = appendBoolean(rc, hostcfg.Privileged, '--privileged') // fixes #49
94 | // TODO something about devices or capabilities instead of privileged?
95 | // --cap-add: Add Linux capabilities
96 | // --cap-drop: Drop Linux capabilities
97 | // --device=[]: Allows you to run devices inside the container without the --privileged flag
98 | // see https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
99 |
100 | if (hostcfg.Runtime) rc = append(rc, '--runtime', hostcfg.Runtime)
101 | rc = appendArray(rc, '-v', hostcfg.Binds)
102 | rc = appendArray(rc, '--volumes-from', hostcfg.VolumesFrom)
103 | if (hostcfg.PortBindings && isCompatible('-p', modes)) {
104 | rc = appendObjectKeys(rc, '-p', hostcfg.PortBindings, ipPort => {
105 | return ipPort.HostIp ? ipPort.HostIp + ':' + ipPort.HostPort : ipPort.HostPort
106 | })
107 | }
108 | rc = appendArray(rc, '--link', hostcfg.Links, link => {
109 | link = link.split(':')
110 | if (link[0] && ~link[0].lastIndexOf('/')) link[0] = link[0].substring(link[0].lastIndexOf('/') + 1)
111 | if (link[1] && ~link[1].lastIndexOf('/')) link[1] = link[1].substring(link[1].lastIndexOf('/') + 1)
112 | return link[0] + ':' + link[1]
113 | })
114 | if (hostcfg.PublishAllPorts && isCompatible('-P', modes)) rc = rc + ' -P'
115 |
116 | if (networkMode && networkMode !== 'default') {
117 | rc = append(rc, '--net', networkMode)
118 | }
119 | if (utsMode && isCompatible('--uts', modes)) {
120 | rc = append(rc, '--uts', utsMode)
121 | }
122 | if (hostcfg.RestartPolicy && hostcfg.RestartPolicy.Name) {
123 | rc = append(rc, '--restart', hostcfg.RestartPolicy, policy => {
124 | return policy.Name === 'on-failure' ? policy.Name + ':' + policy.MaximumRetryCount : policy.Name
125 | })
126 | }
127 | if (isCompatible('--add-host', modes)) rc = appendArray(rc, '--add-host', hostcfg.ExtraHosts)
128 | rc = appendArray(rc, '--group-add', hostcfg.GroupAdd)
129 | if (hostcfg.PidMode) rc = append(rc, '--pid', hostcfg.PidMode)
130 | rc = appendArray(rc, '--security-opt', hostcfg.SecurityOpt, quote)
131 |
132 | const cfg = inspectObj.Config || {}
133 |
134 | if (cfg.Hostname && isCompatible('-h', modes)) rc = append(rc, '-h', cfg.Hostname)
135 | if (cfg.Domainname && isCompatible('--domainname', modes)) rc = append(rc, '--domainname', cfg.Domainname)
136 |
137 | if (cfg.ExposedPorts && isCompatible('--expose', modes)) {
138 | rc = appendObjectKeys(rc, '--expose', cfg.ExposedPorts)
139 | }
140 | if (cfg.Labels) {
141 | // rc = appendObjectEntries(rc, '-l', cfg.Labels, '=')
142 | rc = appendObjectKeys(rc, '-l', cfg.Labels)
143 | }
144 | rc = appendArray(rc, '-e', cfg.Env, quote)
145 | rc = appendConfigBooleans(rc, cfg)
146 | if (cfg.Entrypoint) rc = appendJoinedArray(rc, '--entrypoint', cfg.Entrypoint, ' ')
147 |
148 | rc = rc + ' ' + (cfg.Image || inspectObj.Image)
149 |
150 | if (cfg.Cmd) rc = appendJoinedArray(rc, null, cfg.Cmd, ' ')
151 |
152 | return rc
153 | }
154 |
155 | // The following options are invalid in 'container' NetworkMode:
156 | // --add-host
157 | // -h, --hostname
158 | // --dns
159 | // --dns-search
160 | // --dns-option
161 | // --mac-address
162 | // -p, --publish
163 | // -P, --publish-all
164 | // --expose
165 | // The following options are invalid in 'host' UTSMode:
166 | // -h, --hostname
167 | // --domainname
168 | function isCompatible (flag, modes) {
169 | switch (flag) {
170 | case '-h':
171 | return !(modes.networkMode || '').startsWith('container:') && modes.utsMode !== 'host'
172 | case '--add-host':
173 | case '--dns':
174 | case '--dns-search':
175 | case '--dns-option':
176 | case '--mac-address':
177 | case '-p':
178 | case '-P':
179 | case '--expose':
180 | return !(modes.networkMode || '').startsWith('container:')
181 | case '--domainname':
182 | return modes.utsMode !== 'host'
183 | default:
184 | return true
185 | }
186 | }
187 |
188 | function quote (str) {
189 | return '\'' + str.replace(/'/g, '\'\\\'\'') + '\''
190 | }
191 |
192 | function appendConfigBooleans (str, cfg) {
193 | const stdin = cfg.AttachStdin === true
194 | const stdout = cfg.AttachStdout === true
195 | const stderr = cfg.AttachStderr === true
196 | str = appendBoolean(str, !stdin && !stdout && !stderr, '-d')
197 | str = appendBoolean(str, stdin, '-a', 'stdin')
198 | str = appendBoolean(str, stdout, '-a', 'stdout')
199 | str = appendBoolean(str, stderr, '-a', 'stderr')
200 | str = appendBoolean(str, cfg.Tty === true, '-t')
201 | str = appendBoolean(str, cfg.OpenStdin === true, '-i')
202 | return str
203 | }
204 |
205 | function appendBoolean (str, bool, key, val) {
206 | return bool ? (val ? append(str, key, val) : str + ' ' + key) : str
207 | }
208 |
209 | function appendJoinedArray (str, key, array, join) {
210 | if (!Array.isArray(array)) return str
211 |
212 | // --entrypoint "tini -- /docker-entrypoint.sh"
213 | if (key) return append(str, key, array.join(join), joined => '"' + joined + '"')
214 |
215 | // 'sh' '-c' '(a -a) && (b -b)'
216 | return append(str, key, array.map(quote).join(join))
217 | }
218 |
219 | function appendObjectKeys (str, key, obj, transformer) {
220 | let newStr = str
221 | Object.keys(obj).forEach(k => {
222 | newStr = append(newStr, key, { key: k, val: obj[k] }, agg => {
223 | if (!agg.val) return agg.key
224 | let v = ''
225 | if (Array.isArray(agg.val)) {
226 | // used for PortBindings
227 | agg.val.forEach(valObj => {
228 | v = (typeof transformer === 'function' ? transformer(valObj) : valObj)
229 | })
230 | } else if (typeof agg.val === 'string') {
231 | // used for Labels
232 | return agg.key + '=' + quote(agg.val)
233 | }
234 | // prefix used for PortBindings, key-only used for ExposedPorts
235 | return (v ? v + ':' : '') + agg.key
236 | })
237 | })
238 | return newStr
239 | }
240 |
241 | // function appendObjectEntries (str, key, obj, joiner) {
242 | // let newStr = str
243 | // Object.entries(obj).forEach(([k, v]) => {
244 | // newStr = append(newStr, key, { key: k, val: v }, typeof joiner === 'function'
245 | // ? joiner
246 | // : agg => `${agg.key}${joiner}${quote(agg.val)}`
247 | // )
248 | // })
249 | // return newStr
250 | // }
251 |
252 | function appendArray (str, key, array, transformer) {
253 | if (!Array.isArray(array)) return str
254 | let newStr = str
255 | array.forEach(v => {
256 | newStr = append(newStr, key, v, transformer)
257 | })
258 | return newStr
259 | }
260 |
261 | function append (str, key, val, transformer) {
262 | if (!val) return str
263 | return str + ' ' + (key ? key + ' ' : '') + (typeof transformer === 'function' ? transformer(val) : val)
264 | }
265 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nexdrew/rekcod/37d4715b3a06f8c495750a03c23bc17c274db243/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rekcod",
3 | "version": "3.0.0",
4 | "description": "docker inspect → docker run",
5 | "main": "index.js",
6 | "bin": {
7 | "rekcod": "cli.js"
8 | },
9 | "files": [
10 | "cli.js",
11 | "index.js"
12 | ],
13 | "engines": {
14 | "node": ">=10"
15 | },
16 | "scripts": {
17 | "pretest": "standard",
18 | "test": "nyc --use-spawn-wrap=true tape test/test*.js",
19 | "coverage": "nyc report --reporter=text-lcov | coveralls",
20 | "release": "standard-version"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/nexdrew/rekcod.git"
25 | },
26 | "keywords": [
27 | "docker",
28 | "inspect",
29 | "run"
30 | ],
31 | "author": "nexdrew",
32 | "license": "ISC",
33 | "bugs": {
34 | "url": "https://github.com/nexdrew/rekcod/issues"
35 | },
36 | "homepage": "https://github.com/nexdrew/rekcod#readme",
37 | "devDependencies": {
38 | "coveralls": "^3.0.7",
39 | "nyc": "^15.0.1",
40 | "standard": "^14.3.1",
41 | "standard-version": "^8.0.0",
42 | "tape": "^5.0.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test/docker:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var containers = process.argv.slice(3)
3 | if (~containers.indexOf('error')) {
4 | console.log('Preparing to error out')
5 | console.error('An error has occurred')
6 | process.exit(127)
7 | }
8 | var dockerCommand = process.argv[2]
9 | var path = require('path')
10 | var file = path.join(__dirname, 'fixtures', dockerCommand + '-' + containers.join('-') + '.json')
11 | var fixture = require('fs').readFileSync(file, {
12 | encoding: 'utf8'
13 | })
14 | console.log(fixture)
15 |
--------------------------------------------------------------------------------
/test/fixtures/inspect-empty.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/test/fixtures/inspect-invalid.json:
--------------------------------------------------------------------------------
1 | docker: "inspect" requires a minimum of 1 argument.
2 | See 'docker inspect --help'.
3 |
4 | Usage: docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
5 |
6 | Return low-level information on a container, image or task
7 |
--------------------------------------------------------------------------------
/test/fixtures/inspect-one-two.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Id": "9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79",
4 | "Created": "2016-10-01T21:39:34.257638774Z",
5 | "Path": "/etc/npme/start.sh",
6 | "Args": [
7 | "-g"
8 | ],
9 | "State": {
10 | "Status": "running",
11 | "Running": true,
12 | "Paused": false,
13 | "Restarting": false,
14 | "OOMKilled": false,
15 | "Dead": false,
16 | "Pid": 4608,
17 | "ExitCode": 0,
18 | "Error": "",
19 | "StartedAt": "2016-10-01T21:39:35.339305242Z",
20 | "FinishedAt": "0001-01-01T00:00:00Z"
21 | },
22 | "Image": "sha256:a6c71b3e8fb33c8a3e1501f497915af8057d5da84fc4560b720d350eff38318d",
23 | "ResolvConfPath": "/var/lib/docker/containers/9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79/resolv.conf",
24 | "HostnamePath": "/var/lib/docker/containers/9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79/hostname",
25 | "HostsPath": "/var/lib/docker/containers/9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79/hosts",
26 | "LogPath": "/var/lib/docker/containers/9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79/9c397236341e1e45702d3168e5d8ae1f9bb89c2faf83f44de350f698a5469c79-json.log",
27 | "Name": "/project_service_1",
28 | "RestartCount": 0,
29 | "Driver": "aufs",
30 | "MountLabel": "",
31 | "ProcessLabel": "",
32 | "AppArmorProfile": "",
33 | "ExecIDs": null,
34 | "HostConfig": {
35 | "Binds": [
36 | "/var/lib/replicated:/var/lib/replicated",
37 | "/proc:/host/proc:ro"
38 | ],
39 | "ContainerIDFile": "",
40 | "LogConfig": {
41 | "Type": "json-file",
42 | "Config": {}
43 | },
44 | "NetworkMode": "host",
45 | "PortBindings": {
46 | "4700/tcp": [
47 | {
48 | "HostIp": "",
49 | "HostPort": "4700"
50 | }
51 | ],
52 | "4702/tcp": [
53 | {
54 | "HostIp": "",
55 | "HostPort": "4702"
56 | }
57 | ]
58 | },
59 | "RestartPolicy": {
60 | "Name": "on-failure",
61 | "MaximumRetryCount": 5
62 | },
63 | "AutoRemove": false,
64 | "VolumeDriver": "",
65 | "VolumesFrom": [],
66 | "CapAdd": null,
67 | "CapDrop": null,
68 | "Dns": null,
69 | "DnsOptions": null,
70 | "DnsSearch": null,
71 | "ExtraHosts": [
72 | "xyz:1.2.3.4",
73 | "abc:5.6.7.8"
74 | ],
75 | "GroupAdd": null,
76 | "IpcMode": "",
77 | "Cgroup": "",
78 | "Links": [
79 | "/project_postgres_1:/project_service_1/postgres",
80 | "/project_rrservice_1:/project_service_1/project_rrservice_1"
81 | ],
82 | "OomScoreAdj": 0,
83 | "PidMode": "container:9ca8ac5c5b829c5c0a65a290b7c4eb74e9ba36f69344ee11392841fd41d5e3de",
84 | "Privileged": false,
85 | "PublishAllPorts": true,
86 | "ReadonlyRootfs": false,
87 | "SecurityOpt": [
88 | "label=level:s0:c100,c200",
89 | "label=user:USER",
90 | "label=role:ROLE",
91 | "label=type:TYPE",
92 | "label=level:LEVEL",
93 | "label=disable",
94 | "apparmor=docker-default",
95 | "no-new-privileges:true",
96 | "seccomp=unconfined",
97 | "label=type:svirt_apache_t"
98 | ],
99 | "UTSMode": "host",
100 | "UsernsMode": "",
101 | "ShmSize": 67108864,
102 | "Runtime": "runc",
103 | "ConsoleSize": [
104 | 0,
105 | 0
106 | ],
107 | "Isolation": "",
108 | "CpuShares": 0,
109 | "Memory": 0,
110 | "CgroupParent": "",
111 | "BlkioWeight": 0,
112 | "BlkioWeightDevice": null,
113 | "BlkioDeviceReadBps": null,
114 | "BlkioDeviceWriteBps": null,
115 | "BlkioDeviceReadIOps": null,
116 | "BlkioDeviceWriteIOps": null,
117 | "CpuPeriod": 0,
118 | "CpuQuota": 0,
119 | "CpusetCpus": "",
120 | "CpusetMems": "",
121 | "Devices": null,
122 | "DiskQuota": 0,
123 | "KernelMemory": 0,
124 | "MemoryReservation": 0,
125 | "MemorySwap": 0,
126 | "MemorySwappiness": -1,
127 | "OomKillDisable": false,
128 | "PidsLimit": 0,
129 | "Ulimits": null,
130 | "CpuCount": 0,
131 | "CpuPercent": 0,
132 | "IOMaximumIOps": 0,
133 | "IOMaximumBandwidth": 0
134 | },
135 | "GraphDriver": {
136 | "Name": "aufs",
137 | "Data": null
138 | },
139 | "Mounts": [],
140 | "Config": {
141 | "Hostname": "9c397236341e",
142 | "Domainname": "",
143 | "User": "",
144 | "AttachStdin": false,
145 | "AttachStdout": false,
146 | "AttachStderr": false,
147 | "ExposedPorts": {
148 | "4700/tcp": {},
149 | "4702/tcp": {}
150 | },
151 | "Tty": false,
152 | "OpenStdin": false,
153 | "StdinOnce": false,
154 | "Env": [
155 | "DB_USER=postgres",
156 | "no_proxy=*.local, 169.254/16",
157 | "special_char_env_var1=abc(!)123",
158 | "special_char_env_var2=abc(')123",
159 | "special_char_env_var3=abc()123"
160 | ],
161 | "Cmd": [
162 | "/etc/npme/start.sh",
163 | "-g"
164 | ],
165 | "Image": "project_service",
166 | "Volumes": null,
167 | "WorkingDir": "/etc/npme",
168 | "Entrypoint": [
169 | "tini",
170 | "--",
171 | "/docker-entrypoint.sh"
172 | ],
173 | "OnBuild": null,
174 | "Labels": {
175 | "com.docker.compose.config-hash": "9f94e0df059d6b68fa0e306b9ee555b4fb9d6dbdb3982a0b0f6c7adca2945f26",
176 | "com.docker.compose.container-number": "1",
177 | "com.docker.compose.oneoff": "False",
178 | "com.docker.compose.project": "project",
179 | "com.docker.compose.service": "service",
180 | "com.docker.compose.version": "1.8.0"
181 | }
182 | },
183 | "NetworkSettings": {
184 | "Bridge": "",
185 | "SandboxID": "8dc1bc73503c8e1e36df101bdd365a7a6ba3284159d2d9fbe83caa8cdeeacff3",
186 | "HairpinMode": false,
187 | "LinkLocalIPv6Address": "",
188 | "LinkLocalIPv6PrefixLen": 0,
189 | "Ports": {
190 | "4700/tcp": [
191 | {
192 | "HostIp": "0.0.0.0",
193 | "HostPort": "4700"
194 | }
195 | ],
196 | "4702/tcp": [
197 | {
198 | "HostIp": "0.0.0.0",
199 | "HostPort": "4702"
200 | }
201 | ]
202 | },
203 | "SandboxKey": "/var/run/docker/netns/8dc1bc73503c",
204 | "SecondaryIPAddresses": null,
205 | "SecondaryIPv6Addresses": null,
206 | "EndpointID": "571dccdf8fa4ad85884a5b9f5cfba56af385ca13e06c72374d0c62475e922e6b",
207 | "Gateway": "172.17.0.1",
208 | "GlobalIPv6Address": "",
209 | "GlobalIPv6PrefixLen": 0,
210 | "IPAddress": "172.17.0.12",
211 | "IPPrefixLen": 16,
212 | "IPv6Gateway": "",
213 | "MacAddress": "02:42:ac:11:00:0c",
214 | "Networks": {
215 | "bridge": {
216 | "IPAMConfig": null,
217 | "Links": null,
218 | "Aliases": null,
219 | "NetworkID": "72eb90354bbd39f08e67088ad1fd88245b67eb95ca7dbf57b2717c75d1449dbf",
220 | "EndpointID": "571dccdf8fa4ad85884a5b9f5cfba56af385ca13e06c72374d0c62475e922e6b",
221 | "Gateway": "172.17.0.1",
222 | "IPAddress": "172.17.0.12",
223 | "IPPrefixLen": 16,
224 | "IPv6Gateway": "",
225 | "GlobalIPv6Address": "",
226 | "GlobalIPv6PrefixLen": 0,
227 | "MacAddress": "02:42:ac:11:00:0c"
228 | }
229 | }
230 | }
231 | },
232 | {
233 | "Id": "46d567b",
234 | "Created": "2016-10-02T02:34:20.126539319Z",
235 | "Path": "/hello",
236 | "Args": [],
237 | "State": {
238 | "Status": "exited",
239 | "Running": false,
240 | "Paused": false,
241 | "Restarting": false,
242 | "OOMKilled": false,
243 | "Dead": false,
244 | "Pid": 0,
245 | "ExitCode": 0,
246 | "Error": "",
247 | "StartedAt": "2016-10-02T02:34:20.60324311Z",
248 | "FinishedAt": "2016-10-02T02:34:20.660511976Z"
249 | },
250 | "Image": "sha256:c54a2cc56cbb2f04003c1cd4507e118af7c0d340fe7e2720f70976c4b75237dc",
251 | "ResolvConfPath": "/mnt/docker/containers/46d567b2ef86a959c7e4d01a4b4acb2f379f340c0106a92285a0ddf0bbf88811/resolv.conf",
252 | "HostnamePath": "/mnt/docker/containers/46d567b2ef86a959c7e4d01a4b4acb2f379f340c0106a92285a0ddf0bbf88811/hostname",
253 | "HostsPath": "/mnt/docker/containers/46d567b2ef86a959c7e4d01a4b4acb2f379f340c0106a92285a0ddf0bbf88811/hosts",
254 | "LogPath": "/mnt/docker/containers/46d567b2ef86a959c7e4d01a4b4acb2f379f340c0106a92285a0ddf0bbf88811/46d567b2ef86a959c7e4d01a4b4acb2f379f340c0106a92285a0ddf0bbf88811-json.log",
255 | "Name": "/hello",
256 | "RestartCount": 0,
257 | "Driver": "aufs",
258 | "MountLabel": "",
259 | "ProcessLabel": "",
260 | "AppArmorProfile": "",
261 | "ExecIDs": null,
262 | "HostConfig": {
263 | "Binds": null,
264 | "ContainerIDFile": "",
265 | "LogConfig": {
266 | "Type": "json-file",
267 | "Config": {}
268 | },
269 | "NetworkMode": "default",
270 | "PortBindings": {},
271 | "RestartPolicy": {
272 | "Name": "no",
273 | "MaximumRetryCount": 0
274 | },
275 | "AutoRemove": false,
276 | "VolumeDriver": "",
277 | "VolumesFrom": [
278 | "admiring_brown",
279 | "silly_jang"
280 | ],
281 | "CapAdd": null,
282 | "CapDrop": null,
283 | "Dns": [],
284 | "DnsOptions": [],
285 | "DnsSearch": [],
286 | "ExtraHosts": null,
287 | "GroupAdd": [
288 | "audio",
289 | "nogroup",
290 | "777"
291 | ],
292 | "IpcMode": "",
293 | "Cgroup": "",
294 | "Links": null,
295 | "OomScoreAdj": 0,
296 | "PidMode": "",
297 | "Privileged": true,
298 | "PublishAllPorts": false,
299 | "ReadonlyRootfs": false,
300 | "SecurityOpt": null,
301 | "StorageOpt": null,
302 | "UTSMode": "",
303 | "UsernsMode": "",
304 | "ShmSize": 67108864,
305 | "Runtime": "nvidia",
306 | "ConsoleSize": [
307 | 0,
308 | 0
309 | ],
310 | "Isolation": "",
311 | "CpuShares": 0,
312 | "Memory": 0,
313 | "CgroupParent": "",
314 | "BlkioWeight": 0,
315 | "BlkioWeightDevice": null,
316 | "BlkioDeviceReadBps": null,
317 | "BlkioDeviceWriteBps": null,
318 | "BlkioDeviceReadIOps": null,
319 | "BlkioDeviceWriteIOps": null,
320 | "CpuPeriod": 0,
321 | "CpuQuota": 0,
322 | "CpusetCpus": "",
323 | "CpusetMems": "",
324 | "Devices": [],
325 | "DiskQuota": 0,
326 | "KernelMemory": 0,
327 | "MemoryReservation": 0,
328 | "MemorySwap": 0,
329 | "MemorySwappiness": -1,
330 | "OomKillDisable": false,
331 | "PidsLimit": 0,
332 | "Ulimits": null,
333 | "CpuCount": 0,
334 | "CpuPercent": 0,
335 | "BlkioIOps": 0,
336 | "BlkioBps": 0,
337 | "SandboxSize": 0
338 | },
339 | "GraphDriver": {
340 | "Name": "aufs",
341 | "Data": null
342 | },
343 | "Mounts": [
344 | {
345 | "Source": "/mnt/couchdb",
346 | "Destination": "/usr/local/var/lib/couchdb",
347 | "Mode": "",
348 | "RW": true,
349 | "Propagation": "rprivate"
350 | },
351 | {
352 | "Source": "/var/lib/replicated-operator/e3e076ef87c34eca632dc87455961737/usr/local/etc/couchdb/local.ini",
353 | "Destination": "/usr/local/etc/couchdb/local.ini",
354 | "Mode": "",
355 | "RW": true,
356 | "Propagation": "rprivate"
357 | },
358 | {
359 | "Source": "/mnt/redis",
360 | "Destination": "/data",
361 | "Mode": "",
362 | "RW": true,
363 | "Propagation": "rprivate"
364 | }
365 | ],
366 | "Config": {
367 | "Hostname": "46d567b2ef86",
368 | "Domainname": "rekcod.xyz",
369 | "User": "",
370 | "AttachStdin": false,
371 | "AttachStdout": true,
372 | "AttachStderr": true,
373 | "Tty": true,
374 | "OpenStdin": true,
375 | "StdinOnce": false,
376 | "Env": [
377 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
378 | ],
379 | "Cmd": [
380 | "sh",
381 | "-c",
382 | "(a -a) \u0026\u0026 (b -b)"
383 | ],
384 | "Image": "hello-world",
385 | "Volumes": null,
386 | "WorkingDir": "",
387 | "Entrypoint": null,
388 | "OnBuild": null,
389 | "Labels": {}
390 | },
391 | "NetworkSettings": {
392 | "Bridge": "",
393 | "SandboxID": "dd1fbcde35d210b5b504de23f582091ec77b9092b2474c3aa288e277cf5bce59",
394 | "HairpinMode": false,
395 | "LinkLocalIPv6Address": "",
396 | "LinkLocalIPv6PrefixLen": 0,
397 | "Ports": null,
398 | "SandboxKey": "/var/run/docker/netns/dd1fbcde35d2",
399 | "SecondaryIPAddresses": null,
400 | "SecondaryIPv6Addresses": null,
401 | "EndpointID": "",
402 | "Gateway": "",
403 | "GlobalIPv6Address": "",
404 | "GlobalIPv6PrefixLen": 0,
405 | "IPAddress": "",
406 | "IPPrefixLen": 0,
407 | "IPv6Gateway": "",
408 | "MacAddress": "",
409 | "Networks": {
410 | "bridge": {
411 | "IPAMConfig": null,
412 | "Links": null,
413 | "Aliases": null,
414 | "NetworkID": "98129d0f0aa4472bbb92f3d2ee3573a7d6c0fb4151256b626337722af80186c8",
415 | "EndpointID": "",
416 | "Gateway": "",
417 | "IPAddress": "",
418 | "IPPrefixLen": 0,
419 | "IPv6Gateway": "",
420 | "GlobalIPv6Address": "",
421 | "GlobalIPv6PrefixLen": 0,
422 | "MacAddress": ""
423 | }
424 | }
425 | }
426 | }
427 | ]
428 |
--------------------------------------------------------------------------------
/test/test-cli.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const childProcess = require('child_process')
4 | const execFileSync = childProcess.execFileSync
5 | const path = require('path')
6 | const test = require('tape')
7 |
8 | const cliPath = path.resolve(__dirname, '..', 'cli.js')
9 | const oneTwoFixture = path.resolve(__dirname, 'fixtures', 'inspect-one-two.json')
10 |
11 | function mockedDockerEnv (envPath) {
12 | const env = JSON.parse(JSON.stringify(process.env))
13 | env.PATH = envPath || [__dirname].concat(env.PATH.split(path.delimiter)).join(path.delimiter)
14 | return env
15 | }
16 |
17 | function cli (args, envPath) {
18 | return execFileSync(cliPath, args.split(/\s/), {
19 | env: mockedDockerEnv(envPath),
20 | encoding: 'utf8'
21 | })
22 | }
23 |
24 | const expectedOneTwo = '\n' +
25 | 'docker run ' +
26 | '--name project_service_1 ' +
27 | '--runtime runc ' +
28 | '-v /var/lib/replicated:/var/lib/replicated -v /proc:/host/proc:ro ' +
29 | '-p 4700:4700/tcp -p 4702:4702/tcp ' +
30 | '--link project_postgres_1:postgres --link project_rrservice_1:project_rrservice_1 ' +
31 | '-P ' +
32 | '--net host ' +
33 | '--uts host ' +
34 | '--restart on-failure:5 ' +
35 | '--add-host xyz:1.2.3.4 --add-host abc:5.6.7.8 ' +
36 | '--pid container:9ca8ac5c5b829c5c0a65a290b7c4eb74e9ba36f69344ee11392841fd41d5e3de ' +
37 | '--security-opt \'label=level:s0:c100,c200\' ' +
38 | '--security-opt \'label=user:USER\' ' +
39 | '--security-opt \'label=role:ROLE\' ' +
40 | '--security-opt \'label=type:TYPE\' ' +
41 | '--security-opt \'label=level:LEVEL\' ' +
42 | '--security-opt \'label=disable\' ' +
43 | '--security-opt \'apparmor=docker-default\' ' +
44 | '--security-opt \'no-new-privileges:true\' ' +
45 | '--security-opt \'seccomp=unconfined\' ' +
46 | '--security-opt \'label=type:svirt_apache_t\' ' +
47 | '--expose 4700/tcp --expose 4702/tcp ' +
48 | '-l com.docker.compose.config-hash=\'9f94e0df059d6b68fa0e306b9ee555b4fb9d6dbdb3982a0b0f6c7adca2945f26\' ' +
49 | '-l com.docker.compose.container-number=\'1\' ' +
50 | '-l com.docker.compose.oneoff=\'False\' ' +
51 | '-l com.docker.compose.project=\'project\' ' +
52 | '-l com.docker.compose.service=\'service\' ' +
53 | '-l com.docker.compose.version=\'1.8.0\' ' +
54 | '-e \'DB_USER=postgres\' ' +
55 | '-e \'no_proxy=*.local, 169.254/16\' ' +
56 | '-e \'special_char_env_var1=abc(!)123\' ' +
57 | '-e \'special_char_env_var2=abc(\'\\\'\')123\' ' +
58 | '-e \'special_char_env_var3=abc()123\' ' +
59 | '-d ' +
60 | '--entrypoint "tini -- /docker-entrypoint.sh" ' +
61 | 'project_service \'/etc/npme/start.sh\' \'-g\'' +
62 | '\n\n' +
63 | 'docker run ' +
64 | '--name hello ' +
65 | '--privileged ' +
66 | '--runtime nvidia ' +
67 | '--volumes-from admiring_brown --volumes-from silly_jang ' +
68 | '--restart no ' +
69 | '--group-add audio --group-add nogroup --group-add 777 ' +
70 | '-h 46d567b2ef86 ' +
71 | '--domainname rekcod.xyz ' +
72 | '-e \'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\' ' +
73 | '-a stdout -a stderr ' +
74 | '-t -i ' +
75 | 'hello-world \'sh\' \'-c\' \'(a -a) && (b -b)\'' +
76 | '\n\n'
77 |
78 | test('cli works for docker inspect happy path', t => {
79 | const dockerRunCommands = cli('one two')
80 | t.strictEqual(dockerRunCommands, expectedOneTwo)
81 | t.end()
82 | })
83 |
84 | test('cli accepts file name arg', t => {
85 | const dockerRunCommands = cli(oneTwoFixture)
86 | t.strictEqual(dockerRunCommands, expectedOneTwo)
87 | t.end()
88 | })
89 |
90 | test('pipe json to cli', t => {
91 | childProcess.exec(`cat ${oneTwoFixture} | ${cliPath}`, (err, stdout, stderr) => {
92 | t.notOk(err)
93 | t.strictEqual(stdout, expectedOneTwo)
94 | t.end()
95 | })
96 | })
97 |
98 | test('pipe file name to cli', t => {
99 | childProcess.exec(`ls ${oneTwoFixture} | ${cliPath}`, (err, stdout, stderr) => {
100 | t.notOk(err)
101 | t.strictEqual(stdout, expectedOneTwo)
102 | t.end()
103 | })
104 | })
105 |
106 | test('pipe container ids to cli', t => {
107 | childProcess.exec(`echo 'one two' | ${cliPath}`, {
108 | env: mockedDockerEnv(),
109 | encoding: 'utf8'
110 | }, (err, stdout, stderr) => {
111 | t.notOk(err)
112 | t.strictEqual(stdout, expectedOneTwo)
113 | t.end()
114 | })
115 | })
116 |
117 | test('cli handles docker inspect invalid json', t => {
118 | let err
119 | try {
120 | cli('invalid')
121 | } catch (e) {
122 | err = e
123 | }
124 | t.ok(err)
125 | t.ok(/Unexpected token d/.test(err.stderr))
126 | t.strictEqual(err.status, 1)
127 | t.end()
128 | })
129 |
130 | test('cli handles invalid json file', t => {
131 | let err
132 | try {
133 | cli(path.resolve(__dirname, 'fixtures', 'inspect-invalid.json'))
134 | } catch (e) {
135 | err = e
136 | }
137 | t.ok(err)
138 | t.ok(/Unexpected token d/.test(err.stderr))
139 | t.strictEqual(err.status, 1)
140 | t.end()
141 | })
142 |
143 | test('cli handles docker inspect empty array', t => {
144 | const output = cli('empty')
145 | t.strictEqual(output, '\nNothing to translate\n\n')
146 | t.end()
147 | })
148 |
149 | test('cli handles docker inspect error', t => {
150 | let err
151 | try {
152 | cli('error')
153 | } catch (e) {
154 | err = e
155 | }
156 | t.ok(err)
157 | t.strictEqual(err.stdout, 'Preparing to error out\n\n')
158 | t.strictEqual(err.stderr, 'An error has occurred\n\n')
159 | t.strictEqual(err.status, 127)
160 | t.end()
161 | })
162 |
163 | test('cli handles no docker', t => {
164 | let err
165 | try {
166 | cli('dne', '/dne')
167 | } catch (e) {
168 | err = e
169 | }
170 | t.ok(err)
171 | t.ok(/spawn docker ENOENT/.test(err.stderr))
172 | t.strictEqual(err.status, 1)
173 | t.end()
174 | })
175 |
--------------------------------------------------------------------------------