From bacd6b4b3ac3127b28a1e1920c23bf1c2cadbb85 Mon Sep 17 00:00:00 2001 From: Dmitry Shibanov Date: Thu, 31 Mar 2022 21:10:37 +0200 Subject: [PATCH] Caching on GHES (#452) * add support for ghes caching * fix licesnses * work on resolving comments * change internal error to warning * fix warning for internal errors * update version --- .licenses/npm/@actions/cache.dep.yml | 2 +- __tests__/cache-utils.test.ts | 54 ++++++++++++++++++---------- __tests__/installer.test.ts | 48 +++++++++++++++++++++++++ dist/cache-save/index.js | 36 ++++++++++++++++--- dist/setup/index.js | 49 +++++++++++++++++-------- package-lock.json | 26 +++++++------- package.json | 4 +-- src/cache-utils.ts | 26 ++++++++++++++ src/main.ts | 14 ++------ 9 files changed, 192 insertions(+), 67 deletions(-) diff --git a/.licenses/npm/@actions/cache.dep.yml b/.licenses/npm/@actions/cache.dep.yml index c2d23e2..35b0a4b 100644 --- a/.licenses/npm/@actions/cache.dep.yml +++ b/.licenses/npm/@actions/cache.dep.yml @@ -1,6 +1,6 @@ --- name: "@actions/cache" -version: 1.0.8 +version: 2.0.0 type: npm summary: Actions cache lib homepage: https://github.com/actions/toolkit/tree/main/packages/cache diff --git a/__tests__/cache-utils.test.ts b/__tests__/cache-utils.test.ts index 0ceabeb..d8934eb 100644 --- a/__tests__/cache-utils.test.ts +++ b/__tests__/cache-utils.test.ts @@ -1,37 +1,28 @@ import * as core from '@actions/core'; +import * as cache from '@actions/cache'; import path from 'path'; import * as utils from '../src/cache-utils'; -import {PackageManagerInfo} from '../src/cache-utils'; +import {PackageManagerInfo, isCacheFeatureAvailable} from '../src/cache-utils'; describe('cache-utils', () => { - const commonPath = '/some/random/path'; const versionYarn1 = '1.2.3'; - const versionYarn2 = '2.3.4'; let debugSpy: jest.SpyInstance; let getCommandOutputSpy: jest.SpyInstance; - - function getPackagePath(name: string) { - if (name === utils.supportedPackageManagers.npm.getCacheFolderCommand) { - return `${commonPath}/npm`; - } else if ( - name === utils.supportedPackageManagers.pnpm.getCacheFolderCommand - ) { - return `${commonPath}/pnpm`; - } else { - if (name === utils.supportedPackageManagers.yarn1.getCacheFolderCommand) { - return `${commonPath}/yarn1`; - } else { - return `${commonPath}/yarn2`; - } - } - } + let isFeatureAvailable: jest.SpyInstance; + let info: jest.SpyInstance; + let warningSpy: jest.SpyInstance; beforeEach(() => { process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); debugSpy = jest.spyOn(core, 'debug'); debugSpy.mockImplementation(msg => {}); + info = jest.spyOn(core, 'info'); + warningSpy = jest.spyOn(core, 'warning'); + + isFeatureAvailable = jest.spyOn(cache, 'isFeatureAvailable'); + getCommandOutputSpy = jest.spyOn(utils, 'getCommandOutput'); }); @@ -51,7 +42,32 @@ describe('cache-utils', () => { }); }); + it('isCacheFeatureAvailable for GHES is false', () => { + isFeatureAvailable.mockImplementation(() => false); + process.env['GITHUB_SERVER_URL'] = 'https://www.test.com'; + + expect(() => isCacheFeatureAvailable()).toThrowError( + '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.' + ); + }); + + it('isCacheFeatureAvailable for GHES has an interhal error', () => { + isFeatureAvailable.mockImplementation(() => false); + process.env['GITHUB_SERVER_URL'] = ''; + isCacheFeatureAvailable(); + expect(warningSpy).toHaveBeenCalledWith( + 'The runner was not able to contact the cache service. Caching will be skipped' + ); + }); + + it('isCacheFeatureAvailable for GHES is available', () => { + isFeatureAvailable.mockImplementation(() => true); + + expect(isCacheFeatureAvailable()).toStrictEqual(true); + }); + afterEach(() => { + process.env['GITHUB_SERVER_URL'] = ''; jest.resetAllMocks(); jest.clearAllMocks(); }); diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 4565dd4..19692cf 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -2,6 +2,7 @@ import * as core from '@actions/core'; import * as io from '@actions/io'; import * as tc from '@actions/tool-cache'; import * as im from '../src/installer'; +import * as cache from '@actions/cache'; import fs from 'fs'; import cp from 'child_process'; import osm = require('os'); @@ -36,6 +37,7 @@ describe('setup-node', () => { let execSpy: jest.SpyInstance; let authSpy: jest.SpyInstance; let parseNodeVersionSpy: jest.SpyInstance; + let isCacheActionAvailable: jest.SpyInstance; beforeEach(() => { // @actions/core @@ -67,6 +69,9 @@ describe('setup-node', () => { existsSpy = jest.spyOn(fs, 'existsSync'); mkdirpSpy = jest.spyOn(io, 'mkdirP'); + // @actions/tool-cache + isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); + // disable authentication portion for installer tests authSpy = jest.spyOn(auth, 'configAuthentication'); authSpy.mockImplementation(() => {}); @@ -644,6 +649,49 @@ describe('setup-node', () => { ); }); }); + + describe('cache on GHES', () => { + it('Should throw an error, because cache is not supported', async () => { + inputs['node-version'] = '12'; + inputs['cache'] = 'npm'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + + // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + process.env['GITHUB_SERVER_URL'] = 'https://www.test.com'; + isCacheActionAvailable.mockImplementation(() => false); + + await main.run(); + + expect(cnSpy).toHaveBeenCalledWith( + `::error::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.${osm.EOL}` + ); + }); + + it('Should throw an internal error', async () => { + inputs['node-version'] = '12'; + inputs['cache'] = 'npm'; + + inSpy.mockImplementation(name => inputs[name]); + + let toolPath = path.normalize('/cache/node/12.16.1/x64'); + findSpy.mockImplementation(() => toolPath); + + // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); + process.env['GITHUB_SERVER_URL'] = ''; + isCacheActionAvailable.mockImplementation(() => false); + + await main.run(); + + expect(warningSpy).toHaveBeenCalledWith( + 'The runner was not able to contact the cache service. Caching will be skipped' + ); + }); + }); + describe('LTS version', () => { beforeEach(() => { os.platform = 'linux'; diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index e29e4f8..5836c4a 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -3139,10 +3139,7 @@ const options_1 = __webpack_require__(248); const requestUtils_1 = __webpack_require__(826); const versionSalt = '1.0'; function getCacheApiUrl(resource) { - // Ideally we just use ACTIONS_CACHE_URL - const baseUrl = (process.env['ACTIONS_CACHE_URL'] || - process.env['ACTIONS_RUNTIME_URL'] || - '').replace('pipelines', 'artifactcache'); + const baseUrl = process.env['ACTIONS_CACHE_URL'] || ''; if (!baseUrl) { throw new Error('Cache Service Url not found, unable to restore cache.'); } @@ -3811,6 +3808,7 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const exec = __importStar(__webpack_require__(986)); +const cache = __importStar(__webpack_require__(692)); exports.supportedPackageManagers = { npm: { lockFilePatterns: ['package-lock.json', 'yarn.lock'], @@ -3875,6 +3873,24 @@ exports.getCacheDirectoryPath = (packageManagerInfo, packageManager) => __awaite core.debug(`${packageManager} path is ${stdOut}`); return stdOut; }); +function isGhes() { + const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +} +exports.isGhes = isGhes; +function isCacheFeatureAvailable() { + if (!cache.isFeatureAvailable()) { + if (isGhes()) { + throw new Error('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.'); + } + else { + core.warning('The runner was not able to contact the cache service. Caching will be skipped'); + } + return false; + } + return true; +} +exports.isCacheFeatureAvailable = isCacheFeatureAvailable; /***/ }), @@ -5703,7 +5719,8 @@ function downloadCacheStorageSDK(archiveLocation, archivePath, options) { // // If the file exceeds the buffer maximum length (~1 GB on 32-bit systems and ~2 GB // on 64-bit systems), split the download into multiple segments - const maxSegmentSize = buffer.constants.MAX_LENGTH; + // ~2 GB = 2147483647, beyond this, we start getting out of range error. So, capping it accordingly. + const maxSegmentSize = Math.min(2147483647, buffer.constants.MAX_LENGTH); const downloadProgress = new DownloadProgress(contentLength); const fd = fs.openSync(archivePath, 'w'); try { @@ -43258,6 +43275,15 @@ function checkKey(key) { throw new ValidationError(`Key Validation Error: ${key} cannot contain commas.`); } } +/** + * isFeatureAvailable to check the presence of Actions cache service + * + * @returns boolean return true if Actions cache service feature is available, otherwise false + */ +function isFeatureAvailable() { + return !!process.env['ACTIONS_CACHE_URL']; +} +exports.isFeatureAvailable = isFeatureAvailable; /** * Restores cache from keys * diff --git a/dist/setup/index.js b/dist/setup/index.js index b4b1cfd..19df95d 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -5410,10 +5410,7 @@ const options_1 = __webpack_require__(161); const requestUtils_1 = __webpack_require__(246); const versionSalt = '1.0'; function getCacheApiUrl(resource) { - // Ideally we just use ACTIONS_CACHE_URL - const baseUrl = (process.env['ACTIONS_CACHE_URL'] || - process.env['ACTIONS_RUNTIME_URL'] || - '').replace('pipelines', 'artifactcache'); + const baseUrl = process.env['ACTIONS_CACHE_URL'] || ''; if (!baseUrl) { throw new Error('Cache Service Url not found, unable to restore cache.'); } @@ -6588,7 +6585,7 @@ const fs_1 = __importDefault(__webpack_require__(747)); const auth = __importStar(__webpack_require__(749)); const path = __importStar(__webpack_require__(622)); const cache_restore_1 = __webpack_require__(409); -const url_1 = __webpack_require__(835); +const cache_utils_1 = __webpack_require__(570); const os = __webpack_require__(87); function run() { return __awaiter(this, void 0, void 0, function* () { @@ -6610,7 +6607,7 @@ function run() { } if (version) { let token = core.getInput('token'); - let auth = !token || isGhes() ? undefined : `token ${token}`; + let auth = !token || cache_utils_1.isGhes() ? undefined : `token ${token}`; let stable = (core.getInput('stable') || 'true').toUpperCase() === 'TRUE'; const checkLatest = (core.getInput('check-latest') || 'false').toUpperCase() === 'TRUE'; yield installer.getNode(version, stable, checkLatest, auth, arch); @@ -6620,10 +6617,7 @@ function run() { if (registryUrl) { auth.configAuthentication(registryUrl, alwaysAuth); } - if (cache) { - if (isGhes()) { - throw new Error('Caching is not supported on GHES'); - } + if (cache && cache_utils_1.isCacheFeatureAvailable()) { const cacheDependencyPath = core.getInput('cache-dependency-path'); yield cache_restore_1.restoreCache(cache, cacheDependencyPath); } @@ -6638,10 +6632,6 @@ function run() { }); } exports.run = run; -function isGhes() { - const ghUrl = new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); - return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; -} function resolveVersionInput() { let version = core.getInput('node-version'); const versionFileInput = core.getInput('node-version-file'); @@ -9525,7 +9515,8 @@ function downloadCacheStorageSDK(archiveLocation, archivePath, options) { // // If the file exceeds the buffer maximum length (~1 GB on 32-bit systems and ~2 GB // on 64-bit systems), split the download into multiple segments - const maxSegmentSize = buffer.constants.MAX_LENGTH; + // ~2 GB = 2147483647, beyond this, we start getting out of range error. So, capping it accordingly. + const maxSegmentSize = Math.min(2147483647, buffer.constants.MAX_LENGTH); const downloadProgress = new DownloadProgress(contentLength); const fd = fs.openSync(archivePath, 'w'); try { @@ -46070,6 +46061,7 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const exec = __importStar(__webpack_require__(986)); +const cache = __importStar(__webpack_require__(638)); exports.supportedPackageManagers = { npm: { lockFilePatterns: ['package-lock.json', 'yarn.lock'], @@ -46134,6 +46126,24 @@ exports.getCacheDirectoryPath = (packageManagerInfo, packageManager) => __awaite core.debug(`${packageManager} path is ${stdOut}`); return stdOut; }); +function isGhes() { + const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com'); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +} +exports.isGhes = isGhes; +function isCacheFeatureAvailable() { + if (!cache.isFeatureAvailable()) { + if (isGhes()) { + throw new Error('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.'); + } + else { + core.warning('The runner was not able to contact the cache service. Caching will be skipped'); + } + return false; + } + return true; +} +exports.isCacheFeatureAvailable = isCacheFeatureAvailable; /***/ }), @@ -47526,6 +47536,15 @@ function checkKey(key) { throw new ValidationError(`Key Validation Error: ${key} cannot contain commas.`); } } +/** + * isFeatureAvailable to check the presence of Actions cache service + * + * @returns boolean return true if Actions cache service feature is available, otherwise false + */ +function isFeatureAvailable() { + return !!process.env['ACTIONS_CACHE_URL']; +} +exports.isFeatureAvailable = isFeatureAvailable; /** * Restores cache from keys * diff --git a/package-lock.json b/package-lock.json index 3151483..7610fb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "setup-node", - "version": "2.0.0", + "version": "3.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "setup-node", - "version": "2.0.0", + "version": "3.1.0", "license": "MIT", "dependencies": { - "@actions/cache": "^1.0.8", + "@actions/cache": "^2.0.0", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", "@actions/github": "^1.1.0", @@ -32,17 +32,17 @@ } }, "node_modules/@actions/cache": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-1.0.8.tgz", - "integrity": "sha512-GWNNB67w93HGJRQXlsV56YqrdAuDoP3esK/mo5mzU8WoDCVjtQgJGsTdkYUX7brswtT7xnI30bWNo1WLKQ8FZQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.0.tgz", + "integrity": "sha512-d7n8ul6HjWX6oDrNEPoqn8ZvqyyDhp9Uek6WOxALyxGVsXU+8+ND+viD3UfrXVWfs/GQiqI5Eq4cOozZj0yRFQ==", "dependencies": { "@actions/core": "^1.2.6", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", "@actions/http-client": "^1.0.9", "@actions/io": "^1.0.1", - "@azure/ms-rest-js": "^2.0.7", - "@azure/storage-blob": "^12.1.2", + "@azure/ms-rest-js": "^2.6.0", + "@azure/storage-blob": "^12.8.0", "semver": "^6.1.0", "uuid": "^3.3.3" } @@ -5077,17 +5077,17 @@ }, "dependencies": { "@actions/cache": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-1.0.8.tgz", - "integrity": "sha512-GWNNB67w93HGJRQXlsV56YqrdAuDoP3esK/mo5mzU8WoDCVjtQgJGsTdkYUX7brswtT7xnI30bWNo1WLKQ8FZQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.0.tgz", + "integrity": "sha512-d7n8ul6HjWX6oDrNEPoqn8ZvqyyDhp9Uek6WOxALyxGVsXU+8+ND+viD3UfrXVWfs/GQiqI5Eq4cOozZj0yRFQ==", "requires": { "@actions/core": "^1.2.6", "@actions/exec": "^1.0.1", "@actions/glob": "^0.1.0", "@actions/http-client": "^1.0.9", "@actions/io": "^1.0.1", - "@azure/ms-rest-js": "^2.0.7", - "@azure/storage-blob": "^12.1.2", + "@azure/ms-rest-js": "^2.6.0", + "@azure/storage-blob": "^12.8.0", "semver": "^6.1.0", "uuid": "^3.3.3" }, diff --git a/package.json b/package.json index 8c6eee0..03f0241 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "setup-node", - "version": "2.0.0", + "version": "3.1.0", "private": true, "description": "setup node action", "main": "lib/setup-node.js", @@ -23,7 +23,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@actions/cache": "^1.0.8", + "@actions/cache": "^2.0.0", "@actions/core": "^1.6.0", "@actions/exec": "^1.1.0", "@actions/github": "^1.1.0", diff --git a/src/cache-utils.ts b/src/cache-utils.ts index 36531bd..b71bcc8 100644 --- a/src/cache-utils.ts +++ b/src/cache-utils.ts @@ -1,5 +1,6 @@ import * as core from '@actions/core'; import * as exec from '@actions/exec'; +import * as cache from '@actions/cache'; type SupportedPackageManagers = { [prop: string]: PackageManagerInfo; @@ -95,3 +96,28 @@ export const getCacheDirectoryPath = async ( return stdOut; }; + +export function isGhes(): boolean { + const ghUrl = new URL( + process.env['GITHUB_SERVER_URL'] || 'https://github.com' + ); + return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; +} + +export function isCacheFeatureAvailable(): boolean { + if (!cache.isFeatureAvailable()) { + if (isGhes()) { + throw new Error( + '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.' + ); + } else { + core.warning( + 'The runner was not able to contact the cache service. Caching will be skipped' + ); + } + + return false; + } + + return true; +} diff --git a/src/main.ts b/src/main.ts index 2de86ca..3c5661b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import fs from 'fs'; import * as auth from './authutil'; import * as path from 'path'; import {restoreCache} from './cache-restore'; -import {URL} from 'url'; +import {isGhes, isCacheFeatureAvailable} from './cache-utils'; import os = require('os'); export async function run() { @@ -45,10 +45,7 @@ export async function run() { auth.configAuthentication(registryUrl, alwaysAuth); } - if (cache) { - if (isGhes()) { - throw new Error('Caching is not supported on GHES'); - } + if (cache && isCacheFeatureAvailable()) { const cacheDependencyPath = core.getInput('cache-dependency-path'); await restoreCache(cache, cacheDependencyPath); } @@ -66,13 +63,6 @@ export async function run() { } } -function isGhes(): boolean { - const ghUrl = new URL( - process.env['GITHUB_SERVER_URL'] || 'https://github.com' - ); - return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'; -} - function resolveVersionInput(): string { let version = core.getInput('node-version'); const versionFileInput = core.getInput('node-version-file');