Only fetch checksums for unknown wrapper versions (#292)

The checksum values for most wrapper versions are hard-coded into the
action. These known checksum values are first used for validation: only
if none of the known values work do we download checksums.

Previously, we blindly downloaded all of the checksum values in this
case: we now only download the checksums for versions that are not in
our "known" set.

Fixes #171
This commit is contained in:
Daz DeBoer 2024-07-16 13:04:57 -06:00 committed by GitHub
parent 01254b3eaa
commit dff3ef9b8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 37 additions and 22 deletions

View File

@ -4,21 +4,27 @@ import fileWrapperChecksums from './wrapper-checksums.json'
const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3}) const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3})
function getKnownValidChecksums(): Map<string, Set<string>> { export class WrapperChecksums {
const versionsMap = new Map<string, Set<string>>() checksums = new Map<string, Set<string>>()
versions = new Set<string>()
add(version: string, checksum: string): void {
if (this.checksums.has(checksum)) {
this.checksums.get(checksum)!.add(version)
} else {
this.checksums.set(checksum, new Set([version]))
}
this.versions.add(version)
}
}
function loadKnownChecksums(): WrapperChecksums {
const checksums = new WrapperChecksums()
for (const entry of fileWrapperChecksums) { for (const entry of fileWrapperChecksums) {
const checksum = entry.checksum checksums.add(entry.version, entry.checksum)
let versionNames = versionsMap.get(checksum)
if (versionNames === undefined) {
versionNames = new Set()
versionsMap.set(checksum, versionNames)
} }
return checksums
versionNames.add(entry.version)
}
return versionsMap
} }
/** /**
@ -26,9 +32,12 @@ function getKnownValidChecksums(): Map<string, Set<string>> {
* *
* Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum. * Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum.
*/ */
export const KNOWN_VALID_CHECKSUMS = getKnownValidChecksums() export const KNOWN_CHECKSUMS = loadKnownChecksums()
export async function fetchValidChecksums(allowSnapshots: boolean): Promise<Set<string>> { export async function fetchUnknownChecksums(
allowSnapshots: boolean,
knownChecksums: WrapperChecksums
): Promise<Set<string>> {
const all = await httpGetJsonArray('https://services.gradle.org/versions/all') const all = await httpGetJsonArray('https://services.gradle.org/versions/all')
const withChecksum = all.filter( const withChecksum = all.filter(
entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl') entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl')
@ -37,7 +46,11 @@ export async function fetchValidChecksums(allowSnapshots: boolean): Promise<Set<
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => allowSnapshots || !entry.snapshot (entry: any) => allowSnapshots || !entry.snapshot
) )
const checksumUrls = allowed.map( const notKnown = allowed.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => !knownChecksums.versions.has(entry.version)
)
const checksumUrls = notKnown.map(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => entry.wrapperChecksumUrl as string (entry: any) => entry.wrapperChecksumUrl as string
) )

View File

