Initial commit
Some checks failed
CI-integ-test-full / caching-integ-tests (push) Failing after 32s
CI-integ-test-full / other-integ-tests (push) Failing after 29m15s
CI-codeql / Analyze (javascript-typescript) (push) Failing after 1m9s
Update Wrapper checksums file / Update checksums (push) Failing after 1m32s

This commit is contained in:
2024-09-25 17:07:42 +02:00
commit b3863e10a2
202 changed files with 944555 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
import fs from 'fs'
import path from 'path'
import {ACTION_METADATA_DIR} from '../configuration'
export class ChecksumCache {
private readonly cacheFile: string
constructor(gradleUserHome: string) {
this.cacheFile = path.resolve(gradleUserHome, ACTION_METADATA_DIR, 'valid-wrappers.json')
}
load(): string[] {
// Load previously validated checksums saved in Gradle User Home
if (fs.existsSync(this.cacheFile)) {
return JSON.parse(fs.readFileSync(this.cacheFile, 'utf-8'))
}
return []
}
save(checksums: string[]): void {
const uniqueChecksums = [...new Set(checksums)]
// Save validated checksums to Gradle User Home
fs.mkdirSync(path.dirname(this.cacheFile), {recursive: true})
fs.writeFileSync(this.cacheFile, JSON.stringify(uniqueChecksums))
}
}

View File

@@ -0,0 +1,96 @@
import * as httpm from 'typed-rest-client/HttpClient'
import * as cheerio from 'cheerio'
import fileWrapperChecksums from './wrapper-checksums.json'
const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3})
export class WrapperChecksums {
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) {
checksums.add(entry.version, entry.checksum)
}
return checksums
}
/**
* Known checksums from previously published Wrapper versions.
*
* Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum.
*/
export const KNOWN_CHECKSUMS = loadKnownChecksums()
export async function fetchUnknownChecksums(
allowSnapshots: boolean,
knownChecksums: WrapperChecksums
): Promise<Set<string>> {
const all = await httpGetJsonArray('https://services.gradle.org/versions/all')
const withChecksum = all.filter(
entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl')
)
const allowed = withChecksum.filter(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(entry: any) => allowSnapshots || !entry.snapshot
)
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
(entry: any) => entry.wrapperChecksumUrl as string
)
if (allowSnapshots) {
await addDistributionSnapshotChecksums(checksumUrls)
}
const checksums = await Promise.all(
checksumUrls.map(async (url: string) => {
// console.log(`Fetching checksum from ${url}`)
return httpGetText(url)
})
)
return new Set(checksums)
}
async function httpGetJsonArray(url: string): Promise<unknown[]> {
return JSON.parse(await httpGetText(url))
}
async function httpGetText(url: string): Promise<string> {
const response = await httpc.get(url)
return await response.readBody()
}
// Public for testing
export async function addDistributionSnapshotChecksums(checksumUrls: string[]): Promise<void> {
// Load the index page of the distribution snapshot repository
const indexPage = await httpGetText('https://services.gradle.org/distributions-snapshots/')
// // Extract all wrapper checksum from the index page. These end in -wrapper.jar.sha256
// // Load the HTML into cheerio
const $ = cheerio.load(indexPage)
// // Find all links ending with '-wrapper.jar.sha256'
const wrapperChecksumLinks = $('a[href$="-wrapper.jar.sha256"]')
// build the absolute URL for each wrapper checksum
wrapperChecksumLinks.each((index, element) => {
const url = $(element).attr('href')
checksumUrls.push(`https://services.gradle.org${url}`)
})
}

View File

@@ -0,0 +1,27 @@
import * as util from 'util'
import * as path from 'path'
import * as fs from 'fs'
import unhomoglyph from 'unhomoglyph'
const readdir = util.promisify(fs.readdir)
export async function findWrapperJars(baseDir: string): Promise<string[]> {
const files = await recursivelyListFiles(baseDir)
return files
.filter(file => unhomoglyph(file).endsWith('gradle-wrapper.jar'))
.map(wrapperJar => path.relative(baseDir, wrapperJar))
.sort((a, b) => a.localeCompare(b))
}
async function recursivelyListFiles(baseDir: string): Promise<string[]> {
const childrenNames = await readdir(baseDir)
const childrenPaths = await Promise.all(
childrenNames.map(async childName => {
const childPath = path.resolve(baseDir, childName)
return fs.lstatSync(childPath).isDirectory()
? recursivelyListFiles(childPath)
: new Promise(resolve => resolve([childPath]))
})
)
return Array.prototype.concat(...childrenPaths)
}

