Skip to content

Commit

Permalink
feat(manager:bundler): move constraint extraction to update artifacts (
Browse files Browse the repository at this point in the history
…#15125)

* feat(manager:bundler): move constraint extraction to update artifacts

* fix: missing null checks

* chore: add comment

* chore: not null save transitives

* chore: fix snapshot
  • Loading branch information
viceice committed Apr 15, 2022
1 parent 8554f60 commit c2adeff
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 104 deletions.
21 changes: 0 additions & 21 deletions lib/modules/manager/bundler/__snapshots__/extract.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

exports[`modules/manager/bundler/extract extractPackageFile() parse Ruby CI Gemfile 1`] = `
Object {
"constraints": Object {
"bundler": "2.0.2",
},
"deps": Array [
Object {
"currentValue": "~> 5.2.1",
Expand Down Expand Up @@ -154,9 +151,6 @@ Object {

exports[`modules/manager/bundler/extract extractPackageFile() parse mastodon Gemfile 1`] = `
Object {
"constraints": Object {
"ruby": ">= 2.4.0",
},
"deps": Array [
Object {
"currentValue": "~> 1.4",
Expand Down Expand Up @@ -1393,9 +1387,6 @@ Object {

exports[`modules/manager/bundler/extract extractPackageFile() parse webpacker Gemfile 1`] = `
Object {
"constraints": Object {
"bundler": "1.17.3",
},
"deps": Array [
Object {
"datasource": "rubygems",
Expand Down Expand Up @@ -1457,9 +1448,6 @@ Object {

exports[`modules/manager/bundler/extract extractPackageFile() parses rails Gemfile 1`] = `
Object {
"constraints": Object {
"bundler": "1.17.2",
},
"deps": Array [
Object {
"currentValue": ">= 11.1",
Expand Down Expand Up @@ -2158,9 +2146,6 @@ Object {

exports[`modules/manager/bundler/extract extractPackageFile() parses sourceGroups 1`] = `
Object {
"constraints": Object {
"ruby": "~> 1.5.3",
},
"deps": Array [
Object {
"datasource": "rubygems",
Expand Down Expand Up @@ -2228,9 +2213,6 @@ Object {

exports[`modules/manager/bundler/extract parse Gitlab Foss Gemfile 1`] = `
Object {
"constraints": Object {
"bundler": "1.17.3",
},
"deps": Array [
Object {
"currentValue": "5.2.3",
Expand Down Expand Up @@ -4740,9 +4722,6 @@ Object {

exports[`modules/manager/bundler/extract parse source blocks with spaces in Gemfile 1`] = `
Object {
"constraints": Object {
"bundler": "1.16.6",
},
"deps": Array [
Object {
"datasource": "rubygems",
Expand Down
16 changes: 6 additions & 10 deletions lib/modules/manager/bundler/artifacts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { exec as _exec } from 'child_process';
import { join } from 'upath';
import { envMock, mockExecAll } from '../../../../test/exec-util';
import { fs, git, mocked } from '../../../../test/util';
import { envMock, exec, mockExecAll } from '../../../../test/exec-util';
import { env, fs, git, mocked } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import type { RepoGlobalConfig } from '../../../config/types';
import * as docker from '../../../util/exec/docker';
import * as _env from '../../../util/exec/env';
import type { StatusResult } from '../../../util/git/types';
import * as _datasource from '../../datasource';
import type { UpdateArtifactsConfig } from '../types';
import * as _bundlerHostRules from './host-rules';
import { updateArtifacts } from '.';

const exec: jest.Mock<typeof _exec> = _exec as any;
const env = mocked(_env);
const datasource = mocked(_datasource);
const bundlerHostRules = mocked(_bundlerHostRules);

Expand Down Expand Up @@ -77,7 +73,7 @@ describe('modules/manager/bundler/artifacts', () => {
fs.writeLocalFile.mockResolvedValueOnce(null as never);
const execSnapshots = mockExecAll(exec);
git.getRepoStatus.mockResolvedValueOnce({
modified: [],
modified: [] as string[],
} as StatusResult);
fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock' as any);
expect(
Expand All @@ -94,7 +90,7 @@ describe('modules/manager/bundler/artifacts', () => {
it('works for default binarySource', async () => {
fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
fs.writeLocalFile.mockResolvedValueOnce(null as never);
fs.readLocalFile.mockResolvedValueOnce(null);
fs.readLocalFile.mockResolvedValueOnce(null as never);
const execSnapshots = mockExecAll(exec);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['Gemfile.lock'],
Expand All @@ -115,7 +111,7 @@ describe('modules/manager/bundler/artifacts', () => {
GlobalConfig.set({ ...adminConfig, binarySource: 'global' });
fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
fs.writeLocalFile.mockResolvedValueOnce(null as never);
fs.readLocalFile.mockResolvedValueOnce(null);
fs.readLocalFile.mockResolvedValueOnce(null as never);
const execSnapshots = mockExecAll(exec);
git.getRepoStatus.mockResolvedValueOnce({
modified: ['Gemfile.lock'],
Expand Down Expand Up @@ -406,7 +402,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',
Expand Down
51 changes: 14 additions & 37 deletions lib/modules/manager/bundler/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { exec } from '../../../util/exec';
import type { ExecOptions } from '../../../util/exec/types';
import {
ensureCacheDir,
getSiblingFileName,
readLocalFile,
writeLocalFile,
} from '../../../util/fs';
Expand All @@ -20,43 +19,16 @@ import { regEx } from '../../../util/regex';
import { addSecretForSanitizing } from '../../../util/sanitize';
import { isValid } from '../../versioning/ruby';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import { getBundlerConstraint, getRubyConstraint } from './common';
import {
findAllAuthenticatable,
getAuthenticationHeaderValue,
} from './host-rules';

const hostConfigVariablePrefix = 'BUNDLE_';

async function getRubyConstraint(
updateArtifact: UpdateArtifact
): Promise<string> {
const { packageFileName, config } = updateArtifact;
const { constraints = {} } = config;
const { ruby } = constraints;

let rubyConstraint: string;
if (ruby) {
logger.debug('Using rubyConstraint from config');
rubyConstraint = ruby;
} else {
const rubyVersionFile = getSiblingFileName(
packageFileName,
'.ruby-version'
);
const rubyVersionFileContent = await readLocalFile(rubyVersionFile, 'utf8');
if (rubyVersionFileContent) {
logger.debug('Using ruby version specified in .ruby-version');
rubyConstraint = rubyVersionFileContent
.replace(regEx(/^ruby-/), '')
.replace(regEx(/\n/g), '')
.trim();
}
}
return rubyConstraint;
}

function buildBundleHostVariable(hostRule: HostRule): Record<string, string> {
if (hostRule.resolvedHost.includes('-')) {
if (!hostRule.resolvedHost || hostRule.resolvedHost.includes('-')) {
return {};
}
const varName = hostConfigVariablePrefix.concat(
Expand All @@ -75,7 +47,6 @@ export async function updateArtifacts(
): Promise<UpdateArtifactsResult[] | null> {
const { packageFileName, updatedDeps, newPackageFileContent, config } =
updateArtifact;
const { constraints = {} } = config;
logger.debug(`bundler.updateArtifacts(${packageFileName})`);
const existingError = memCache.get<string>('bundlerArtifactsError');
// istanbul ignore if
Expand All @@ -93,13 +64,13 @@ export async function updateArtifacts(
try {
await writeLocalFile(packageFileName, newPackageFileContent);

let cmd;
let cmd: string;

if (config.isLockFileMaintenance) {
cmd = 'bundler lock --update';
} else {
cmd = `bundler lock --update ${updatedDeps
.map((dep) => dep.depName)
.map((dep) => `${dep.depName}`)
.map(quote)
.join(' ')}`;
}
Expand All @@ -121,7 +92,8 @@ export async function updateArtifacts(
// with the bundler config
const bundlerHostRulesAuthCommands: string[] = bundlerHostRules.reduce(
(authCommands: string[], hostRule) => {
if (hostRule.resolvedHost.includes('-')) {
if (hostRule.resolvedHost?.includes('-')) {
// TODO: fix me, hostrules can missing all auth
const creds = getAuthenticationHeaderValue(hostRule);
authCommands.push(`${hostRule.resolvedHost} ${creds}`);
// sanitize the authentication
Expand All @@ -132,7 +104,10 @@ export async function updateArtifacts(
[]
);

const { bundler } = constraints || {};
const bundler = getBundlerConstraint(
updateArtifact,
existingLockFileContent
);
const preCommands = ['ruby --version'];

// Bundler < 2 has a different config option syntax than >= 2
Expand Down Expand Up @@ -227,14 +202,16 @@ export async function updateArtifacts(
const resolveMatchRe = regEx('\\s+(.*) was resolved to', 'g');
if (output.match(resolveMatchRe) && !config.isLockFileMaintenance) {
logger.debug({ err }, 'Bundler has a resolve error');
const resolveMatches = [];
let resolveMatch;
// 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 },
Expand Down
97 changes: 97 additions & 0 deletions lib/modules/manager/bundler/common.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { join } from 'upath';
import { Fixtures } from '../../../../test/fixtures';
import { fs, partial } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import type { RepoGlobalConfig } from '../../../config/types';
import type { UpdateArtifact } from '../types';
import { getBundlerConstraint, getRubyConstraint } from './common';

jest.mock('../../../util/fs');

const gemfile = Fixtures.get('Gemfile.sourceGroup');
const lockedContent = Fixtures.get('Gemfile.gitlab-foss.lock');

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

describe('modules/manager/bundler/common', () => {
beforeEach(() => {
GlobalConfig.set(adminConfig);
});

describe('getBundlerConstraint', () => {
it('uses existing contraint', () => {
const config: Pick<UpdateArtifact, 'config'> = {
config: {
constraints: { bundler: '2.1.0' },
},
};
const version = getBundlerConstraint(config, lockedContent);
expect(version).toBe('2.1.0');
});

it('extracts from lockfile', () => {
const config: Pick<UpdateArtifact, 'config'> = {
config: {},
};
const version = getBundlerConstraint(config, lockedContent);
expect(version).toBe('1.17.3');
});

it('returns null', () => {
const config: Pick<UpdateArtifact, 'config'> = {
config: {},
};
const version = getBundlerConstraint(config, '');
expect(version).toBeNull();
});
});

describe('getRubyConstraint', () => {
it('uses existing contraint', async () => {
const config = partial<UpdateArtifact>({
packageFileName: 'Gemfile',
newPackageFileContent: gemfile,
config: {
constraints: { ruby: '2.1.0' },
},
});
const version = await getRubyConstraint(config);
expect(version).toBe('2.1.0');
});

it('extracts from lockfile', async () => {
const config = partial<UpdateArtifact>({
packageFileName: 'Gemfile',
newPackageFileContent: gemfile,
config: {},
});
const version = await getRubyConstraint(config);
expect(version).toBe('~> 1.5.3');
});

it('extracts from .ruby-version', async () => {
const config = partial<UpdateArtifact>({
packageFileName: 'Gemfile',
newPackageFileContent: '',
config: {},
});
fs.readLocalFile.mockResolvedValueOnce('ruby-1.2.3');
const version = await getRubyConstraint(config);
expect(version).toBe('1.2.3');
});

it('returns null', async () => {
const config = partial<UpdateArtifact>({
packageFileName: 'Gemfile',
newPackageFileContent: '',
config: {},
});
const version = await getRubyConstraint(config);
expect(version).toBeNull();
});
});
});

0 comments on commit c2adeff

Please sign in to comment.