mirror of https://github.com/actions/toolkit
Port dependencies & remove dependency on toolkit/artifacts
parent
d109d9c03e
commit
9dff82c727
|
@ -17,8 +17,6 @@ import {
|
||||||
import { CacheFileSizeLimit } from './internal/constants'
|
import { CacheFileSizeLimit } from './internal/constants'
|
||||||
import { UploadCacheFile } from './internal/blob/upload-cache'
|
import { UploadCacheFile } from './internal/blob/upload-cache'
|
||||||
import { DownloadCacheFile } from './internal/blob/download-cache'
|
import { DownloadCacheFile } from './internal/blob/download-cache'
|
||||||
import { getBackendIdsFromToken, BackendIds } from '@actions/artifact/lib/internal/shared/util'
|
|
||||||
|
|
||||||
export class ValidationError extends Error {
|
export class ValidationError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message)
|
super(message)
|
||||||
|
@ -62,7 +60,6 @@ function checkKey(key: string): void {
|
||||||
*
|
*
|
||||||
* @returns boolean return true if Actions cache service feature is available, otherwise false
|
* @returns boolean return true if Actions cache service feature is available, otherwise false
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function isFeatureAvailable(): boolean {
|
export function isFeatureAvailable(): boolean {
|
||||||
return !!process.env['ACTIONS_CACHE_URL']
|
return !!process.env['ACTIONS_CACHE_URL']
|
||||||
}
|
}
|
||||||
|
@ -215,7 +212,8 @@ async function restoreCachev2(
|
||||||
restoreKeys = restoreKeys || []
|
restoreKeys = restoreKeys || []
|
||||||
const keys = [primaryKey, ...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) {
|
if (keys.length > 10) {
|
||||||
throw new ValidationError(
|
throw new ValidationError(
|
||||||
|
@ -229,7 +227,7 @@ async function restoreCachev2(
|
||||||
let archivePath = ''
|
let archivePath = ''
|
||||||
try {
|
try {
|
||||||
const twirpClient = cacheTwirpClient.internalCacheTwirpClient()
|
const twirpClient = cacheTwirpClient.internalCacheTwirpClient()
|
||||||
const backendIds: BackendIds = getBackendIdsFromToken()
|
const backendIds: utils.BackendIds = utils.getBackendIdsFromToken()
|
||||||
const compressionMethod = await utils.getCompressionMethod()
|
const compressionMethod = await utils.getCompressionMethod()
|
||||||
|
|
||||||
const request: GetCacheEntryDownloadURLRequest = {
|
const request: GetCacheEntryDownloadURLRequest = {
|
||||||
|
@ -289,8 +287,7 @@ async function restoreCachev2(
|
||||||
|
|
||||||
return request.key
|
return request.key
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO: handle all the possible error scenarios
|
throw new Error(`Failed to restore: ${error.message}`)
|
||||||
throw new Error(`Unable to download and extract cache: ${error.message}`)
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await utils.unlinkFile(archivePath)
|
await utils.unlinkFile(archivePath)
|
||||||
|
@ -450,7 +447,7 @@ async function saveCachev2(
|
||||||
enableCrossOsArchive = false
|
enableCrossOsArchive = false
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
// BackendIds are retrieved form the signed JWT
|
// BackendIds are retrieved form the signed JWT
|
||||||
const backendIds: BackendIds = getBackendIdsFromToken()
|
const backendIds: utils.BackendIds = utils.getBackendIdsFromToken()
|
||||||
const compressionMethod = await utils.getCompressionMethod()
|
const compressionMethod = await utils.getCompressionMethod()
|
||||||
const twirpClient = cacheTwirpClient.internalCacheTwirpClient()
|
const twirpClient = cacheTwirpClient.internalCacheTwirpClient()
|
||||||
let cacheId = -1
|
let cacheId = -1
|
||||||
|
@ -504,16 +501,13 @@ async function saveCachev2(
|
||||||
version: version
|
version: version
|
||||||
}
|
}
|
||||||
const response: CreateCacheEntryResponse = await twirpClient.CreateCacheEntry(request)
|
const response: CreateCacheEntryResponse = await twirpClient.CreateCacheEntry(request)
|
||||||
core.info(`CreateCacheEntryResponse: ${JSON.stringify(response)}`)
|
|
||||||
// TODO: handle the error cases here
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new ReserveCacheError(
|
throw new ReserveCacheError(
|
||||||
`Unable to reserve cache with key ${key}, another job may be creating this cache.`
|
`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: ${core.setSecret(response.signedUploadUrl)}`)
|
||||||
core.debug(`Saving Cache to: ${response.signedUploadUrl}`)
|
|
||||||
await UploadCacheFile(
|
await UploadCacheFile(
|
||||||
response.signedUploadUrl,
|
response.signedUploadUrl,
|
||||||
archivePath,
|
archivePath,
|
||||||
|
@ -536,11 +530,10 @@ async function saveCachev2(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is not great, we should handle the types without parsing
|
|
||||||
cacheId = parseInt(finalizeResponse.entryId)
|
cacheId = parseInt(finalizeResponse.entryId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const typedError = error as Error
|
const typedError = error as Error
|
||||||
core.debug(typedError.message)
|
core.warning(`Failed to save: ${typedError.message}`)
|
||||||
} finally {
|
} finally {
|
||||||
// Try to delete the archive to save space
|
// Try to delete the archive to save space
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as semver from 'semver'
|
import * as semver from 'semver'
|
||||||
import * as util from 'util'
|
import * as util from 'util'
|
||||||
|
import jwt_decode from 'jwt-decode'
|
||||||
import {
|
import {
|
||||||
CacheFilename,
|
CacheFilename,
|
||||||
CompressionMethod,
|
CompressionMethod,
|
||||||
|
@ -170,3 +171,79 @@ export function getCacheVersion(
|
||||||
|
|
||||||
return crypto.createHash('sha256').update(components.join('|')).digest('hex')
|
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<ActionsToken>(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
|
||||||
|
}
|
Loading…
Reference in New Issue