2020-05-01 16:03:48 +00:00
|
|
|
import { ExecOptions as ChildProcessExecOptions } from 'child_process';
|
2020-11-12 20:46:08 +00:00
|
|
|
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';
|
2020-01-22 08:19:29 +00:00
|
|
|
import {
|
|
|
|
BinarySource,
|
2020-05-01 16:03:48 +00:00
|
|
|
DockerOptions,
|
2020-01-22 08:19:29 +00:00
|
|
|
ExecConfig,
|
|
|
|
ExecResult,
|
2020-05-01 16:03:48 +00:00
|
|
|
Opt,
|
2020-01-22 08:19:29 +00:00
|
|
|
RawExecOptions,
|
|
|
|
rawExec,
|
|
|
|
} from './common';
|
2020-05-01 16:03:48 +00:00
|
|
|
import {
|
|
|
|
generateDockerCommand,
|
|
|
|
removeDanglingContainers,
|
|
|
|
removeDockerContainer,
|
|
|
|
} from './docker';
|
|
|
|
import { getChildProcessEnv } from './env';
|
2020-01-20 15:50:32 +00:00
|
|
|
|
|
|
|
const execConfig: ExecConfig = {
|
|
|
|
binarySource: null,
|
2021-01-19 08:11:45 +00:00
|
|
|
customEnvVariables: null,
|
2020-09-08 10:59:47 +00:00
|
|
|
dockerImagePrefix: null,
|
2020-01-20 15:50:32 +00:00
|
|
|
dockerUser: null,
|
|
|
|
localDir: null,
|
|
|
|
cacheDir: null,
|
|
|
|
};
|
|
|
|
|
2020-03-09 14:56:50 +00:00
|
|
|
export async function setExecConfig(
|
|
|
|
config: Partial<RenovateConfig>
|
|
|
|
): Promise<void> {
|
2020-01-20 15:50:32 +00:00
|
|
|
for (const key of Object.keys(execConfig)) {
|
|
|
|
const value = config[key];
|
|
|
|
execConfig[key] = value || null;
|
|
|
|
}
|
2020-03-09 14:56:50 +00:00
|
|
|
if (execConfig.binarySource === 'docker') {
|
|
|
|
await removeDanglingContainers();
|
|
|
|
}
|
2020-01-12 18:47:39 +00:00
|
|
|
}
|
|
|
|
|
2020-01-14 11:12:03 +00:00
|
|
|
type ExtraEnv<T = unknown> = Record<string, T>;
|
|
|
|
|
2019-12-23 14:59:57 +00:00
|
|
|
export interface ExecOptions extends ChildProcessExecOptions {
|
2020-02-12 16:54:22 +00:00
|
|
|
cwdFile?: string;
|
2020-01-20 15:50:32 +00:00
|
|
|
extraEnv?: Opt<ExtraEnv>;
|
|
|
|
docker?: Opt<DockerOptions>;
|
2019-12-23 14:59:57 +00:00
|
|
|
}
|
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> {
|
2020-01-24 09:42:09 +00:00
|
|
|
const extraEnvEntries = Object.entries({ ...extraEnv }).filter(([_, val]) => {
|
2020-03-17 11:15:22 +00:00
|
|
|
if (val === null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (val === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-01-24 09:42:09 +00:00
|
|
|
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]) => {
|
2020-03-17 11:15:22 +00:00
|
|
|
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 || {});
|
2020-01-24 09:42:09 +00:00
|
|
|
return extraEnvKeys.filter(
|
2020-04-12 16:09:36 +00:00
|
|
|
(key) => typeof childEnv[key] === 'string' && childEnv[key].length > 0
|
2020-01-24 09:42:09 +00:00
|
|
|
);
|
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> {
|
2021-01-19 08:11:45 +00:00
|
|
|
const { env, docker, cwdFile } = opts;
|
|
|
|
const extraEnv = { ...opts.extraEnv, ...execConfig.customEnvVariables };
|
2020-01-21 17:04:50 +00:00
|
|
|
let cwd;
|
|
|
|
// istanbul ignore if
|
2020-02-12 16:54:22 +00:00
|
|
|
if (cwdFile) {
|
|
|
|
cwd = join(execConfig.localDir, dirname(cwdFile));
|
2020-01-21 17:04:50 +00:00
|
|
|
}
|
|
|
|
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;
|
2020-02-12 16:54:22 +00:00
|
|
|
delete execOptions.cwdFile;
|
2020-01-10 14:18:41 +00:00
|
|
|
|
2020-01-22 08:19:29 +00:00
|
|
|
const rawExecOptions: RawExecOptions = {
|
2020-01-10 14:18:41 +00:00
|
|
|
encoding: 'utf-8',
|
2020-01-14 11:12:03 +00:00
|
|
|
...execOptions,
|
|
|
|
env: childEnv,
|
2020-01-12 18:47:39 +00:00
|
|
|
cwd,
|
2020-01-10 14:18:41 +00:00
|
|
|
};
|
2020-03-09 12:33:45 +00:00
|
|
|
// Set default timeout to 15 minutes
|
|
|
|
rawExecOptions.timeout = rawExecOptions.timeout || 15 * 60 * 1000;
|
2020-09-10 05:00:40 +00:00
|
|
|
// Set default max buffer size to 10MB
|
|
|
|
rawExecOptions.maxBuffer = rawExecOptions.maxBuffer || 10 * 1024 * 1024;
|
2020-01-14 11:12:03 +00:00
|
|
|
|
2020-01-16 21:05:50 +00:00
|
|
|
let commands = typeof cmd === 'string' ? [cmd] : cmd;
|
2020-03-09 12:33:45 +00:00
|
|
|
const useDocker = execConfig.binarySource === BinarySource.Docker && docker;
|
|
|
|
if (useDocker) {
|
2020-01-20 15:50:32 +00:00
|
|
|
logger.debug('Using docker to execute');
|
2020-01-10 14:18:41 +00:00
|
|
|
const dockerOptions = {
|
|
|
|
...docker,
|
|
|
|
cwd,
|
2020-01-14 11:12:03 +00:00
|
|
|
envVars: dockerEnvVars(extraEnv, childEnv),
|
2020-01-10 14:18:41 +00:00
|
|
|
};
|
2019-12-23 14:59:57 +00:00
|
|
|
|
2020-01-22 10:45:21 +00:00
|
|
|
const dockerCommand = await generateDockerCommand(
|
|
|
|
commands,
|
2020-01-22 10:08:29 +00:00
|
|
|
dockerOptions,
|
|
|
|
execConfig
|
|
|
|
);
|
|
|
|
commands = [dockerCommand];
|
2020-01-16 21:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let res: ExecResult | null = null;
|
2020-01-22 08:19:29 +00:00
|
|
|
for (const rawExecCommand of commands) {
|
2020-05-05 10:05:30 +00:00
|
|
|
const startTime = Date.now();
|
2020-03-09 12:33:45 +00:00
|
|
|
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');
|
2020-03-09 12:33:45 +00:00
|
|
|
try {
|
|
|
|
res = await rawExec(rawExecCommand, rawExecOptions);
|
|
|
|
} catch (err) {
|
|
|
|
logger.trace({ err }, 'rawExec err');
|
|
|
|
clearTimeout(timer);
|
2020-03-19 07:20:26 +00:00
|
|
|
if (useDocker) {
|
2020-08-27 07:11:10 +00:00
|
|
|
await removeDockerContainer(docker.image).catch((removeErr: Error) => {
|
|
|
|
const message: string = err.message;
|
2020-03-19 07:20:26 +00:00
|
|
|
throw new Error(
|
2020-08-27 07:11:10 +00:00
|
|
|
`Error: "${removeErr.message}" - Original Error: "${message}"`
|
2020-03-19 07:20:26 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2020-03-09 12:33:45 +00:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
clearTimeout(timer);
|
2020-05-05 10:05:30 +00:00
|
|
|
const durationMs = Math.round(Date.now() - startTime);
|
2020-01-16 21:05:50 +00:00
|
|
|
if (res) {
|
|
|
|
logger.debug(
|
2020-01-22 08:19:29 +00:00
|
|
|
{
|
|
|
|
cmd: rawExecCommand,
|
2020-05-05 10:05:30 +00:00
|
|
|
durationMs,
|
2020-01-22 08:19:29 +00:00
|
|
|
stdout: res.stdout,
|
|
|
|
stderr: res.stderr,
|
|
|
|
},
|
2020-01-16 21:05:50 +00:00
|
|
|
'exec completed'
|
|
|
|
);
|
|
|
|
}
|
2019-12-23 14:59:57 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 07:14:44 +00:00
|
|
|
return res;
|
2019-07-23 12:39:15 +00:00
|
|
|
}
|