@ -8,7 +8,7 @@ export async function findInvalidWrapperJars(
minWrapperCount: number, minWrapperCount: number,
allowSnapshots: boolean, allowSnapshots: boolean,
allowedChecksums: string[], allowedChecksums: string[],
knownValidChecksums: Map<string, Set<string>> = checksums.KNOWN_VALID_CHECKSUMS knownValidChecksums: checksums.WrapperChecksums = checksums.KNOWN_CHECKSUMS
): Promise<ValidationResult> { ): Promise<ValidationResult> {
const wrapperJars = await find.findWrapperJars(gitRepoRoot) const wrapperJars = await find.findWrapperJars(gitRepoRoot)
const result = new ValidationResult([], []) const result = new ValidationResult([], [])
@ -21,7 +21,7 @@ export async function findInvalidWrapperJars(
const notYetValidatedWrappers = [] const notYetValidatedWrappers = []
for (const wrapperJar of wrapperJars) { for (const wrapperJar of wrapperJars) {
const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar)) const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar))
if (allowedChecksums.includes(sha) || knownValidChecksums.has(sha)) { if (allowedChecksums.includes(sha) || knownValidChecksums.checksums.has(sha)) {
result.valid.push(new WrapperJar(wrapperJar, sha)) result.valid.push(new WrapperJar(wrapperJar, sha))
} else { } else {
notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha)) notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha))
@ -31,7 +31,7 @@ export async function findInvalidWrapperJars(
// Otherwise fall back to fetching checksums from Gradle API and compare against them // Otherwise fall back to fetching checksums from Gradle API and compare against them
if (notYetValidatedWrappers.length > 0) { if (notYetValidatedWrappers.length > 0) {
result.fetchedChecksums = true result.fetchedChecksums = true
const fetchedValidChecksums = await checksums.fetchValidChecksums(allowSnapshots) const fetchedValidChecksums = await checksums.fetchUnknownChecksums(allowSnapshots, knownValidChecksums)
for (const wrapperJar of notYetValidatedWrappers) { for (const wrapperJar of notYetValidatedWrappers) {
if (!fetchedValidChecksums.has(wrapperJar.checksum)) { if (!fetchedValidChecksums.has(wrapperJar.checksum)) {

View File

@ -6,22 +6,22 @@ jest.setTimeout(30000)
test('has loaded hardcoded wrapper jars checksums', async () => { test('has loaded hardcoded wrapper jars checksums', async () => {
// Sanity check that generated checksums file is not empty and was properly imported // Sanity check that generated checksums file is not empty and was properly imported
expect(checksums.KNOWN_VALID_CHECKSUMS.size).toBeGreaterThan(10) expect(checksums.KNOWN_CHECKSUMS.checksums.size).toBeGreaterThan(10)
// Verify that checksums of arbitrary versions are contained // Verify that checksums of arbitrary versions are contained
expect( expect(
checksums.KNOWN_VALID_CHECKSUMS.get( checksums.KNOWN_CHECKSUMS.checksums.get(
'660ab018b8e319e9ae779fdb1b7ac47d0321bde953bf0eb4545f14952cfdcaa3' '660ab018b8e319e9ae779fdb1b7ac47d0321bde953bf0eb4545f14952cfdcaa3'
) )
).toEqual(new Set(['4.10.3'])) ).toEqual(new Set(['4.10.3']))
expect( expect(
checksums.KNOWN_VALID_CHECKSUMS.get( checksums.KNOWN_CHECKSUMS.checksums.get(
'28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e' '28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e'
) )
).toEqual(new Set(['6.0-rc-1', '6.0-rc-2', '6.0-rc-3', '6.0', '6.0.1'])) ).toEqual(new Set(['6.0-rc-1', '6.0-rc-2', '6.0-rc-3', '6.0', '6.0.1']))
}) })
test('fetches wrapper jars checksums', async () => { test('fetches wrapper jars checksums', async () => {
const validChecksums = await checksums.fetchValidChecksums(false) const validChecksums = await checksums.fetchUnknownChecksums(false, new checksums.WrapperChecksums)
expect(validChecksums.size).toBeGreaterThan(10) expect(validChecksums.size).toBeGreaterThan(10)
// Verify that checksum of arbitrary version is contained // Verify that checksum of arbitrary version is contained
expect( expect(
@ -47,7 +47,7 @@ describe('retry', () => {
code: 'ECONNREFUSED' code: 'ECONNREFUSED'
}) })
const validChecksums = await checksums.fetchValidChecksums(false) const validChecksums = await checksums.fetchUnknownChecksums(false, new checksums.WrapperChecksums)
expect(validChecksums.size).toBeGreaterThan(10) expect(validChecksums.size).toBeGreaterThan(10)
nock.isDone() nock.isDone()
}) })

View File

@ -1,6 +1,7 @@
import * as path from 'path' import * as path from 'path'
import * as validate from '../../../src/wrapper-validation/validate' import * as validate from '../../../src/wrapper-validation/validate'
import {expect, test, jest} from '@jest/globals' import {expect, test, jest} from '@jest/globals'
import { WrapperChecksums } from '../../../src/wrapper-validation/checksums'
jest.setTimeout(30000) jest.setTimeout(30000)
@ -24,7 +25,7 @@ test('succeeds if all found wrapper jars are valid', async () => {
}) })
test('succeeds if all found wrapper jars are valid (and checksums are fetched from Gradle API)', async () => { test('succeeds if all found wrapper jars are valid (and checksums are fetched from Gradle API)', async () => {
const knownValidChecksums = new Map<string, Set<string>>() const knownValidChecksums = new WrapperChecksums()
const result = await validate.findInvalidWrapperJars( const result = await validate.findInvalidWrapperJars(
baseDir, baseDir,
1, 1,
@ -32,6 +33,7 @@ test('succeeds if all found wrapper jars are valid (and checksums are fetched fr
['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], ['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],
knownValidChecksums knownValidChecksums
) )
console.log(`fetchedChecksums = ${result.fetchedChecksums}`)
expect(result.isValid()).toBe(true) expect(result.isValid()).toBe(true)
// Should have fetched checksums because no known checksums were provided // Should have fetched checksums because no known checksums were provided