Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

Experiment: runner pause container #621

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/one-app-runner/src/asyncSpawn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { spawn } = require('child_process');

module.exports = async function asyncSpawn(command, args) {
return new Promise((resolve, reject) => {
const spawnedProcess = spawn(command, args);

let stdout = Buffer.from('');
let stderr = Buffer.from('');

spawnedProcess.stdout.on('data', (chunk) => {
stdout = Buffer.concat([stdout, chunk]);
});

spawnedProcess.stderr.on('data', (chunk) => {
stderr = Buffer.concat([stderr, chunk]);
});

spawnedProcess.on('close', (code) => {
if (code !== 0) {
return reject(Object.assign(new Error('process exited with an error'), { code, stdout, stderr }));
}
return resolve({ code, stdout, stderr });
});
});
};
89 changes: 89 additions & 0 deletions packages/one-app-runner/src/generateContainerArgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const path = require('node:path');

const semver = require('semver');

function generateEnvironmentVariableArgs(envVars) {
return new Map([
['NODE_ENV', 'development'],
...Object.entries(envVars),
process.env.HTTP_PROXY ? ['HTTP_PROXY', process.env.HTTP_PROXY] : null,
process.env.HTTPS_PROXY ? ['HTTPS_PROXY', process.env.HTTPS_PROXY] : null,
process.env.NO_PROXY ? ['NO_PROXY', process.env.NO_PROXY] : null,
process.env.HTTP_PORT ? ['HTTP_PORT', process.env.HTTP_PORT] : null,
process.env.HTTP_ONE_APP_DEV_CDN_PORT
? ['HTTP_ONE_APP_DEV_CDN_PORT', process.env.HTTP_ONE_APP_DEV_CDN_PORT]
: null,
process.env.HTTP_ONE_APP_DEV_PROXY_SERVER_PORT
? ['HTTP_ONE_APP_DEV_PROXY_SERVER_PORT', process.env.HTTP_ONE_APP_DEV_PROXY_SERVER_PORT]
: null,
process.env.HTTP_METRICS_PORT
? ['HTTP_METRICS_PORT', process.env.HTTP_METRICS_PORT]
: null,
].filter(Boolean));
}

function generateSetMiddlewareCommand(pathToMiddlewareFile) {
if (!pathToMiddlewareFile) {
return '';
}
const pathArray = pathToMiddlewareFile.split(path.sep);
return `npm run set-middleware '/opt/module-workspace/${pathArray[pathArray.length - 2]}/${pathArray[pathArray.length - 1]}' &&`;
}

function generateSetDevEndpointsCommand(pathToDevEndpointsFile) {
if (!pathToDevEndpointsFile) {
return '';
}
const pathArray = pathToDevEndpointsFile.split(path.sep);
return `npm run set-dev-endpoints '/opt/module-workspace/${pathArray[pathArray.length - 2]}/${pathArray[pathArray.length - 1]}' &&`;
}

function generateUseMocksFlag(shouldUseMocks) { return shouldUseMocks ? '-m' : ''; }

function generateNpmConfigCommands() { return 'npm config set update-notifier false &&'; }

function generateServeModuleCommands(modules) {
let command = '';
if (modules && modules.length > 0) {
modules.forEach((modulePath) => {
const moduleRootDir = path.basename(modulePath);
command += `npm run serve-module '/opt/module-workspace/${moduleRootDir}' &&`;
});
}
return command;
}

function generateModuleMap(moduleMapUrl) { return moduleMapUrl ? `--module-map-url=${moduleMapUrl}` : ''; }

function generateLogLevel(logLevel) { return logLevel ? `--log-level=${logLevel}` : ''; }

function generateLogFormat(logFormat) { return logFormat ? `--log-format=${logFormat}` : ''; }

function generateDebug(port, useDebug) { return useDebug ? `--inspect=0.0.0.0:${port}` : ''; }

// NOTE: Node 12 does not support --dns-result-order or --no-experimental-fetch
// So we have to remove those flags if the one-app version is less than 5.13.0
// 5.13.0 is when node 16 was introduced.
function generateNodeFlags(appVersion) {
if (semver.intersects(appVersion, '^5.13.0', { includePrerelease: true })) {
return '--dns-result-order=ipv4first --no-experimental-fetch';
}
return '';
}

function generateUseHostFlag(useHost) { return useHost ? '--use-host' : ''; }

module.exports = {
generateEnvironmentVariableArgs,
generateSetMiddlewareCommand,
generateSetDevEndpointsCommand,
generateUseMocksFlag,
generateNpmConfigCommands,
generateServeModuleCommands,
generateModuleMap,
generateLogLevel,
generateLogFormat,
generateDebug,
generateNodeFlags,
generateUseHostFlag,
};
22 changes: 22 additions & 0 deletions packages/one-app-runner/src/spawnAndPipe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { spawn } = require('child_process');

