Update tool-cache for download retries (#120)

This commit is contained in:
eric sciple 2020-03-10 11:51:57 -04:00 committed by GitHub
parent ada4b782b3
commit 83c9f7a7df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 266 additions and 177 deletions

View file

@ -40,6 +40,20 @@ jobs:
- name: Verify node and npm
run: __tests__/verify-node.sh 10
test-fallback:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Clear tool cache
run: mv "${{ runner.tool_cache }}" "${{ runner.tool_cache }}.old"
- name: Setup node 0.12.18 # For non LTS versions of Node, the zip is not always available
uses: ./ # and falls back to downloading node.exe and node.lib
with:
node-version: 0.12.18
- name: Verify node
shell: bash
run: __tests__/verify-node.sh 0.12.18 SKIP_NPM
test-proxy:
runs-on: ubuntu-latest
container:

View file

@ -1,28 +1,27 @@
import io = require('@actions/io');
import fs = require('fs');
import path = require('path');
const tempDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'temp'
);
const rcFile = path.join(tempDir, '.npmrc');
process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo';
process.env['RUNNER_TEMP'] = tempDir;
import * as io from '@actions/io';
import * as fs from 'fs';
import * as path from 'path';
import * as auth from '../src/authutil';
let rcFile: string;
describe('installer tests', () => {
beforeAll(async () => {
const tempDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'temp'
);
await io.rmRF(tempDir);
await io.mkdirP(tempDir);
process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo';
process.env['RUNNER_TEMP'] = tempDir;
rcFile = path.join(tempDir, '.npmrc');
}, 100000);
beforeEach(() => {

View file

@ -1,39 +1,39 @@
import io = require('@actions/io');
import fs = require('fs');
import os = require('os');
import path = require('path');
const toolDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'tools'
);
const tempDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'temp'
);
process.env['RUNNER_TOOL_CACHE'] = toolDir;
process.env['RUNNER_TEMP'] = tempDir;
import * as io from '@actions/io';
import * as tc from '@actions/tool-cache';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as installer from '../src/installer';
const IS_WINDOWS = process.platform === 'win32';
const isWindows = process.platform === 'win32';
let toolDir: string;
describe('installer tests', () => {
beforeAll(async () => {
toolDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'tools'
);
const tempDir = path.join(
__dirname,
'runner',
path.join(
Math.random()
.toString(36)
.substring(7)
),
'temp'
);
await io.rmRF(toolDir);
await io.rmRF(tempDir);
process.env['RUNNER_TOOL_CACHE'] = toolDir;
process.env['RUNNER_TEMP'] = tempDir;
}, 100000);
it('Acquires version of node if no matching version is installed', async () => {
@ -41,14 +41,14 @@ describe('installer tests', () => {
const nodeDir = path.join(toolDir, 'node', '10.16.0', os.arch());
expect(fs.existsSync(`${nodeDir}.complete`)).toBe(true);
if (IS_WINDOWS) {
if (isWindows) {
expect(fs.existsSync(path.join(nodeDir, 'node.exe'))).toBe(true);
} else {
expect(fs.existsSync(path.join(nodeDir, 'bin', 'node'))).toBe(true);
}
}, 100000);
if (IS_WINDOWS) {
if (isWindows) {
it('Falls back to backup location if first one doesnt contain correct version', async () => {
await installer.getNode('5.10.1');
const nodeDir = path.join(toolDir, 'node', '5.10.1', os.arch());
@ -81,7 +81,7 @@ describe('installer tests', () => {
const nodeDir = path.join(toolDir, 'node', '8.8.1', os.arch());
expect(fs.existsSync(`${nodeDir}.complete`)).toBe(true);
if (IS_WINDOWS) {
if (isWindows) {
expect(fs.existsSync(path.join(nodeDir, 'node.exe'))).toBe(true);
} else {
expect(fs.existsSync(path.join(nodeDir, 'bin', 'node'))).toBe(true);

View file

@ -7,13 +7,17 @@ fi
node_version="$(node --version)"
echo "Found node version '$node_version'"
if [ -z "$(echo $node_version | grep v$1)" ]; then
if [ -z "$(echo $node_version | grep --fixed-strings v$1)" ]; then
echo "Unexpected version"
exit 1
fi
echo "Testing npm install"
mkdir -p test-npm-install
cd test-npm-install
npm init -y || exit 1
npm install @actions/core || exit 1
if [ -z "$2" ]; then
echo "Testing npm install"
mkdir -p test-npm-install
cd test-npm-install
npm init -y || exit 1
npm install @actions/core || exit 1
else
echo "Skip testing npm"
fi

269
dist/index.js vendored
View file

@ -10096,9 +10096,12 @@ const os = __importStar(__webpack_require__(87));
const path = __importStar(__webpack_require__(622));
const httpm = __importStar(__webpack_require__(539));
const semver = __importStar(__webpack_require__(280));
const stream = __importStar(__webpack_require__(794));
const util = __importStar(__webpack_require__(669));
const v4_1 = __importDefault(__webpack_require__(826));
const exec_1 = __webpack_require__(986);
const assert_1 = __webpack_require__(357);
const retry_helper_1 = __webpack_require__(979);
class HTTPError extends Error {
constructor(httpStatusCode) {
super(`Unexpected HTTP response: ${httpStatusCode}`);
@ -10109,31 +10112,6 @@ class HTTPError extends Error {
exports.HTTPError = HTTPError;
const IS_WINDOWS = process.platform === 'win32';
const userAgent = 'actions/tool-cache';
// On load grab temp directory and cache directory and remove them from env (currently don't want to expose this)
let tempDirectory = process.env['RUNNER_TEMP'] || '';
let cacheRoot = process.env['RUNNER_TOOL_CACHE'] || '';
// If directories not found, place them in common temp locations
if (!tempDirectory || !cacheRoot) {
let baseLocation;
if (IS_WINDOWS) {
// On windows use the USERPROFILE env variable
baseLocation = process.env['USERPROFILE'] || 'C:\\';
}
else {
if (process.platform === 'darwin') {
baseLocation = '/Users';
}
else {
baseLocation = '/home';
}
}
if (!tempDirectory) {
tempDirectory = path.join(baseLocation, 'actions', 'temp');
}
if (!cacheRoot) {
cacheRoot = path.join(baseLocation, 'actions', 'cache');
}
}
/**
* Download a tool from an url and stream it into a file
*
@ -10143,52 +10121,71 @@ if (!tempDirectory || !cacheRoot) {
*/
function downloadTool(url, dest) {
return __awaiter(this, void 0, void 0, function* () {
// Wrap in a promise so that we can resolve from within stream callbacks
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
const http = new httpm.HttpClient(userAgent, [], {
allowRetries: true,
maxRetries: 3
});
dest = dest || path.join(tempDirectory, v4_1.default());
yield io.mkdirP(path.dirname(dest));
core.debug(`Downloading ${url}`);
core.debug(`Downloading ${dest}`);
if (fs.existsSync(dest)) {
throw new Error(`Destination file path ${dest} already exists`);
dest = dest || path.join(_getTempDirectory(), v4_1.default());
yield io.mkdirP(path.dirname(dest));
core.debug(`Downloading ${url}`);
core.debug(`Destination ${dest}`);
const maxAttempts = 3;
const minSeconds = _getGlobal('TEST_DOWNLOAD_TOOL_RETRY_MIN_SECONDS', 10);
const maxSeconds = _getGlobal('TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS', 20);
const retryHelper = new retry_helper_1.RetryHelper(maxAttempts, minSeconds, maxSeconds);
return yield retryHelper.execute(() => __awaiter(this, void 0, void 0, function* () {
return yield downloadToolAttempt(url, dest || '');
}), (err) => {
if (err instanceof HTTPError && err.httpStatusCode) {
// Don't retry anything less than 500, except 408 Request Timeout and 429 Too Many Requests
if (err.httpStatusCode < 500 &&
err.httpStatusCode !== 408 &&
err.httpStatusCode !== 429) {
return false;
}
const response = yield http.get(url);
if (response.message.statusCode !== 200) {
const err = new HTTPError(response.message.statusCode);
core.debug(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
throw err;
}
const file = fs.createWriteStream(dest);
file.on('open', () => __awaiter(this, void 0, void 0, function* () {
try {
const stream = response.message.pipe(file);
stream.on('close', () => {
core.debug('download complete');
resolve(dest);
});
}
catch (err) {
core.debug(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
reject(err);
}
}));
file.on('error', err => {
file.end();
reject(err);
});
}
catch (err) {
reject(err);
}
}));
// Otherwise retry
return true;
});
});
}
exports.downloadTool = downloadTool;
function downloadToolAttempt(url, dest) {
return __awaiter(this, void 0, void 0, function* () {
if (fs.existsSync(dest)) {
throw new Error(`Destination file path ${dest} already exists`);
}
// Get the response headers
const http = new httpm.HttpClient(userAgent, [], {
allowRetries: false
});
const response = yield http.get(url);
if (response.message.statusCode !== 200) {
const err = new HTTPError(response.message.statusCode);
core.debug(`Failed to download from "${url}". Code(${response.message.statusCode}) Message(${response.message.statusMessage})`);
throw err;
}
// Download the response body
const pipeline = util.promisify(stream.pipeline);
const responseMessageFactory = _getGlobal('TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY', () => response.message);
const readStream = responseMessageFactory();
let succeeded = false;
try {
yield pipeline(readStream, fs.createWriteStream(dest));
core.debug('download complete');
succeeded = true;
return dest;
}
finally {
// Error, delete dest before retry
if (!succeeded) {
core.debug('download failed');
try {
yield io.rmRF(dest);
}
catch (err) {
core.debug(`Failed to delete '${dest}'. ${err.message}`);
}
}
}
});
}
/**
* Extract a .7z file
*
@ -10278,14 +10275,17 @@ function extractTar(file, dest, flags = 'xz') {
// Create dest
dest = yield _createExtractFolder(dest);
// Determine whether GNU tar
core.debug('Checking tar --version');
let versionOutput = '';
yield exec_1.exec('tar --version', [], {
ignoreReturnCode: true,
silent: true,
listeners: {
stdout: (data) => (versionOutput += data.toString()),
stderr: (data) => (versionOutput += data.toString())
}
});
core.debug(versionOutput.trim());
const isGnuTar = versionOutput.toUpperCase().includes('GNU TAR');
// Initialize args
const args = [flags];
@ -10446,7 +10446,7 @@ function find(toolName, versionSpec, arch) {
let toolPath = '';
if (versionSpec) {
versionSpec = semver.clean(versionSpec) || '';
const cachePath = path.join(cacheRoot, toolName, versionSpec, arch);
const cachePath = path.join(_getCacheDirectory(), toolName, versionSpec, arch);
core.debug(`checking cache: ${cachePath}`);
if (fs.existsSync(cachePath) && fs.existsSync(`${cachePath}.complete`)) {
core.debug(`Found tool in cache ${toolName} ${versionSpec} ${arch}`);
@ -10468,7 +10468,7 @@ exports.find = find;
function findAllVersions(toolName, arch) {
const versions = [];
arch = arch || os.arch();
const toolPath = path.join(cacheRoot, toolName);
const toolPath = path.join(_getCacheDirectory(), toolName);
if (fs.existsSync(toolPath)) {
const children = fs.readdirSync(toolPath);
for (const child of children) {
@ -10487,7 +10487,7 @@ function _createExtractFolder(dest) {
return __awaiter(this, void 0, void 0, function* () {
if (!dest) {
// create a temp dir
dest = path.join(tempDirectory, v4_1.default());
dest = path.join(_getTempDirectory(), v4_1.default());
}
yield io.mkdirP(dest);
return dest;
@ -10495,7 +10495,7 @@ function _createExtractFolder(dest) {
}
function _createToolPath(tool, version, arch) {
return __awaiter(this, void 0, void 0, function* () {
const folderPath = path.join(cacheRoot, tool, semver.clean(version) || version, arch || '');
const folderPath = path.join(_getCacheDirectory(), tool, semver.clean(version) || version, arch || '');
core.debug(`destination ${folderPath}`);
const markerPath = `${folderPath}.complete`;
yield io.rmRF(folderPath);
@ -10505,7 +10505,7 @@ function _createToolPath(tool, version, arch) {
});
}
function _completeToolPath(tool, version, arch) {
const folderPath = path.join(cacheRoot, tool, semver.clean(version) || version, arch || '');
const folderPath = path.join(_getCacheDirectory(), tool, semver.clean(version) || version, arch || '');
const markerPath = `${folderPath}.complete`;
fs.writeFileSync(markerPath, '');
core.debug('finished caching tool');
@ -10542,6 +10542,31 @@ function _evaluateVersions(versions, versionSpec) {
}
return version;
}
/**
* Gets RUNNER_TOOL_CACHE
*/
function _getCacheDirectory() {
const cacheDirectory = process.env['RUNNER_TOOL_CACHE'] || '';
assert_1.ok(cacheDirectory, 'Expected RUNNER_TOOL_CACHE to be defined');
return cacheDirectory;
}
/**
* Gets RUNNER_TEMP
*/
function _getTempDirectory() {
const tempDirectory = process.env['RUNNER_TEMP'] || '';
assert_1.ok(tempDirectory, 'Expected RUNNER_TEMP to be defined');
return tempDirectory;
}
/**
* Gets a global variable
*/
function _getGlobal(key, defaultValue) {
/* eslint-disable @typescript-eslint/no-explicit-any */
const value = global[key];
/* eslint-enable @typescript-eslint/no-explicit-any */
return value !== undefined ? value : defaultValue;
}
//# sourceMappingURL=tool-cache.js.map
/***/ }),
@ -12091,8 +12116,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
// Load tempDirectory before it gets wiped by tool-cache
let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || '';
const assert = __importStar(__webpack_require__(357));
const core = __importStar(__webpack_require__(470));
const hc = __importStar(__webpack_require__(539));
const io = __importStar(__webpack_require__(1));
@ -12102,22 +12126,6 @@ const path = __importStar(__webpack_require__(622));
const semver = __importStar(__webpack_require__(280));
let osPlat = os.platform();
let osArch = translateArchToDistUrl(os.arch());
if (!tempDirectory) {
let baseLocation;
if (process.platform === 'win32') {
// On windows use the USERPROFILE env variable
baseLocation = process.env['USERPROFILE'] || 'C:\\';
}
else {
if (process.platform === 'darwin') {
baseLocation = '/Users';
}
else {
baseLocation = '/home';
}
}
tempDirectory = path.join(baseLocation, 'actions', 'temp');
}
function getNode(versionSpec) {
return __awaiter(this, void 0, void 0, function* () {
// check cache
@ -12276,8 +12284,10 @@ function acquireNode(version) {
function acquireNodeFromFallbackLocation(version) {
return __awaiter(this, void 0, void 0, function* () {
// Create temporary folder to download in to
let tempDownloadFolder = 'temp_' + Math.floor(Math.random() * 2000000000);
let tempDir = path.join(tempDirectory, tempDownloadFolder);
const tempDownloadFolder = 'temp_' + Math.floor(Math.random() * 2000000000);
const tempDirectory = process.env['RUNNER_TEMP'] || '';
assert.ok(tempDirectory, 'Expected RUNNER_TEMP to be defined');
const tempDir = path.join(tempDirectory, tempDownloadFolder);
yield io.mkdirP(tempDir);
let exeUrl;
let libUrl;
@ -15815,6 +15825,83 @@ function onceStrict (fn) {
}
/***/ }),
/***/ 979:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(470));
/**
* Internal class for retries
*/
class RetryHelper {
constructor(maxAttempts, minSeconds, maxSeconds) {
if (maxAttempts < 1) {
throw new Error('max attempts should be greater than or equal to 1');
}
this.maxAttempts = maxAttempts;
this.minSeconds = Math.floor(minSeconds);
this.maxSeconds = Math.floor(maxSeconds);
if (this.minSeconds > this.maxSeconds) {
throw new Error('min seconds should be less than or equal to max seconds');
}
}
execute(action, isRetryable) {
return __awaiter(this, void 0, void 0, function* () {
let attempt = 1;
while (attempt < this.maxAttempts) {
// Try
try {
return yield action();
}
catch (err) {
if (isRetryable && !isRetryable(err)) {
throw err;
}
core.info(err.message);
}
// Sleep
const seconds = this.getSleepAmount();
core.info(`Waiting ${seconds} seconds before trying again`);
yield this.sleep(seconds);
attempt++;
}
// Last attempt
return yield action();
});
}
getSleepAmount() {
return (Math.floor(Math.random() * (this.maxSeconds - this.minSeconds + 1)) +
this.minSeconds);
}
sleep(seconds) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
});
}
}
exports.RetryHelper = RetryHelper;
//# sourceMappingURL=retry-helper.js.map
/***/ }),
/***/ 986:

18
package-lock.json generated
View file

@ -40,9 +40,9 @@
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
},
"@actions/tool-cache": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.1.tgz",
"integrity": "sha512-sKoEJv0/c7WzjPEq2PO12Sc8QdEp58XIBHMm3c4lUn/iZWgLz9HBeCuFGpLQjDvXJNfLZ4g+WD+rMjgOmpH4Ag==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-1.3.3.tgz",
"integrity": "sha512-AFVyTLcIxusDVI1gMhbZW8m/On7YNJG+xYaxorV+qic+f7lO7h37aT2mfzxqAq7mwHxtP1YlVFNrXe9QDf/bPg==",
"requires": {
"@actions/core": "^1.2.0",
"@actions/exec": "^1.0.0",
@ -738,9 +738,9 @@
},
"dependencies": {
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
"integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"dev": true
}
}
@ -3484,9 +3484,9 @@
}
},
"kind-of": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"kleur": {

View file

@ -27,7 +27,7 @@
"@actions/github": "^1.1.0",
"@actions/http-client": "^1.0.6",
"@actions/io": "^1.0.2",
"@actions/tool-cache": "^1.3.1",
"@actions/tool-cache": "^1.3.3",
"semver": "^6.1.1"
},
"devDependencies": {

View file

@ -1,5 +1,3 @@
// Load tempDirectory before it gets wiped by tool-cache
let tempDirectory = process.env['RUNNER_TEMPDIRECTORY'] || '';
import * as assert from 'assert';
import * as core from '@actions/core';
import * as hc from '@actions/http-client';
@ -12,21 +10,6 @@ import * as semver from 'semver';
let osPlat: string = os.platform();
let osArch: string = translateArchToDistUrl(os.arch());
if (!tempDirectory) {
let baseLocation;
if (process.platform === 'win32') {
// On windows use the USERPROFILE env variable
baseLocation = process.env['USERPROFILE'] || 'C:\\';
} else {
if (process.platform === 'darwin') {
baseLocation = '/Users';
} else {
baseLocation = '/home';
}
}
tempDirectory = path.join(baseLocation, 'actions', 'temp');
}
//
// Node versions interface
// see https://nodejs.org/dist/index.json
@ -206,9 +189,11 @@ async function acquireNodeFromFallbackLocation(
version: string
): Promise<string> {
// Create temporary folder to download in to
let tempDownloadFolder: string =
const tempDownloadFolder: string =
'temp_' + Math.floor(Math.random() * 2000000000);
let tempDir: string = path.join(tempDirectory, tempDownloadFolder);
const tempDirectory = process.env['RUNNER_TEMP'] || '';
assert.ok(tempDirectory, 'Expected RUNNER_TEMP to be defined');
const tempDir: string = path.join(tempDirectory, tempDownloadFolder);
await io.mkdirP(tempDir);
let exeUrl: string;
let libUrl: string;