From 5824bbc4ef7c33829f4ed79b8809cd82cdf93ae0 Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Mon, 13 Jul 2020 09:47:28 -0400 Subject: [PATCH] Add new Docker SDK-based client (#2134) * Implement client for Docker SDK * Add another stopped container state * Assume linux if we can't tell what OS * Disable stop * Monkey patch in a label for compose proj * Add getCurrentContext method --- package-lock.json | 441 +++++++++++++++++- package.json | 1 + .../containers/attachShellContainer.ts | 11 +- src/docker/Containers.ts | 11 +- src/docker/ContextManager.ts | 11 +- .../DockerServeClient/DockerServeClient.ts | 80 +++- .../DockerServeClient/DockerServeUtils.ts | 77 +++ .../DockerodeApiClient/DockerodeApiClient.ts | 5 +- .../DockerodeApiClient/DockerodeUtils.ts | 91 ++++ .../DockerodeApiClient/refreshDockerode.ts | 98 ---- src/docker/NotSupportedError.ts | 4 +- src/tree/containers/ContainerProperties.ts | 1 + 12 files changed, 696 insertions(+), 135 deletions(-) create mode 100644 src/docker/DockerServeClient/DockerServeUtils.ts delete mode 100644 src/docker/DockerodeApiClient/refreshDockerode.ts diff --git a/package-lock.json b/package-lock.json index 084c4bb8b9..87b0652498 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,40 @@ "js-tokens": "^4.0.0" } }, + "@docker/sdk": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@docker/sdk/-/sdk-0.1.7.tgz", + "integrity": "sha512-ucK8Sb//cyFAPlYQQLoWNDPfk5lAT0Tq0K3kUVJoR6oi2jdC1f6KmE4oS0A4Pf4nzJ7NKwOuVxYYaxUOj8s+pQ==", + "requires": { + "@grpc/grpc-js": "^1.0.5", + "@octokit/rest": "^18.0.0", + "google-auth-library": "^6.0.1", + "google-protobuf": "^3.12.2", + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz", + "integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==" + } + } + }, + "@grpc/grpc-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.1.tgz", + "integrity": "sha512-mhZRszS0SKwnWPJaNyrECePZ9U7vaHFGqrzxQbWinWR3WznBIU+nmh2L5J3elF+lp5DEUIzARXkifbs6LQVAHA==", + "requires": { + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "@gulp-sourcemaps/identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", @@ -81,6 +115,127 @@ } } }, + "@octokit/auth-token": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.2.tgz", + "integrity": "sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ==", + "requires": { + "@octokit/types": "^5.0.0" + } + }, + "@octokit/core": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.1.0.tgz", + "integrity": "sha512-yPyQSmxIXLieEIRikk2w8AEtWkFdfG/LXcw1KvEtK3iP0ENZLW/WYQmdzOKqfSaLhooz4CJ9D+WY79C8ZliACw==", + "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/graphql": "^4.3.1", + "@octokit/request": "^5.4.0", + "@octokit/types": "^5.0.0", + "before-after-hook": "^2.1.0", + "universal-user-agent": "^5.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz", + "integrity": "sha512-Y900+r0gIz+cWp6ytnkibbD95ucEzDSKzlEnaWS52hbCDNcCJYO5mRmWW7HRAnDc7am+N/5Lnd8MppSaTYx1Yg==", + "requires": { + "@octokit/types": "^5.0.0", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==" + } + } + }, + "@octokit/graphql": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.1.tgz", + "integrity": "sha512-qgMsROG9K2KxDs12CO3bySJaYoUu2aic90qpFrv7A8sEBzZ7UFGvdgPKiLw5gOPYEYbS0Xf8Tvf84tJutHPulQ==", + "requires": { + "@octokit/request": "^5.3.0", + "@octokit/types": "^5.0.0", + "universal-user-agent": "^5.0.0" + } + }, + "@octokit/plugin-paginate-rest": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz", + "integrity": "sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg==", + "requires": { + "@octokit/types": "^5.0.0" + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", + "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==" + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.0.0.tgz", + "integrity": "sha512-emS6gysz4E9BNi9IrCl7Pm4kR+Az3MmVB0/DoDCmF4U48NbYG3weKyDlgkrz6Jbl4Mu4nDx8YWZwC4HjoTdcCA==", + "requires": { + "@octokit/types": "^5.0.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.5.tgz", + "integrity": "sha512-atAs5GAGbZedvJXXdjtKljin+e2SltEs48B3naJjqWupYl2IUBbB/CJisyjbNHcKpHzb3E+OYEZ46G8eakXgQg==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.0.0", + "@octokit/types": "^5.0.0", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==" + } + } + }, + "@octokit/request-error": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz", + "integrity": "sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw==", + "requires": { + "@octokit/types": "^5.0.1", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/rest": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.0.tgz", + "integrity": "sha512-4G/a42lry9NFGuuECnua1R1eoKkdBYJap97jYbWDNYBOUboWcM75GJ1VIcfvwDV/pW0lMPs7CEmhHoVrSV5shg==", + "requires": { + "@octokit/core": "^3.0.0", + "@octokit/plugin-paginate-rest": "^2.2.0", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "4.0.0" + } + }, + "@octokit/types": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.0.1.tgz", + "integrity": "sha512-GorvORVwp244fGKEt3cgt/P+M0MGy4xEDbckw+K5ojEezxyMDgCaYPKVct+/eWQfZXOT7uq0xRpmrl/+hliabA==", + "requires": { + "@types/node": ">= 8" + } + }, "@types/adm-zip": { "version": "0.4.33", "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz", @@ -193,8 +348,7 @@ "@types/node": { "version": "12.12.47", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.47.tgz", - "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==", - "dev": true + "integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -577,6 +731,14 @@ "integrity": "sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "acorn": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", @@ -1015,6 +1177,11 @@ "is-string": "^1.0.4" } }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1389,12 +1556,22 @@ "tweetnacl": "^0.14.3" } }, + "before-after-hook": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", + "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -2485,7 +2662,6 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -2497,8 +2673,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -2847,6 +3022,11 @@ "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", "dev": true }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -3880,6 +4060,11 @@ "through": "^2.3.8" } }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "events": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", @@ -3896,6 +4081,20 @@ "safe-buffer": "^5.1.1" } }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -4162,6 +4361,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -5201,12 +5405,65 @@ "wide-align": "^1.1.0" } }, + "gaxios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", + "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + } + } + }, + "gcp-metadata": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", + "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^0.3.0" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -5459,6 +5716,56 @@ "sparkles": "^1.0.0" } }, + "google-auth-library": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.3.tgz", + "integrity": "sha512-2Np6ojPmaJGXHSMsBhtTQEKfSMdLc8hefoihv7N2cwEr8E5bq39fhoat6TcXHwa0XoGO5Guh9sp3nxHFPmibMw==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "google-p12-pem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", + "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "google-protobuf": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.12.2.tgz", + "integrity": "sha512-4CZhpuRr1d6HjlyrxoXoocoGFnRYgKULgMtikMddA9ztRyYR59Aondv2FioyxWVamRo0rF2XpYawkTCBEQOSkA==" + }, "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", @@ -5478,6 +5785,43 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "gtoken": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", + "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -6583,8 +6927,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -6634,6 +6977,14 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-bigint": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz", + "integrity": "sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-edm-parser": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/json-edm-parser/-/json-edm-parser-0.1.2.tgz", @@ -6933,7 +7284,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "requires": { "yallist": "^3.0.2" }, @@ -6941,8 +7291,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -6955,6 +7304,11 @@ "es5-ext": "~0.10.2" } }, + "macos-release": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.4.0.tgz", + "integrity": "sha512-ko6deozZYiAkqa/0gmcsz+p4jSy3gY7/ZsCEokPaYd8k+6/aXGkiTgr61+Owup7Sf+xjqW8u2ElhoM9SEcEfuA==" + }, "make-array": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/make-array/-/make-array-1.0.5.tgz", @@ -7915,8 +8269,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node-abi": { "version": "2.15.0", @@ -7935,6 +8288,16 @@ } } }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -8080,6 +8443,14 @@ "once": "^1.3.2" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -8331,6 +8702,15 @@ "lcid": "^1.0.0" } }, + "os-name": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -8347,6 +8727,11 @@ "os-tmpdir": "^1.0.0" } }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, "p-limit": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", @@ -8588,8 +8973,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.6", @@ -9549,7 +9933,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -9557,8 +9940,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shell-quote": { "version": "1.7.2", @@ -9574,8 +9956,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-concat": { "version": "1.0.0", @@ -10171,6 +10552,11 @@ "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", "dev": true }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -10947,6 +11333,14 @@ "through2-filter": "^3.0.0" } }, + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "requires": { + "os-name": "^3.1.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -12373,7 +12767,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -12399,6 +12792,14 @@ "string-width": "^1.0.2 || 2" } }, + "windows-release": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.1.tgz", + "integrity": "sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A==", + "requires": { + "execa": "^1.0.0" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/package.json b/package.json index d2d79d03a1..089ce4d330 100644 --- a/package.json +++ b/package.json @@ -2652,6 +2652,7 @@ "webpack-cli": "^3.3.12" }, "dependencies": { + "@docker/sdk": "^0.1.7", "adal-node": "^0.2.1", "azure-arm-containerregistry": "^5.1.0", "azure-arm-website": "^5.7.0", diff --git a/src/commands/containers/attachShellContainer.ts b/src/commands/containers/attachShellContainer.ts index c32c4c0dca..3dc54f4f77 100644 --- a/src/commands/containers/attachShellContainer.ts +++ b/src/commands/containers/attachShellContainer.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { IActionContext } from 'vscode-azureextensionui'; +import { DockerOSType } from '../../docker/Common'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; @@ -20,7 +21,15 @@ export async function attachShellContainer(context: IActionContext, node?: Conta }); } - let osType = await getDockerOSType(context); + let osType: DockerOSType; + try { + // TODO: get OS type from container instead of from system + osType = await getDockerOSType(context); + } catch { + // Assume linux + osType = 'linux'; + } + context.telemetry.properties.dockerOSType = osType; let shellCommand: string; diff --git a/src/docker/Containers.ts b/src/docker/Containers.ts index dfd67267b3..e068bf15c6 100644 --- a/src/docker/Containers.ts +++ b/src/docker/Containers.ts @@ -12,6 +12,12 @@ export interface DockerPort { readonly Type?: string; } +// Ports from inspect have a different shape entirely +export interface InspectionPort { + readonly HostIp?: string; + readonly HostPort?: string; +} + export interface DockerContainer extends DockerObject { readonly State: string; readonly Status: string; @@ -32,10 +38,7 @@ export interface DockerContainerInspection extends DockerObject { }; readonly NetworkSettings?: { readonly Ports?: { - readonly [portAndProtocol: string]: { - readonly HostIp?: string; - readonly HostPort?: string; - }[]; + readonly [portAndProtocol: string]: InspectionPort[]; }; }; } diff --git a/src/docker/ContextManager.ts b/src/docker/ContextManager.ts index 253821cccf..0ef4061478 100644 --- a/src/docker/ContextManager.ts +++ b/src/docker/ContextManager.ts @@ -46,6 +46,7 @@ export interface ContextManager { readonly onContextChanged: Event; refresh(): Promise; getContexts(): Promise; + getCurrentContext(): Promise; inspect(actionContext: IActionContext, contextName: string): Promise; use(actionContext: IActionContext, contextName: string): Promise; @@ -96,8 +97,9 @@ export class DockerContextManager implements ContextManager, Disposable { this.refreshing = true; this.contextsCache.clear(); - const contexts = await this.contextsCache.getValue(); - const currentContext = contexts.find(c => c.Current); + + // Because the cache is cleared, this will load all the contexts before returning the current one + const currentContext = await this.getCurrentContext(); void ext.dockerClient?.dispose(); @@ -128,6 +130,11 @@ export class DockerContextManager implements ContextManager, Disposable { return this.contextsCache.getValue(); } + public async getCurrentContext(): Promise { + const contexts = await this.getContexts(); + return contexts.find(c => c.Current); + } + public async inspect(actionContext: IActionContext, contextName: string): Promise { const { stdout } = await execAsync(`docker context inspect ${contextName}`, { timeout: 10000 }); diff --git a/src/docker/DockerServeClient/DockerServeClient.ts b/src/docker/DockerServeClient/DockerServeClient.ts index 6d4462b99d..cf2bc704bc 100644 --- a/src/docker/DockerServeClient/DockerServeClient.ts +++ b/src/docker/DockerServeClient/DockerServeClient.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Containers as ContainersClient } from '@docker/sdk'; +import { DeleteRequest, InspectRequest, InspectResponse, ListRequest, ListResponse } from '@docker/sdk/containers'; import { CancellationToken } from 'vscode'; import { IActionContext } from 'vscode-azureextensionui'; +import { localize } from '../../localize'; import { DockerInfo, PruneResult } from '../Common'; import { DockerContainer, DockerContainerInspection } from '../Containers'; import { ContextChangeCancelClient } from '../ContextChangeCancelClient'; @@ -13,22 +16,60 @@ import { DockerImage, DockerImageInspection } from '../Images'; import { DockerNetwork, DockerNetworkInspection, DriverType } from '../Networks'; import { NotSupportedError } from '../NotSupportedError'; import { DockerVolume, DockerVolumeInspection } from '../Volumes'; +import { containerPortsToInspectionPorts, containerToDockerContainer } from './DockerServeUtils'; + +// 20 s timeout for all calls (enough time for any call, but short enough to be UX-reasonable) +const dockerServeCallTimeout = 20 * 1000; export class DockerServeClient extends ContextChangeCancelClient implements DockerApiClient { + private readonly containersClient: ContainersClient; + + public constructor() { + super(); + this.containersClient = new ContainersClient(); + } + + public dispose(): void { + super.dispose(); + void this.containersClient?.close(); + } + public async info(context: IActionContext, token?: CancellationToken): Promise { throw new NotSupportedError(context); } public async getContainers(context: IActionContext, token?: CancellationToken): Promise { - throw new NotSupportedError(context); + const request = new ListRequest() + .setAll(true); + + const response: ListResponse = await this.promisify(context, this.containersClient, this.containersClient.list, request, token); + const result = response.getContainersList(); + + return result.map(c => containerToDockerContainer(c.toObject())); } - // #region Not supported by the Docker SDK yet public async inspectContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise { - throw new NotSupportedError(context); + const request = new InspectRequest() + .setId(ref); + + const response: InspectResponse = await this.promisify(context, this.containersClient, this.containersClient.inspect, request, token); + const container = containerToDockerContainer(response.toObject().container); + + if (!container) { + throw new Error(localize('vscode-docker.dockerServeClient.noContainer', 'No container with name \'{0}\' was found.', ref)); + } + + return { + ...container, + NetworkSettings: { + Ports: containerPortsToInspectionPorts(container), + }, + }; } + // #region Not supported by the Docker SDK yet public async getContainerLogs(context: IActionContext, ref: string, token?: CancellationToken): Promise { + // Supported by SDK, but used only for debugging which will not work in ACI, and complicated to implement throw new NotSupportedError(context); } @@ -43,14 +84,19 @@ export class DockerServeClient extends ContextChangeCancelClient implements Dock public async restartContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise { throw new NotSupportedError(context); } - // #endregion Not supported by the Docker SDK yet public async stopContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise { + // Supported by SDK, but is not really the same thing; containers in ACI must stop/start as a group throw new NotSupportedError(context); } + // #endregion Not supported by the Docker SDK yet public async removeContainer(context: IActionContext, ref: string, token?: CancellationToken): Promise { - throw new NotSupportedError(context); + const request = new DeleteRequest() + .setId(ref) + .setForce(true); + + await this.promisify(context, this.containersClient, this.containersClient.delete, request, token) } // #region Not supported by the Docker SDK yet @@ -110,4 +156,28 @@ export class DockerServeClient extends ContextChangeCancelClient implements Dock throw new NotSupportedError(context); } // #endregion Not supported by the Docker SDK yet + + private async promisify( + context: IActionContext, + thisArg: unknown, + clientCallback: (request: TRequest, callback: (err: unknown, response: TResponse) => void) => unknown, + request: TRequest, + token?: CancellationToken): Promise { + + const callPromise: Promise = new Promise((resolve, reject) => { + try { + clientCallback.call(thisArg, request, (err, response) => { + if (err) { + reject(err); + } + + resolve(response); + }); + } catch (err) { + reject(err); + } + }); + + return this.withTimeoutAndCancellations(context, async () => callPromise, dockerServeCallTimeout, token); + } } diff --git a/src/docker/DockerServeClient/DockerServeUtils.ts b/src/docker/DockerServeClient/DockerServeUtils.ts new file mode 100644 index 0000000000..296ce7f83c --- /dev/null +++ b/src/docker/DockerServeClient/DockerServeUtils.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Container } from '@docker/sdk/containers'; +import { DockerContainer, InspectionPort } from '../Containers'; + +// Group 1 is container group name; group 2 is container name +const containerGroupAndName = /(?:([a-z0-9\-]+)_)?([a-z0-9\-]+)/i; + +export function containerToDockerContainer(container: Container.AsObject): DockerContainer | undefined { + if (!container) { + return undefined; + } + + const ports = container.portsList.map(p => { + return { + IP: p.hostIp, + PublicPort: p.hostPort, + PrivatePort: p.containerPort, + Type: p.protocol, + }; + }); + + const labels: { [key: string]: string } = {}; + container.labelsList.forEach(l => { + const [label, value] = l.split(/=|:/i); + labels[label] = value; + }); + + // If the containers are in a group and there's no com.docker.compose.project label, + // use the group name as that label so that grouping in the UI works + let match: string; + if (labels['com.docker.compose.project'] === undefined && + (match = containerGroupAndName.exec(container.id)?.[1])) { // Assignment and check is intentional + labels['com.docker.compose.project'] = match; + } + + return { + Id: container.id, + Image: container.image, + Name: container.id, // TODO ? + State: container.status, + Status: container.status, + ImageID: undefined, // TODO ? + CreatedTime: undefined, // TODO ? + Labels: labels, // TODO--not yet supported on ACI + Ports: ports, + }; +} + +export function containerPortsToInspectionPorts(container: DockerContainer): { [portAndProtocol: string]: InspectionPort[] } | undefined { + if (container?.Ports === undefined) { + return undefined; + } + + const result: { [portAndProtocol: string]: InspectionPort[] } = {}; + + for (const port of container.Ports) { + // Get the key + const key = `${port.PrivatePort}/${port.Type}`; + + // If there's no entries for this key yet, create an empty list + if (result[key] === undefined) { + result[key] = []; + } + + // Add the value to the list + result[key].push({ + HostIp: port.IP, + HostPort: port.PublicPort.toString(), + }); + } + + return result; +} diff --git a/src/docker/DockerodeApiClient/DockerodeApiClient.ts b/src/docker/DockerodeApiClient/DockerodeApiClient.ts index a87458753b..2b6ed9f72e 100644 --- a/src/docker/DockerodeApiClient/DockerodeApiClient.ts +++ b/src/docker/DockerodeApiClient/DockerodeApiClient.ts @@ -15,10 +15,9 @@ import { DockerApiClient } from '../DockerApiClient'; import { DockerImage, DockerImageInspection } from '../Images'; import { DockerNetwork, DockerNetworkInspection, DriverType } from '../Networks'; import { DockerVolume, DockerVolumeInspection } from '../Volumes'; -import { getContainerName, getFullTagFromDigest } from './DockerodeUtils'; -import { refreshDockerode } from './refreshDockerode'; +import { getContainerName, getFullTagFromDigest, refreshDockerode } from './DockerodeUtils'; -// 20 s timeout for all calls (enough time for a possible Dockerode refresh + the call, but short enough to be UX-reasonable) +// 20 s timeout for all calls (enough time for any call, but short enough to be UX-reasonable) const dockerodeCallTimeout = 20 * 1000; export class DockerodeApiClient extends ContextChangeCancelClient implements DockerApiClient { diff --git a/src/docker/DockerodeApiClient/DockerodeUtils.ts b/src/docker/DockerodeApiClient/DockerodeUtils.ts index b68ecf6195..e792f1d5e1 100644 --- a/src/docker/DockerodeApiClient/DockerodeUtils.ts +++ b/src/docker/DockerodeApiClient/DockerodeUtils.ts @@ -4,6 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import Dockerode = require('dockerode'); +import { Socket } from 'net'; +import { CancellationTokenSource, workspace } from 'vscode'; +import { ext } from '../../extensionVariables'; +import { localize } from '../../localize'; +import { addDockerSettingsToEnv } from '../../utils/addDockerSettingsToEnv'; +import { cloneObject } from '../../utils/cloneObject'; +import { isWindows } from '../../utils/osUtils'; +import { TimeoutPromiseSource } from '../../utils/promiseUtils'; +import { DockerContext } from '../Contexts'; export function getFullTagFromDigest(image: Dockerode.ImageInfo): string { let repo = ''; @@ -28,3 +37,85 @@ export function getContainerName(containerInfo: Dockerode.ContainerInfo): string return canonicalName ?? names[0]; } +const SSH_URL_REGEX = /ssh:\/\//i; + +/** + * Dockerode parses and handles the well-known `DOCKER_*` environment variables, but it doesn't let us pass those values as-is to the constructor + * Thus we will temporarily update `process.env` and pass nothing to the constructor + */ +export function refreshDockerode(currentContext: DockerContext): Dockerode { + // If the docker.dockerodeOptions setting is present, use it only + const config = workspace.getConfiguration('docker'); + const overrideDockerodeOptions = config.get('dockerodeOptions'); + // eslint-disable-next-line @typescript-eslint/tslint/config + if (overrideDockerodeOptions && Object.keys(overrideDockerodeOptions).length > 0) { + return new Dockerode(overrideDockerodeOptions); + } + + // Set up environment variables + const oldEnv = process.env; + const newEnv: NodeJS.ProcessEnv = cloneObject(process.env); // make a clone before we change anything + + if (currentContext.Name === 'default') { + // If the current context is default, just make use of addDockerSettingsToEnv + the current environment + addDockerSettingsToEnv(newEnv, oldEnv); + } else { + // Otherwise get the host from the Docker context + newEnv.DOCKER_HOST = currentContext.DockerEndpoint; + } + + // If host is an SSH URL, we need to configure / validate SSH_AUTH_SOCK for Dockerode + if (newEnv.DOCKER_HOST && SSH_URL_REGEX.test(newEnv.DOCKER_HOST)) { + if (!newEnv.SSH_AUTH_SOCK && isWindows()) { + // On Windows, we can use this one by default + newEnv.SSH_AUTH_SOCK = '\\\\.\\pipe\\openssh-ssh-agent'; + } + + // Don't wait + void validateSshAuthSock(newEnv.SSH_AUTH_SOCK).then((result) => { + if (!result) { + // Don't wait + void ext.ui.showWarningMessage(localize('vscode-docker.utils.dockerode.sshAgent', 'In order to use an SSH DOCKER_HOST, you must configure an ssh-agent.'), { learnMoreLink: 'https://aka.ms/AA7assy' }); + } + }); + } + + try { + process.env = newEnv; + return new Dockerode(); + } finally { + process.env = oldEnv; + } +} + +async function validateSshAuthSock(authSock: string): Promise { + if (!authSock) { + // On Mac and Linux, if SSH_AUTH_SOCK isn't set there's nothing we can do + // Running ssh-agent would yield a new agent that doesn't have the needed keys + return false; + } + + const socket = new Socket(); + const cts = new CancellationTokenSource(); + + const connectPromise = new Promise(resolve => { + socket.on('error', (err) => { + cts.cancel(); + resolve(false); + }); + + socket.on('connect', () => { + cts.cancel(); + resolve(true); + }); + + socket.connect(authSock); + }); + + // Unfortunately Socket.setTimeout() does not actually work when attempting to establish a connection, so we need to race + return await Promise.race([connectPromise, new TimeoutPromiseSource(1000).promise]) + .finally(() => { + socket.end(); + cts.dispose(); + }); +} diff --git a/src/docker/DockerodeApiClient/refreshDockerode.ts b/src/docker/DockerodeApiClient/refreshDockerode.ts deleted file mode 100644 index 9579d9e9fd..0000000000 --- a/src/docker/DockerodeApiClient/refreshDockerode.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import Dockerode = require('dockerode'); -import { Socket } from 'net'; -import { CancellationTokenSource, workspace } from 'vscode'; -import { ext } from '../../extensionVariables'; -import { localize } from '../../localize'; -import { addDockerSettingsToEnv } from '../../utils/addDockerSettingsToEnv'; -import { cloneObject } from '../../utils/cloneObject'; -import { isWindows } from '../../utils/osUtils'; -import { TimeoutPromiseSource } from '../../utils/promiseUtils'; -import { DockerContext } from '../Contexts'; - -const SSH_URL_REGEX = /ssh:\/\//i; - -/** - * Dockerode parses and handles the well-known `DOCKER_*` environment variables, but it doesn't let us pass those values as-is to the constructor - * Thus we will temporarily update `process.env` and pass nothing to the constructor - */ -export function refreshDockerode(currentContext: DockerContext): Dockerode { - // If the docker.dockerodeOptions setting is present, use it only - const config = workspace.getConfiguration('docker'); - const overrideDockerodeOptions = config.get('dockerodeOptions'); - // eslint-disable-next-line @typescript-eslint/tslint/config - if (overrideDockerodeOptions && Object.keys(overrideDockerodeOptions).length > 0) { - return new Dockerode(overrideDockerodeOptions); - } - - // Set up environment variables - const oldEnv = process.env; - const newEnv: NodeJS.ProcessEnv = cloneObject(process.env); // make a clone before we change anything - - if (currentContext.Name === 'default') { - // If the current context is default, just make use of addDockerSettingsToEnv + the current environment - addDockerSettingsToEnv(newEnv, oldEnv); - } else { - // Otherwise get the host from the Docker context - newEnv.DOCKER_HOST = currentContext.DockerEndpoint; - } - - // If host is an SSH URL, we need to configure / validate SSH_AUTH_SOCK for Dockerode - if (newEnv.DOCKER_HOST && SSH_URL_REGEX.test(newEnv.DOCKER_HOST)) { - if (!newEnv.SSH_AUTH_SOCK && isWindows()) { - // On Windows, we can use this one by default - newEnv.SSH_AUTH_SOCK = '\\\\.\\pipe\\openssh-ssh-agent'; - } - - // Don't wait - void validateSshAuthSock(newEnv.SSH_AUTH_SOCK).then((result) => { - if (!result) { - // Don't wait - void ext.ui.showWarningMessage(localize('vscode-docker.utils.dockerode.sshAgent', 'In order to use an SSH DOCKER_HOST, you must configure an ssh-agent.'), { learnMoreLink: 'https://aka.ms/AA7assy' }); - } - }); - } - - try { - process.env = newEnv; - return new Dockerode(); - } finally { - process.env = oldEnv; - } -} - -async function validateSshAuthSock(authSock: string): Promise { - if (!authSock) { - // On Mac and Linux, if SSH_AUTH_SOCK isn't set there's nothing we can do - // Running ssh-agent would yield a new agent that doesn't have the needed keys - return false; - } - - const socket = new Socket(); - const cts = new CancellationTokenSource(); - - const connectPromise = new Promise(resolve => { - socket.on('error', (err) => { - cts.cancel(); - resolve(false); - }); - - socket.on('connect', () => { - cts.cancel(); - resolve(true); - }); - - socket.connect(authSock); - }); - - // Unfortunately Socket.setTimeout() does not actually work when attempting to establish a connection, so we need to race - return await Promise.race([connectPromise, new TimeoutPromiseSource(1000).promise]) - .finally(() => { - socket.end(); - cts.dispose(); - }); -} diff --git a/src/docker/NotSupportedError.ts b/src/docker/NotSupportedError.ts index bdb20ce7c9..e8a2dc90a3 100644 --- a/src/docker/NotSupportedError.ts +++ b/src/docker/NotSupportedError.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IActionContext } from "vscode-azureextensionui"; -import { localize } from "../localize"; +import { IActionContext } from 'vscode-azureextensionui'; +import { localize } from '../localize'; export class NotSupportedError extends Error { public static ErrorType: string = 'NotSupportedError'; diff --git a/src/tree/containers/ContainerProperties.ts b/src/tree/containers/ContainerProperties.ts index 1b7c1a8300..ae0490d2a5 100644 --- a/src/tree/containers/ContainerProperties.ts +++ b/src/tree/containers/ContainerProperties.ts @@ -29,6 +29,7 @@ export function getContainerStateIcon(state: string): IconPath { case 'exited': case 'removing': case 'terminated': + case 'unknown': case 'waiting': icon = 'statusStop'; break;