diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts index 0f003ac..ac91835 100644 --- a/__tests__/restore.test.ts +++ b/__tests__/restore.test.ts @@ -241,7 +241,7 @@ test("restore with cache found", async () => { await run(); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(getCacheMock).toHaveBeenCalledWith([key]); + expect(getCacheMock).toHaveBeenCalledWith(); expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); expect(downloadCacheMock).toHaveBeenCalledWith( @@ -307,7 +307,7 @@ test("restore with a pull request event and cache found", async () => { await run(); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(getCacheMock).toHaveBeenCalledWith([key]); + expect(getCacheMock).toHaveBeenCalledWith(); expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); expect(downloadCacheMock).toHaveBeenCalledWith( @@ -374,7 +374,7 @@ test("restore with cache found for restore key", async () => { await run(); expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); - expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey]); + expect(getCacheMock).toHaveBeenCalledWith(); expect(setCacheStateMock).toHaveBeenCalledWith(cacheEntry); expect(createTempDirectoryMock).toHaveBeenCalledTimes(1); expect(downloadCacheMock).toHaveBeenCalledWith( diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index dbdeec0..031fd12 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -161,11 +161,10 @@ test("save with missing input outputs warning", async () => { await run(); - // TODO: this shouldn't be necessary if tarball contains entries relative to workspace - expect(logWarningMock).not.toHaveBeenCalledWith( + expect(logWarningMock).toHaveBeenCalledWith( "Input required and not supplied: path" ); - expect(logWarningMock).toHaveBeenCalledTimes(0); + expect(logWarningMock).toHaveBeenCalledTimes(1); expect(failedMock).toHaveBeenCalledTimes(0); }); diff --git a/dist/restore/index.js b/dist/restore/index.js index 2f7436c..1cd4d25 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -2183,9 +2183,12 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const fs = __importStar(__webpack_require__(747)); +const crypto = __importStar(__webpack_require__(417)); const auth_1 = __webpack_require__(226); const http_client_1 = __webpack_require__(539); const utils = __importStar(__webpack_require__(443)); +const constants_1 = __webpack_require__(694); +const versionSalt = "1.0"; function isSuccessStatusCode(statusCode) { if (!statusCode) { return false; @@ -2231,11 +2234,25 @@ function createHttpClient() { const bearerCredentialHandler = new auth_1.BearerCredentialHandler(token); return new http_client_1.HttpClient("actions/cache", [bearerCredentialHandler], getRequestOptions()); } -function getCacheEntry(keys) { +function getCacheVersion() { + // Add salt to cache version to support breaking changes in cache entry + const components = [ + core.getInput(constants_1.Inputs.Key), + core.getInput(constants_1.Inputs.RestoreKeys), + core.getInput(constants_1.Inputs.Path), + versionSalt + ]; + return crypto + .createHash("sha256") + .update(components.join("|")) + .digest("hex"); +} +function getCacheEntry() { var _a; return __awaiter(this, void 0, void 0, function* () { const httpClient = createHttpClient(); - const resource = `cache?keys=${encodeURIComponent(keys.join(","))}`; + const version = getCacheVersion(); + const resource = `cache?version=${version}`; const response = yield httpClient.getJson(getCacheApiUrl(resource)); if (response.statusCode === 204) { return null; @@ -2278,8 +2295,10 @@ function reserveCache(key) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { const httpClient = createHttpClient(); + const version = getCacheVersion(); const reserveCacheRequest = { - key + key, + version }; const response = yield httpClient.postJson(getCacheApiUrl("caches"), reserveCacheRequest); return _c = (_b = (_a = response) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.cacheId, (_c !== null && _c !== void 0 ? _c : -1); @@ -4568,7 +4587,7 @@ function run() { } } try { - const cacheEntry = yield cacheHttpClient.getCacheEntry(keys); + const cacheEntry = yield cacheHttpClient.getCacheEntry(); if (!((_a = cacheEntry) === null || _a === void 0 ? void 0 : _a.archiveLocation)) { core.info(`Cache not found for input keys: ${keys.join(", ")}`); return; diff --git a/dist/save/index.js b/dist/save/index.js index d9ae2b3..e5ebd56 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -2183,9 +2183,12 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const fs = __importStar(__webpack_require__(747)); +const crypto = __importStar(__webpack_require__(417)); const auth_1 = __webpack_require__(226); const http_client_1 = __webpack_require__(539); const utils = __importStar(__webpack_require__(443)); +const constants_1 = __webpack_require__(694); +const versionSalt = "1.0"; function isSuccessStatusCode(statusCode) { if (!statusCode) { return false; @@ -2231,11 +2234,25 @@ function createHttpClient() { const bearerCredentialHandler = new auth_1.BearerCredentialHandler(token); return new http_client_1.HttpClient("actions/cache", [bearerCredentialHandler], getRequestOptions()); } -function getCacheEntry(keys) { +function getCacheVersion() { + // Add salt to cache version to support breaking changes in cache entry + const components = [ + core.getInput(constants_1.Inputs.Key), + core.getInput(constants_1.Inputs.RestoreKeys), + core.getInput(constants_1.Inputs.Path), + versionSalt + ]; + return crypto + .createHash("sha256") + .update(components.join("|")) + .digest("hex"); +} +function getCacheEntry() { var _a; return __awaiter(this, void 0, void 0, function* () { const httpClient = createHttpClient(); - const resource = `cache?keys=${encodeURIComponent(keys.join(","))}`; + const version = getCacheVersion(); + const resource = `cache?version=${version}`; const response = yield httpClient.getJson(getCacheApiUrl(resource)); if (response.statusCode === 204) { return null; @@ -2278,8 +2295,10 @@ function reserveCache(key) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { const httpClient = createHttpClient(); + const version = getCacheVersion(); const reserveCacheRequest = { - key + key, + version }; const response = yield httpClient.postJson(getCacheApiUrl("caches"), reserveCacheRequest); return _c = (_b = (_a = response) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.cacheId, (_c !== null && _c !== void 0 ? _c : -1); @@ -4473,7 +4492,7 @@ function run() { } core.debug(`Cache ID: ${cacheId}`); const cachePaths = yield utils.resolvePaths(core - .getInput(constants_1.Inputs.Path) + .getInput(constants_1.Inputs.Path, { required: true }) .split("\n") .filter(x => x !== "")); core.debug("Cache Paths:"); diff --git a/package-lock.json b/package-lock.json index 98e8202..669979b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2244,7 +2244,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -2265,12 +2266,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2285,17 +2288,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2412,7 +2418,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2424,6 +2431,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2438,6 +2446,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2445,12 +2454,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2469,6 +2480,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2549,7 +2561,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2561,6 +2574,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2646,7 +2660,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2682,6 +2697,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2701,6 +2717,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2744,12 +2761,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/cacheHttpClient.ts b/src/cacheHttpClient.ts index 62ae2c1..d3cfd6c 100644 --- a/src/cacheHttpClient.ts +++ b/src/cacheHttpClient.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core"; import * as fs from "fs"; +import * as crypto from "crypto"; import { BearerCredentialHandler } from "@actions/http-client/auth"; import { HttpClient, HttpCodes } from "@actions/http-client"; import { @@ -14,6 +15,9 @@ import { ReserveCacheResponse } from "./contracts"; import * as utils from "./utils/actionUtils"; +import { Inputs } from "./constants"; + +const versionSalt = "1.0"; function isSuccessStatusCode(statusCode?: number): boolean { if (!statusCode) { @@ -77,11 +81,25 @@ function createHttpClient(): HttpClient { ); } -export async function getCacheEntry( - keys: string[] -): Promise { +function getCacheVersion(): string { + // Add salt to cache version to support breaking changes in cache entry + const components = [ + core.getInput(Inputs.Key), + core.getInput(Inputs.RestoreKeys), + core.getInput(Inputs.Path), + versionSalt + ]; + + return crypto + .createHash("sha256") + .update(components.join("|")) + .digest("hex"); +} + +export async function getCacheEntry(): Promise { const httpClient = createHttpClient(); - const resource = `cache?keys=${encodeURIComponent(keys.join(","))}`; + const version = getCacheVersion(); + const resource = `cache?version=${version}`; const response = await httpClient.getJson( getCacheApiUrl(resource) @@ -129,9 +147,11 @@ export async function downloadCache( // Reserve Cache export async function reserveCache(key: string): Promise { const httpClient = createHttpClient(); + const version = getCacheVersion(); const reserveCacheRequest: ReserveCacheRequest = { - key + key, + version }; const response = await httpClient.postJson( getCacheApiUrl("caches"), diff --git a/src/restore.ts b/src/restore.ts index f70c17b..7d7e864 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -54,7 +54,7 @@ async function run(): Promise { } try { - const cacheEntry = await cacheHttpClient.getCacheEntry(keys); + const cacheEntry = await cacheHttpClient.getCacheEntry(); if (!cacheEntry?.archiveLocation) { core.info(`Cache not found for input keys: ${keys.join(", ")}`); return; diff --git a/src/save.ts b/src/save.ts index 89a3b23..93af886 100644 --- a/src/save.ts +++ b/src/save.ts @@ -45,7 +45,7 @@ async function run(): Promise { core.debug(`Cache ID: ${cacheId}`); const cachePaths = await utils.resolvePaths( core - .getInput(Inputs.Path) + .getInput(Inputs.Path, { required: true }) .split("\n") .filter(x => x !== "") );