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: debian datasource #13463

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bc79714
Add Debian package datasource
Ka0o0 Jan 10, 2022
1c5a116
use http class
Ka0o0 Jan 13, 2022
270e363
fix caching
Ka0o0 Jan 13, 2022
3c2f433
use different url format
Ka0o0 Jan 13, 2022
5fe458c
switch to gz compression using Node.JS zlib
Ka0o0 Jan 13, 2022
0cf2328
cleanup
Ka0o0 Jan 13, 2022
8faee2d
cleanup
Ka0o0 Jan 14, 2022
bd88abb
more cleanup
Ka0o0 Jan 14, 2022
3206401
add comment
Ka0o0 Jan 14, 2022
1de78f1
use custom fs layer
Ka0o0 Jan 14, 2022
314f709
remove cache dir config
Ka0o0 Jan 14, 2022
096c1f3
fix tests, make binary arch configurable per repo link
Ka0o0 Jan 14, 2022
c79f809
cleanup
Ka0o0 Jan 14, 2022
a8e7a32
allow returning multiple releases per package
Ka0o0 Jan 14, 2022
bc30c95
use for of
Ka0o0 Jan 14, 2022
b4bc85c
use creation timestamp of extracted file
Ka0o0 Jan 17, 2022
727b292
put cache artifacts into one folder
Ka0o0 Jan 17, 2022
ddeeced
remove possibility to specify a default binary architecture
Ka0o0 Jan 17, 2022
a2ed06e
add typehint for intellisense
Ka0o0 Jan 17, 2022
01b606e
remove remainings of config
Ka0o0 Jan 17, 2022
d9a24f6
Merge branch 'main' into feat/deb-datasource
rarkins Jan 18, 2022
ea50609
Merge remote-tracking branch 'main' into feat/deb-datasource
Ka0o0 Feb 14, 2022
0bcb6ff
Add test for invalid server response
Ka0o0 Feb 14, 2022
e8f1468
refactor: extract extraction of componentUrl from registryUrl
Ka0o0 Feb 14, 2022
615f8a1
change test to cover two components case
Ka0o0 Feb 14, 2022
56d4964
add test describing parsing of registry url
Ka0o0 Feb 14, 2022
8b8f6da
add test for behavior if different metadata across components
Ka0o0 Feb 14, 2022
389aa60
add test suite/release synonym
Ka0o0 Feb 14, 2022
c439a68
update comment
Ka0o0 Feb 14, 2022
283055e
add deb datasource in correct order
Ka0o0 Feb 14, 2022
6caa868
Merge branch 'main' into feat/deb-datasource
viceice Feb 18, 2022
d95a3bb
enable cache
Ka0o0 Feb 21, 2022
27196a0
refactor logging
Ka0o0 Feb 21, 2022
ba1a006
remove duplicate test
Ka0o0 Feb 21, 2022
483bc2a
use tmp-promise
Ka0o0 Feb 21, 2022
331a39f
fix local path creation
Ka0o0 Feb 21, 2022
90b3d72
make some methods static
Ka0o0 Feb 21, 2022
30913ea
fix optional access
Ka0o0 Feb 21, 2022
c7dd40a
remove unused import
Ka0o0 Feb 21, 2022
dcae944
update contributor list
Ka0o0 Feb 21, 2022
193ae88
Merge branch 'main' into feat/deb-datasource
rarkins Mar 4, 2022
f01b8ba
Merge branch 'main' into feat/deb-datasource
rarkins Apr 28, 2022
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
41 changes: 41 additions & 0 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createReadStream } from 'fs';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this file?

import { performance } from 'perf_hooks';
import readline from 'readline';

async function probeExtractedPackage(extractedFile, packageName) {
const requiredPackageKeys = ['Package', 'Version', 'Homepage'];
const rl = readline.createInterface({
input: createReadStream(extractedFile),
terminal: false,
});
let pd = {};
for await (const line of rl) {
if (line === '') {
// now we should have all information available
if (pd.Package === packageName) {
// return { releases: [{ version: pd.Version }], homepage: pd.Homepage };
}
pd = {};
continue;
}

for (let i = 0; i < requiredPackageKeys.length; i++) {
if (line.startsWith(requiredPackageKeys[i])) {
pd[requiredPackageKeys[i]] = line
.substring(requiredPackageKeys[i].length + 1)
.trim();
break;
}
}
}

return null;
}

const startTime = performance.now();
const p = await probeExtractedPackage(
'/tmp/renovate-deb/packages/6a3a33ecf7f7cfdd630f78c3e840c5bf3bbb9c090f139a178c44dd6dd2de7154.txt',
'curl'
);
var endTime = performance.now();
console.log(`Took ${endTime - startTime} ms`);
182 changes: 182 additions & 0 deletions lib/datasource/deb/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { copyFile, mkdirp, stat } from 'fs-extra';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file structure changed, please move datasource to right folder.

please don't force-push

import { DirectoryResult, dir } from 'tmp-promise';
import upath from 'upath';
import { getPkgReleases } from '..';
import type { GetPkgReleasesConfig } from '..';
import * as httpMock from '../../../test/http-mock';
import { GlobalConfig } from '../../config/global';
import { DebDatasource } from '.';

