Skip to content
Snippets Groups Projects
Commit 9277da89 authored by Sergio Zharinov's avatar Sergio Zharinov Committed by Rhys Arkins
Browse files

refactor: Introduce centralized `docker run` execution (#4983)

parent 6d6a9a67
No related branches found
No related tags found
No related merge requests found
type Opt<T> = T | null | undefined;
export interface DockerOptions {
image: string;
dockerUser?: Opt<string>;
volumes?: Opt<Opt<string>[]>;
envVars?: Opt<Opt<string>[]>;
cwd?: Opt<string>;
tag?: Opt<string>;
cmdWrap?: Opt<string>;
}
export function dockerCmd(cmd: string, options: DockerOptions): string {
const { dockerUser, volumes, envVars, cwd, image, tag, cmdWrap } = options;
const result = ['docker run --rm'];
if (dockerUser) result.push(`--user=${dockerUser}`);
if (volumes)
result.push(...volumes.filter(x => !!x).map(vol => `-v "${vol}":"${vol}"`));
if (envVars) result.push(...envVars.filter(x => !!x).map(e => `-e ${e}`));
if (cwd) result.push(`-w "${cwd}"`);
const taggedImage = tag ? `${image}:${tag}` : `${image}`;
result.push(taggedImage);
if (cmdWrap) {
const regex = /{{\s*cmd\s*}}/;
if (regex.test(cmdWrap)) {
result.push(cmdWrap.replace(regex, cmd));
} /* istanbul ignore next */ else {
throw new Error(
'dockerCmd(): Provide {{ cmd }} placeholder inside `wrapCmd` parameter'
);
}
} else {
result.push(cmd);
}
return result.join(' ');
}
// istanbul ignore file
import { promisify } from 'util'; import { promisify } from 'util';
import { exec as cpExec, ExecOptions } from 'child_process'; import {
exec as cpExec,
ExecOptions as ChildProcessExecOptions,
} from 'child_process';
import { dockerCmd, DockerOptions } from './docker';
const pExec = promisify(cpExec); const pExec: (
cmd: string,
opts: ChildProcessExecOptions & { encoding: string }
) => Promise<ExecResult> = promisify(cpExec);
export interface ExecOptions extends ChildProcessExecOptions {
docker?: DockerOptions;
}
export interface ExecResult { export interface ExecResult {
stdout: string; stdout: string;
stderr: string; stderr: string;
} }
export function exec(cmd: string, options?: ExecOptions): Promise<ExecResult> { export function exec(
return pExec(cmd, { ...options, encoding: 'utf-8' }); cmd: string,
options?: ExecOptions & { docker?: DockerOptions }
): Promise<ExecResult> {
let pExecCommand = cmd;
const pExecOptions = { ...options, encoding: 'utf-8' };
if (options && options.docker) {
const { cwd, docker } = options;
pExecCommand = dockerCmd(cmd, { ...docker, cwd });
delete pExecOptions.docker;
}
return pExec(pExecCommand, pExecOptions);
} }
import {
exec as _cpExec,
ExecOptions as ChildProcessExecOptions,
} from 'child_process';
import { exec, ExecOptions } from '../../lib/util/exec';
const cpExec: jest.Mock<typeof _cpExec> = _cpExec as any;
jest.mock('child_process');
describe('exec()', () => {
it('wraps original exec() from "child_process" module', async () => {
const cases = [
['foo', {}, 'foo', { encoding: 'utf-8' }],
[
'foo',
{ docker: { image: 'bar' } },
'docker run --rm bar foo',
{ encoding: 'utf-8' },
],
[
'foo',
{ docker: { image: 'bar', cmdWrap: 'su user -c {{ cmd }}' } },
'docker run --rm bar su user -c foo',
{ encoding: 'utf-8' },
],
[
'foo',
{ docker: { image: 'bar', tag: 'latest' } },
'docker run --rm bar:latest foo',
{ encoding: 'utf-8' },
],
[
'foo',
{ docker: { image: 'bar' }, cwd: '/current/working/directory' },
'docker run --rm -w "/current/working/directory" bar foo',
{ encoding: 'utf-8', cwd: '/current/working/directory' },
],
[
'foo',
{
docker: { image: 'bar', dockerUser: 'baz' },
},
'docker run --rm --user=baz bar foo',
{ encoding: 'utf-8' },
],
[
'foo',
{
docker: { image: 'bar', volumes: ['/path/to/volume'] },
},
'docker run --rm -v "/path/to/volume":"/path/to/volume" bar foo',
{ encoding: 'utf-8' },
],
[
'foo',
{
docker: { image: 'bar', envVars: ['SOMETHING_SENSIBLE'] },
},
'docker run --rm -e SOMETHING_SENSIBLE bar foo',
{ encoding: 'utf-8' },
],
];
for (const [cmd, opts, expectedCmd, expectedOpts] of cases) {
let actualCmd: string | null = null;
let actualOpts: ChildProcessExecOptions | null = null;
cpExec.mockImplementationOnce((execCmd, execOpts, callback) => {
actualCmd = execCmd;
actualOpts = execOpts;
callback(null, { stdout: '', stderr: '' });
return undefined;
});
await exec(cmd as string, opts as ExecOptions);
expect(actualCmd).toEqual(expectedCmd);
expect(actualOpts).toEqual(expectedOpts);
}
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment