diff --git a/lib/util/exec/common.ts b/lib/util/exec/common.ts index 3c8464e14d36543e614247e92b9ac35bcd8944c8..c229cf62dde44b9900c7ee2c6a438c4c20655ef2 100644 --- a/lib/util/exec/common.ts +++ b/lib/util/exec/common.ts @@ -20,7 +20,10 @@ export interface ExecConfig { } export type VolumesPair = [string, string]; -export type VolumeOption = string | VolumesPair | null | undefined; +export type VolumeOption = Opt<string | VolumesPair>; + +export type DockerExtraCommand = Opt<string>; +export type DockerExtraCommands = Opt<DockerExtraCommand[]>; export interface DockerOptions { image: string; @@ -28,6 +31,8 @@ export interface DockerOptions { volumes?: Opt<VolumeOption[]>; envVars?: Opt<Opt<string>[]>; cwd?: Opt<string>; + preCommands?: DockerExtraCommands; + postCommands?: DockerExtraCommands; } export interface RawExecOptions extends ChildProcessExecOptions { diff --git a/lib/util/exec/docker/index.ts b/lib/util/exec/docker/index.ts index 2aa4b5315586b8b445f908be789f356b8d041233..94e4d9f5e9daf90455345b18c8433501617b4376 100644 --- a/lib/util/exec/docker/index.ts +++ b/lib/util/exec/docker/index.ts @@ -3,6 +3,7 @@ import { VolumesPair, DockerOptions, ExecConfig, + DockerExtraCommands, rawExec, } from '../common'; import { logger } from '../../../logger'; @@ -56,12 +57,24 @@ function prepareVolumes(volumes: VolumeOption[] = []): string[] { }); } +function prepareCommands(commands: DockerExtraCommands = []): string[] { + return commands.filter(command => command && typeof command === 'string'); +} + export async function generateDockerCommand( - cmd: string, + commands: string[], options: DockerOptions, config: ExecConfig ): Promise<string> { - const { image, tag, envVars, cwd, volumes = [] } = options; + const { + image, + tag, + envVars, + cwd, + volumes = [], + preCommands, + postCommands, + } = options; const { localDir, cacheDir, dockerUser } = config; const result = ['docker run --rm']; @@ -83,7 +96,12 @@ export async function generateDockerCommand( await prefetchDockerImage(taggedImage); result.push(taggedImage); - result.push(cmd); + const bashCommand = [ + ...prepareCommands(preCommands), + ...commands, + ...prepareCommands(postCommands), + ].join(' && '); + result.push(`bash -l -c "${bashCommand.replace(/"/g, '\\"')}"`); return result.join(' '); } diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts index 578e4a326d5ed8b512bdbe818efefae449e0f02d..a47b6e93239d09bd3bb1c68e5d02c2bce3c7bd3a 100644 --- a/lib/util/exec/index.ts +++ b/lib/util/exec/index.ts @@ -103,10 +103,8 @@ export async function exec( envVars: dockerEnvVars(extraEnv, childEnv), }; - let dockerCommand = commands.join(' && '); - dockerCommand = `bash -l -c "${dockerCommand.replace(/"/g, '\\"')}"`; - dockerCommand = await generateDockerCommand( - dockerCommand, + const dockerCommand = await generateDockerCommand( + commands, dockerOptions, execConfig ); diff --git a/test/util/exec.spec.ts b/test/util/exec.spec.ts index e182903c8da5b060fa3002a0e676412c57d29c96..453f819358e6de3df50f68c301e88901d7c2cd81 100644 --- a/test/util/exec.spec.ts +++ b/test/util/exec.spec.ts @@ -295,7 +295,7 @@ describe(`Child process execution wrapper`, () => { }, processEnv, inCmd, - inOpts: { docker: { image } }, + inOpts: { docker }, outCmd: [ dockerPullCmd, `docker run --rm --user=foobar ${defaultVolumes} -w "${cwd}" ${image} bash -l -c "${inCmd}"`, @@ -303,6 +303,30 @@ describe(`Child process execution wrapper`, () => { outOpts: [dockerPullOpts, { cwd, encoding, env: envMock.basic }], }, ], + + [ + 'Docker extra commands', + { + execConfig: { + ...execConfig, + binarySource: BinarySource.Docker, + }, + processEnv, + inCmd, + inOpts: { + docker: { + image, + preCommands: ['preCommand1', 'preCommand2', null], + postCommands: ['postCommand1', undefined, 'postCommand2'], + }, + }, + outCmd: [ + dockerPullCmd, + `docker run --rm ${defaultVolumes} -w "${cwd}" ${image} bash -l -c "preCommand1 && preCommand2 && ${inCmd} && postCommand1 && postCommand2"`, + ], + outOpts: [dockerPullOpts, { cwd, encoding, env: envMock.basic }], + }, + ], ]; test.each(testInputs)('%s', async (_msg, testOpts) => {