You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by al...@apache.org on 2020/04/21 03:11:44 UTC

[openwhisk-wskdebug] 01/01: use docker api client dockerode instead of `docker` child process

This is an automated email from the ASF dual-hosted git repository.

alexkli pushed a commit to branch dockerode
in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git

commit 657f4c364f25c202fcbee425441f7217f65efc81
Author: Alexander Klimetschek <ak...@adobe.com>
AuthorDate: Mon Apr 20 20:09:05 2020 -0700

    use docker api client dockerode instead of `docker` child process
    
    - improves performance slightly as no child process execution is required
    - easier to use API than building cli arguments & parsing stdout
---
 package-lock.json | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 package.json      |   1 +
 src/debugger.js   |  10 ++-
 src/invoker.js    | 127 ++++++++++++++++++++++++--------
 src/log.js        |   6 ++
 5 files changed, 319 insertions(+), 37 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index ae04f84..9912708 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -462,6 +462,14 @@
             "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=",
             "dev": true
         },
+        "asn1": {
+            "version": "0.2.4",
+            "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+            "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+            "requires": {
+                "safer-buffer": "~2.1.0"
+            }
+        },
         "astral-regex": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
@@ -479,11 +487,34 @@
             "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
             "dev": true
         },
+        "base64-js": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+            "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
+        },
+        "bcrypt-pbkdf": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+            "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+            "requires": {
+                "tweetnacl": "^0.14.3"
+            }
+        },
         "binary-extensions": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
             "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow=="
         },
+        "bl": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
+            "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
+            "requires": {
+                "buffer": "^5.5.0",
+                "inherits": "^2.0.4",
+                "readable-stream": "^3.4.0"
+            }
+        },
         "brace-expansion": {
             "version": "1.1.11",
             "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -508,6 +539,20 @@
             "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
             "dev": true
         },
+        "buffer": {
+            "version": "5.6.0",
+            "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
+            "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+            "requires": {
+                "base64-js": "^1.0.2",
+                "ieee754": "^1.1.4"
+            }
+        },
+        "buffer-from": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+            "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+        },
         "caching-transform": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
@@ -567,6 +612,11 @@
                 "readdirp": "~3.3.0"
             }
         },
+        "chownr": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+            "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+        },
         "clean-stack": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -645,6 +695,17 @@
             "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
             "dev": true
         },
+        "concat-stream": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+            "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+            "requires": {
+                "buffer-from": "^1.0.0",
+                "inherits": "^2.0.3",
+                "readable-stream": "^3.0.2",
+                "typedarray": "^0.0.6"
+            }
+        },
         "convert-source-map": {
             "version": "1.7.0",
             "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@@ -733,6 +794,27 @@
             "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
             "dev": true
         },
+        "docker-modem": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.2.tgz",
+            "integrity": "sha512-fwlfnsK9WV+m+qc/NZCiGt+oYAMjmCUeir0a/l3oHb0yc8FhRAucdwT4htKD3aLtV+1w2syQePH9pQFxsq1GFA==",
+            "requires": {
+                "debug": "^4.1.1",
+                "readable-stream": "^3.5.0",
+                "split-ca": "^1.0.1",
+                "ssh2": "^0.8.7"
+            }
+        },
+        "dockerode": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.2.0.tgz",
+            "integrity": "sha512-C+y/W4Kks7YLBsfUOTMkk1IVilb4cdj+rE+UZ5hnE+rpcn2frSs7kX+6H8GteTqHcv8sln+GyxuP1qdno3IzIw==",
+            "requires": {
+                "concat-stream": "~2.0.0",
+                "docker-modem": "^2.1.0",
+                "tar-fs": "~2.0.1"
+            }
+        },
         "doctrine": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -755,6 +837,14 @@
                 "iconv-lite": "~0.4.13"
             }
         },
+        "end-of-stream": {
+            "version": "1.4.4",
+            "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+            "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+            "requires": {
+                "once": "^1.4.0"
+            }
+        },
         "es-abstract": {
             "version": "1.17.5",
             "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
@@ -1202,6 +1292,11 @@
             "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==",
             "dev": true
         },
+        "fs-constants": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+            "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
+        },
         "fs-extra": {
             "version": "8.1.0",
             "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@@ -1402,6 +1497,11 @@
                 "safer-buffer": ">= 2.1.2 < 3"
             }
         },
+        "ieee754": {
+            "version": "1.1.13",
+            "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+            "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+        },
         "ignore": {
             "version": "4.0.6",
             "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -1452,8 +1552,7 @@
         "inherits": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-            "dev": true
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
         },
         "inquirer": {
             "version": "7.1.0",
@@ -1971,6 +2070,11 @@
                 "minimist": "^1.2.5"
             }
         },