module.exports = async function spawnAndPipe(command, args, logStream) {
return new Promise((resolve, reject) => {
const spawnedProcess = spawn(command, args);

spawnedProcess.on('close', (code) => {
if (code !== 0) {
return reject(code);
}
return resolve(code);
});

if (logStream) {
spawnedProcess.stdout.pipe(logStream, { end: false });
spawnedProcess.stderr.pipe(logStream, { end: false });
} else {
spawnedProcess.stdout.pipe(process.stdout);
spawnedProcess.stderr.pipe(process.stderr);
}
});
};
169 changes: 32 additions & 137 deletions packages/one-app-runner/src/startApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,136 +12,33 @@
* the License.
*/

const { spawn } = require('child_process');
const path = require('node:path');
const fs = require('node:fs');
const os = require('node:os');
const Docker = require('dockerode');
const semver = require('semver');

async function spawnAndPipe(command, args, logStream) {
return new Promise((resolve, reject) => {
const spawnedProcess = spawn(command, args);

spawnedProcess.on('close', (code) => {
if (code !== 0) {
return reject(code);
}
return resolve(code);
});

if (logStream) {
spawnedProcess.stdout.pipe(logStream, { end: false });
spawnedProcess.stderr.pipe(logStream, { end: false });
} else {
spawnedProcess.stdout.pipe(process.stdout);
spawnedProcess.stderr.pipe(process.stderr);
}
});
}
const asyncSpawn = require('./asyncSpawn');
const spawnAndPipe = require('./spawnAndPipe');
const startAppContainer = require('./startAppContainer');
const {
generateEnvironmentVariableArgs,
generateSetMiddlewareCommand,
generateSetDevEndpointsCommand,
generateUseMocksFlag,
generateNpmConfigCommands,
generateServeModuleCommands,
generateModuleMap,
generateLogLevel,
generateLogFormat,
generateDebug,
generateNodeFlags,
generateUseHostFlag,
} = require('./generateContainerArgs');

async function dockerPull(imageReference, logStream) {
return spawnAndPipe('docker', ['pull', imageReference], logStream);
}

async function startAppContainer({
imageReference,
containerShellCommand,
ports /* = [] */,
envVars /* = new Map() */,
mounts /* = new Map() */,
name,
network,
logStream,
}) {
return spawnAndPipe(
'docker',
[
'run',
'-t',
...ports.map((port) => `-p=${port}:${port}`),
...[...envVars.entries()].map(([envVarName, envVarValue]) => `-e=${envVarName}=${envVarValue}`),
...[...mounts.entries()].map(([hostPath, containerPath]) => `-v=${hostPath}:${containerPath}`),
name ? `--name=${name}` : null,
network ? `--network=${network}` : null,
imageReference,
'/bin/sh',
'-c',
containerShellCommand,
].filter(Boolean),
logStream
);
}

function generateEnvironmentVariableArgs(envVars) {
return new Map([
['NODE_ENV', 'development'],
...Object.entries(envVars),
process.env.HTTP_PROXY ? ['HTTP_PROXY', process.env.HTTP_PROXY] : null,
process.env.HTTPS_PROXY ? ['HTTPS_PROXY', process.env.HTTPS_PROXY] : null,
process.env.NO_PROXY ? ['NO_PROXY', process.env.NO_PROXY] : null,
process.env.HTTP_PORT ? ['HTTP_PORT', process.env.HTTP_PORT] : null,
process.env.HTTP_ONE_APP_DEV_CDN_PORT
? ['HTTP_ONE_APP_DEV_CDN_PORT', process.env.HTTP_ONE_APP_DEV_CDN_PORT]
: null,
process.env.HTTP_ONE_APP_DEV_PROXY_SERVER_PORT
? ['HTTP_ONE_APP_DEV_PROXY_SERVER_PORT', process.env.HTTP_ONE_APP_DEV_PROXY_SERVER_PORT]
: null,
process.env.HTTP_METRICS_PORT
? ['HTTP_METRICS_PORT', process.env.HTTP_METRICS_PORT]
: null,
].filter(Boolean));
}

const generateSetMiddlewareCommand = (pathToMiddlewareFile) => {
if (pathToMiddlewareFile) {
const pathArray = pathToMiddlewareFile.split(path.sep);
return `npm run set-middleware '/opt/module-workspace/${pathArray[pathArray.length - 2]}/${pathArray[pathArray.length - 1]}' &&`;
}
return '';
};

