Skip to content

Commit 627f0f4

Browse files
authored
Add fail-on-cache-miss option (#1036)
* Add fail-on-cache-miss option * Small improvements * Changes after rebase * Update description * Only fail if no cache entry is found * Code review * Update readme * Add additional test case * Bump version + changelog * Update package-lock.json * Update Readme
1 parent 8e3048d commit 627f0f4

File tree

15 files changed

+174
-15
lines changed

15 files changed

+174
-15
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/ac
3030
* Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable `SEGMENT_DOWNLOAD_TIMEOUT_MINS`. Default is 60 minutes.
3131
* Two new actions available for granular control over caches - [restore](restore/action.yml) and [save](save/action.yml)
3232
* Support cross-os caching as an opt-in feature. See [Cross OS caching](./tips-and-workarounds.md#cross-os-cache) for more info.
33+
* Added option to fail job on cache miss. See [Exit workflow on cache miss](./restore/README.md#exit-workflow-on-cache-miss) for more info.
3334

3435
Refer [here](https://github.com/actions/cache/blob/v2/README.md) for previous versions
3536

@@ -49,6 +50,7 @@ If you are using a `self-hosted` Windows runner, `GNU tar` and `zstd` are requir
4950
* `key` - An explicit key for restoring and saving the cache. Refer [creating a cache key](#creating-a-cache-key).
5051
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
5152
* `enableCrossOsArchive` - An optional boolean when enabled, allows Windows runners to save or restore caches that can be restored or saved respectively on other platforms. Default: false
53+
* `fail-on-cache-miss` - Fail the workflow if cache entry is not found. Default: false
5254

5355
#### Environment Variables
5456

RELEASES.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,7 @@
6666

6767
### 3.2.3
6868
- Support cross os caching on Windows as an opt-in feature.
69-
- Fix issue with symlink restoration on Windows for cross-os caches.
69+
- Fix issue with symlink restoration on Windows for cross-os caches.
70+
71+
### 3.2.4
72+
- Added option to fail job on cache miss.

__tests__/restore.test.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,128 @@ test("restore with cache found for restore key", async () => {
205205
);
206206
expect(failedMock).toHaveBeenCalledTimes(0);
207207
});
208+
209+
test("Fail restore when fail on cache miss is enabled and primary + restore keys not found", async () => {
210+
const path = "node_modules";
211+
const key = "node-test";
212+
const restoreKey = "node-";
213+
testUtils.setInputs({
214+
path: path,
215+
key,
216+
restoreKeys: [restoreKey],
217+
failOnCacheMiss: true
218+
});
219+
220+
const failedMock = jest.spyOn(core, "setFailed");
221+
const stateMock = jest.spyOn(core, "saveState");
222+
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
223+
const restoreCacheMock = jest
224+
.spyOn(cache, "restoreCache")
225+
.mockImplementationOnce(() => {
226+
return Promise.resolve(undefined);
227+
});
228+
229+
await run();
230+
231+
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
232+
expect(restoreCacheMock).toHaveBeenCalledWith(
233+
[path],
234+
key,
235+
[restoreKey],
236+
{},
237+
false
238+
);
239+
240+
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
241+
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0);
242+
243+
expect(failedMock).toHaveBeenCalledWith(
244+
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}`
245+
);
246+
expect(failedMock).toHaveBeenCalledTimes(1);
247+
});
248+
249+
test("restore when fail on cache miss is enabled and primary key doesn't match restored key", async () => {
250+
const path = "node_modules";
251+
const key = "node-test";
252+
const restoreKey = "node-";
253+
testUtils.setInputs({
254+
path: path,
255+
key,
256+
restoreKeys: [restoreKey],
257+
failOnCacheMiss: true
258+
});
259+
260+
const infoMock = jest.spyOn(core, "info");
261+
const failedMock = jest.spyOn(core, "setFailed");
262+
const stateMock = jest.spyOn(core, "saveState");
263+
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
264+
const restoreCacheMock = jest
265+
.spyOn(cache, "restoreCache")
266+
.mockImplementationOnce(() => {
267+
return Promise.resolve(restoreKey);
268+
});
269+
270+
await run();
271+
272+
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
273+
expect(restoreCacheMock).toHaveBeenCalledWith(
274+
[path],
275+
key,
276+
[restoreKey],
277+
{},
278+
false
279+
);
280+
281+
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
282+
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
283+
expect(stateMock).toHaveBeenCalledTimes(2);
284+
285+
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
286+
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
287+
288+
expect(infoMock).toHaveBeenCalledWith(
289+
`Cache restored from key: ${restoreKey}`
290+
);
291+
expect(failedMock).toHaveBeenCalledTimes(0);
292+
});
293+
294+
test("restore with fail on cache miss disabled and no cache found", async () => {
295+
const path = "node_modules";
296+
const key = "node-test";
297+
const restoreKey = "node-";
298+
testUtils.setInputs({
299+
path: path,
300+
key,
301+
restoreKeys: [restoreKey],
302+
failOnCacheMiss: false
303+
});
304+
305+
const infoMock = jest.spyOn(core, "info");
306+
const failedMock = jest.spyOn(core, "setFailed");
307+
const stateMock = jest.spyOn(core, "saveState");
308+
const restoreCacheMock = jest
309+
.spyOn(cache, "restoreCache")
310+
.mockImplementationOnce(() => {
311+
return Promise.resolve(undefined);
312+
});
313+
314+
await run();
315+
316+
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
317+
expect(restoreCacheMock).toHaveBeenCalledWith(
318+
[path],
319+
key,
320+
[restoreKey],
321+
{},
322+
false
323+
);
324+
325+
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
326+
expect(stateMock).toHaveBeenCalledTimes(1);
327+
328+
expect(infoMock).toHaveBeenCalledWith(
329+
`Cache not found for input keys: ${key}, ${restoreKey}`
330+
);
331+
expect(failedMock).toHaveBeenCalledTimes(0);
332+
});

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ inputs:
1818
description: 'An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms'
1919
default: 'false'
2020
required: false
21+
fail-on-cache-miss:
22+
description: 'Fail the workflow if cache entry is not found'
23+
default: 'false'
24+
required: false
2125
outputs:
2226
cache-hit:
2327
description: 'A boolean value to indicate an exact match was found for the primary key'

dist/restore-only/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4978,7 +4978,8 @@ var Inputs;
49784978
Inputs["Path"] = "path";
49794979
Inputs["RestoreKeys"] = "restore-keys";
49804980
Inputs["UploadChunkSize"] = "upload-chunk-size";
4981-
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
4981+
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
4982+
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
49824983
})(Inputs = exports.Inputs || (exports.Inputs = {}));
49834984
var Outputs;
49844985
(function (Outputs) {
@@ -50495,8 +50496,12 @@ function restoreImpl(stateProvider) {
5049550496
required: true
5049650497
});
5049750498
const enableCrossOsArchive = utils.getInputAsBool(constants_1.Inputs.EnableCrossOsArchive);
50499+
const failOnCacheMiss = utils.getInputAsBool(constants_1.Inputs.FailOnCacheMiss);
5049850500
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys, {}, enableCrossOsArchive);
5049950501
if (!cacheKey) {
50502+
if (failOnCacheMiss) {
50503+
throw new Error(`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`);
50504+
}
5050050505
core.info(`Cache not found for input keys: ${[
5050150506
primaryKey,
5050250507
...restoreKeys

dist/restore/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4978,7 +4978,8 @@ var Inputs;
49784978
Inputs["Path"] = "path";
49794979
Inputs["RestoreKeys"] = "restore-keys";
49804980
Inputs["UploadChunkSize"] = "upload-chunk-size";
4981-
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
4981+
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
4982+
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
49824983
})(Inputs = exports.Inputs || (exports.Inputs = {}));
49834984
var Outputs;
49844985
(function (Outputs) {
@@ -50495,8 +50496,12 @@ function restoreImpl(stateProvider) {
5049550496
required: true
5049650497
});
5049750498
const enableCrossOsArchive = utils.getInputAsBool(constants_1.Inputs.EnableCrossOsArchive);
50499+
const failOnCacheMiss = utils.getInputAsBool(constants_1.Inputs.FailOnCacheMiss);
5049850500
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys, {}, enableCrossOsArchive);
5049950501
if (!cacheKey) {
50502+
if (failOnCacheMiss) {
50503+
throw new Error(`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`);
50504+
}
5050050505
core.info(`Cache not found for input keys: ${[
5050150506
primaryKey,
5050250507
...restoreKeys

dist/save-only/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5034,7 +5034,8 @@ var Inputs;
50345034
Inputs["Path"] = "path";
50355035
Inputs["RestoreKeys"] = "restore-keys";
50365036
Inputs["UploadChunkSize"] = "upload-chunk-size";
5037-
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
5037+
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
5038+
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
50385039
})(Inputs = exports.Inputs || (exports.Inputs = {}));
50395040
var Outputs;
50405041
(function (Outputs) {

dist/save/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4978,7 +4978,8 @@ var Inputs;
49784978
Inputs["Path"] = "path";
49794979
Inputs["RestoreKeys"] = "restore-keys";
49804980
Inputs["UploadChunkSize"] = "upload-chunk-size";
4981-
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
4981+
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
4982+
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
49824983
})(Inputs = exports.Inputs || (exports.Inputs = {}));
49834984
var Outputs;
49844985
(function (Outputs) {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cache",
3-
"version": "3.2.3",
3+
"version": "3.2.4",
44
"private": true,
55
"description": "Cache dependencies and build outputs",
66
"main": "dist/restore/index.js",

0 commit comments

Comments
 (0)