├── .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 | # ![rekcod](https://raw.githubusercontent.com/nexdrew/rekcod/master/logo.png) 2 | 3 | > docker inspect → docker run 4 | 5 | [![Build Status](https://travis-ci.com/nexdrew/rekcod.svg?branch=master)](https://travis-ci.com/nexdrew/rekcod) 6 | [![Coverage Status](https://coveralls.io/repos/github/nexdrew/rekcod/badge.svg?branch=master)](https://coveralls.io/github/nexdrew/rekcod?branch=master) 7 | [![JavaScript Style Guide](https://badgen.net/badge/code%20style/standard/green)](https://standardjs.com) 8 | [![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version) 9 | ![Dependabot Badge](https://badgen.net/dependabot/nexdrew/rekcod?icon=dependabot&label=dependabot) 10 | [![Docker Pulls](https://badgen.net/docker/pulls/nexdrew/rekcod?icon=docker)](https://hub.docker.com/r/nexdrew/rekcod) 11 | [![Docker Image Size](https://badgen.net/docker/size/nexdrew/rekcod?icon=docker)](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 | --------------------------------------------------------------------------------