From 9dff82c727b2c306c2f3fb0daf40919be780a426 Mon Sep 17 00:00:00 2001 From: Bassem Dghaidi <568794+Link-@users.noreply.github.com> Date: Thu, 14 Nov 2024 03:01:04 -0800 Subject: [PATCH] Port dependencies & remove dependency on toolkit/artifacts --- packages/cache/src/cache.ts | 21 +++---- packages/cache/src/internal/cacheUtils.ts | 77 +++++++++++++++++++++++ 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/cache/src/cache.ts b/packages/cache/src/cache.ts index 2659b848..ab5ccb9e 100644 --- a/packages/cache/src/cache.ts +++ b/packages/cache/src/cache.ts @@ -17,8 +17,6 @@ import { import { CacheFileSizeLimit } from './internal/constants' import { UploadCacheFile } from './internal/blob/upload-cache' import { DownloadCacheFile } from './internal/blob/download-cache' -import { getBackendIdsFromToken, BackendIds } from '@actions/artifact/lib/internal/shared/util' - export class ValidationError extends Error { constructor(message: string) { super(message) @@ -62,7 +60,6 @@ function checkKey(key: string): void { * * @returns boolean return true if Actions cache service feature is available, otherwise false */ - export function isFeatureAvailable(): boolean { return !!process.env['ACTIONS_CACHE_URL'] } @@ -215,7 +212,8 @@ async function restoreCachev2( restoreKeys = restoreKeys || [] const keys = [primaryKey, ...restoreKeys] - core.debug(`Resolved Keys: JSON.stringify(keys)`) + core.debug('Resolved Keys:') + core.debug(JSON.stringify(keys)) if (keys.length > 10) { throw new ValidationError( @@ -229,7 +227,7 @@ async function restoreCachev2( let archivePath = '' try { const twirpClient = cacheTwirpClient.internalCacheTwirpClient() - const backendIds: BackendIds = getBackendIdsFromToken() + const backendIds: utils.BackendIds = utils.getBackendIdsFromToken() const compressionMethod = await utils.getCompressionMethod() const request: GetCacheEntryDownloadURLRequest = { @@ -289,8 +287,7 @@ async function restoreCachev2( return request.key } catch (error) { - // TODO: handle all the possible error scenarios - throw new Error(`Unable to download and extract cache: ${error.message}`) + throw new Error(`Failed to restore: ${error.message}`) } finally { try { await utils.unlinkFile(archivePath) @@ -450,7 +447,7 @@ async function saveCachev2( enableCrossOsArchive = false ): Promise { // BackendIds are retrieved form the signed JWT - const backendIds: BackendIds = getBackendIdsFromToken() + const backendIds: utils.BackendIds = utils.getBackendIdsFromToken() const compressionMethod = await utils.getCompressionMethod() const twirpClient = cacheTwirpClient.internalCacheTwirpClient() let cacheId = -1 @@ -504,16 +501,13 @@ async function saveCachev2( version: version } const response: CreateCacheEntryResponse = await twirpClient.CreateCacheEntry(request) - core.info(`CreateCacheEntryResponse: ${JSON.stringify(response)}`) - // TODO: handle the error cases here if (!response.ok) { throw new ReserveCacheError( `Unable to reserve cache with key ${key}, another job may be creating this cache.` ) } - // TODO: mask the signed upload URL - core.debug(`Saving Cache to: ${response.signedUploadUrl}`) + core.debug(`Saving Cache to: ${core.setSecret(response.signedUploadUrl)}`) await UploadCacheFile( response.signedUploadUrl, archivePath, @@ -536,11 +530,10 @@ async function saveCachev2( ) } - // TODO: this is not great, we should handle the types without parsing cacheId = parseInt(finalizeResponse.entryId) } catch (error) { const typedError = error as Error - core.debug(typedError.message) + core.warning(`Failed to save: ${typedError.message}`) } finally { // Try to delete the archive to save space try { diff --git a/packages/cache/src/internal/cacheUtils.ts b/packages/cache/src/internal/cacheUtils.ts index bd493172..ef09969b 100644 --- a/packages/cache/src/internal/cacheUtils.ts +++ b/packages/cache/src/internal/cacheUtils.ts @@ -7,6 +7,7 @@ import * as fs from 'fs' import * as path from 'path' import * as semver from 'semver' import * as util from 'util' +import jwt_decode from 'jwt-decode' import { CacheFilename, CompressionMethod, @@ -169,4 +170,80 @@ export function getCacheVersion( components.push(versionSalt) return crypto.createHash('sha256').update(components.join('|')).digest('hex') +} + +export function getRuntimeToken(): string { + const token = process.env['ACTIONS_RUNTIME_TOKEN'] + if (!token) { + throw new Error('Unable to get the ACTIONS_RUNTIME_TOKEN env variable') + } + return token +} + +export interface BackendIds { + workflowRunBackendId: string + workflowJobRunBackendId: string +} + +interface ActionsToken { + scp: string +} + +const InvalidJwtError = new Error( + 'Failed to get backend IDs: The provided JWT token is invalid and/or missing claims' +) + +// uses the JWT token claims to get the +// workflow run and workflow job run backend ids +export function getBackendIdsFromToken(): BackendIds { + const token = getRuntimeToken() + const decoded = jwt_decode(token) + if (!decoded.scp) { + throw InvalidJwtError + } + + /* + * example decoded: + * { + * scp: "Actions.ExampleScope Actions.Results:ce7f54c7-61c7-4aae-887f-30da475f5f1a:ca395085-040a-526b-2ce8-bdc85f692774" + * } + */ + + const scpParts = decoded.scp.split(' ') + if (scpParts.length === 0) { + throw InvalidJwtError + } + /* + * example scpParts: + * ["Actions.ExampleScope", "Actions.Results:ce7f54c7-61c7-4aae-887f-30da475f5f1a:ca395085-040a-526b-2ce8-bdc85f692774"] + */ + + for (const scopes of scpParts) { + const scopeParts = scopes.split(':') + if (scopeParts?.[0] !== 'Actions.Results') { + // not the Actions.Results scope + continue + } + + /* + * example scopeParts: + * ["Actions.Results", "ce7f54c7-61c7-4aae-887f-30da475f5f1a", "ca395085-040a-526b-2ce8-bdc85f692774"] + */ + if (scopeParts.length !== 3) { + // missing expected number of claims + throw InvalidJwtError + } + + const ids = { + workflowRunBackendId: scopeParts[1], + workflowJobRunBackendId: scopeParts[2] + } + + core.debug(`Workflow Run Backend ID: ${ids.workflowRunBackendId}`) + core.debug(`Workflow Job Run Backend ID: ${ids.workflowJobRunBackendId}`) + + return ids + } + + throw InvalidJwtError } \ No newline at end of file