Avoid saving config-cache from Gradle < 8.6

Earlier versions of Gradle didn't support the `GRADLE_ENCRYPTION_KEY`
for the configuration-cache, and so are either not useful to save,
or are actually unsafe due to unencrypted secrets.

We use semver to compare the Gradle version used to produce the config-cache
entry with the minimum Gradle version required.
This commit is contained in:
daz 2023-12-22 22:12:22 -07:00
parent 63ce84df08
commit b3f092e821
No known key found for this signature in database
3 changed files with 218 additions and 600 deletions

View file

@ -3,13 +3,14 @@ import fs from 'fs'
import crypto from 'crypto'
import * as core from '@actions/core'
import * as glob from '@actions/glob'
import * as semver from 'semver'
import * as params from './input-params'
import {META_FILE_DIR} from './cache-base'
import {CacheEntryListener, CacheListener} from './cache-reporting'
import {cacheDebug, getCacheKeyPrefix, hashFileNames, restoreCache, saveCache, tryDelete} from './cache-utils'
import {loadBuildResults} from './build-results'
import {BuildResult, loadBuildResults} from './build-results'
const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE'
@ -47,6 +48,7 @@ class ExtractedCacheEntryDefinition {
pattern: string
bundle: boolean
uniqueFileNames = true
notCacheableReason: string | undefined
constructor(artifactType: string, pattern: string, bundle: boolean) {
this.artifactType = artifactType
@ -54,10 +56,24 @@ class ExtractedCacheEntryDefinition {
this.bundle = bundle
}
/**
* Indicate that the file names matching the cache entry pattern are NOT sufficient to uniquely identify the contents.
* If the file names are sufficient, then we use a hash of the file names to identify the entry.
* With non-unique-file-names, we hash the file contents to identify the cache entry.
*/
withNonUniqueFileNames(): ExtractedCacheEntryDefinition {
this.uniqueFileNames = false
return this
}
/**
* Specify that the cache entry, should not be saved for some reason, even though the contents exist.
* This is used to prevent configuration-cache entries being cached when they were generated by Gradle < 8.6,
*/
notCacheableBecause(reason: string): ExtractedCacheEntryDefinition {
this.notCacheableReason = reason
return this
}
}
/**
@ -143,6 +159,11 @@ abstract class AbstractEntryExtractor {
const artifactType = cacheEntryDefinition.artifactType
const pattern = cacheEntryDefinition.pattern
if (cacheEntryDefinition.notCacheableReason) {
listener.entry(pattern).markNotSaved(cacheEntryDefinition.notCacheableReason)
continue
}
// Find all matching files for this cache entry definition
const globber = await glob.create(pattern, {
implicitDescendants: false
@ -402,25 +423,51 @@ export class ConfigurationCacheEntryExtractor extends AbstractEntryExtractor {
* Extract cache entries for the configuration cache in each project.
*/
protected getExtractedCacheEntryDefinitions(): ExtractedCacheEntryDefinition[] {
return this.getConfigCacheDirs().map(configCachePath => {
return new ExtractedCacheEntryDefinition(
// Group BuildResult by existing configCacheDir
const groupedResults = this.getConfigCacheDirectoriesWithAssociatedBuildResults()
return Object.entries(groupedResults).map(([configCachePath, pathResults]) => {
// Create a entry definition for each unique configuration cache directory
const definition = new ExtractedCacheEntryDefinition(
'configuration-cache',
configCachePath,
true
).withNonUniqueFileNames()
// If any associated build result used Gradle < 8.6, then mark it as not cacheable
if (
pathResults.find(result => {
const gradleVersion = semver.coerce(result.gradleVersion)
return gradleVersion && semver.lt(gradleVersion, '8.6.0')
})
) {
core.info(
`Not saving config-cache data for ${configCachePath}. Configuration cache data is only saved for Gradle 8.6+`
)
definition.notCacheableBecause('Configuration cache data only saved for Gradle 8.6+')
}
return definition
})
}
/**
* For every Gradle invocation, we record the project root directory.
* This method returns any configuraton-cache directories found in any of these project roots.
*/
private getConfigCacheDirs(): string[] {
const buildResults = loadBuildResults()
const configCacheDirs = buildResults
.map(x => path.resolve(x.rootProjectDir, '.gradle/configuration-cache'))
.filter(x => fs.existsSync(x))
private getConfigCacheDirectoriesWithAssociatedBuildResults(): Record<string, BuildResult[]> {
return loadBuildResults().reduce(
(acc, buildResult) => {
// For each build result, find the config-cache dir
const configCachePath = path.resolve(buildResult.rootProjectDir, '.gradle/configuration-cache')
// Ignore case where config-cache dir doesn't exist
if (!fs.existsSync(configCachePath)) {
return acc
}
return [...new Set(configCacheDirs)] // Remove duplicates
// Group by unique config cache directories and collect associated build results
if (!acc[configCachePath]) {
acc[configCachePath] = []
}
acc[configCachePath].push(buildResult)
return acc
},
{} as Record<string, BuildResult[]>
)
}
}