Decoupled cache from GitHub API

Signed-off-by: Guillermo Mazzola <guillermo.mazzola@glovoapp.com>
This commit is contained in:
Guillermo Mazzola 2023-06-26 15:42:06 +02:00
parent 1b2daf5833
commit 4ff2ffb7bf
No known key found for this signature in database
GPG key ID: 6A17887FBC885E08
6 changed files with 63 additions and 24 deletions

View file

@ -28,6 +28,12 @@ inputs:
required: false required: false
default: ${{ github.event.repository != null && github.ref_name != github.event.repository.default_branch }} default: ${{ github.event.repository != null && github.ref_name != github.event.repository.default_branch }}
cache-provider:
description: |
The cache provider to use for caching files. Currently only supports `github`.
required: false
default: github
cache-write-only: cache-write-only:
description: | description: |
When 'true', entries will not be restored from the cache but will be saved at the end of the Job. When 'true', entries will not be restored from the cache but will be saved at the end of the Job.

View file

@ -0,0 +1,24 @@
import * as cache from '@actions/cache'
import {CacheEntry, CacheProvider} from './cache-provider'
const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS'
const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes
class GitHubCache implements CacheProvider {
// Only override the read timeout if the SEGMENT_DOWNLOAD_TIMEOUT_MINS env var has NOT been set
private cacheRestoreOptions = !process.env[SEGMENT_DOWNLOAD_TIMEOUT_VAR]
? {segmentTimeoutInMs: SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT}
: {}
async saveCache(paths: string[], key: string): Promise<CacheEntry> {
return cache.saveCache(paths, key)
}
async restoreCache(paths: string[], primaryKey: string, restoreKeys?: string[]): Promise<CacheEntry | undefined> {
return cache.restoreCache(paths, primaryKey, restoreKeys, this.cacheRestoreOptions)
}
}
export default function createGitHubCache(): CacheProvider | undefined {
return cache.isFeatureAvailable() ? new GitHubCache() : undefined
}

10
src/cache-provider.ts Normal file
View file

@ -0,0 +1,10 @@
export interface CacheProvider {
saveCache(paths: string[], key: string): Promise<CacheEntry>
restoreCache(paths: string[], primaryKey: string, restoreKeys?: string[]): Promise<CacheEntry | undefined>
}
export interface CacheEntry {
key: string
size?: number
}

View file

