Skip to content

Commit 136d96b

Browse files
authored
Enabling actions/cache for GHES based on presence of AC service (#774)
* initial changes * Update package-lock.json * Update package-lock.json * review comments and updated test cases * package.json * changed name * added new line * changed tookit * updated with 2.0 * changed with public released package * ran code format * linting errors * Update actionUtils.test.ts * Update cache.dep.yml * Update package.json * Update README.md * Create RELEASES.md * Update RELEASES.md * Update package.json * Update package-lock.json * typo
1 parent 7d4f40b commit 136d96b

13 files changed

Lines changed: 7680 additions & 105 deletions

File tree

.licenses/npm/@actions/cache.dep.yml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ See ["Caching dependencies to speed up workflows"](https://help.github.com/githu
1010

1111
## What's New
1212
### v3
13+
* Added support for caching from GHES 3.5.
1314
* Fixed download issue for files > 2GB during restore.
1415
* Updated the minimum runner version support from node 12 -> node 16.
1516

@@ -176,18 +177,6 @@ steps:
176177

177178
> Note: The `id` defined in `actions/cache` must match the `id` in the `if` statement (i.e. `steps.[ID].outputs.cache-hit`)
178179

179-
## Known limitation
180-
181-
- `action/cache` is currently not supported on GitHub Enterprise Server. <https://github.com/github/roadmap/issues/273> is tracking this.
182-
183-
Since GitHub Enterprise Server uses self-hosted runners, dependencies are typically cached on the runner by whatever dependency management tool is being used (npm, maven, etc.). This eliminates the need for explicit caching in some scenarios.
184-
185-
## Changelog schedule and history
186-
187-
| Status | Version | Date | Highlights |
188-
|:---|:---|:---|:---|
189-
| Published | v3.0.0 | Mar 21st, 2022 | - Updated minimum runner version support from node 12 -> node 16 <br> |
190-
191180
## Contributing
192181
We would love for you to contribute to `actions/cache`, pull requests are welcome! Please see the [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
193182

RELEASES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Releases
2+
3+
### 3.0.0
4+
- Updated minimum runner version support from node 12 -> node 16
5+
6+
### 3.0.1
7+
- Added support for caching from GHES 3.5.
8+
- Fixed download issue for files > 2GB during restore.

__tests__/actionUtils.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import * as cache from "@actions/cache";
12
import * as core from "@actions/core";
23

34
import { Events, Outputs, RefKey, State } from "../src/constants";
45
import * as actionUtils from "../src/utils/actionUtils";
56
import * as testUtils from "../src/utils/testUtils";
67

78
jest.mock("@actions/core");
9+
jest.mock("@actions/cache");
810

911
beforeAll(() => {
1012
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
@@ -232,3 +234,41 @@ test("getInputAsInt throws if required and value missing", () => {
232234
actionUtils.getInputAsInt("undefined", { required: true })
233235
).toThrowError();
234236
});
237+
238+
test("isCacheFeatureAvailable for ac enabled", () => {
239+
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => true);
240+
241+
expect(actionUtils.isCacheFeatureAvailable()).toBe(true);
242+
});
243+
244+
test("isCacheFeatureAvailable for ac disabled on GHES", () => {
245+
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false);
246+
247+
const message =
248+
"Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.";
249+
const infoMock = jest.spyOn(core, "info");
250+
251+
try {
252+
process.env["GITHUB_SERVER_URL"] = "http://example.com";
253+
expect(actionUtils.isCacheFeatureAvailable()).toBe(false);
254+
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`);
255+
} finally {
256+
delete process.env["GITHUB_SERVER_URL"];
257+
}
258+
});
259+
260+
test("isCacheFeatureAvailable for ac disabled on dotcom", () => {
261+
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false);
262+
263+
const message =
264+
"An internal error has occurred in cache backend. Please check https://www.githubstatus.com/ for any ongoing issue in actions.";
265+
const infoMock = jest.spyOn(core, "info");
266+
267+
try {
268+
process.env["GITHUB_SERVER_URL"] = "http://github.com";
269+
expect(actionUtils.isCacheFeatureAvailable()).toBe(false);
270+
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`);
271+
} finally {
272+
delete process.env["GITHUB_SERVER_URL"];
273+
}
274+
});

__tests__/restore.test.ts

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ beforeEach(() => {
3434
process.env[RefKey] = "refs/heads/feature-branch";
3535

3636
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
37+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
38+
() => true
39+
);
3740
});
3841

