diff --git a/install.ts b/install.ts index 4c5f690..95327fb 100644 --- a/install.ts +++ b/install.ts @@ -30,9 +30,10 @@ export async function install(engine: SandboxEngine) { entry.engine === engine && entry.arch === arch && entry.type === type )?.url if (!url) { - throw new Error( + console.warn( `No ${engine} binary is available for your OS type (${type}) and architecture (${arch}).` ) + return } const archiveFilename = posix diff --git a/package-lock.json b/package-lock.json index 91d575b..a982ca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@opensearch-project/opensearch": "^2.2.0", + "dockerode": "^4.0.0", "env-paths": "^3.0.0", "lodash": "^4.17.21", "make-fetch-happen": "^11.0.3", @@ -21,6 +22,7 @@ "devDependencies": { "@nasa-gcn/eslint-config-gitignore": "^0.0.1", "@tsconfig/node14": "^1.0.3", + "@types/dockerode": "^3.3.23", "@types/lodash": "^4.14.202", "@types/make-fetch-happen": "^10.0.1", "@types/node": "^18.13.0", @@ -39,6 +41,11 @@ "node": ">=18" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" + }, "node_modules/@esbuild/android-arm": { "version": "0.17.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", @@ -540,6 +547,26 @@ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "dev": true }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.23", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.23.tgz", + "integrity": "sha512-Lz5J+NFgZS4cEVhquwjIGH4oQwlVn2h7LXD3boitujBnzOE5o7s9H8hchEjoDK2SlRsJTogdKnQeiJgPPKLIEw==", + "dev": true, + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -591,6 +618,15 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/ssh2": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.18.tgz", + "integrity": "sha512-7eH4ppQMFlzvn//zhwD54MWaITR1aSc1oFBye9vb76GZ2Y9PSFYdwVIwOlxRXWs5+1hifntXyt+8a6SUbOD7Hg==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18" + } + }, "node_modules/@types/ssri": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@types/ssri/-/ssri-7.1.1.tgz", @@ -946,6 +982,14 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -983,6 +1027,33 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", @@ -995,6 +1066,16 @@ "node": "*" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1017,6 +1098,29 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -1025,6 +1129,15 @@ "node": ">=0.2.0" } }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cacache": { "version": "17.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", @@ -1188,6 +1301,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cpu-features": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", + "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.17.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1269,6 +1396,33 @@ "node": ">=8" } }, + "node_modules/docker-modem": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.1.tgz", + "integrity": "sha512-vqrE/nrweCyzmCpVpdFRC41qS+tfTF+IoUKlTZr52O82urbUqdfyJBGWMvT01pYUprWepLr8IkyVTEWJKRTQSg==", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.11.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.0.tgz", + "integrity": "sha512-3LF7/3MPz5+9RsUo91rD0MCcx0yxjC9bnbtgtVjOLKyKxlZSJ7/Kk3OPAgARlwlWHqXwAGYhmkAHYx7IwD0tJQ==", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "docker-modem": "^5.0.0", + "tar-fs": "~2.0.1" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1302,6 +1456,14 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/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==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -1865,6 +2027,11 @@ "node": ">= 6" } }, + "node_modules/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==" + }, "node_modules/fs-minipass": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.1.tgz", @@ -2247,6 +2414,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3164,11 +3350,22 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -3688,6 +3885,15 @@ "node": ">=10" } }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -3743,6 +3949,19 @@ "node": ">=4" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -3905,6 +4124,25 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -3922,8 +4160,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/secure-json-parse": { "version": "2.7.0", @@ -4109,6 +4346,28 @@ "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", "dev": true }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + }, + "node_modules/ssh2": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.14.0.tgz", + "integrity": "sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.8", + "nan": "^2.17.0" + } + }, "node_modules/ssri": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", @@ -4120,6 +4379,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -4302,6 +4569,37 @@ "node": ">=10" } }, + "node_modules/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==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -4383,6 +4681,11 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4501,6 +4804,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index f1ba9cf..985c03c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@opensearch-project/opensearch": "^2.2.0", + "dockerode": "^4.0.0", "env-paths": "^3.0.0", "lodash": "^4.17.21", "make-fetch-happen": "^11.0.3", @@ -37,6 +38,7 @@ "devDependencies": { "@nasa-gcn/eslint-config-gitignore": "^0.0.1", "@tsconfig/node14": "^1.0.3", + "@types/dockerode": "^3.3.23", "@types/lodash": "^4.14.202", "@types/make-fetch-happen": "^10.0.1", "@types/node": "^18.13.0", diff --git a/run.ts b/run.ts index 9c4733b..eae43e0 100644 --- a/run.ts +++ b/run.ts @@ -12,7 +12,6 @@ import { install } from './install.js' import { mkdtemp } from 'fs/promises' import { mkdirP, temp } from './paths.js' import rimraf from 'rimraf' -import type { ChildProcess } from 'child_process' import { spawn, untilTerminated, @@ -21,87 +20,125 @@ import { import { pipeline } from 'stream/promises' import { createReadStream } from 'fs' import type { SandboxEngine } from './engines.js' +import Dockerode from 'dockerode' -export class LocalSearch { - private readonly child!: ChildProcess - private readonly tempDir!: string - readonly port!: number - readonly url!: string +async function BaseLauncher({ + port, + engine, +}: { + port?: number + engine: SandboxEngine +}) { + port ??= 9200 + const url = `http://localhost:${port}` - private constructor(props: { - child: ChildProcess - tempDir: string - port: number - url: string - }) { - return Object.assign(this, props) - } + const opts = [ + `http.port=${port}`, + 'discovery.type=single-node', + engine === 'elasticsearch' + ? 'xpack.security.enabled=false' + : 'plugins.security.disabled=true', + ] - static async launch({ - port, - engine, - }: { - port?: number - engine: SandboxEngine - }) { - port ??= 9200 - const url = `http://localhost:${port}` + const tempDir = await mkdtemp(join(temp, 'run-')) + const [dataDir, logsDir] = ['data', 'logs'].map((s) => join(tempDir, s)) + await Promise.all([dataDir, logsDir].map(mkdirP)) - const bin = await install(engine) - let child + return { + url, + port, + opts, + dataDir, + logsDir, + async stop() { + console.log('Removing temporary directory') + await rimraf(tempDir) + }, + } +} - await mkdirP(temp) - const tempDir = await mkdtemp(join(temp, 'run-')) - try { - const dataDir = join(tempDir, 'data') - const logsDir = join(tempDir, 'logs') - await Promise.all([dataDir, logsDir].map(mkdirP)) +async function BinLauncher({ + bin, + ...props +}: { + bin: string +} & Parameters[0]) { + const { port, url, dataDir, logsDir, opts, stop } = await BaseLauncher(props) - const args: string[] = [ - `-Epath.data=${dataDir}`, - `-Epath.logs=${logsDir}`, - `-Ehttp.port=${port}`, - '-Ediscovery.type=single-node', - engine === 'elasticsearch' - ? '-Expack.security.enabled=false' - : '-Eplugins.security.disabled=true', - ] + opts.push(`path.data=${dataDir}`, `path.logs=${logsDir}`) + const args = opts.map((opt) => `-E${opt}`) - console.log('Spawning', bin, ...args) - child = await spawn(bin, args, { - stdio: ['ignore', 'ignore', 'inherit'], - }) + console.log('Spawning', bin, ...args) + const child = await spawn(bin, args, { + stdio: ['ignore', 'ignore', 'inherit'], + }) - try { - await Promise.race([ - waitPort({ port }), - untilTerminatedUnexpectedly(child), - ]) - } catch (e) { - await pipeline( - createReadStream(join(logsDir, 'opensearch.log')), - process.stderr - ) - throw e - } - console.log(`${engine} is ready at`, url) - } catch (e) { - await rimraf(tempDir) - throw e - } + try { + await Promise.race([ + waitPort({ port, protocol: 'http' }), + untilTerminatedUnexpectedly(child), + ]) + } catch (e) { + await pipeline( + createReadStream(join(logsDir, 'opensearch.log')), + process.stderr + ) + throw e + } + console.log('OpenSearch is ready at', url) - return new this({ child, tempDir, port, url }) + return { + port, + url, + async stop() { + console.log('Stopping child process') + child.kill() + await untilTerminated(child) + await stop() + }, } +} - async stop() { - console.log('Stopping child process') - this.child.kill() - await untilTerminated(this.child) - console.log('Removing temporary directory') - await rimraf(this.tempDir) +async function DockerLauncher(props: Parameters[0]) { + const { port, dataDir, logsDir, opts, stop } = await BaseLauncher(props) + opts.push('path.data=/var/lib/search', 'path.logs=/var/log/search') + const Image = + props.engine === 'elasticsearch' + ? 'elastic/elasticsearch:8.6.2' + : 'opensearchproject/opensearch:2.11.0' + + console.log('Launching Docker container', Image) + const docker = new Dockerode() + const container = await docker.createContainer({ + Env: opts, + HostConfig: { + AutoRemove: true, + Mounts: [ + { Source: dataDir, Target: '/var/lib/search', Type: 'bind' }, + { Source: logsDir, Target: '/var/log/search', Type: 'bind' }, + ], + PortBindings: { + [`${port}/tcp`]: [{ HostIP: '127.0.0.1', HostPort: `${port}` }], + }, + }, + Image, + }) + const stream = await container.attach({ stream: true, stderr: true }) + stream.pipe(process.stderr) + await container.start() + await waitPort({ port, protocol: 'http' }) + + return { + port, + async stop() { + console.log('Stopping Docker container') + await container.kill() + await stop() + }, } } -export async function launch(...args: Parameters) { - return await LocalSearch.launch(...args) +export async function launch(engine: SandboxEngine) { + const bin = await install(engine) + return await (bin ? BinLauncher({ bin, engine }) : DockerLauncher({ engine })) }