@ -1,5 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as cache from '@actions/cache' import {cache} from './cache-utils'
/** /**
* Collects information on what entries were saved and restored during the action. * Collects information on what entries were saved and restored during the action.
@ -16,7 +16,7 @@ export class CacheListener {
} }
get cacheStatus(): string { get cacheStatus(): string {
if (!cache.isFeatureAvailable()) return 'not available' if (!cache) return 'not available'
if (this.cacheDisabled) return 'disabled' if (this.cacheDisabled) return 'disabled'
if (this.cacheWriteOnly) return 'write-only' if (this.cacheWriteOnly) return 'write-only'
if (this.cacheReadOnly) return 'read-only' if (this.cacheReadOnly) return 'read-only'

View file

@ -1,5 +1,4 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
@ -10,6 +9,9 @@ import * as fs from 'fs'
import * as params from './input-params' import * as params from './input-params'
import {CacheEntryListener} from './cache-reporting' import {CacheEntryListener} from './cache-reporting'
import {CacheEntry, CacheProvider} from './cache-provider'
import {ReserveCacheError, ValidationError} from '@actions/cache'
import createGitHubCache from './cache-provider-github'
const CACHE_PROTOCOL_VERSION = 'v8-' const CACHE_PROTOCOL_VERSION = 'v8-'
@ -19,14 +21,14 @@ const CACHE_KEY_JOB_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB'
const CACHE_KEY_JOB_INSTANCE_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_INSTANCE' const CACHE_KEY_JOB_INSTANCE_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_INSTANCE'
const CACHE_KEY_JOB_EXECUTION_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION' const CACHE_KEY_JOB_EXECUTION_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION'
const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS' export const cache = provisionCache()
const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes
function provisionCache(): CacheProvider | undefined {
return createGitHubCache()
}
export function isCacheDisabled(): boolean { export function isCacheDisabled(): boolean {
if (!cache.isFeatureAvailable()) { return !cache || params.isCacheDisabled()
return true
}
return params.isCacheDisabled()
} }
export function isCacheReadOnly(): boolean { export function isCacheReadOnly(): boolean {
@ -146,14 +148,10 @@ export async function restoreCache(
cacheKey: string, cacheKey: string,
cacheRestoreKeys: string[], cacheRestoreKeys: string[],
listener: CacheEntryListener listener: CacheEntryListener
): Promise<cache.CacheEntry | undefined> { ): Promise<CacheEntry | undefined> {
listener.markRequested(cacheKey, cacheRestoreKeys) listener.markRequested(cacheKey, cacheRestoreKeys)
try { try {
// Only override the read timeout if the SEGMENT_DOWNLOAD_TIMEOUT_MINS env var has NOT been set const restoredEntry = await cache?.restoreCache(cachePath, cacheKey, cacheRestoreKeys)
const cacheRestoreOptions = process.env[SEGMENT_DOWNLOAD_TIMEOUT_VAR]
? {}
: {segmentTimeoutInMs: SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT}
const restoredEntry = await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys, cacheRestoreOptions)
if (restoredEntry !== undefined) { if (restoredEntry !== undefined) {
listener.markRestored(restoredEntry.key, restoredEntry.size) listener.markRestored(restoredEntry.key, restoredEntry.size)
} }
@ -167,10 +165,12 @@ export async function restoreCache(
export async function saveCache(cachePath: string[], cacheKey: string, listener: CacheEntryListener): Promise<void> { export async function saveCache(cachePath: string[], cacheKey: string, listener: CacheEntryListener): Promise<void> {
try { try {
const savedEntry = await cache.saveCache(cachePath, cacheKey) const savedEntry = await cache?.saveCache(cachePath, cacheKey)
listener.markSaved(savedEntry.key, savedEntry.size) if (savedEntry) {
listener.markSaved(savedEntry.key, savedEntry.size)
}
} catch (error) { } catch (error) {
if (error instanceof cache.ReserveCacheError) { if (error instanceof ReserveCacheError) {
listener.markAlreadyExists(cacheKey) listener.markAlreadyExists(cacheKey)
} else { } else {
listener.markNotSaved((error as Error).message) listener.markNotSaved((error as Error).message)
@ -188,11 +188,11 @@ export function cacheDebug(message: string): void {
} }
export function handleCacheFailure(error: unknown, message: string): void { export function handleCacheFailure(error: unknown, message: string): void {
if (error instanceof cache.ValidationError) { if (error instanceof ValidationError) {
// Fail on cache validation errors // Fail on cache validation errors
throw error throw error
} }
if (error instanceof cache.ReserveCacheError) { if (error instanceof ReserveCacheError) {
// Reserve cache errors are expected if the artifact has been previously cached // Reserve cache errors are expected if the artifact has been previously cached
core.info(`${message}: ${error}`) core.info(`${message}: ${error}`)
} else { } else {

View file

@ -3,13 +3,12 @@ import * as os from 'os'
import * as path from 'path' import * as path from 'path'
import * as httpm from '@actions/http-client' import * as httpm from '@actions/http-client'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as cache from '@actions/cache'
import * as toolCache from '@actions/tool-cache' import * as toolCache from '@actions/tool-cache'
import * as gradlew from './gradlew' import * as gradlew from './gradlew'
import * as params from './input-params' import * as params from './input-params'
import * as layout from './repository-layout' import * as layout from './repository-layout'
import {handleCacheFailure, isCacheDisabled, isCacheReadOnly} from './cache-utils' import {cache, handleCacheFailure, isCacheDisabled, isCacheReadOnly} from './cache-utils'
const gradleVersionsBaseUrl = 'https://services.gradle.org/versions' const gradleVersionsBaseUrl = 'https://services.gradle.org/versions'
@ -133,7 +132,7 @@ async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo
const cacheKey = `gradle-${versionInfo.version}` const cacheKey = `gradle-${versionInfo.version}`
try { try {
const restoreKey = await cache.restoreCache([downloadPath], cacheKey) const restoreKey = await cache?.restoreCache([downloadPath], cacheKey)
if (restoreKey) { if (restoreKey) {
core.info(`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`) core.info(`Restored Gradle distribution ${cacheKey} from cache to ${downloadPath}`)
return downloadPath return downloadPath
@ -145,7 +144,7 @@ async function downloadAndCacheGradleDistribution(versionInfo: GradleVersionInfo
core.info(`Gradle distribution ${versionInfo.version} not found in cache. Will download.`) core.info(`Gradle distribution ${versionInfo.version} not found in cache. Will download.`)
await downloadGradleDistribution(versionInfo, downloadPath) await downloadGradleDistribution(versionInfo, downloadPath)
if (!isCacheReadOnly()) { if (!isCacheReadOnly() && cache) {
try { try {
await cache.saveCache([downloadPath], cacheKey) await cache.saveCache([downloadPath], cacheKey)
} catch (error) { } catch (error) {