From 0ac561d5022b9a873fa19d8767bd0a77e096cf14 Mon Sep 17 00:00:00 2001 From: h0x0er Date: Tue, 13 Sep 2022 23:10:57 +0530 Subject: [PATCH] added cache utility functions --- src/cache.ts | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/cache.ts diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..35a31c1 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,116 @@ +import * as core from "@actions/core"; +import { HttpClient } from "@actions/http-client"; +import { RequestOptions } from "@actions/http-client/lib/interfaces"; +import { BearerCredentialHandler } from "@actions/http-client/lib/auth"; +import * as crypto from "crypto"; + +const versionSalt = "1.0"; +export const cacheKey = "harden-runner-cacheKey"; +export const cacheFile = "/home/agent/cache.txt"; + +function 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; +} + +function createAcceptHeader(type: string, apiVersion: string): string { + return `${type};api-version=${apiVersion}`; +} + +function getRequestOptions(): RequestOptions { + const token = process.env["ACTIONS_RUNTIME_TOKEN"] || ""; + + const requestOptions: RequestOptions = { + headers: { + Accept: createAcceptHeader("application/json", "6.0-preview.1"), + Authorization: `Bearer ${token}`, + }, + }; + + return requestOptions; +} + +function createHttpClient(): HttpClient { + const token = process.env["ACTIONS_RUNTIME_TOKEN"] || ""; + const bhandler = new BearerCredentialHandler(token); + return new HttpClient("actions/cache", [bhandler], getRequestOptions()); +} + +export function getCacheVersion( + paths: string[], + compressionMethod?: CompressionMethod +): string { + const components = paths.concat( + !compressionMethod || compressionMethod === CompressionMethod.Gzip + ? [] + : [compressionMethod] + ); + + // Add salt to cache version to support breaking changes in cache entry + components.push(versionSalt); + + return crypto.createHash("sha256").update(components.join("|")).digest("hex"); +} + +export async function getCacheEntry( + keys: string[], + paths: string[], + options?: InternalCacheOptions +): Promise { + const httpClient = createHttpClient(); + const version = getCacheVersion(paths, options?.compressionMethod); + const resource = `cache?keys=${encodeURIComponent( + keys.join(",") + )}&version=${version}`; + + const response = await httpClient.getJson( + getCacheApiUrl(resource) + ); + if (response.statusCode === 204) { + throw new Error("Request returned 204 status"); + } + if (!isSuccessStatusCode(response.statusCode)) { + throw new Error(`Cache service responded with ${response.statusCode}`); + } + + const cacheResult = response.result; + const cacheDownloadUrl = cacheResult?.archiveLocation; + if (!cacheDownloadUrl) { + throw new Error("Cache still be done, but not found."); + } + + return cacheResult; +} + +export interface InternalCacheOptions { + compressionMethod?: CompressionMethod; + cacheSize?: number; +} + +export interface ArtifactCacheEntry { + cacheKey?: string; + scope?: string; + creationTime?: string; + archiveLocation?: string; +} + +function isSuccessStatusCode(statusCode?: number): boolean { + if (!statusCode) { + return false; + } + return statusCode >= 200 && statusCode < 300; +} + +export enum CompressionMethod { + Gzip = "gzip", + // Long range mode was added to zstd in v1.3.2. + // This enum is for earlier version of zstd that does not have --long support + ZstdWithoutLong = "zstd-without-long", + Zstd = "zstd", +}