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

fix(repology): handle edge cases for multi-package projects #6489

Merged
merged 1 commit into from
Jun 11, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/datasource/repology/__fixtures__/gcc.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/datasource/repology/__fixtures__/pulseaudio.json

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions lib/datasource/repology/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,56 @@ Array [
]
`;

exports[`datasource/repology/index getReleases returns correct version for multi-package project with different name 1`] = `
Object {
"releases": Array [
Object {
"version": "12.2-4+deb10u1",
},
],
}
`;

exports[`datasource/repology/index getReleases returns correct version for multi-package project with different name 2`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate",
"host": "repology.org",
"user-agent": "https://github.com/renovatebot/renovate",
},
"method": "GET",
"url": "https://repology.org/tools/project-by?repo=debian_stable&name_type=binname&target_page=api_v1_project&noautoresolve=on&name=pulseaudio-utils",
},
]
`;

exports[`datasource/repology/index getReleases returns correct version for multi-package project with same name 1`] = `
Object {
"releases": Array [
Object {
"version": "9.3.0-r2",
},
],
}
`;

exports[`datasource/repology/index getReleases returns correct version for multi-package project with same name 2`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate",
"host": "repology.org",
"user-agent": "https://github.com/renovatebot/renovate",
},
"method": "GET",
"url": "https://repology.org/tools/project-by?repo=alpine_3_12&name_type=binname&target_page=api_v1_project&noautoresolve=on&name=gcc",
},
]
`;

exports[`datasource/repology/index getReleases returns correct version for source package 1`] = `
Object {
"releases": Array [
Expand Down
62 changes: 60 additions & 2 deletions lib/datasource/repology/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as httpMock from '../../../test/httpMock';
import { getName } from '../../../test/util';
import { DATASOURCE_FAILURE } from '../../constants/error-messages';
import * as ds from '.';
import { RepologyPackage } from '.';

const repologyApiHost = 'https://repology.org/';

Expand Down Expand Up @@ -45,6 +46,14 @@ const fixtureGccDefaults = fs.readFileSync(
`${__dirname}/__fixtures__/gcc-defaults.json`,
'utf8'
);
const fixtureGcc = fs.readFileSync(
`${__dirname}/__fixtures__/gcc.json`,
'utf8'
);
const fixturePulseaudio = fs.readFileSync(
`${__dirname}/__fixtures__/pulseaudio.json`,
'utf8'
);

describe(getName(__filename), () => {
describe('getReleases', () => {
Expand Down Expand Up @@ -132,7 +141,7 @@ describe(getName(__filename), () => {
const res = await ds.getReleases({ lookupName: 'debian_stable/nginx' });
expect(res).toMatchSnapshot();
expect(res.releases).toHaveLength(1);
expect(res.releases[0].version).toBeString();
expect(res.releases[0].version).toEqual('1.14.2-2+deb10u1');
expect(httpMock.getTrace()).toMatchSnapshot();
});

Expand All @@ -149,8 +158,57 @@ describe(getName(__filename), () => {
});
expect(res).toMatchSnapshot();
expect(res.releases).toHaveLength(1);
expect(res.releases[0].version).toBeString();
expect(res.releases[0].version).toEqual('1.181');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('returns correct version for multi-package project with same name', async () => {
mockProjectBy(
'alpine_3_12',
'gcc',
{ status: 200, body: fixtureGcc },
null
);

const res = await ds.getReleases({ lookupName: 'alpine_3_12/gcc' });
expect(res).toMatchSnapshot();
expect(res.releases).toHaveLength(1);
expect(res.releases[0].version).toEqual('9.3.0-r2');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('returns correct version for multi-package project with different name', async () => {
mockProjectBy(
'debian_stable',
'pulseaudio-utils',
{ status: 200, body: fixturePulseaudio },
null
);

const res = await ds.getReleases({
lookupName: 'debian_stable/pulseaudio-utils',
});
expect(res).toMatchSnapshot();
expect(res.releases).toHaveLength(1);
expect(res.releases[0].version).toEqual('12.2-4+deb10u1');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('returns null for ambiguous package results', async () => {
const pkgs: RepologyPackage[] = [
{ repo: 'dummy', version: '1.0.0', visiblename: 'example' },
{ repo: 'dummy', version: '2.0.0', visiblename: 'example' },
];
const pkgsJSON = JSON.stringify(pkgs);

mockProjectBy(
'dummy',
'example',
{ status: 200, body: pkgsJSON },
{ status: 200, body: pkgsJSON }
);

expect(await ds.getReleases({ lookupName: 'dummy/example' })).toBeNull();
});
});
});
28 changes: 24 additions & 4 deletions lib/datasource/repology/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export interface RepologyPackage {
repo: string;
visiblename: string;
version: string;
origversion: string | null;
srcname?: string;
binname?: string;
origversion?: string;
}

async function queryPackage(
Expand All @@ -33,12 +35,30 @@ async function queryPackage(
name: pkgName,
}).toString();

// Retrieve list of packages by looking up Repology project
const url = `https://repology.org/tools/project-by?${query}`;
const res = await http.getJson<RepologyPackage[]>(url);
let pkgs = res.body.filter((pkg) => pkg.repo === repoName);

return res.body.find(
(x) => x.repo.toLowerCase() === repoName.toLowerCase()
);
// In some cases Repology bundles multiple packages into a single project,
// which would result in ambiguous results. If we have more than one result
// left, we should try to determine the correct package by comparing either
// binname or srcname (depending on pkgType) to the given dependency name.
if (pkgs.length > 1) {
pkgs = pkgs.filter((pkg) => !pkg[pkgType] || pkg[pkgType] === pkgName);
}

// Abort if there is still more than one package left, as the result would
// be ambiguous and unreliable. This should usually not happen...
if (pkgs.length > 1) {
logger.warn(
{ repoName, pkgName, pkgType, pkgs },
'Repology lookup returned ambiguous results, ignoring...'
);
return null;
}

return pkgs[0];
} catch (err) {
if (err.statusCode === 404) {
logger.debug(
Expand Down