+        "mkdirp-classic": {
+            "version": "0.5.2",
+            "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
+            "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g=="
+        },
         "mocha": {
             "version": "7.1.1",
             "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz",
@@ -2472,7 +2576,6 @@
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
             "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-            "dev": true,
             "requires": {
                 "wrappy": "1"
             }
@@ -2690,6 +2793,15 @@
             "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
             "dev": true
         },
+        "pump": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+            "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+            "requires": {
+                "end-of-stream": "^1.1.0",
+                "once": "^1.3.1"
+            }
+        },
         "punycode": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -2702,6 +2814,16 @@
             "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==",
             "dev": true
         },
+        "readable-stream": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+            "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+            "requires": {
+                "inherits": "^2.0.3",
+                "string_decoder": "^1.1.1",
+                "util-deprecate": "^1.0.1"
+            }
+        },
         "readdirp": {
             "version": "3.3.0",
             "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
@@ -2922,12 +3044,35 @@
                 }
             }
         },
+        "split-ca": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz",
+            "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY="
+        },
         "sprintf-js": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
             "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
             "dev": true
         },
+        "ssh2": {
+            "version": "0.8.9",
+            "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz",
+            "integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==",
+            "requires": {
+                "ssh2-streams": "~0.4.10"
+            }
+        },
+        "ssh2-streams": {
+            "version": "0.4.10",
+            "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz",
+            "integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==",
+            "requires": {
+                "asn1": "~0.2.0",
+                "bcrypt-pbkdf": "^1.0.2",
+                "streamsearch": "~0.1.2"
+            }
+        },
         "stream-events": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
@@ -2937,6 +3082,11 @@
                 "stubs": "^3.0.0"
             }
         },
+        "streamsearch": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
+            "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
+        },
         "string-width": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
@@ -2989,6 +3139,21 @@
                 "es-abstract": "^1.17.5"
             }
         },
+        "string_decoder": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+            "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+            "requires": {
+                "safe-buffer": "~5.2.0"
+            },
+            "dependencies": {
+                "safe-buffer": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+                    "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+                }
+            }
+        },
         "strip-ansi": {
             "version": "6.0.0",
             "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
@@ -3082,6 +3247,29 @@
                 }
             }
         },
+        "tar-fs": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
+            "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
+            "requires": {
+                "chownr": "^1.1.1",
+                "mkdirp-classic": "^0.5.2",
+                "pump": "^3.0.0",
+                "tar-stream": "^2.0.0"
+            }
+        },
+        "tar-stream": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
+            "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
+            "requires": {
+                "bl": "^4.0.1",
+                "end-of-stream": "^1.4.1",
+                "fs-constants": "^1.0.0",
+                "inherits": "^2.0.3",
+                "readable-stream": "^3.1.1"
+            }
+        },
         "teeny-request": {
             "version": "6.0.1",
             "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz",
@@ -3155,6 +3343,11 @@
             "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
             "dev": true
         },
+        "tweetnacl": {
+            "version": "0.14.5",
+            "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+            "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        },
         "type-check": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -3170,6 +3363,11 @@
             "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
             "dev": true
         },
+        "typedarray": {
+            "version": "0.0.6",
+            "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+            "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+        },
         "typedarray-to-buffer": {
             "version": "3.1.5",
             "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
@@ -3199,6 +3397,11 @@
             "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=",
             "dev": true
         },