describe('datasource/deb/index', () => {
describe('getReleases', () => {
const testPackagesFile = upath.join(__dirname, 'test-data', 'Packages.gz');
const extractedTestFile = upath.join(__dirname, 'test-data', 'Packages');
let cacheDir: DirectoryResult;
let extractionFolder: string;
let extractedPackageFile: string;
let cfg: GetPkgReleasesConfig; // this can be modified within the test cases

beforeEach(async () => {
jest.resetAllMocks();
cacheDir = await dir({ unsafeCleanup: true });
GlobalConfig.set({ cacheDir: cacheDir.path });
extractionFolder = upath.join(
cacheDir.path,
'others',
DebDatasource.cacheSubDir
);
extractedPackageFile = upath.join(
extractionFolder,
'0b01d9df270158d22c09c85f21b0f403d31b0da3cae4930fdb305df8f7749c27.txt'
);

cfg = {
datasource: 'deb',
depName: 'steam-devices',
registryUrls: [
'http://ftp.debian.org/debian?suite=stable&components=non-free&binaryArch=amd64',
],
};
});

it('returns a valid version for the package `steam-devices` and does not require redownload', async () => {
// copy the Packages file to the appropriate location
await mkdirp(extractionFolder);
await copyFile(extractedTestFile, extractedPackageFile);
const stats = await stat(extractedPackageFile);
const ts = stats.ctime;

httpMock
.scope('http://ftp.debian.org')
.head('/debian/dists/stable/non-free/binary-amd64/Packages.gz')
.reply(304);

const res = await getPkgReleases(cfg);
expect(res).toBeObject();
expect(res.releases).toHaveLength(1);

// validate that the server was called correctly
expect(httpMock.getTrace()).toHaveLength(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(httpMock.getTrace()).toHaveLength(1);

This will be always equal to your mocked http requests, so no benefit. 😉

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfulfilled mocks are only evaluated after the test block. In the next line the array access on getTrace() would therefore be executed which would lead to an runtime exception in a failing test case scenario.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no, if you look at the http mock code, you can see a afterEach which will validate this, so we don't have to repeat it here. 😉

const modifiedTs = httpMock.getTrace()[0].headers['if-modified-since'];
expect(modifiedTs).toBeDefined();
expect(modifiedTs).toEqual(ts.toUTCString());
});

describe('parsing of registry url', () => {
it('returns null when registry url misses components', async () => {
cfg.registryUrls = [
'http://ftp.debian.org/debian?suite=stable&binaryArch=amd64',
];
const res = await getPkgReleases(cfg);
expect(res).toBeNull();
});

it('returns null when registry url misses binaryArch', async () => {
cfg.registryUrls = [
'http://ftp.debian.org/debian?suite=stable&components=non-free',
];
const res = await getPkgReleases(cfg);
expect(res).toBeNull();
});

it('returns null when registry url misses suite', async () => {
cfg.registryUrls = [
'http://ftp.debian.org/debian?components=non-free&binaryArch=amd64',
];
const res = await getPkgReleases(cfg);
expect(res).toBeNull();
});
});

describe('without local version', () => {
beforeEach(() => {
httpMock
.scope('http://ftp.debian.org')
.get('/debian/dists/stable/non-free/binary-amd64/Packages.gz')
.replyWithFile(200, testPackagesFile);
});

it('returns a valid version for the package `steam-devices`', async () => {
const res = await getPkgReleases(cfg);
expect(res).toBeObject();
expect(res.releases).toHaveLength(1);
});

it('returns a valid version for the package `steam-devices` if release is used in the registryUrl', async () => {
// release is a synonym for suite
cfg.registryUrls = [
'http://ftp.debian.org/debian?release=stable&components=non-free&binaryArch=amd64',
];
const res = await getPkgReleases(cfg);
expect(res).toBeObject();
expect(res.releases).toHaveLength(1);
});

it('returns null for an unknown package', async () => {
cfg.depName = 'you-will-never-find-me';
const res = await getPkgReleases(cfg);
expect(res).toBeNull();
});

describe('with two components', () => {
const testPackagesFile2 = __dirname + '/test-data/Packages2.gz';

beforeEach(() => {
httpMock
.scope('http://ftp.debian.org')
.get(
'/debian/dists/stable/non-free-second/binary-amd64/Packages.gz'
)
.replyWithFile(200, testPackagesFile2);

// overwrite the previously set registryUrls to have two components
cfg.registryUrls = [
'http://ftp.debian.org/debian?suite=stable&components=non-free,non-free-second&binaryArch=amd64',
];
});

it('returns two releases for `steam-devices` which is the same across the components', async () => {
const res = await getPkgReleases(cfg);
expect(res).toBeObject();
expect(res.releases).toHaveLength(2);
});

it('returns two releases for `album` which has different metadata across the components', async () => {
cfg.depName = 'album';
const res = await getPkgReleases(cfg);
expect(res?.releases).toHaveLength(2);
});
});
});

describe('without server response', () => {
beforeEach(() => {
httpMock
.scope('http://ftp.debian.org')
.get('/debian/dists/stable/non-free/binary-amd64/Packages.gz')
.reply(404);
});

it('returns null for the package', async () => {
cfg.depName = 'you-will-never-find-me';
const res = await getPkgReleases(cfg);
expect(res).toBeNull();
});
});

it('supports specifying a custom binary arch', async () => {
httpMock
.scope('http://ftp.debian.org')
.get('/debian/dists/stable/non-free/binary-riscv/Packages.gz')
.replyWithFile(200, testPackagesFile);

cfg.registryUrls = [
'http://ftp.debian.org/debian?suite=stable&components=non-free&binaryArch=riscv',
];

const res = await getPkgReleases(cfg);
expect(res).toBeObject();
expect(res.releases).toHaveLength(1);
});
});
});
Loading