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(github-releases): getDigest() #10947

Merged
merged 30 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d43b1d9
github-releases: getDigest()
thepwagner Jul 23, 2021
fe52e89
github-releases: getDigest cache
thepwagner Jul 23, 2021
cabb0d5
github-releases: test failure cases
thepwagner Jul 23, 2021
c8ae0eb
github-releases: getDigest() without digest file
thepwagner Jul 23, 2021
5a8a041
Merge branch 'main' into renovate-7928
rarkins Jul 27, 2021
5a8bc0d
Apply suggestions from code review
thepwagner Jul 28, 2021
daa3d52
github-releases: getDigest cache 24h
thepwagner Jul 28, 2021
e275df4
github-releases: DigestAsset to types.ts
thepwagner Jul 28, 2021
c46e93b
Merge remote-tracking branch 'renovatebot/main' into renovate-7928
thepwagner Jul 28, 2021
1c9e0a6
github-releases: cache expensive asset digests
thepwagner Jul 29, 2021
644ae1b
github-releases: extract+test common.ts
thepwagner Jul 30, 2021
c3342fa
github-release t ReleaseMocker test helper
thepwagner Jul 30, 2021
1f1f90e
github-releases: extract digest.ts
thepwagner Jul 30, 2021
f021dfe
github-releases: add digest.spec.ts
thepwagner Jul 30, 2021
0b9a548
github-releases: findNewDigest to digest.ts
thepwagner Jul 30, 2021
3e09bc5
github-releases: mapDigestAssetToRelease spec
thepwagner Jul 30, 2021
b1a3653
github-releases: remove cache test
thepwagner Aug 2, 2021
ca4afde
github-release: rename __testutil__
thepwagner Aug 2, 2021
32cad87
github-release: getDigest success case
thepwagner Aug 2, 2021
11f09a4
Merge remote-tracking branch 'renovatebot/main' into renovate-7928
thepwagner Aug 2, 2021
0633d4d
Merge branch 'main' into renovate-7928
viceice Aug 2, 2021
46c06f2
Merge remote-tracking branch 'renovatebot/main' into renovate-7928
thepwagner Aug 5, 2021
965e531
add "**/test/**" to tsconfig.app.json exclude
thepwagner Aug 5, 2021
efdaf81
Merge branch 'main' into renovate-7928
rarkins Aug 5, 2021
a084052
Merge branch 'main' into renovate-7928
viceice Aug 5, 2021
d4284cb
chore: exclude tests from coverage
viceice Aug 5, 2021
953ce28
Merge branch 'main' into renovate-7928
viceice Aug 5, 2021
deca82b
Update lib/datasource/github-releases/digest.ts
rarkins Aug 5, 2021
5b286af
Merge branch 'main' into renovate-7928
rarkins Aug 5, 2021
63865a1
Merge branch 'main' into renovate-7928
rarkins Aug 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
281 changes: 280 additions & 1 deletion lib/datasource/github-releases/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { getPkgReleases } from '..';
import fs from 'fs-extra';
import hasha from 'hasha';
import { DirectoryResult, dir } from 'tmp-promise';
import { getDigest, getPkgReleases } from '..';
import * as httpMock from '../../../test/http-mock';
import { getName } from '../../../test/util';
import * as memCache from '../../util/cache/memory';
import * as packageCache from '../../util/cache/package';
import * as _hostRules from '../../util/host-rules';
import { id as datasource } from '.';
import * as github from '.';
Expand Down Expand Up @@ -67,4 +72,278 @@ describe(getName(), () => {
expect(httpMock.getTrace()).toMatchSnapshot();
});
});

describe('getDigest', () => {
const lookupName = 'some/dep';
const currentValue = 'v1.0.0';
const currentDigest = 'v1.0.0-digest';

const mockReleaseWithAssets = (
version: string,
assets: { [key: string]: string }
) => {
const releaseData = {
tag_name: version,
published_at: '2020-03-09T11:00:00Z',
assets: [],
};
for (const assetFn of Object.keys(assets)) {
const assetPath = `/repos/${lookupName}/releases/download/${version}/${assetFn}`;
const assetData = assets[assetFn];
releaseData.assets.push({
name: assetFn,
size: assetData.length,
browser_download_url: `${githubApiHost}${assetPath}`,
});
httpMock
.scope(githubApiHost)
.get(assetPath)
.once()
.reply(200, assetData);
}
httpMock
.scope(githubApiHost)
.get(`/repos/${lookupName}/releases/tags/${version}`)
.reply(200, releaseData);
};

const mockReleaseWithDigestFile = (version: string, digests: string[]) => {
mockReleaseWithAssets(version, { 'SHASUMS.txt': digests.join('\n') });
};

it('requires currentDigest', async () => {
const digest = await getDigest({ datasource, lookupName }, currentValue);
expect(digest).toBeNull();
});

it('defaults to currentDigest when currentVersion is missing', async () => {
const digest = await getDigest(
{
datasource,
lookupName,
currentDigest,
},
currentValue
);
expect(digest).toEqual(currentDigest);
});

it('verifies currentDigest from digest file', async () => {
mockReleaseWithDigestFile(currentValue, [
`${currentDigest} linux-amd64.tar.gz`,
`another-digest linux-arm64.tar.gz`,
]);
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
currentValue
);
expect(digest).toEqual(currentDigest);
});

// TODO: reviewers - this is awkward, but I found returning `null` in this case to not produce an update
// I'd prefer a PR with the old digest (that I can manually patch) to no PR, so I made this decision.
it('ignores failures verifying currentDigest', async () => {
mockReleaseWithAssets(currentValue, {});
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
currentValue
);
expect(digest).toEqual(currentDigest);
});