+        "util-deprecate": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+            "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+        },
         "uuid": {
             "version": "3.4.0",
             "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
@@ -3299,8 +3502,7 @@
         "wrappy": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-            "dev": true
+            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
         },
         "write": {
             "version": "1.0.3",
diff --git a/package.json b/package.json
index 3c90c97..f98e612 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
         "chalk": "^4.0.0",
         "clone": "^2.1.2",
         "debug": "^4.1.1",
+        "dockerode": "^3.2.0",
         "fetch-retry": "^3.1.0",
         "fs-extra": "^8.1.0",
         "isomorphic-fetch": "^2.2.1",
diff --git a/src/debugger.js b/src/debugger.js
index e7d250b..58462ef 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -287,11 +287,19 @@ class Debugger {
         if (this.agentMgr) {
             await this.tryCatch(this.agentMgr.shutdown());
         }
+
+        // ------------< critical removal must happen above this line >---------------
+
+        // in VS Code, we will not run beyond this line upon debug stop.
+        // this is because invoker.stop() will kill the container & thus close the
+        // debug port, upon which VS Code kills the debug process (us)
         if (this.invoker) {
             await this.tryCatch(this.invoker.stop());
-            log.debug(`stopped container: ${this.invoker.name()}`);
         }
+
         if (this.watcher) {
+            // this is not critical on a process exit, only if Debugger is used programmatically
+            // and might be reused for a new run()
             await this.tryCatch(this.watcher.stop());
             log.debug("stopped source file watching");
         }
diff --git a/src/invoker.js b/src/invoker.js
index 32b9d65..7d85134 100644
--- a/src/invoker.js
+++ b/src/invoker.js
@@ -22,6 +22,7 @@ const fetch = require('fetch-retry')(require('isomorphic-fetch'));
 const kinds = require('./kinds/kinds');
 const path = require('path');
 const log = require("./log");
+const Docker = require('dockerode');
 
 const RUNTIME_PORT = 8080;
 const INIT_RETRY_DELAY_MS = 100;
@@ -32,11 +33,13 @@ const OPENWHISK_DEFAULTS = {
     memory: 256
 };
 
-function execute(cmd, options, debug2) {
+function execute(cmd, options) {
     cmd = cmd.replace(/\s+/g, ' ');
+
+    log.verboseStep(`${cmd}`)
+
     const result = execSync(cmd, options);
 
-    (debug2 || log.debug)(`executed: ${cmd}`);
     if (result) {
         return result.toString().trim();
     } else {
@@ -54,6 +57,39 @@ function resolveValue(value, ...args) {
     }
 }
 
+function asContainerName(name) {
+    // docker container names are restricted to [a-zA-Z0-9][a-zA-Z0-9_.-]*
+
+    // 1. replace special characters with dash
+    name = name.replace(/[^a-zA-Z0-9_.-]+/g, '-');
+    // 2. leading character is more limited
+    name = name.replace(/^[^a-zA-Z0-9]+/g, '');
+    // 3. (nice to have) remove trailing special chars
+    name = name.replace(/[^a-zA-Z0-9]+$/g, '');
+
+    return name;
+}
+
+function addressForContainerPort(containerInfo, port) {
+    if (containerInfo && containerInfo.NetworkSettings && containerInfo.NetworkSettings.Ports) {
+        const ports = containerInfo.NetworkSettings.Ports;
+        // example:
+        // Ports {
+        //   '8080/tcp': [ { HostIp: '0.0.0.0', HostPort: '32812' } ],
+        //   '9229/tcp': [ { HostIp: '0.0.0.0', HostPort: '9229' } ]
+        // }
+        const portEntry = ports[`${port}/tcp`];
+        if (portEntry && Array.isArray(portEntry) && portEntry.length >= 1) {
+            const address = portEntry[0];
+            return `${address.HostIp}:${address.HostPort}`;
+        } else {
+            return null;
+        }
+    } else {
+        return null;
+    }
+}
+
 class OpenWhiskInvoker {
     constructor(actionName, action, options, wskProps, wsk) {
         this.actionName = actionName;
@@ -79,12 +115,14 @@ class OpenWhiskInvoker {
         this.wskProps = wskProps;
         this.wsk = wsk;
 
-        this.containerName = this.asContainerName(`wskdebug-${this.action.name}-${Date.now()}`);
+        this.containerName = asContainerName(`wskdebug-${this.action.name}-${Date.now()}`);
+        this.docker = new Docker();
     }
 
     async checkIfDockerAvailable() {
         try {
-            execute("docker info", {stdio: 'ignore'});
+            await this.docker.info();
+            log.debug("docker - availability check")
         } catch (e) {
             throw new Error("Docker not running on local system. A local docker environment is required for the debugger.")
         }
@@ -190,8 +228,10 @@ class OpenWhiskInvoker {
         await this.checkIfDockerAvailable();
 
         try {
-            execute(`docker inspect --type=image ${this.image} 2> /dev/null`);
+            await this.docker.getImage(this.image).inspect();
+            debug2(`docker - image inspected, is present: ${this.image}`)
         } catch (e) {
+            debug2(`docker - image inspected, not found: ${this.image}`)
             // make sure the user can see the image download process as part of docker run
             showDockerRunOutput = true;
             log.warn(`
@@ -208,10 +248,42 @@ class OpenWhiskInvoker {
 `);
         }
 
+        // console.log(this.debug.command);
+        // console.log(this.debug.command.split(" "));
+
+        // TODO: switch docker run to dockerode.run()
+        //       - find the minimal HostConfig that works for the below run options
+        //         https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate
+        //         https://github.com/apocas/dockerode/issues/257
+        //         https://github.com/apocas/dockerode/blob/master/lib/docker.js#L1442
+        //         https://medium.com/@johnnyeric/how-to-reproduce-command-docker-run-via-docker-remote-api-with-node-js-5918d7b221ea
+        //       - kinds/nodejs.js has to switch from docker args to HostConfig map for -e and -v
+        //       - --docker-args (this.dockerArgsFromUser) must be parsed and turned into HostConfig
+        //       - replaces docker logs call as well, using streams to pass and write into sdtout/err
+        //         - allows to intercept logging using our log.log() & log.error() calls (?)
+        //         - also must use global.mochaLogFile
+        //        - no stdin needed
+        //        - returns dockerode container object (store as this.container)
+        //        - call this.container.kill() on it to get rid of it (already done in stop())
+
+        // await this.docker.run(
+        //     this.image,
+        //     [ 'sh', '-c', ...this.debug.command.split(" ") ],
+        //     showDockerRunOutput ? [process.stdout] : [],
+        //     {
+        //         HostConfig: {
+        //             AutoRemove: true,
+        //             PortBindings: {
+        //                 [`${RUNTIME_PORT}/tcp`]: [{ HostPort: RUNTIME_PORT }]
+        //             }
+        //         }
+        //     }
+        // );
+        // log.debug("docker - run");
         execute(
             `docker run
                 -d
-                --name ${this.name()}
+                --name ${this.containerName}
                 --rm
                 -m ${this.memory}
                 -p ${RUNTIME_PORT}
@@ -222,11 +294,16 @@ class OpenWhiskInvoker {
                 ${this.debug.command}
             `,
             // live stream view for docker image download output
-            { stdio: showDockerRunOutput ? "inherit" : null },
-            debug2
+            { stdio: showDockerRunOutput ? "inherit" : null }
         );
+        debug2(`docker - started container ${this.containerName}`);
 
-        this.containerRunning = true;
+        this.container = this.docker.getContainer(this.containerName);
+
+        // ask docker for the exposed IP and port of the RUNTIME_PORT on the container
+        const containerInfo = await this.container.inspect();
+        debug2(`docker - retrieved container metadata`);
+        this.containerURL = `http://${addressForContainerPort(containerInfo, RUNTIME_PORT)}`;
 
         log.stopSpinner();
         spawn("docker", ["logs", "-t", "-f", this.name()], {
@@ -236,6 +313,7 @@ class OpenWhiskInvoker {
                 global.mochaLogFile || "inherit"  // stderr
             ]
         });
+        log.debug(`docker - trailing logs`);
     }
 
     getSourcePath() {
@@ -308,40 +386,27 @@ class OpenWhiskInvoker {
     }
 
     async stop() {
-        if (this.containerRunning) {
-            execute(`docker kill ${this.name()}`);
+        if (this.container) {
+            // log this here for VS Code, will be the last visible log message since
+            // we will be killed by VS code after the container is gone after the kill()
+            log.log(`Stopping container ${this.name()}.`);
+            await this.container.kill();
+            delete this.container;
+            log.debug(`docker - stopped container ${this.name()}`);
         }
     }
 
     name() {
-        return this.containerName;
+        return this.container ? this.container.id : "";
     }
 
     url() {
-        if (!this.containerURL) {
-            // ask docker for the exposed IP and port of the RUNTIME_PORT on the container
-            const host = execute(`docker port ${this.name()} ${RUNTIME_PORT}`);
-            this.containerURL = `http://${host}`;
-        }
-        return this.containerURL;
+        return this.containerURL || "";
     }
 
     timeout() {
         return this.action.limits.timeout || OPENWHISK_DEFAULTS.timeout;
     }
-
-    asContainerName(name) {
-        // docker container names are restricted to [a-zA-Z0-9][a-zA-Z0-9_.-]*
-
-        // 1. replace special characters with dash
-        name = name.replace(/[^a-zA-Z0-9_.-]+/g, '-');
-        // 2. leading character is more limited
-        name = name.replace(/^[^a-zA-Z0-9]+/g, '');
-        // 3. (nice to have) remove trailing special chars
-        name = name.replace(/[^a-zA-Z0-9]+$/g, '');
-
-        return name;
-    }
 }
 
 module.exports = OpenWhiskInvoker;
diff --git a/src/log.js b/src/log.js
index fc39e25..8c13932 100644
--- a/src/log.js
+++ b/src/log.js
@@ -117,6 +117,12 @@ module.exports = {
         }
     },
 
+    verboseStep: function(...args) {
+        if (this.isVerbose) {
+            this.step(...args);
+        }
+    },
+
     verboseWrite: function(text) {
         if (this.isVerbose) {
             process.stdout.write(text);