diff --git a/lib/modules/manager/bundler/__snapshots__/artifacts.spec.ts.snap b/lib/modules/manager/bundler/__snapshots__/artifacts.spec.ts.snap deleted file mode 100644 index 65b76e989d6925..00000000000000 --- a/lib/modules/manager/bundler/__snapshots__/artifacts.spec.ts.snap +++ /dev/null @@ -1,393 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`modules/manager/bundler/artifacts Docker .ruby-version 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:1.2.0", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker constraints options 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:latest", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:latest bash -l -c \\"install-tool bundler 3.2.1 && ruby --version && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker injects bundler host configuration as command with bundler < 2 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:1.2.0", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"install-tool bundler 1.2 && ruby --version && bundler config --local gems-private.com some-user:some-password && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker injects bundler host configuration as command with bundler == latest 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:1.2.0", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"install-tool bundler 2.3.5 && ruby --version && bundler config set --local gems-private.com some-user:some-password && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker injects bundler host configuration as command with bundler >= 2 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:1.2.0", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"install-tool bundler 2.1 && ruby --version && bundler config set --local gems-private.com some-user:some-password && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker injects bundler host configuration environment variables 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:1.2.0", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e BUNDLE_GEMS__PRIVATE__COM -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:1.2.0 bash -l -c \\"install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "BUNDLE_GEMS__PRIVATE__COM": "some-user:some-password", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts Docker invalid constraints options 1`] = ` -Array [ - Object { - "cmd": "docker pull renovate/ruby:latest", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker ps --filter name=renovate_ruby -aq", - "options": Object { - "encoding": "utf-8", - }, - }, - Object { - "cmd": "docker run --rm --name=renovate_ruby --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/cache\\":\\"/tmp/cache\\" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w \\"/tmp/github/some/repo\\" renovate/ruby:latest bash -l -c \\"install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar\\"", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "BUILDPACK_CACHE_DIR": "/tmp/cache/buildpack", - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts performs lockFileMaintenance 1`] = ` -Array [ - Object { - "cmd": "bundler lock --update", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts returns error when failing in lockFileMaintenance true 1`] = ` -Array [ - Object { - "artifactError": Object { - "lockFile": "Gemfile.lock", - "stderr": " foo was resolved to -", - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts returns error when failing in lockFileMaintenance true 2`] = ` -Array [ - Object { - "cmd": "bundler lock --update", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts returns null if Gemfile.lock was not changed 1`] = ` -Array [ - Object { - "cmd": "bundler lock --update foo bar", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts works explicit global binarySource 1`] = ` -Array [ - Object { - "cmd": "bundler lock --update foo bar", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; - -exports[`modules/manager/bundler/artifacts works for default binarySource 1`] = ` -Array [ - Object { - "cmd": "bundler lock --update foo bar", - "options": Object { - "cwd": "/tmp/github/some/repo", - "encoding": "utf-8", - "env": Object { - "GEM_HOME": "/tmp/cache/others/gem", - "HOME": "/home/user", - "HTTPS_PROXY": "https://example.com", - "HTTP_PROXY": "http://example.com", - "LANG": "en_US.UTF-8", - "LC_ALL": "en_US", - "NO_PROXY": "localhost", - "PATH": "/tmp/path", - }, - "maxBuffer": 10485760, - "timeout": 900000, - }, - }, -] -`; diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts index c86b6f2e65f007..9c4d799b6b2c88 100644 --- a/lib/modules/manager/bundler/artifacts.spec.ts +++ b/lib/modules/manager/bundler/artifacts.spec.ts @@ -1,9 +1,18 @@ import { join } from 'upath'; -import { envMock, mockExecAll } from '../../../../test/exec-util'; +import { + envMock, + mockExecAll, + mockExecSequence, +} from '../../../../test/exec-util'; import { env, fs, git, mocked } from '../../../../test/util'; import { GlobalConfig } from '../../../config/global'; import type { RepoGlobalConfig } from '../../../config/types'; +import { + BUNDLER_INVALID_CREDENTIALS, + TEMPORARY_ERROR, +} from '../../../constants/error-messages'; import * as docker from '../../../util/exec/docker'; +import { ExecError } from '../../../util/exec/exec-error'; import type { StatusResult } from '../../../util/git/types'; import * as _datasource from '../../datasource'; import type { UpdateArtifactsConfig } from '../types'; @@ -74,7 +83,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: [] as string[], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -83,12 +92,13 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toBeNull(); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --update foo bar' }, + ]); }); it('works for default binarySource', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(); fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ @@ -103,13 +113,14 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --update foo bar' }, + ]); }); it('works explicit global binarySource', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(); fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ @@ -124,12 +135,13 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --update foo bar' }, + ]); }); it('supports conservative mode', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(); fs.readLocalFile.mockResolvedValueOnce(null); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ @@ -167,7 +179,6 @@ describe('modules/manager/bundler/artifacts', () => { it('.ruby-version', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], @@ -183,7 +194,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -192,13 +203,18 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:1.2.0' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:1.2.0 bash -l -c "install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar"', + }, + ]); }); it('constraints options', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], }); @@ -213,7 +229,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -228,13 +244,18 @@ describe('modules/manager/bundler/artifacts', () => { }, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:latest' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:latest bash -l -c "install-tool bundler 3.2.1 && ruby --version && bundler lock --update foo bar"', + }, + ]); }); it('invalid constraints options', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], }); @@ -249,7 +270,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -264,13 +285,18 @@ describe('modules/manager/bundler/artifacts', () => { }, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:latest' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:latest bash -l -c "install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar"', + }, + ]); }); it('injects bundler host configuration environment variables', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], @@ -298,7 +324,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -307,13 +333,18 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:1.2.0' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e BUNDLE_GEMS__PRIVATE__COM -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:1.2.0 bash -l -c "install-tool bundler 2.3.5 && ruby --version && bundler lock --update foo bar"', + }, + ]); }); it('injects bundler host configuration as command with bundler < 2', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ @@ -338,7 +369,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -352,13 +383,18 @@ describe('modules/manager/bundler/artifacts', () => { }, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:1.2.0' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:1.2.0 bash -l -c "install-tool bundler 1.2 && ruby --version && bundler config --local gems-private.com some-user:some-password && bundler lock --update foo bar"', + }, + ]); }); it('injects bundler host configuration as command with bundler >= 2', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [ @@ -383,7 +419,7 @@ describe('modules/manager/bundler/artifacts', () => { git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -397,13 +433,18 @@ describe('modules/manager/bundler/artifacts', () => { }, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:1.2.0' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:1.2.0 bash -l -c "install-tool bundler 2.1 && ruby --version && bundler config set --local gems-private.com some-user:some-password && bundler lock --update foo bar"', + }, + ]); }); it('injects bundler host configuration as command with bundler == latest', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); fs.readLocalFile.mockResolvedValueOnce('1.2.0'); datasource.getPkgReleases.mockResolvedValueOnce({ releases: [{ version: '1.17.2' }, { version: '2.3.5' }], @@ -440,14 +481,23 @@ describe('modules/manager/bundler/artifacts', () => { config, }) ).toEqual([updatedGemfileLock]); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([ + { cmd: 'docker pull renovate/ruby:1.2.0' }, + { cmd: 'docker ps --filter name=renovate_ruby -aq' }, + { + cmd: 'docker run --rm --name=renovate_ruby --label=renovate_child -v "/tmp/github/some/repo":"/tmp/github/some/repo" -v "/tmp/cache":"/tmp/cache" -e GEM_HOME -e BUILDPACK_CACHE_DIR -w "/tmp/github/some/repo" renovate/ruby:1.2.0 bash -l -c "install-tool bundler 2.3.5 && ruby --version && bundler config set --local gems-private.com some-user:some-password && bundler lock --update foo bar"', + }, + ]); }); }); it('returns error when failing in lockFileMaintenance true', async () => { - const execError = new Error(); - (execError as any).stdout = ' foo was resolved to'; - (execError as any).stderr = ''; + const execError = new ExecError('Exec error', { + cmd: '', + stdout: ' foo was resolved to', + stderr: '', + options: { encoding: 'utf8' }, + }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); const execSnapshots = mockExecAll(execError); @@ -464,24 +514,18 @@ describe('modules/manager/bundler/artifacts', () => { isLockFileMaintenance: true, }, }) - ).toMatchSnapshot([ - { - artifactError: { - lockFile: 'Gemfile.lock', - }, - }, - ]); - expect(execSnapshots).toMatchSnapshot(); + ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); + expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); }); it('performs lockFileMaintenance', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); - fs.writeLocalFile.mockResolvedValueOnce(null as never); + fs.writeLocalFile.mockResolvedValueOnce(); const execSnapshots = mockExecAll(); git.getRepoStatus.mockResolvedValueOnce({ modified: ['Gemfile.lock'], } as StatusResult); - fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any); + fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock'); expect( await updateArtifacts({ packageFileName: 'Gemfile', @@ -493,6 +537,139 @@ describe('modules/manager/bundler/artifacts', () => { }, }) ).not.toBeNull(); - expect(execSnapshots).toMatchSnapshot(); + expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); + }); + + describe('Error handling', () => { + it('returns error when failing in lockFileMaintenance true', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: ' foo was resolved to', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + const execSnapshots = mockExecAll(execError); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchObject([ + { + artifactError: { + lockFile: 'Gemfile.lock', + }, + }, + ]); + expect(execSnapshots).toMatchObject([{ cmd: 'bundler lock --update' }]); + }); + + it('rethrows for temporary error', async () => { + const execError = new ExecError(TEMPORARY_ERROR, { + cmd: '', + stdout: '', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + await expect( + updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).rejects.toThrow(TEMPORARY_ERROR); + }); + + it('handles "Could not parse object" error', async () => { + const execError = new ExecError('fatal: Could not parse object', { + cmd: '', + stdout: 'but that version could not be found', + stderr: '', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + expect( + await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchObject([{ artifactError: { lockFile: 'Gemfile.lock' } }]); + }); + + it('throws on authentication errors', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: 'Please supply credentials for this source', + stderr: 'Please make sure you have the correct access rights', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); + mockExecAll(execError); + await expect( + updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).rejects.toThrow(BUNDLER_INVALID_CREDENTIALS); + }); + + it('handles recursive resolved dependencies', async () => { + const execError = new ExecError('Exec error', { + cmd: '', + stdout: 'foo was resolved to foo', + stderr: 'bar was resolved to bar', + options: { encoding: 'utf8' }, + }); + fs.readLocalFile.mockResolvedValue('Current Gemfile.lock'); + const execSnapshots = mockExecSequence([ + execError, + { stdout: '', stderr: '' }, + ]); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['Gemfile.lock'], + } as StatusResult); + + const res = await updateArtifacts({ + packageFileName: 'Gemfile', + updatedDeps: [{ depName: 'foo' }], + newPackageFileContent: '{}', + config: { + ...config, + isLockFileMaintenance: false, + }, + }); + + expect(res).toMatchObject([{ file: { path: 'Gemfile.lock' } }]); + expect(execSnapshots).toMatchObject([ + { cmd: 'bundler lock --update foo' }, + { cmd: 'bundler lock --update foo bar' }, + ]); + }); }); }); diff --git a/lib/modules/manager/bundler/artifacts.ts b/lib/modules/manager/bundler/artifacts.ts index 495260a1c5dfd3..ace9f6860fef7e 100644 --- a/lib/modules/manager/bundler/artifacts.ts +++ b/lib/modules/manager/bundler/artifacts.ts @@ -16,7 +16,7 @@ import { writeLocalFile, } from '../../../util/fs'; import { getRepoStatus } from '../../../util/git'; -import { regEx } from '../../../util/regex'; +import { newlineRegex, regEx } from '../../../util/regex'; import { addSecretForSanitizing } from '../../../util/sanitize'; import { isValid } from '../../versioning/ruby'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; @@ -43,8 +43,27 @@ function buildBundleHostVariable(hostRule: HostRule): Record { }; } +const resolvedPkgRegex = regEx( + /(?\S+)(?:\s*\([^)]+\)\s*)? was resolved to/ +); + +function getResolvedPackages(input: string): string[] { + const lines = input.split(newlineRegex); + const result: string[] = []; + for (const line of lines) { + const resolveMatchGroups = line.match(resolvedPkgRegex)?.groups; + if (resolveMatchGroups) { + const { pkg } = resolveMatchGroups; + result.push(pkg); + } + } + + return [...new Set(result)]; +} + export async function updateArtifacts( - updateArtifact: UpdateArtifact + updateArtifact: UpdateArtifact, + recursionLimit = 10 ): Promise { const { packageFileName, updatedDeps, newPackageFileContent, config } = updateArtifact; @@ -68,6 +87,10 @@ export async function updateArtifacts( '--update', ].filter(is.nonEmptyString); + const updatedDepNames = updatedDeps + .map(({ depName }) => depName) + .filter(is.nonEmptyStringAndNotWhitespace); + try { await writeLocalFile(packageFileName, newPackageFileContent); @@ -76,8 +99,7 @@ export async function updateArtifacts( if (config.isLockFileMaintenance) { cmd = 'bundler lock --update'; } else { - cmd = `bundler lock ${args.join(' ')} ${updatedDeps - .map((dep) => `${dep.depName}`) + cmd = `bundler lock ${args.join(' ')} ${updatedDepNames .filter((dep) => dep !== 'ruby') .map(quote) .join(' ')}`; @@ -174,7 +196,7 @@ export async function updateArtifacts( }, }, ]; - } catch (err) /* istanbul ignore next */ { + } catch (err) { if (err.message === TEMPORARY_ERROR) { throw err; } @@ -207,44 +229,36 @@ export async function updateArtifacts( memCache.set('bundlerArtifactsError', BUNDLER_INVALID_CREDENTIALS); throw new Error(BUNDLER_INVALID_CREDENTIALS); } - const resolveMatchRe = regEx('\\s+(.*) was resolved to', 'g'); - if (output.match(resolveMatchRe) && !config.isLockFileMaintenance) { - logger.debug({ err }, 'Bundler has a resolve error'); - // TODO: see below - const resolveMatches: any[] = []; - let resolveMatch: RegExpExecArray | null; - do { - resolveMatch = resolveMatchRe.exec(output); - if (resolveMatch) { - resolveMatches.push(resolveMatch[1].split(' ').shift()); - } - } while (resolveMatch); - // TODO: fixme `updatedDeps.includes(match)` is never true, as updatedDeps is `PackageDependency[]` - if (resolveMatches.some((match) => !updatedDeps.includes(match))) { - logger.debug( - { resolveMatches, updatedDeps }, - 'Found new resolve matches - reattempting recursively' - ); - const newUpdatedDeps = [ - ...new Set([...updatedDeps, ...resolveMatches]), - ]; - return updateArtifacts({ + const resolveMatches: string[] = getResolvedPackages(output).filter( + (depName) => !updatedDepNames.includes(depName) + ); + if ( + recursionLimit > 0 && + resolveMatches.length && + !config.isLockFileMaintenance + ) { + logger.debug( + { resolveMatches, updatedDeps }, + 'Found new resolve matches - reattempting recursively' + ); + const newUpdatedDeps = [ + ...new Set([ + ...updatedDeps, + ...resolveMatches.map((match) => ({ depName: match })), + ]), + ]; + return updateArtifacts( + { packageFileName, updatedDeps: newUpdatedDeps, newPackageFileContent, config, - }); - } - logger.debug( - { err }, - 'Gemfile.lock update failed due to incompatible packages' - ); - } else { - logger.info( - { err }, - 'Gemfile.lock update failed due to an unknown reason' + }, + recursionLimit - 1 ); } + + logger.info({ err }, 'Gemfile.lock update failed due to an unknown reason'); return [ { artifactError: {