Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mix): migrate to modern docker handling #9132

Merged
merged 3 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 28 additions & 5 deletions lib/manager/mix/__snapshots__/artifacts.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`.updateArtifacts() catches errors 1`] = `
exports[`manager/mix/artifacts catches exec errors 1`] = `
Array [
Object {
"artifactError": Object {
"lockFile": "mix.lock",
"stderr": "exec-error",
},
},
]
`;

exports[`manager/mix/artifacts catches write errors 1`] = `
Array [
Object {
"artifactError": Object {
Expand All @@ -11,7 +22,7 @@ Array [
]
`;

exports[`.updateArtifacts() returns null if unchanged 1`] = `
exports[`manager/mix/artifacts returns null if unchanged 1`] = `
Array [
Object {
"cmd": "mix deps.update plug",
Expand All @@ -34,7 +45,7 @@ Array [
]
`;

exports[`.updateArtifacts() returns updated mix.lock 1`] = `
exports[`manager/mix/artifacts returns updated mix.lock 1`] = `
Array [
Object {
"file": Object {
Expand All @@ -45,10 +56,22 @@ Array [
]
`;

exports[`.updateArtifacts() returns updated mix.lock 2`] = `
exports[`manager/mix/artifacts returns updated mix.lock 2`] = `
Array [
Object {
"cmd": "docker run --rm -v /tmp/github/some/repo:/tmp/github/some/repo -w /tmp/github/some/repo renovate/elixir mix deps.update plug",
"cmd": "docker pull renovate/elixir",
"options": Object {
"encoding": "utf-8",
},
},
Object {
"cmd": "docker ps --filter name=renovate_elixir -aq",
"options": Object {
"encoding": "utf-8",
},
},
Object {
"cmd": "docker run --rm --name=renovate_elixir --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -w \\"/tmp/github/some/repo\\" renovate/elixir bash -l -c \\"mix deps.update plug\\"",
"options": Object {
"cwd": "/tmp/github/some/repo",
"encoding": "utf-8",
Expand Down
82 changes: 45 additions & 37 deletions lib/manager/mix/artifacts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import { exec as _exec } from 'child_process';
import _fs from 'fs-extra';
import { envMock, mockExecAll } from '../../../test/exec-util';
import { mocked } from '../../../test/util';
import { join } from 'upath';
import { envMock, exec, mockExecAll } from '../../../test/exec-util';
import { env, fs, getName } from '../../../test/util';
import { setExecConfig } from '../../util/exec';
import { BinarySource } from '../../util/exec/common';
import * as _env from '../../util/exec/env';
import * as docker from '../../util/exec/docker';
import { updateArtifacts } from '.';

const fs: jest.Mocked<typeof _fs> = _fs as any;
const exec: jest.Mock<typeof _exec> = _exec as any;
const env = mocked(_env);

jest.mock('fs-extra');
jest.mock('child_process');
jest.mock('../../util/exec/env');
jest.mock('../../util/fs');

const config = {
localDir: '/tmp/github/some/repo',
// `join` fixes Windows CI
localDir: join('/tmp/github/some/repo'),
};

describe('.updateArtifacts()', () => {
beforeEach(() => {
describe(getName(__filename), () => {
beforeEach(async () => {
jest.resetAllMocks();
jest.resetModules();

env.getChildProcessEnv.mockReturnValue(envMock.basic);
await setExecConfig(config);
});

it('returns null if no mix.lock found', async () => {
expect(
await updateArtifacts({
Expand All @@ -35,6 +34,7 @@ describe('.updateArtifacts()', () => {
})
).toBeNull();
});

it('returns null if no updatedDeps were provided', async () => {
expect(
await updateArtifacts({
Expand All @@ -45,19 +45,7 @@ describe('.updateArtifacts()', () => {
})
).toBeNull();
});
it('returns null if no local directory found', async () => {
const noLocalDirConfig = {
localDir: null,
};
expect(
await updateArtifacts({
packageFileName: 'mix.exs',
updatedDeps: ['plug'],
newPackageFileContent: '',
config: noLocalDirConfig,
})
).toBeNull();
});

it('returns null if updatedDeps is empty', async () => {
expect(
await updateArtifacts({
Expand All @@ -68,10 +56,11 @@ describe('.updateArtifacts()', () => {
})
).toBeNull();
});

it('returns null if unchanged', async () => {
fs.readFile.mockResolvedValueOnce('Current mix.lock' as any);
fs.readLocalFile.mockResolvedValueOnce('Current mix.lock');
const execSnapshots = mockExecAll(exec);
fs.readFile.mockResolvedValueOnce('Current mix.lock' as any);
fs.readLocalFile.mockResolvedValueOnce('Current mix.lock');
expect(
await updateArtifacts({
packageFileName: 'mix.exs',
Expand All @@ -82,26 +71,30 @@ describe('.updateArtifacts()', () => {
).toBeNull();
expect(execSnapshots).toMatchSnapshot();
});

it('returns updated mix.lock', async () => {
fs.readFile.mockResolvedValueOnce('Old mix.lock' as any);
jest.spyOn(docker, 'removeDanglingContainers').mockResolvedValueOnce();
await setExecConfig({
...config,
binarySource: BinarySource.Docker,
});
fs.readLocalFile.mockResolvedValueOnce('Old mix.lock');
const execSnapshots = mockExecAll(exec);
fs.readFile.mockResolvedValueOnce('New mix.lock' as any);
fs.readLocalFile.mockResolvedValueOnce('New mix.lock');
expect(
await updateArtifacts({
packageFileName: 'mix.exs',
updatedDeps: ['plug'],
newPackageFileContent: '{}',
config: {
...config,
binarySource: BinarySource.Docker,
},
config,
})
).toMatchSnapshot();
expect(execSnapshots).toMatchSnapshot();
});
it('catches errors', async () => {
fs.readFile.mockResolvedValueOnce('Current mix.lock' as any);
fs.outputFile.mockImplementationOnce(() => {

it('catches write errors', async () => {
fs.readLocalFile.mockResolvedValueOnce('Current mix.lock');
fs.writeLocalFile.mockImplementationOnce(() => {
throw new Error('not found');
});
expect(
Expand All @@ -113,4 +106,19 @@ describe('.updateArtifacts()', () => {
})
).toMatchSnapshot();
});

it('catches exec errors', async () => {
fs.readLocalFile.mockResolvedValueOnce('Current mix.lock');
exec.mockImplementationOnce(() => {
throw new Error('exec-error');
});
expect(
await updateArtifacts({
packageFileName: 'mix.exs',
updatedDeps: ['plug'],
newPackageFileContent: '{}',
config,
})
).toMatchSnapshot();
});
});
34 changes: 9 additions & 25 deletions lib/manager/mix/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
import { quote } from 'shlex';
import { TEMPORARY_ERROR } from '../../constants/error-messages';
import { logger } from '../../logger';
import { exec } from '../../util/exec';
import { BinarySource } from '../../util/exec/common';
import { ExecOptions, exec } from '../../util/exec';
import { readLocalFile, writeLocalFile } from '../../util/fs';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';

export async function updateArtifacts({
packageFileName,
updatedDeps,
newPackageFileContent,
config,
}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
logger.debug(`mix.getArtifacts(${packageFileName})`);
if (updatedDeps.length < 1) {
logger.debug('No updated mix deps - returning null');
return null;
}

const cwd = config.localDir;
if (!cwd) {
logger.debug('No local dir specified');
return null;
}

const lockFileName = 'mix.lock';
try {
await writeLocalFile(packageFileName, newPackageFileContent);
Expand All @@ -45,30 +37,22 @@ export async function updateArtifacts({
return null;
}

const cmdParts =
config.binarySource === BinarySource.Docker
? [
'docker',
'run',
'--rm',
`-v ${cwd}:${cwd}`,
`-w ${cwd}`,
'renovate/elixir mix',
]
: ['mix'];
cmdParts.push('deps.update');
const execOptions: ExecOptions = {
cwdFile: packageFileName,
docker: { image: 'renovate/elixir' },
};
const command = ['mix', 'deps.update', ...updatedDeps.map(quote)].join(' ');

/* istanbul ignore next */
try {
const command = [...cmdParts, ...updatedDeps.map(quote)].join(' ');
await exec(command, { cwd });
await exec(command, execOptions);
} catch (err) {
// istanbul ignore if
if (err.message === TEMPORARY_ERROR) {
throw err;
}

logger.warn(
{ err, message: err.message },
{ err, message: err.message, command },
'Failed to update Mix lock file'
);

Expand Down