From 388b4fc8a98a63c3ebb00f6b3bd0cf06e5903ed3 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Tue, 25 Jun 2024 12:17:48 -0400 Subject: [PATCH] Refactor Docker container launching code Put subprocess and parent process code in the same file. --- index.ts | 1 - package-lock.json | 21 +++++++--- package.json | 2 +- run.ts | 72 ++------------------------------- runBinary.ts | 33 +++++++++++++++ launchSearch.ts => runDocker.ts | 60 ++++++++++++++++++++------- 6 files changed, 99 insertions(+), 90 deletions(-) create mode 100644 runBinary.ts rename launchSearch.ts => runDocker.ts (62%) diff --git a/index.ts b/index.ts index 01e0bf7..4442503 100644 --- a/index.ts +++ b/index.ts @@ -12,7 +12,6 @@ import { pathToFileURL } from 'url' import { launch } from './run.js' import { populate } from './data.js' import { search as getSearchClient } from '@nasa-gcn/architect-functions-search' -import './launchSearch' import { cloudformationResources as serverlessCloudformationResources, services as serverlessServices, diff --git a/package-lock.json b/package-lock.json index a84694b..c18147a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@types/dockerode": "^3.3.23", "@types/lodash": "^4.14.202", "@types/make-fetch-happen": "^10.0.1", - "@types/node": "^18.13.0", + "@types/node": "^18.19.39", "@types/tar": "^6.1.4", "@types/unzip-stream": "^0.3.1", "@typescript-eslint/eslint-plugin": "^5.53.0", @@ -1569,10 +1569,14 @@ } }, "node_modules/@types/node": { - "version": "18.13.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", - "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", - "dev": true + "version": "18.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", + "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -6149,6 +6153,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", diff --git a/package.json b/package.json index 47c16e6..47460f5 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@types/dockerode": "^3.3.23", "@types/lodash": "^4.14.202", "@types/make-fetch-happen": "^10.0.1", - "@types/node": "^18.13.0", + "@types/node": "^18.19.39", "@types/tar": "^6.1.4", "@types/unzip-stream": "^0.3.1", "@typescript-eslint/eslint-plugin": "^5.53.0", diff --git a/run.ts b/run.ts index 5011870..aa97ccb 100644 --- a/run.ts +++ b/run.ts @@ -12,13 +12,12 @@ import { install } from './install.js' import { mkdtemp } from 'fs/promises' import { mkdirP, temp } from './paths.js' import rimraf from 'rimraf' -import { spawn, untilTerminated } from './processes.js' import type { SandboxEngine } from './engines.js' import { UnexpectedResolveError, neverResolve } from './promises.js' -import { fork } from 'child_process' -import { fileURLToPath } from 'url' +import { launchBinary } from './runBinary.js' +import { launchDocker } from './runDocker.js' -type SearchEngineLauncherFunction = ( +export type SearchEngineLauncherFunction = ( props: T & { options: string[] dataDir: string @@ -31,71 +30,6 @@ type SearchEngineLauncherFunction = ( waitUntilStopped: () => Promise }> -const launchBinary: SearchEngineLauncherFunction<{ bin: string }> = async ({ - bin, - dataDir, - logsDir, - options, -}) => { - const args = [...options, `path.data=${dataDir}`, `path.logs=${logsDir}`].map( - (opt) => `-E${opt}` - ) - - console.log('Spawning', bin, ...args) - const child = await spawn(bin, args, { - stdio: ['ignore', 'ignore', 'inherit'], - }) - - return { - async kill() { - console.log('Killing child process') - child.kill() - }, - async waitUntilStopped() { - await untilTerminated(child) - }, - } -} - -const launchDocker: SearchEngineLauncherFunction = async ({ - dataDir, - logsDir, - engine, - port, - options, -}) => { - const argv = { - dataDir, - logsDir, - engine, - port, - options, - } - // FIXME: fork accepts either a string or URL as the first argument. @types/node has defined the modulePath type as string only. - // new URL(import.meta.url) may be used in place of __filename once this has been updated. - // see: https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/69812 - const __filename = fileURLToPath(import.meta.url) - const subprocess = fork(__filename, [ - 'launch-docker-subprocess', - JSON.stringify(argv), - ]) - - return { - async kill() { - console.log('Killing Docker container') - subprocess.send({ action: 'kill' }) - }, - async waitUntilStopped() { - return new Promise((resolve) => { - subprocess.on('exit', () => { - console.log('Docker container exited') - resolve() - }) - }) - }, - } -} - export async function launch({ port, engine, diff --git a/runBinary.ts b/runBinary.ts new file mode 100644 index 0000000..8f8ff2e --- /dev/null +++ b/runBinary.ts @@ -0,0 +1,33 @@ +/*! + * Copyright © 2023 United States Government as represented by the + * Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { spawn, untilTerminated } from './processes.js' +import { type SearchEngineLauncherFunction } from './run.js' + +export const launchBinary: SearchEngineLauncherFunction<{ + bin: string +}> = async ({ bin, dataDir, logsDir, options }) => { + const args = [...options, `path.data=${dataDir}`, `path.logs=${logsDir}`].map( + (opt) => `-E${opt}` + ) + + console.log('Spawning', bin, ...args) + const child = await spawn(bin, args, { + stdio: ['ignore', 'ignore', 'inherit'], + }) + + return { + async kill() { + console.log('Killing child process') + child.kill() + }, + async waitUntilStopped() { + await untilTerminated(child) + }, + } +} diff --git a/launchSearch.ts b/runDocker.ts similarity index 62% rename from launchSearch.ts rename to runDocker.ts index 484872c..c2ffefb 100644 --- a/launchSearch.ts +++ b/runDocker.ts @@ -7,23 +7,12 @@ */ import Dockerode from 'dockerode' +import { fork } from 'child_process' +import { type SearchEngineLauncherFunction } from './run.js' const [, , command, jsonifiedArgs] = process.argv if (command === 'launch-docker-subprocess') { - const dockerContainer = await launchDockerSearch() - - const signals = ['message', 'SIGTERM', 'SIGINT'] - signals.forEach((signal) => { - process.on(signal, async () => { - await dockerContainer.kill() - }) - }) - - await dockerContainer.wait() -} - -async function launchDockerSearch() { const { dataDir, logsDir, engine, port, options } = JSON.parse(jsonifiedArgs) const Image = engine === 'elasticsearch' @@ -49,5 +38,48 @@ async function launchDockerSearch() { const stream = await container.attach({ stream: true, stderr: true }) stream.pipe(process.stderr) await container.start() - return container + + const signals = ['message', 'SIGTERM', 'SIGINT'] + signals.forEach((signal) => { + process.on(signal, async () => { + await container.kill() + }) + }) + + await container.wait() +} + +export const launchDocker: SearchEngineLauncherFunction = async ({ + dataDir, + logsDir, + engine, + port, + options, +}) => { + const argv = { + dataDir, + logsDir, + engine, + port, + options, + } + const subprocess = fork(new URL(import.meta.url), [ + 'launch-docker-subprocess', + JSON.stringify(argv), + ]) + + return { + async kill() { + console.log('Killing Docker container') + subprocess.send({ action: 'kill' }) + }, + async waitUntilStopped() { + return new Promise((resolve) => { + subprocess.on('exit', () => { + console.log('Docker container exited') + resolve() + }) + }) + }, + } }