From 0149e76f5d573a4a62ed11943d310605e952214c Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Mon, 15 Mar 2021 08:49:56 +0100
Subject: [PATCH] feat(mix): migrate to modern docker handling (#9132)

---
 .../mix/__snapshots__/artifacts.spec.ts.snap  | 33 ++++++--
 lib/manager/mix/artifacts.spec.ts             | 82 ++++++++++---------
 lib/manager/mix/artifacts.ts                  | 34 ++------
 3 files changed, 82 insertions(+), 67 deletions(-)

diff --git a/lib/manager/mix/__snapshots__/artifacts.spec.ts.snap b/lib/manager/mix/__snapshots__/artifacts.spec.ts.snap
index d4cade50af..8606c3a66f 100644
--- a/lib/manager/mix/__snapshots__/artifacts.spec.ts.snap
+++ b/lib/manager/mix/__snapshots__/artifacts.spec.ts.snap
@@ -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 {
@@ -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",
@@ -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 {
@@ -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",
diff --git a/lib/manager/mix/artifacts.spec.ts b/lib/manager/mix/artifacts.spec.ts
index fed8cefc60..e1056f0938 100644
--- a/lib/manager/mix/artifacts.spec.ts
+++ b/lib/manager/mix/artifacts.spec.ts
@@ -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({
@@ -35,6 +34,7 @@ describe('.updateArtifacts()', () => {
       })
     ).toBeNull();
   });
+
   it('returns null if no updatedDeps were provided', async () => {
     expect(
       await updateArtifacts({
@@ -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({
@@ -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',
@@ -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(
@@ -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();
+  });
 });
diff --git a/lib/manager/mix/artifacts.ts b/lib/manager/mix/artifacts.ts
index 06bcc56ca2..118730bec3 100644
--- a/lib/manager/mix/artifacts.ts
+++ b/lib/manager/mix/artifacts.ts
@@ -1,8 +1,7 @@
 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';
 
@@ -10,7 +9,6 @@ export async function updateArtifacts({
   packageFileName,
   updatedDeps,
   newPackageFileContent,
-  config,
 }: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
   logger.debug(`mix.getArtifacts(${packageFileName})`);
   if (updatedDeps.length < 1) {
@@ -18,12 +16,6 @@ export async function updateArtifacts({
     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);
@@ -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'
     );
 
-- 
GitLab