From 3d41dc5e6bb59f42b2418b5d82f2248e280af09c Mon Sep 17 00:00:00 2001 From: eyal0 <109809+eyal0@users.noreply.github.com> Date: Sat, 10 Sep 2022 16:43:05 -0600 Subject: [PATCH] Add update-env-variable to force/disable cache update. This fixes https://github.com/actions/cache/issues/342 --- __tests__/save.test.ts | 75 +++++++++++++++++++++++++++++++++++++++++- action.yml | 3 ++ dist/restore/index.js | 1 + dist/save/index.js | 25 ++++++++++++-- src/constants.ts | 3 +- src/save.ts | 35 +++++++++++++++++--- src/utils/testUtils.ts | 4 +++ 7 files changed, 136 insertions(+), 10 deletions(-) diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index 4a3ae39..924f673 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -52,7 +52,7 @@ beforeAll(() => { beforeEach(() => { process.env[Events.Key] = Events.Push; process.env[RefKey] = "refs/heads/feature-branch"; - + delete process.env["MY_CACHE_ENV_VARIABLE"]; jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( () => true @@ -63,6 +63,7 @@ afterEach(() => { testUtils.clearInputs(); delete process.env[Events.Key]; delete process.env[RefKey]; + delete process.env["MY_CACHE_ENV_VARIABLE"]; }); test("save with invalid event outputs warning", async () => { @@ -194,6 +195,78 @@ test("save with exact match returns early", async () => { expect(failedMock).toHaveBeenCalledTimes(0); }); +test("save with UpdateEnvVariable true updates the cache despite exact match", async () => { + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = primaryKey; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + testUtils.setInput(Inputs.UpdateEnvVariable, "MY_CACHE_ENV_VARIABLE"); + + const cacheId = 4; + const saveCacheMock = jest + .spyOn(cache, "saveCache") + .mockImplementationOnce(() => { + return Promise.resolve(cacheId); + }); + process.env["MY_CACHE_ENV_VARIABLE"] = "true"; + await run(); + + expect(infoMock).toHaveBeenCalledWith( + 'Cache saving was forced by setting "MY_CACHE_ENV_VARIABLE" to "true".' + ); + expect(saveCacheMock).toHaveBeenCalledTimes(1); + expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, { + uploadChunkSize: undefined + }); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + +test("save with UpdateEnvVariable false doesn't update the cache", async () => { + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + + const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; + const savedCacheKey = primaryKey; + + jest.spyOn(core, "getState") + // Cache Entry State + .mockImplementationOnce(() => { + return savedCacheKey; + }) + // Cache Key State + .mockImplementationOnce(() => { + return primaryKey; + }); + + const inputPath = "node_modules"; + testUtils.setInput(Inputs.Path, inputPath); + testUtils.setInput(Inputs.UpdateEnvVariable, "MY_CACHE_ENV_VARIABLE"); + + const saveCacheMock = jest.spyOn(cache, "saveCache"); + process.env["MY_CACHE_ENV_VARIABLE"] = "no"; + await run(); + + expect(infoMock).toHaveBeenCalledWith( + 'Cache saving was disabled by setting "MY_CACHE_ENV_VARIABLE" to "no".' + ); + expect(saveCacheMock).toHaveBeenCalledTimes(0); + expect(failedMock).toHaveBeenCalledTimes(0); +}); + test("save with missing input outputs warning", async () => { const logWarningMock = jest.spyOn(actionUtils, "logWarning"); const failedMock = jest.spyOn(core, "setFailed"); diff --git a/action.yml b/action.yml index 3e158e3..7537615 100644 --- a/action.yml +++ b/action.yml @@ -14,6 +14,9 @@ inputs: upload-chunk-size: description: 'The chunk size used to split up large files during upload, in bytes' required: false + update-env-variable: + description: 'The name of an environment variable. If the environment variable is set to "true" by the end of the job, force updating the cache. If this environment variable is set to "false" by the end of the job, disable updating the cache. Default is to update only if there was no primary-key exact cache hit.' + required: false outputs: cache-hit: description: 'A boolean value to indicate an exact match was found for the primary key' diff --git a/dist/restore/index.js b/dist/restore/index.js index 25bd663..70c0b63 100644 --- a/dist/restore/index.js +++ b/dist/restore/index.js @@ -4931,6 +4931,7 @@ var Inputs; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; Inputs["UploadChunkSize"] = "upload-chunk-size"; + Inputs["UpdateEnvVariable"] = "update-env-variable"; })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { diff --git a/dist/save/index.js b/dist/save/index.js index c8435da..1c18171 100644 --- a/dist/save/index.js +++ b/dist/save/index.js @@ -4931,6 +4931,7 @@ var Inputs; Inputs["Path"] = "path"; Inputs["RestoreKeys"] = "restore-keys"; Inputs["UploadChunkSize"] = "upload-chunk-size"; + Inputs["UpdateEnvVariable"] = "update-env-variable"; })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { @@ -47288,9 +47289,27 @@ function run() { utils.logWarning(`Error retrieving key from state.`); return; } - if (utils.isExactKeyMatch(primaryKey, state)) { - core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); - return; + const envVarName = core.getInput(constants_1.Inputs.UpdateEnvVariable); + let envVarValue; + if (envVarName) { + envVarValue = process.env[envVarName]; + } + if (envVarValue !== undefined) { + const forceUpdate = ["true", "yes"].includes(envVarValue.toLowerCase()); + if (forceUpdate) { + core.info(`Cache saving was forced by setting "${envVarName}" to "${envVarValue}".`); + } + else { + core.info(`Cache saving was disabled by setting "${envVarName}" to "${envVarValue}".`); + return; + } + } + else { + core.info(`"${envVarName}" is not set.`); + if (utils.isExactKeyMatch(primaryKey, state)) { + core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); + return; + } } const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, { required: true diff --git a/src/constants.ts b/src/constants.ts index 133f47d..ad515a4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,7 +2,8 @@ export enum Inputs { Key = "key", Path = "path", RestoreKeys = "restore-keys", - UploadChunkSize = "upload-chunk-size" + UploadChunkSize = "upload-chunk-size", + UpdateEnvVariable = "update-env-variable" } export enum Outputs { diff --git a/src/save.ts b/src/save.ts index a0a21bf..342c81e 100644 --- a/src/save.ts +++ b/src/save.ts @@ -33,11 +33,36 @@ async function run(): Promise { return; } - if (utils.isExactKeyMatch(primaryKey, state)) { - core.info( - `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` - ); - return; + const envVarName = core.getInput(Inputs.UpdateEnvVariable); + let forceUpdate; + if (envVarName) { + let envVarValue; + envVarValue = process.env[envVarName]; + if (["true"].includes(envVarValue.toLowerCase())) { + forcedUpdate = true; + } else if (["false"].includes(envVarValue.toLowerCase())) { + forcedUpdate = false; + } + } + if (forcedUpdate !== undefined) { + if (forceUpdate) { + core.info( + `Cache saving was forced by setting "${envVarName}" to "${envVarValue}".` + ); + } else { + core.info( + `Cache saving was disabled by setting "${envVarName}" to "${envVarValue}".` + ); + return; + } + } else { + core.info(`"${envVarName}" is not set.`); + if (utils.isExactKeyMatch(primaryKey, state)) { + core.info( + `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` + ); + return; + } } const cachePaths = utils.getInputAsArray(Inputs.Path, { diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 9e2134f..8d6c548 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -13,11 +13,14 @@ interface CacheInput { path: string; key: string; restoreKeys?: string[]; + updateEnvVariable?: string; } export function setInputs(input: CacheInput): void { setInput(Inputs.Path, input.path); setInput(Inputs.Key, input.key); + input.updateEnvVariable && + setInput(Inputs.UpdateEnvVariable, input.updateEnvVariable); input.restoreKeys && setInput(Inputs.RestoreKeys, input.restoreKeys.join("\n")); } @@ -25,6 +28,7 @@ export function setInputs(input: CacheInput): void { export function clearInputs(): void { delete process.env[getInputName(Inputs.Path)]; delete process.env[getInputName(Inputs.Key)]; + delete process.env[getInputName(Inputs.UpdateEnvVariable)]; delete process.env[getInputName(Inputs.RestoreKeys)]; delete process.env[getInputName(Inputs.UploadChunkSize)]; }