Compare commits

...

9 Commits

Author SHA1 Message Date
627f0f41f6 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
2023-01-30 16:40:58 +05:30
8e3048d0f7 Merge pull request #1089 from maybeec/patch-1
Added another usage hint to foresee #1072
2023-01-27 16:34:23 +05:30
1b004e8a69 Update tips-and-workarounds.md
Co-authored-by: Lovepreet Singh <pdotl@github.com>
2023-01-27 09:07:57 +01:00
75b110bc85 Added another hint to foresee #1072 2023-01-27 06:35:14 +01:00
2b5a782c64 Update caching-strategies.md (#1084)
Correct typo; `restore` should be `save'.
2023-01-22 19:20:12 +05:30
6c2de3ba98 Merge pull request #1081 from vHeemstra/patch-1
chore: Fix syntax error typo
2023-01-20 10:49:48 +05:30
b63536828e there as well ;) 2023-01-19 21:08:05 +01:00
cd2aaa9df8 chore: Fix syntax error typo 2023-01-19 21:04:46 +01:00
9b7ef12f3e Merge pull request #1077 from actions/pallavx-patch-1
Introduce add-to-project step for new issues/PRs & rename workflow files
2023-01-18 16:32:37 +05:30
17 changed files with 181 additions and 20 deletions

View File

@ -30,6 +30,7 @@ See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/ac
* 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.
* Two new actions available for granular control over caches - [restore](restore/action.yml) and [save](save/action.yml)
* Support cross-os caching as an opt-in feature. See [Cross OS caching](./tips-and-workarounds.md#cross-os-cache) for more info.
* 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.
Refer [here](https://github.com/actions/cache/blob/v2/README.md) for previous versions
@ -49,6 +50,7 @@ If you are using a `self-hosted` Windows runner, `GNU tar` and `zstd` are requir
* `key` - An explicit key for restoring and saving the cache. Refer [creating a cache key](#creating-a-cache-key).
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
* `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
* `fail-on-cache-miss` - Fail the workflow if cache entry is not found. Default: false
#### Environment Variables
@ -117,7 +119,7 @@ jobs:
- name: Restore cached Primes
id: cache-primes-restore
- uses: actions/cache/restore@v3
uses: actions/cache/restore@v3
with:
path: |
path/to/dependencies
@ -128,7 +130,7 @@ jobs:
.
- name: Save Primes
id: cache-primes-save
- uses: actions/cache/save@v3
uses: actions/cache/save@v3
with:
path: |
path/to/dependencies

View File

@ -66,4 +66,7 @@
### 3.2.3
- Support cross os caching on Windows as an opt-in feature.
- Fix issue with symlink restoration on Windows for cross-os caches.
- Fix issue with symlink restoration on Windows for cross-os caches.
### 3.2.4
- Added option to fail job on cache miss.

View File

@ -205,3 +205,128 @@ test("restore with cache found for restore key", async () => {
);
expect(failedMock).toHaveBeenCalledTimes(0);
});
test("Fail restore when fail on cache miss is enabled and primary + restore keys not found", async () => {
const path = "node_modules";
const key = "node-test";
const restoreKey = "node-";
testUtils.setInputs({
path: path,
key,
restoreKeys: [restoreKey],
failOnCacheMiss: true
});
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
return Promise.resolve(undefined);
});
await run();
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
expect(restoreCacheMock).toHaveBeenCalledWith(
[path],
key,
[restoreKey],
{},
false
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0);
expect(failedMock).toHaveBeenCalledWith(
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}`
);
expect(failedMock).toHaveBeenCalledTimes(1);
});
test("restore when fail on cache miss is enabled and primary key doesn't match restored key", async () => {
const path = "node_modules";
const key = "node-test";
const restoreKey = "node-";
testUtils.setInputs({
path: path,
key,
restoreKeys: [restoreKey],
failOnCacheMiss: true
});
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
return Promise.resolve(restoreKey);
});
await run();
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
expect(restoreCacheMock).toHaveBeenCalledWith(
[path],
key,
[restoreKey],
{},
false
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
expect(stateMock).toHaveBeenCalledTimes(2);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(infoMock).toHaveBeenCalledWith(
`Cache restored from key: ${restoreKey}`
);
expect(failedMock).toHaveBeenCalledTimes(0);
});
test("restore with fail on cache miss disabled and no cache found", async () => {
const path = "node_modules";
const key = "node-test";
const restoreKey = "node-";
testUtils.setInputs({
path: path,
key,
restoreKeys: [restoreKey],
failOnCacheMiss: false
});
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
return Promise.resolve(undefined);
});
await run();
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
expect(restoreCacheMock).toHaveBeenCalledWith(
[path],
key,
[restoreKey],
{},
false
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(stateMock).toHaveBeenCalledTimes(1);
expect(infoMock).toHaveBeenCalledWith(
`Cache not found for input keys: ${key}, ${restoreKey}`
);
expect(failedMock).toHaveBeenCalledTimes(0);
});

View File

@ -18,6 +18,10 @@ inputs:
description: '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'
required: false
fail-on-cache-miss:
description: 'Fail the workflow if cache entry is not found'
default: 'false'
required: false
outputs:
cache-hit:
description: 'A boolean value to indicate an exact match was found for the primary key'

View File

@ -182,7 +182,7 @@ steps:
### Reusing primary key from restore cache as input to save action
If you want to avoid re-computing the cache key again in `save` action, the outputs from `restore` action can be used as input to the `restore` action.
If you want to avoid re-computing the cache key again in `save` action, the outputs from `restore` action can be used as input to the `save` action.
```yaml
- uses: actions/cache/restore@v3