it('parses digest file in new release', async () => {
mockReleaseWithDigestFile(currentValue, [
`${currentDigest} linux-amd64.tar.gz`,
`another-digest linux-arm64.tar.gz`,
]);
const nextValue = 'v1.0.1';
const nextDigest = 'v1.0.1-digest';
mockReleaseWithDigestFile(nextValue, [
`a-next-digest linux-arm64.tar.gz`,
`${nextDigest} linux-amd64.tar.gz`,
]);
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
nextValue
);
expect(digest).toEqual(nextDigest);
});

it('returns null when new digest file is not found', async () => {
mockReleaseWithDigestFile(currentValue, [
`${currentDigest} linux-amd64.tar.gz`,
]);
const nextValue = 'v1.0.1';
const releaseData = {
tag_name: nextValue,
published_at: '2020-03-09T11:00:00Z',
assets: [],
};
httpMock
.scope(githubApiHost)
.get(`/repos/${lookupName}/releases/tags/${nextValue}`)
.reply(200, releaseData);

const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
nextValue
);
expect(digest).toBeNull();
});

it('returns null when missing from new digest file', async () => {
mockReleaseWithDigestFile(currentValue, [
`${currentDigest} linux-amd64.tar.gz`,
]);
const nextValue = 'v1.0.1';
mockReleaseWithDigestFile(nextValue, []);
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
nextValue
);
expect(digest).toBeNull();
});

it('parses digest file in new release that embeds version in digested file', async () => {
mockReleaseWithDigestFile(currentValue, [
`${currentDigest} some-dep-1.0.0/linux-amd64.tar.gz`,
`another-digest some-dep-1.0.0/linux-arm64.tar.gz`,
]);
const nextValue = 'v1.0.1';
const nextDigest = 'v1.0.1-digest';
mockReleaseWithDigestFile(nextValue, [
`a-next-digest some-dep-1.0.1/linux-arm64.tar.gz`,
`${nextDigest} some-dep-1.0.1/linux-amd64.tar.gz`,
]);
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest,
},
nextValue
);
expect(digest).toEqual(nextDigest);
});

it('verifies currentDigest from asset', async () => {
const barContent = '1'.repeat(10 * 1024);
mockReleaseWithAssets(currentValue, {
'foo.txt': '0'.repeat(10 * 1024),
'bar.txt': barContent,
});

const barDigest = await hasha.async(barContent, { algorithm: 'sha512' });
const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest: barDigest,
},
currentValue
);
expect(digest).toEqual(barDigest);
});

it('digests assets in new release', async () => {
const barContent = '1'.repeat(10 * 1024);
mockReleaseWithAssets(currentValue, {
'foo.txt': '0'.repeat(8 * 1024),
'bar.txt': barContent,
'baz.txt': '2'.repeat(9 * 1024),
});
const barDigest = await hasha.async(barContent, { algorithm: 'sha256' });

const nextValue = 'v1.0.1';
const nextBarContent = '3'.repeat(10 * 1024);
mockReleaseWithAssets(nextValue, {
'bar.txt': nextBarContent,
});

const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest: barDigest,
},
nextValue
);
const nextBarDigest = await hasha.async(nextBarContent, {
algorithm: 'sha256',
});
expect(digest).toEqual(nextBarDigest);
});

describe('with cache', () => {
let tmpDir: DirectoryResult;
beforeEach(async () => {
tmpDir = await dir();
packageCache.init({ cacheDir: tmpDir.path });
memCache.init();
thepwagner marked this conversation as resolved.
Show resolved Hide resolved
});

afterEach(() => {
fs.rmdirSync(tmpDir.path, { recursive: true });
});

it('caches digested assets', async () => {
const barContent = '1'.repeat(10 * 1024);
mockReleaseWithAssets(currentValue, {
'bar.txt': barContent,
});
const algorithm = 'sha256';
const barDigest = await hasha.async(barContent, { algorithm });

for (const nextValue of ['v1.0.1', 'v1.0.2']) {
const nextBarContent = '3'.repeat(10 * 1024);
mockReleaseWithAssets(nextValue, {
'bar.txt': nextBarContent,
});

const digest = await getDigest(
{
datasource,
lookupName,
currentValue,
currentDigest: barDigest,
},
nextValue
);
const nextBarDigest = await hasha.async(nextBarContent, {
algorithm,
});
expect(digest).toEqual(nextBarDigest);
}
// `currentValue` assets were only mocked once, therefore asset digests were cached
});
});
});
});