diff --git a/dist/index.js b/dist/index.js index 3f451318..c8f5fa19 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1631,7 +1631,7 @@ class StateCacheStorage { yield resetCacheWithOctokit(CACHE_KEY); const fileSize = fs_1.default.statSync(filePath).size; if (fileSize === 0) { - core.info(`the cache ${CACHE_KEY} will be removed`); + core.info(`the state will be removed`); return; } yield cache.saveCache([path_1.default.dirname(filePath)], CACHE_KEY); diff --git a/src/classes/actions-cache-hilevel/download.ts b/src/classes/actions-cache-hilevel/download.ts deleted file mode 100644 index 7bc08ef2..00000000 --- a/src/classes/actions-cache-hilevel/download.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as cache from '@actions/cache'; -import path from 'path'; - -export const downloadFileFromActionsCache = ( - destFileName: string, - cacheKey: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - cacheVersion: string -): Promise => - cache.restoreCache([path.dirname(destFileName)], cacheKey, [ - cacheKey - ]) as Promise; diff --git a/src/classes/actions-cache-hilevel/upload.ts b/src/classes/actions-cache-hilevel/upload.ts deleted file mode 100644 index 1338a333..00000000 --- a/src/classes/actions-cache-hilevel/upload.ts +++ /dev/null @@ -1,43 +0,0 @@ -import fs from 'fs'; -import * as core from '@actions/core'; -import * as cache from '@actions/cache'; -import {getOctokit} from '@actions/github'; -import {retry as octokitRetry} from '@octokit/plugin-retry'; -import path from 'path'; - -const resetCacheWithOctokit = async (cacheKey: string): Promise => { - const token = core.getInput('repo-token'); - const client = getOctokit(token, undefined, octokitRetry); - // TODO: better way to get repository? - const repo = process.env['GITHUB_REPOSITORY']; - core.debug(`remove cache "${cacheKey}"`); - try { - // TODO: replace with client.rest. - await client.request( - `DELETE /repos/${repo}/actions/caches?key=${cacheKey}` - ); - } catch (error) { - if (error.status) { - core.debug(`Cache ${cacheKey} does not exist`); - } else { - throw error; - } - } -}; -export const uploadFileToActionsCache = async ( - filePath: string, - cacheKey: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - cacheVersion: string -) => { - await resetCacheWithOctokit(cacheKey); - const fileSize = fs.statSync(filePath).size; - - if (fileSize === 0) { - core.info(`the cache ${cacheKey} will be removed`); - return; - } - - core.debug('content: ' + fs.readFileSync(filePath).toString()); - cache.saveCache([path.dirname(filePath)], cacheKey); -}; diff --git a/src/classes/actions-cache-internal/download.ts b/src/classes/actions-cache-internal/download.ts deleted file mode 100644 index 7a82342b..00000000 --- a/src/classes/actions-cache-internal/download.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {createActionsCacheClient, getCacheApiUrl} from './http-client'; -import {retryTypedResponse} from './retry'; -import {isSuccessStatusCode} from './http-responses'; -import {HttpClient} from '@actions/http-client'; -import {downloadCacheHttpClient} from '@actions/cache/lib/internal/downloadUtils'; -import * as core from '@actions/core'; - -interface ArtifactCacheEntry { - cacheKey?: string; - scope?: string; - cacheVersion?: string; - creationTime?: string; - archiveLocation?: string; -} -const getCacheArchiveUrl = async ( - httpClient: HttpClient, - cacheKey: string, - cacheVersion: string -): Promise => { - // TODO: should work with delete? - const resource = `cache?keys=${cacheKey}&version=${cacheVersion}`; - - const response = await retryTypedResponse('getCacheEntry', async () => - httpClient.getJson(getCacheApiUrl(resource)) - ); - // Cache not found - if (response.statusCode === 204) { - core.debug( - `There's no cache with key ${cacheKey} & version=${cacheVersion}` - ); - // List cache for primary key only if cache miss occurs - return null; - } - if (!isSuccessStatusCode(response.statusCode)) { - throw new Error(`Cache service responded with ${response.statusCode}`); - } - - const cacheResult = response.result; - core.debug(`getCacheEntry response is:\n${JSON.stringify(cacheResult)}`); - const cacheDownloadUrl = cacheResult?.archiveLocation; - if (!cacheDownloadUrl) { - // Cache archiveLocation not found. This should never happen, and hence bail out. - throw new Error('Cache not found.'); - } - return cacheDownloadUrl; -}; - -export const downloadFileFromActionsCache = async ( - destFileName: string, - cacheKey: string, - cacheVersion: string -) => { - const httpClient = createActionsCacheClient(); - const archiveUrl = await getCacheArchiveUrl( - httpClient, - cacheKey, - cacheVersion - ); - - if (!archiveUrl) { - return undefined; - } - - await downloadCacheHttpClient(archiveUrl, destFileName); -}; diff --git a/src/classes/actions-cache-internal/http-client.ts b/src/classes/actions-cache-internal/http-client.ts deleted file mode 100644 index c3a38822..00000000 --- a/src/classes/actions-cache-internal/http-client.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {HttpClient} from '@actions/http-client'; -import {BearerCredentialHandler} from '@actions/http-client/lib/auth'; -import {RequestOptions} from '@actions/http-client/lib/interfaces'; -import * as core from '@actions/core'; - -const createAcceptHeader = (type: string, apiVersion: string): string => - `${type};api-version=${apiVersion}`; -const getRequestOptions = (): RequestOptions => ({ - headers: { - Accept: createAcceptHeader('application/json', '6.0-preview.1') - } -}); - -export const createActionsCacheClient = (): HttpClient => { - const token = process.env['ACTIONS_RUNTIME_TOKEN'] || ''; - const bearerCredentialHandler = new BearerCredentialHandler(token); - - return new HttpClient( - 'actions/cache', - [bearerCredentialHandler], - getRequestOptions() - ); -}; -export const getGitHubActionsApiUrl = (resource: string): string => { - const baseUrl: string = process.env['GITHUB_API_URL'] || ''; - if (!baseUrl) { - throw new Error('GitHub API Url not found, unable to restore cache.'); - } - - const repo = process.env['GITHUB_REPOSITORY']; - const url = `${baseUrl}/repos/${repo}/actions/${resource}`; - core.debug(`Resource Url: ${url}`); - return url; -}; -export const getCacheApiUrl = (resource: string): string => { - const baseUrl: string = process.env['ACTIONS_CACHE_URL'] || ''; - if (!baseUrl) { - throw new Error('Cache Service Url not found, unable to restore cache.'); - } - - const url = `${baseUrl}_apis/artifactcache/${resource}`; - core.debug(`Resource Url: ${url}`); - return url; -}; diff --git a/src/classes/actions-cache-internal/http-responses.ts b/src/classes/actions-cache-internal/http-responses.ts deleted file mode 100644 index 20ce4baa..00000000 --- a/src/classes/actions-cache-internal/http-responses.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {TypedResponse} from '@actions/http-client/lib/interfaces'; -import {HttpClientError} from '@actions/http-client'; - -export const isSuccessStatusCode = (statusCode?: number): boolean => { - if (!statusCode) { - return false; - } - return statusCode >= 200 && statusCode < 300; -}; -export function isServerErrorStatusCode(statusCode?: number): boolean { - if (!statusCode) { - return true; - } - return statusCode >= 500; -} - -export interface TypedResponseWithError extends TypedResponse { - error?: HttpClientError; -} diff --git a/src/classes/actions-cache-internal/retry.ts b/src/classes/actions-cache-internal/retry.ts deleted file mode 100644 index 8efaacf9..00000000 --- a/src/classes/actions-cache-internal/retry.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - HttpClientError, - HttpClientResponse, - HttpCodes -} from '@actions/http-client'; -import { - isServerErrorStatusCode, - TypedResponseWithError -} from './http-responses'; -import * as core from '@actions/core'; - -const isRetryableStatusCode = (statusCode?: number): boolean => { - if (!statusCode) { - return false; - } - const retryableStatusCodes = [ - HttpCodes.BadGateway, - HttpCodes.ServiceUnavailable, - HttpCodes.GatewayTimeout - ]; - return retryableStatusCodes.includes(statusCode); -}; - -const sleep = (milliseconds: number): Promise => - new Promise(resolve => setTimeout(resolve, milliseconds)); -// The default number of retry attempts. -const DefaultRetryAttempts = 2; -// The default delay in milliseconds between retry attempts. -const DefaultRetryDelay = 5000; - -const retry = async ( - name: string, - method: () => Promise, - getStatusCode: (arg0: T) => number | undefined, - maxAttempts = DefaultRetryAttempts, - delay = DefaultRetryDelay, - onError: ((arg0: Error) => T | undefined) | undefined = undefined -): Promise => { - let errorMessage = ''; - let attempt = 1; - - while (attempt <= maxAttempts) { - let response: T | undefined = undefined; - let statusCode: number | undefined = undefined; - let isRetryable = false; - - try { - response = await method(); - } catch (error) { - if (onError) { - response = onError(error); - } - - isRetryable = true; - errorMessage = error.message; - } - - if (response) { - statusCode = getStatusCode(response); - - if (!isServerErrorStatusCode(statusCode)) { - return response; - } - } - - if (statusCode) { - isRetryable = isRetryableStatusCode(statusCode); - errorMessage = `Cache service responded with ${statusCode}`; - } - - core.debug( - `${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}` - ); - - if (!isRetryable) { - core.debug(`${name} - Error is not retryable`); - break; - } - - await sleep(delay); - attempt++; - } - - throw Error(`${name} failed: ${errorMessage}`); -}; - -export const retryHttpClientResponse = async ( - name: string, - method: () => Promise, - maxAttempts = DefaultRetryAttempts, - delay = DefaultRetryDelay -): Promise => { - return await retry( - name, - method, - (response: HttpClientResponse) => response.message.statusCode, - maxAttempts, - delay - ); -}; -export const retryTypedResponse = ( - name: string, - method: () => Promise>, - maxAttempts = DefaultRetryAttempts, - delay = DefaultRetryDelay -): Promise> => - retry( - name, - method, - (response: TypedResponseWithError) => response.statusCode, - maxAttempts, - delay, - // If the error object contains the statusCode property, extract it and return - // an TypedResponse so it can be processed by the retry logic. - (error: Error) => { - if (error instanceof HttpClientError) { - return { - statusCode: error.statusCode, - result: null, - headers: {}, - error - }; - } else { - return undefined; - } - } - ); diff --git a/src/classes/actions-cache-internal/upload.ts b/src/classes/actions-cache-internal/upload.ts deleted file mode 100644 index 0279f469..00000000 --- a/src/classes/actions-cache-internal/upload.ts +++ /dev/null @@ -1,183 +0,0 @@ -import * as core from '@actions/core'; -import fs from 'fs'; -import {HttpClient} from '@actions/http-client'; -import {TypedResponse} from '@actions/http-client/lib/interfaces'; -import {ReserveCacheError, ValidationError} from '@actions/cache'; -import {isSuccessStatusCode} from './http-responses'; -import {retryHttpClientResponse, retryTypedResponse} from './retry'; -import {getOctokit} from '@actions/github'; -import {retry as octokitRetry} from '@octokit/plugin-retry'; -import {createActionsCacheClient, getCacheApiUrl} from './http-client'; - -const uploadChunk = async (httpClient: HttpClient): Promise => {}; - -const uploadFile = async ( - httpClient: HttpClient, - cacheId: number, - filePath: string, - fileSize: number -): Promise => { - if (fileSize <= 0) return; - const start = 0; - const end = fileSize - 1; - const contentRange = `bytes ${start}-${end}/*`; - core.debug( - `Uploading chunk of size ${ - end - start + 1 - } bytes at offset ${start} with content range: ${contentRange}` - ); - - const additionalHeaders = { - 'Content-Type': 'application/octet-stream', - 'Content-Range': contentRange - }; - - const resourceUrl = getCacheApiUrl(`caches/${cacheId.toString()}`); - const fd = fs.openSync(filePath, 'r'); - const openStream = () => - fs - .createReadStream(filePath, { - fd, - start, - end, - autoClose: false - }) - .on('error', error => { - throw new Error( - `Cache upload failed because file read failed with ${error.message}` - ); - }); - - try { - const uploadChunkResponse = await retryHttpClientResponse( - `uploadChunk (start: ${start}, end: ${end})`, - async () => - httpClient.sendStream( - 'PATCH', - resourceUrl, - openStream(), - additionalHeaders - ) - ); - - if (!isSuccessStatusCode(uploadChunkResponse.message.statusCode)) { - throw new Error( - `Cache service responded with ${uploadChunkResponse.message.statusCode} during upload chunk.` - ); - } - } finally { - fs.closeSync(fd); - } -}; - -const resetCacheWithOctokit = async (cacheKey: string): Promise => { - const token = core.getInput('repo-token'); - const client = getOctokit(token, undefined, octokitRetry); - // TODO: better way to get repository? - const repo = process.env['GITHUB_REPOSITORY']; - core.debug(`remove cache "${cacheKey}"`); - try { - // TODO: replace with client.rest. - await client.request( - `DELETE /repos/${repo}/actions/caches?key=${cacheKey}` - ); - } catch (error) { - if (error.status) { - core.debug(`Cache ${cacheKey} does not exist`); - } else { - throw error; - } - } -}; - -const reserveCache = async ( - httpClient: HttpClient, - fileSize: number, - cacheKey: string, - cacheVersion: string -): Promise => { - const reserveCacheRequest = { - key: cacheKey, - version: cacheVersion, - cacheSize: fileSize - }; - const response = await retryTypedResponse('reserveCache', async () => - httpClient.postJson<{cacheId: number}>( - getCacheApiUrl('caches'), - reserveCacheRequest - ) - ); - - // handle 400 in the special way - if (response?.statusCode === 400) - throw new Error( - response?.error?.message ?? - `Cache size of ~${Math.round( - fileSize / (1024 * 1024) - )} MB (${fileSize} B) is over the data cap limit, not saving cache.` - ); - - const cacheId = response?.result?.cacheId; - - if (cacheId === undefined) - throw new ReserveCacheError( - `Unable to reserve cache with key ${cacheKey}, another job may be creating this cache. More details: ${response?.error?.message}` - ); - return cacheId; -}; - -const commitCache = async ( - httpClient: HttpClient, - cacheId: number, - filesize: number -): Promise => { - const response = (await retryTypedResponse('commitCache', async () => - httpClient.postJson(getCacheApiUrl(`caches/${cacheId.toString()}`), { - size: filesize - }) - )) as TypedResponse; - if (!isSuccessStatusCode(response.statusCode)) { - throw new Error( - `Cache service responded with ${response.statusCode} during commit cache.` - ); - } -}; - -export const uploadFileToActionsCache = async ( - filePath: string, - cacheKey: string, - cacheVersion: string -) => { - try { - await resetCacheWithOctokit(cacheKey); - const fileSize = fs.statSync(filePath).size; - - if (fileSize === 0) { - core.info(`the cache ${cacheKey} will be removed`); - return; - } - - const httpClient = createActionsCacheClient(); - - const cacheId = await reserveCache( - httpClient, - fileSize, - cacheKey, - cacheVersion - ); - - await uploadFile(httpClient, cacheId, filePath, fileSize); - - await commitCache(httpClient, cacheId, fileSize); - } catch (error) { - const typedError = error as Error; - if (typedError.name === ValidationError.name) { - throw error; - } - if (typedError.name === ReserveCacheError.name) { - core.info(`Failed to save: ${typedError.message}`); - return; - } - core.warning(`Failed to save: ${typedError.message}`); - } -}; diff --git a/src/classes/state/state-cache-storage.ts b/src/classes/state/state-cache-storage.ts index 9e085ba0..7510a02d 100644 --- a/src/classes/state/state-cache-storage.ts +++ b/src/classes/state/state-cache-storage.ts @@ -87,7 +87,7 @@ export class StateCacheStorage implements IStateStorage { const fileSize = fs.statSync(filePath).size; if (fileSize === 0) { - core.info(`the cache ${CACHE_KEY} will be removed`); + core.info(`the state will be removed`); return; }