3942
afterEach(() => {
@@ -55,10 +58,12 @@ test("restore with invalid event outputs warning", async () => {
5558
expect(failedMock).toHaveBeenCalledTimes(0);
5659
});
5760

58-
test("restore on GHES should no-op", async () => {
59-
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
61+
test("restore without AC available should no-op", async () => {
62+
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
63+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
64+
() => false
65+
);
6066

61-
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
6267
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
6368
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
6469

@@ -67,9 +72,54 @@ test("restore on GHES should no-op", async () => {
6772
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
6873
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
6974
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
70-
expect(logWarningMock).toHaveBeenCalledWith(
71-
"Cache action is not supported on GHES. See https://github.com/actions/cache/issues/505 for more details"
75+
});
76+
77+
test("restore on GHES without AC available should no-op", async () => {
78+
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
79+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
80+
() => false
7281
);
82+
83+
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
84+
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
85+
86+
await run();
87+
88+
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
89+
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
90+
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
91+
});
92+
93+
test("restore on GHES with AC available ", async () => {
94+
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
95+
const path = "node_modules";
96+
const key = "node-test";
97+
testUtils.setInputs({
98+
path: path,
99+
key
100+
});
101+
102+
const infoMock = jest.spyOn(core, "info");
103+
const failedMock = jest.spyOn(core, "setFailed");
104+
const stateMock = jest.spyOn(core, "saveState");
105+
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
106+
const restoreCacheMock = jest
107+
.spyOn(cache, "restoreCache")
108+
.mockImplementationOnce(() => {
109+
return Promise.resolve(key);
110+
});
111+
112+
await run();
113+
114+
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
115+
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
116+
117+
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
118+
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
119+
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
120+
121+
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
122+
expect(failedMock).toHaveBeenCalledTimes(0);
73123
});
74124

75125
test("restore with no path should fail", async () => {

__tests__/save.test.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ beforeEach(() => {
5454
process.env[RefKey] = "refs/heads/feature-branch";
5555

5656
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
57+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
58+
() => true
59+
);
5760
});
5861

5962
afterEach(() => {
@@ -101,18 +104,67 @@ test("save with no primary key in state outputs warning", async () => {
101104
expect(failedMock).toHaveBeenCalledTimes(0);
102105
});
103106

104-
test("save on GHES should no-op", async () => {
105-
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
107+
test("save without AC available should no-op", async () => {
108+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
109+
() => false
110+
);
106111

107-
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
108112
const saveCacheMock = jest.spyOn(cache, "saveCache");
109113

110114
await run();
111115

112116
expect(saveCacheMock).toHaveBeenCalledTimes(0);
113-
expect(logWarningMock).toHaveBeenCalledWith(
114-
"Cache action is not supported on GHES. See https://github.com/actions/cache/issues/505 for more details"
117+
});
118+
119+
test("save on ghes without AC available should no-op", async () => {
120+
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
121+
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
122+
() => false
115123
);
124+
125+
const saveCacheMock = jest.spyOn(cache, "saveCache");
126+
127+
await run();
128+
129+
expect(saveCacheMock).toHaveBeenCalledTimes(0);
130+
});
131+
132+
test("save on GHES with AC available", async () => {
133+
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
134+
const failedMock = jest.spyOn(core, "setFailed");
135+
136+
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
137+
const savedCacheKey = "Linux-node-";
138+
139+
jest.spyOn(core, "getState")
140+
// Cache Entry State
141+
.mockImplementationOnce(() => {
142+
return savedCacheKey;
143+
})
144+
// Cache Key State
145+
.mockImplementationOnce(() => {
146+
return primaryKey;
147+
});
148+
149+
const inputPath = "node_modules";
150+
testUtils.setInput(Inputs.Path, inputPath);
151+
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
152+
153+
const cacheId = 4;
154+
const saveCacheMock = jest
155+
.spyOn(cache, "saveCache")
156+
.mockImplementationOnce(() => {
157+
return Promise.resolve(cacheId);
158+
});
159+
160+
await run();
161+
162+
expect(saveCacheMock).toHaveBeenCalledTimes(1);
163+
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
164+
uploadChunkSize: 4000000
165+
});
166+
167+
expect(failedMock).toHaveBeenCalledTimes(0);
116168
});
117169

118170
test("save with exact match returns early", async () => {

dist/restore/index.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3221,10 +3221,7 @@ const options_1 = __webpack_require__(538);
32213221
const requestUtils_1 = __webpack_require__(899);
32223222
const versionSalt = '1.0';
32233223
function getCacheApiUrl(resource) {
3224-
// Ideally we just use ACTIONS_CACHE_URL
3225-
const baseUrl = (process.env['ACTIONS_CACHE_URL'] ||
3226-
process.env['ACTIONS_RUNTIME_URL'] ||
3227-
'').replace('pipelines', 'artifactcache');
3224+
const baseUrl = process.env['ACTIONS_CACHE_URL'] || '';
32283225
if (!baseUrl) {
32293226
throw new Error('Cache Service Url not found, unable to restore cache.');
32303227
}
@@ -37460,7 +37457,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
3746037457
return result;
3746137458
};
3746237459
Object.defineProperty(exports, "__esModule", { value: true });
37463-
exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.getCacheState = exports.setOutputAndState = exports.setCacheHitOutput = exports.setCacheState = exports.isExactKeyMatch = exports.isGhes = void 0;
37460+
exports.isCacheFeatureAvailable = exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.getCacheState = exports.setOutputAndState = exports.setCacheHitOutput = exports.setCacheState = exports.isExactKeyMatch = exports.isGhes = void 0;
37461+
const cache = __importStar(__webpack_require__(692));
3746437462
const core = __importStar(__webpack_require__(470));
3746537463
const constants_1 = __webpack_require__(196);
3746637464
function isGhes() {
@@ -37525,6 +37523,19 @@ function getInputAsInt(name, options) {
3752537523
return value;
3752637524
}
3752737525
exports.getInputAsInt = getInputAsInt;
37526+
function isCacheFeatureAvailable() {
37527+
if (!cache.isFeatureAvailable()) {
37528+
if (isGhes()) {
37529+
logWarning("Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.");
37530+
}
37531+
else {
37532+
logWarning("An internal error has occurred in cache backend. Please check https://www.githubstatus.com/ for any ongoing issue in actions.");
37533+
}
37534+
return false;
37535+
}
37536+
return true;
37537+
}
37538+
exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
3752837539

3752937540

3753037541
/***/ }),
@@ -46440,6 +46451,15 @@ function checkKey(key) {
4644046451
throw new ValidationError(`Key Validation Error: ${key} cannot contain commas.`);
4644146452
}
4644246453
}
46454+
/**
46455+
* isFeatureAvailable to check the presence of Actions cache service
46456+
*
46457+
* @returns boolean return true if Actions cache service feature is available, otherwise false
46458+
*/
46459+
function isFeatureAvailable() {
46460+
return !!process.env['ACTIONS_CACHE_URL'];
46461+
}
46462+
exports.isFeatureAvailable = isFeatureAvailable;
4644346463
/**
4644446464
* Restores cache from keys
4644546465
*
@@ -48101,8 +48121,7 @@ const utils = __importStar(__webpack_require__(443));
4810148121
function run() {
4810248122
return __awaiter(this, void 0, void 0, function* () {
4810348123
try {
48104-
if (utils.isGhes()) {
48105-
utils.logWarning("Cache action is not supported on GHES. See https://github.com/actions/cache/issues/505 for more details");
48124+
if (!utils.isCacheFeatureAvailable()) {
4810648125
utils.setCacheHitOutput(false);
4810748126
return;
4810848127
}

0 commit comments

Comments
 (0)