View File

@ -4978,7 +4978,8 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["RestoreKeys"] = "restore-keys";
Inputs["UploadChunkSize"] = "upload-chunk-size";
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var Outputs;
(function (Outputs) {
@ -50495,8 +50496,12 @@ function restoreImpl(stateProvider) {
required: true
});
const enableCrossOsArchive = utils.getInputAsBool(constants_1.Inputs.EnableCrossOsArchive);
const failOnCacheMiss = utils.getInputAsBool(constants_1.Inputs.FailOnCacheMiss);
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys, {}, enableCrossOsArchive);
if (!cacheKey) {
if (failOnCacheMiss) {
throw new Error(`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`);
}
core.info(`Cache not found for input keys: ${[
primaryKey,
...restoreKeys

View File

@ -4978,7 +4978,8 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["RestoreKeys"] = "restore-keys";
Inputs["UploadChunkSize"] = "upload-chunk-size";
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var Outputs;
(function (Outputs) {
@ -50495,8 +50496,12 @@ function restoreImpl(stateProvider) {
required: true
});
const enableCrossOsArchive = utils.getInputAsBool(constants_1.Inputs.EnableCrossOsArchive);
const failOnCacheMiss = utils.getInputAsBool(constants_1.Inputs.FailOnCacheMiss);
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys, {}, enableCrossOsArchive);
if (!cacheKey) {
if (failOnCacheMiss) {
throw new Error(`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`);
}
core.info(`Cache not found for input keys: ${[
primaryKey,
...restoreKeys

View File

@ -5034,7 +5034,8 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["RestoreKeys"] = "restore-keys";
Inputs["UploadChunkSize"] = "upload-chunk-size";
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var Outputs;
(function (Outputs) {

3
dist/save/index.js vendored
View File

@ -4978,7 +4978,8 @@ var Inputs;
Inputs["Path"] = "path";
Inputs["RestoreKeys"] = "restore-keys";
Inputs["UploadChunkSize"] = "upload-chunk-size";
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive"; // Input for cache, restore, save action
Inputs["EnableCrossOsArchive"] = "enableCrossOsArchive";
Inputs["FailOnCacheMiss"] = "fail-on-cache-miss"; // Input for cache, restore action
})(Inputs = exports.Inputs || (exports.Inputs = {}));
var Outputs;
(function (Outputs) {

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "cache",
"version": "3.2.3",
"version": "3.2.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cache",
"version": "3.2.3",
"version": "3.2.4",
"license": "MIT",
"dependencies": {
"@actions/cache": "^3.1.2",

View File

@ -1,6 +1,6 @@
{
"name": "cache",
"version": "3.2.3",
"version": "3.2.4",
"private": true,
"description": "Cache dependencies and build outputs",
"main": "dist/restore/index.js",

View File

@ -7,6 +7,7 @@ The restore action, as the name suggest, restores a cache. It acts similar to th
* `path` - A list of files, directories, and wildcard patterns to cache and restore. See [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob) for supported patterns.
* `key` - String used while saving cache for restoring the cache
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
* `fail-on-cache-miss` - Fail the workflow if cache entry is not found. Default: false
## Outputs
@ -95,7 +96,7 @@ steps:
### Exit workflow on cache miss
You can use the output of this action to exit the workflow on cache miss. This way you can restrict your workflow to only initiate the build when `cache-hit` occurs, in other words, cache with exact key is found.
You can use `fail-on-cache-miss: true` to exit the workflow on a cache miss. This way you can restrict your workflow to only initiate the build when a cache is matched. Also, if you want to fail if cache did not match primary key, additionally leave `restore-keys` empty!
```yaml
steps:
@ -106,10 +107,7 @@ steps:
with:
path: path/to/dependencies
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: Check cache hit
if: steps.cache.outputs.cache-hit != 'true'
run: exit 1
fail-on-cache-miss: true
- name: Build
run: /build.sh

View File

@ -15,6 +15,10 @@ inputs:
description: 'An optional boolean when enabled, allows windows runners to restore caches that were saved on other platforms'
default: 'false'
required: false
fail-on-cache-miss:
description: 'Fail the workflow if cache entry is not found'
default: 'false'
required: false
outputs:
cache-hit:
description: 'A boolean value to indicate an exact match was found for the primary key'
@ -27,4 +31,4 @@ runs:
main: '../dist/restore-only/index.js'
branding:
icon: 'archive'
color: 'gray-dark'
color: 'gray-dark'

View File

@ -3,7 +3,8 @@ export enum Inputs {
Path = "path", // Input for cache, restore, save action
RestoreKeys = "restore-keys", // Input for cache, restore action
UploadChunkSize = "upload-chunk-size", // Input for cache, save action
EnableCrossOsArchive = "enableCrossOsArchive" // Input for cache, restore, save action
EnableCrossOsArchive = "enableCrossOsArchive", // Input for cache, restore, save action
FailOnCacheMiss = "fail-on-cache-miss" // Input for cache, restore action
}
export enum Outputs {

View File

@ -34,6 +34,7 @@ async function restoreImpl(
const enableCrossOsArchive = utils.getInputAsBool(
Inputs.EnableCrossOsArchive
);
const failOnCacheMiss = utils.getInputAsBool(Inputs.FailOnCacheMiss);
const cacheKey = await cache.restoreCache(
cachePaths,
@ -44,6 +45,11 @@ async function restoreImpl(
);
if (!cacheKey) {
if (failOnCacheMiss) {
throw new Error(
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`
);
}
core.info(
`Cache not found for input keys: ${[
primaryKey,

View File

@ -14,6 +14,7 @@ interface CacheInput {
key: string;
restoreKeys?: string[];
enableCrossOsArchive?: boolean;
failOnCacheMiss?: boolean;
}
export function setInputs(input: CacheInput): void {
@ -26,6 +27,8 @@ export function setInputs(input: CacheInput): void {
Inputs.EnableCrossOsArchive,
input.enableCrossOsArchive.toString()
);
input.failOnCacheMiss !== undefined &&
setInput(Inputs.FailOnCacheMiss, input.failOnCacheMiss.toString());
}
export function clearInputs(): void {
@ -34,4 +37,5 @@ export function clearInputs(): void {
delete process.env[getInputName(Inputs.RestoreKeys)];
delete process.env[getInputName(Inputs.UploadChunkSize)];
delete process.env[getInputName(Inputs.EnableCrossOsArchive)];
delete process.env[getInputName(Inputs.FailOnCacheMiss)];
}

View File

@ -22,7 +22,9 @@ Reusing cache across feature branches is not allowed today to provide cache [iso
## Cross OS cache
From `v3.2.3` cache is cross-os compatible when `enableCrossOsArchive` input is passed as true. This means that a cache created on `ubuntu-latest` or `mac-latest` can be used by `windows-latest` and vice versa, provided the workflow which runs on `windows-latest` have input `enableCrossOsArchive` as true. This is useful to cache dependencies which are independent of the runner platform. This will help reduce the consumption of the cache quota and help build for multiple platforms from the same cache. Things to keep in mind while using this feature:
- Only cache those files which are compatible across OSs.
- Caching symlinks might cause issues while restoration as they work differently on different OSs.
- Caching symlinks might cause issues while restoration as they work differently on different OSs.
- Only cache files from within your github workspace directory.
- Avoid using directory pointers such as `${{ github.workspace }}` or `~` (home) which eventually evaluate to an absolute path and will not match across OSs.
## Force deletion of caches overriding default cache eviction policy
Caches have [branch scope restriction](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache) in place. This means that if caches for a specific branch are using a lot of storage quota, it may result into more frequently used caches from `default` branch getting thrashed. For example, if there are many pull requests happening on a repo and are creating caches, these cannot be used in default branch scope but will still occupy a lot of space till they get cleaned up by [eviction policy](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy). But sometime we want to clean them up on a faster cadence so as to ensure default branch is not thrashing. In order to achieve this, [gh-actions-cache cli](https://github.com/actions/gh-actions-cache/) can be used to delete caches for specific branches.
@ -67,4 +69,4 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
</details>
</details>