renovate/lib/util/exec/index.ts

192 lines
4.8 KiB
TypeScript
Raw Normal View History

2020-05-01 16:03:48 +00:00
import { ExecOptions as ChildProcessExecOptions } from 'child_process';
import { dirname, join } from 'upath';
2020-05-01 16:03:48 +00:00
import { RenovateConfig } from '../../config/common';
2020-01-15 07:14:44 +00:00
import { logger } from '../../logger';
import {
BinarySource,
2020-05-01 16:03:48 +00:00
DockerOptions,
ExecConfig,
ExecResult,
2020-05-01 16:03:48 +00:00
Opt,
RawExecOptions,
rawExec,
} from './common';
2020-05-01 16:03:48 +00:00
import {
generateDockerCommand,
removeDanglingContainers,
removeDockerContainer,
} from './docker';
import { getChildProcessEnv } from './env';
const execConfig: ExecConfig = {
binarySource: null,
customEnvVariables: null,
dockerImagePrefix: null,
dockerUser: null,
localDir: null,
cacheDir: null,
};
export async function setExecConfig(
config: Partial<RenovateConfig>
): Promise<void> {
for (const key of Object.keys(execConfig)) {
const value = config[key];
execConfig[key] = value || null;
}
if (execConfig.binarySource === 'docker') {
await removeDanglingContainers();
}
}
2020-01-14 11:12:03 +00:00
type ExtraEnv<T = unknown> = Record<string, T>;
export interface ExecOptions extends ChildProcessExecOptions {
cwdFile?: string;
extraEnv?: Opt<ExtraEnv>;
docker?: Opt<DockerOptions>;
}
2019-07-23 12:39:15 +00:00
2020-01-14 11:12:03 +00:00
function createChildEnv(
env: NodeJS.ProcessEnv,
extraEnv: ExtraEnv
): ExtraEnv<string> {
const extraEnvEntries = Object.entries({ ...extraEnv }).filter(([_, val]) => {
if (val === null) {
return false;
}
if (val === undefined) {
return false;
}
return true;
});
const extraEnvKeys = Object.keys(extraEnvEntries);
2020-01-14 11:12:03 +00:00
const childEnv =
env || extraEnv
? {
...extraEnv,
...getChildProcessEnv(extraEnvKeys),
...env,
}
: getChildProcessEnv();
const result: ExtraEnv<string> = {};
Object.entries(childEnv).forEach(([key, val]) => {
if (val === null) {
return;
}
if (val === undefined) {
return;
}
2020-01-14 11:12:03 +00:00
result[key] = val.toString();
});
return result;
}
function dockerEnvVars(
extraEnv: ExtraEnv,
childEnv: ExtraEnv<string>
): string[] {
const extraEnvKeys = Object.keys(extraEnv || {});
return extraEnvKeys.filter(
(key) => typeof childEnv[key] === 'string' && childEnv[key].length > 0
);
2020-01-14 11:12:03 +00:00
}
2020-01-15 07:14:44 +00:00
export async function exec(
2020-01-14 11:12:03 +00:00
cmd: string | string[],
opts: ExecOptions = {}
): Promise<ExecResult> {
const { env, docker, cwdFile } = opts;
const extraEnv = { ...opts.extraEnv, ...execConfig.customEnvVariables };
let cwd;
// istanbul ignore if
if (cwdFile) {
cwd = join(execConfig.localDir, dirname(cwdFile));
}
cwd = cwd || opts.cwd || execConfig.localDir;
2020-01-14 11:12:03 +00:00
const childEnv = createChildEnv(env, extraEnv);
const execOptions: ExecOptions = { ...opts };
delete execOptions.extraEnv;
delete execOptions.docker;
delete execOptions.cwdFile;
const rawExecOptions: RawExecOptions = {
encoding: 'utf-8',
2020-01-14 11:12:03 +00:00
...execOptions,
env: childEnv,
cwd,
};
// Set default timeout to 15 minutes
rawExecOptions.timeout = rawExecOptions.timeout || 15 * 60 * 1000;
// Set default max buffer size to 10MB
rawExecOptions.maxBuffer = rawExecOptions.maxBuffer || 10 * 1024 * 1024;
2020-01-14 11:12:03 +00:00
let commands = typeof cmd === 'string' ? [cmd] : cmd;
const useDocker = execConfig.binarySource === BinarySource.Docker && docker;
if (useDocker) {
logger.debug('Using docker to execute');
const dockerOptions = {
...docker,
cwd,
2020-01-14 11:12:03 +00:00
envVars: dockerEnvVars(extraEnv, childEnv),
};
const dockerCommand = await generateDockerCommand(
commands,
2020-01-22 10:08:29 +00:00
dockerOptions,
execConfig
);
commands = [dockerCommand];
}
let res: ExecResult | null = null;
for (const rawExecCommand of commands) {
const startTime = Date.now();
let timer;
const { timeout } = rawExecOptions;
if (useDocker) {
await removeDockerContainer(docker.image);
// istanbul ignore next
timer = setTimeout(() => {
removeDockerContainer(docker.image); // eslint-disable-line
logger.info({ timeout, rawExecCommand }, 'Docker run timed out');
}, timeout);
}
2020-03-09 07:18:03 +00:00
logger.debug({ command: rawExecCommand }, 'Executing command');
logger.trace({ commandOptions: rawExecOptions }, 'Command options');
try {
res = await rawExec(rawExecCommand, rawExecOptions);
} catch (err) {
logger.trace({ err }, 'rawExec err');
clearTimeout(timer);
if (useDocker) {
await removeDockerContainer(docker.image).catch((removeErr: Error) => {
const message: string = err.message;
throw new Error(
`Error: "${removeErr.message}" - Original Error: "${message}"`
);
});
}
throw err;
}
clearTimeout(timer);
const durationMs = Math.round(Date.now() - startTime);
if (res) {
logger.debug(
{
cmd: rawExecCommand,
durationMs,
stdout: res.stdout,
stderr: res.stderr,
},
'exec completed'
);
}
}
2020-01-15 07:14:44 +00:00
return res;
2019-07-23 12:39:15 +00:00
}