mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 18:19:51 +00:00
193 lines
5.4 KiB
TypeScript
193 lines
5.4 KiB
TypeScript
import is from '@sindresorhus/is';
|
|
import upath from 'upath';
|
|
import { GlobalConfig } from '../../config/global';
|
|
import { TEMPORARY_ERROR } from '../../constants/error-messages';
|
|
import { logger } from '../../logger';
|
|
import { generateInstallCommands, isDynamicInstall } from './buildpack';
|
|
import { rawExec } from './common';
|
|
import { generateDockerCommand, removeDockerContainer } from './docker';
|
|
import { getChildProcessEnv } from './env';
|
|
import type {
|
|
DockerOptions,
|
|
ExecOptions,
|
|
ExecResult,
|
|
ExtraEnv,
|
|
Opt,
|
|
RawExecOptions,
|
|
} from './types';
|
|
|
|
function getChildEnv({
|
|
extraEnv,
|
|
env: forcedEnv = {},
|
|
}: ExecOptions): Record<string, string> {
|
|
const globalConfigEnv = GlobalConfig.get('customEnvVariables');
|
|
|
|
const inheritedKeys: string[] = [];
|
|
for (const [key, val] of Object.entries(extraEnv ?? {})) {
|
|
if (is.string(val)) {
|
|
inheritedKeys.push(key);
|
|
}
|
|
}
|
|
|
|
const parentEnv = getChildProcessEnv(inheritedKeys);
|
|
const combinedEnv = {
|
|
...extraEnv,
|
|
...parentEnv,
|
|
...globalConfigEnv,
|
|
...forcedEnv,
|
|
};
|
|
|
|
const result: Record<string, string> = {};
|
|
for (const [key, val] of Object.entries(combinedEnv)) {
|
|
if (is.string(val)) {
|
|
result[key] = `${val}`;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function dockerEnvVars(extraEnv: ExtraEnv, childEnv: ExtraEnv): string[] {
|
|
const extraEnvKeys = Object.keys(extraEnv || {});
|
|
return extraEnvKeys.filter((key) => is.nonEmptyString(childEnv[key]));
|
|
}
|
|
|
|
function getCwd({ cwd, cwdFile }: ExecOptions): string | undefined {
|
|
const defaultCwd = GlobalConfig.get('localDir');
|
|
const paramCwd = cwdFile
|
|
? upath.join(defaultCwd, upath.dirname(cwdFile))
|
|
: cwd;
|
|
return paramCwd ?? defaultCwd;
|
|
}
|
|
|
|
function getRawExecOptions(opts: ExecOptions): RawExecOptions {
|
|
const defaultExecutionTimeout = GlobalConfig.get('executionTimeout');
|
|
const childEnv = getChildEnv(opts);
|
|
const cwd = getCwd(opts);
|
|
const rawExecOptions: RawExecOptions = {
|
|
cwd,
|
|
encoding: 'utf-8',
|
|
env: childEnv,
|
|
maxBuffer: opts.maxBuffer,
|
|
timeout: opts.timeout,
|
|
};
|
|
// Set default timeout config.executionTimeout if specified; othrwise to 15 minutes
|
|
if (!rawExecOptions.timeout) {
|
|
if (defaultExecutionTimeout) {
|
|
rawExecOptions.timeout = defaultExecutionTimeout * 60 * 1000;
|
|
} else {
|
|
rawExecOptions.timeout = 15 * 60 * 1000;
|
|
}
|
|
}
|
|
|
|
// Set default max buffer size to 10MB
|
|
rawExecOptions.maxBuffer = rawExecOptions.maxBuffer ?? 10 * 1024 * 1024;
|
|
return rawExecOptions;
|
|
}
|
|
|
|
function isDocker(docker: Opt<DockerOptions>): docker is DockerOptions {
|
|
const { binarySource } = GlobalConfig.get();
|
|
return binarySource === 'docker' && !!docker;
|
|
}
|
|
|
|
interface RawExecArguments {
|
|
rawCommands: string[];
|
|
rawOptions: RawExecOptions;
|
|
}
|
|
|
|
async function prepareRawExec(
|
|
cmd: string | string[],
|
|
opts: ExecOptions = {}
|
|
): Promise<RawExecArguments> {
|
|
const { docker } = opts;
|
|
const { customEnvVariables } = GlobalConfig.get();
|
|
|
|
const rawOptions = getRawExecOptions(opts);
|
|
|
|
let rawCommands = typeof cmd === 'string' ? [cmd] : cmd;
|
|
|
|
if (isDocker(docker)) {
|
|
logger.debug({ image: docker.image }, 'Using docker to execute');
|
|
const extraEnv = { ...opts.extraEnv, ...customEnvVariables };
|
|
const childEnv = getChildEnv(opts);
|
|
const envVars = dockerEnvVars(extraEnv, childEnv);
|
|
const cwd = getCwd(opts);
|
|
const dockerOptions: DockerOptions = { ...docker, cwd, envVars };
|
|
const preCommands = [
|
|
...(await generateInstallCommands(opts.toolConstraints)),
|
|
...(opts.preCommands ?? []),
|
|
];
|
|
const dockerCommand = await generateDockerCommand(
|
|
rawCommands,
|
|
preCommands,
|
|
dockerOptions
|
|
);
|
|
rawCommands = [dockerCommand];
|
|
} else if (isDynamicInstall(opts.toolConstraints)) {
|
|
logger.debug('Using buildpack dynamic installs');
|
|
rawCommands = [
|
|
...(await generateInstallCommands(opts.toolConstraints)),
|
|
...rawCommands,
|
|
];
|
|
}
|
|
|
|
return { rawCommands, rawOptions };
|
|
}
|
|
|
|
export async function exec(
|
|
cmd: string | string[],
|
|
opts: ExecOptions = {}
|
|
): Promise<ExecResult> {
|
|
const { docker } = opts;
|
|
const dockerChildPrefix =
|
|
GlobalConfig.get('dockerChildPrefix') ?? 'renovate_';
|
|
|
|
const { rawCommands, rawOptions } = await prepareRawExec(cmd, opts);
|
|
const useDocker = isDocker(docker);
|
|
|
|
let res: ExecResult = { stdout: '', stderr: '' };
|
|
for (const rawCmd of rawCommands) {
|
|
const startTime = Date.now();
|
|
if (useDocker) {
|
|
await removeDockerContainer(docker.image, dockerChildPrefix);
|
|
}
|
|
logger.debug({ command: rawCmd }, 'Executing command');
|
|
logger.trace({ commandOptions: rawOptions }, 'Command options');
|
|
try {
|
|
res = await rawExec(rawCmd, rawOptions);
|
|
} catch (err) {
|
|
logger.debug({ err }, 'rawExec err');
|
|
if (useDocker) {
|
|
await removeDockerContainer(docker.image, dockerChildPrefix).catch(
|
|
(removeErr: Error) => {
|
|
const message: string = err.message;
|
|
throw new Error(
|
|
`Error: "${removeErr.message}" - Original Error: "${message}"`
|
|
);
|
|
}
|
|
);
|
|
}
|
|
if (err.signal === `SIGTERM`) {
|
|
logger.debug(
|
|
{ err },
|
|
'exec interrupted by SIGTERM - run needs to be aborted'
|
|
);
|
|
throw new Error(TEMPORARY_ERROR);
|
|
}
|
|
throw err;
|
|
}
|
|
const durationMs = Math.round(Date.now() - startTime);
|
|
logger.debug(
|
|
{
|
|
cmd: rawCmd,
|
|
durationMs,
|
|
stdout: res.stdout,
|
|
stderr: res.stderr,
|
|
},
|
|
'exec completed'
|
|
);
|
|
}
|
|
|
|
return res;
|
|
}
|