const generateSetDevEndpointsCommand = (pathToDevEndpointsFile) => {
if (pathToDevEndpointsFile) {
const pathArray = pathToDevEndpointsFile.split(path.sep);
return `npm run set-dev-endpoints '/opt/module-workspace/${pathArray[pathArray.length - 2]}/${pathArray[pathArray.length - 1]}' &&`;
}
return '';
};

const generateUseMocksFlag = (shouldUseMocks) => (shouldUseMocks ? '-m' : '');

const generateNpmConfigCommands = () => 'npm config set update-notifier false &&';

const generateServeModuleCommands = (modules) => {
let command = '';
if (modules && modules.length > 0) {
modules.forEach((modulePath) => {
const moduleRootDir = path.basename(modulePath);
command += `npm run serve-module '/opt/module-workspace/${moduleRootDir}' &&`;
});
}
return command;
};

const generateModuleMap = (moduleMapUrl) => (moduleMapUrl ? `--module-map-url=${moduleMapUrl}` : '');

const generateLogLevel = (logLevel) => (logLevel ? `--log-level=${logLevel}` : '');

const generateLogFormat = (logFormat) => (logFormat ? `--log-format=${logFormat}` : '');

const generateDebug = (port, useDebug) => (useDebug ? `--inspect=0.0.0.0:${port}` : '');

// NOTE: Node 12 does not support --dns-result-order or --no-experimental-fetch
// So we have to remove those flags if the one-app version is less than 5.13.0
// 5.13.0 is when node 16 was introduced.
const generateNodeFlags = (appVersion) => {
if (semver.intersects(appVersion, '^5.13.0', { includePrerelease: true })) {
return '--dns-result-order=ipv4first --no-experimental-fetch';
}
return '';
};

module.exports = async function startApp({
moduleMapUrl,
rootModuleName,
Expand All @@ -160,6 +57,15 @@ module.exports = async function startApp({
logLevel,
logFormat,
}) {
try {
// need a command that both invokes the CLI but also connects to the daemon
await asyncSpawn('docker', ['version']);
} catch (error) {
throw new Error(
`Error running docker. Are you sure you have it installed?\nFor installation and setup details see https://www.docker.com/products/docker-desktop\nExit code ${error.code}, error messages ${error.stderr.toString('utf8')}`
);
}

if (createDockerNetwork) {
if (!dockerNetworkToJoin) {
throw new Error(
Expand All @@ -176,8 +82,6 @@ module.exports = async function startApp({
}
}

const generateUseHostFlag = () => (useHost ? '--use-host' : '');

const appPort = Number.parseInt(process.env.HTTP_PORT, 10) || 3000;
const devCDNPort = Number.parseInt(process.env.HTTP_ONE_APP_DEV_CDN_PORT, 10) || 3001;
const devProxyServerPort = Number.parseInt(
Expand All @@ -202,7 +106,7 @@ module.exports = async function startApp({

const hostNodeExtraCaCerts = envVars.NODE_EXTRA_CA_CERTS || process.env.NODE_EXTRA_CA_CERTS;
if (hostNodeExtraCaCerts) {
console.log('mounting host NODE_EXTRA_CA_CERTS');
console.log('adding NODE_EXTRA_CA_CERTS to the volume mount list');
const mountPath = '/opt/certs.pem';
mounts.set(hostNodeExtraCaCerts, mountPath);
containerEnvVars.set('NODE_EXTRA_CA_CERTS', mountPath);
Expand All @@ -221,7 +125,7 @@ module.exports = async function startApp({
`lib/server/index.js --root-module-name=${rootModuleName}`,
generateModuleMap(moduleMapUrl),
generateUseMocksFlag(parrotMiddlewareFile),
generateUseHostFlag(),
generateUseHostFlag(useHost),
generateLogLevel(logLevel),
generateLogFormat(logFormat),
].filter(Boolean).join(' ');
Expand Down Expand Up @@ -254,22 +158,13 @@ module.exports = async function startApp({
logStream: logFileStream,
});
} catch (error) {
throw new Error(
'Error running docker. Are you sure you have it installed? For installation and setup details see https://www.docker.com/products/docker-desktop',
{ cause: error }
);
if (error.stderr) {
throw new Error(error.stderr.toString('utf8'));
}
throw error;
} finally {
if (logFileStream) {
logFileStream.end();
}
}

[
'SIGINT',
'SIGTERM',
].forEach((signal) => {
// process is a global referring to current running process https://nodejs.org/api/globals.html#globals_process
/* istanbul ignore next */
process.on(signal, () => 'noop - just need to pass signal to one app process so it can handle it');
});
};
Loading
Loading