View File

@@ -0,0 +1,18 @@
import * as crypto from 'crypto'
import * as fs from 'fs'
export async function sha256File(path: string): Promise<string> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256')
const stream = fs.createReadStream(path)
stream.on('data', data => hash.update(data))
stream.on('end', () => {
stream.destroy()
resolve(hash.digest('hex'))
})
stream.on('error', error => {
stream.destroy()
reject(error)
})
})
}

View File

@@ -0,0 +1,92 @@
import * as find from './find'
import * as checksums from './checksums'
import * as hash from './hash'
import {resolve} from 'path'
export async function findInvalidWrapperJars(
gitRepoRoot: string,
allowSnapshots: boolean,
allowedChecksums: string[],
previouslyValidatedChecksums: string[] = [],
knownValidChecksums: checksums.WrapperChecksums = checksums.KNOWN_CHECKSUMS
): Promise<ValidationResult> {
const wrapperJars = await find.findWrapperJars(gitRepoRoot)
const result = new ValidationResult([], [])
if (wrapperJars.length > 0) {
const notYetValidatedWrappers = []
for (const wrapperJar of wrapperJars) {
const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar))
if (
allowedChecksums.includes(sha) ||
previouslyValidatedChecksums.includes(sha) ||
knownValidChecksums.checksums.has(sha)
) {
result.valid.push(new WrapperJar(wrapperJar, sha))
} else {
notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha))
}
}
// Otherwise fall back to fetching checksums from Gradle API and compare against them
if (notYetValidatedWrappers.length > 0) {
result.fetchedChecksums = true
const fetchedValidChecksums = await checksums.fetchUnknownChecksums(allowSnapshots, knownValidChecksums)
for (const wrapperJar of notYetValidatedWrappers) {
if (!fetchedValidChecksums.has(wrapperJar.checksum)) {
result.invalid.push(wrapperJar)
} else {
result.valid.push(wrapperJar)
}
}
}
}
return result
}
export class ValidationResult {
valid: WrapperJar[]
invalid: WrapperJar[]
fetchedChecksums = false
constructor(valid: WrapperJar[], invalid: WrapperJar[]) {
this.valid = valid
this.invalid = invalid
}
isValid(): boolean {
return this.invalid.length === 0
}
toDisplayString(): string {
let displayString = ''
if (this.invalid.length > 0) {
displayString += `✗ Found unknown Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList(
this.invalid
)}`
}
if (this.valid.length > 0) {
if (displayString.length > 0) displayString += '\n'
displayString += `✓ Found known Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList(this.valid)}`
}
return displayString
}
private static toDisplayList(wrapperJars: WrapperJar[]): string {
return ` ${wrapperJars.map(wj => wj.toDisplayString()).join(`\n `)}`
}
}
export class WrapperJar {
path: string
checksum: string
constructor(path: string, checksum: string) {
this.path = path
this.checksum = checksum
}
toDisplayString(): string {
return `${this.checksum} ${this.path}`
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
import * as core from '@actions/core'
import {WrapperValidationConfig} from '../configuration'
import {ChecksumCache} from './cache'
import {findInvalidWrapperJars} from './validate'
import {JobFailure} from '../errors'
export async function validateWrappers(
config: WrapperValidationConfig,
workspaceRoot: string,
gradleUserHome: string
): Promise<void> {
if (!config.doValidateWrappers()) {
return // Wrapper validation is disabled
}
const checksumCache = new ChecksumCache(gradleUserHome)
const allowedChecksums = process.env['ALLOWED_GRADLE_WRAPPER_CHECKSUMS']?.split(',') || []
const previouslyValidatedChecksums = checksumCache.load()
const result = await findInvalidWrapperJars(
workspaceRoot,
config.allowSnapshotWrappers(),
allowedChecksums,
previouslyValidatedChecksums
)
if (result.isValid()) {
await core.group('All Gradle Wrapper jars are valid', async () => {
core.debug(`Loaded previously validated checksums from cache: ${previouslyValidatedChecksums.join(', ')}`)
core.info(result.toDisplayString())
})
} else {
core.info(result.toDisplayString())
throw new JobFailure(
`At least one Gradle Wrapper Jar failed validation!\n See https://github.com/gradle/actions/blob/main/docs/wrapper-validation.md#validation-failures\n${result.toDisplayString()}`
)
}
checksumCache.save(result.valid.map(wrapper => wrapper.checksum))
}