Compare commits

...

10 Commits

Author SHA1 Message Date
e448a9b857 Add retries to all HTTP calls + resolve dependabot alerts (#160)
* Bump @actions/artifact to version 0.5.0

* Resolve dependabot alert for node-notifier

* Resolve dependabot alert for node-fetch

* Bump artifact.dep.yml

* Update http-client.dep.yml
2021-01-04 15:48:10 +01:00
e6bd6b7749 Replace "file(s)" with "file" or "files" (#159) 2021-01-04 11:24:12 +01:00
1fd4c858f9 Merge pull request #152 from actions/joshmgross/fix-codeowners
Fix CODEOWNERS team name
2020-12-07 14:24:45 -05:00
a2af908e3a Fix CODEOWNERS team name 2020-12-07 13:36:14 -05:00
928d1a16d9 Create CODEOWNERS (#149) 2020-11-25 15:24:41 -05:00
e4a7ffadfc Update README with more retention documentation 2020-11-17 12:57:17 -05:00
726a6dcd01 Adding example of retention-days option. (#131) 2020-11-13 12:25:13 -05:00
3db166e2ea Merge pull request #145 from actions/joshmgross/update-actions-core
Update @actions/core to 1.2.6
2020-11-13 10:52:34 -05:00
d86048c66c Update @actions/core license 2020-11-12 16:46:49 -05:00
328d69042a Update @actions/core to 1.2.6 2020-11-12 16:32:13 -05:00
9 changed files with 1957 additions and 1925 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @actions/artifacts-actions

View File

@ -1,6 +1,6 @@
--- ---
name: "@actions/artifact" name: "@actions/artifact"
version: 0.4.0 version: 0.5.0
type: npm type: npm
summary: summary:
homepage: homepage:

View File

@ -1,30 +1,20 @@
--- ---
name: "@actions/core" name: "@actions/core"
version: 1.2.3 version: 1.2.6
type: npm type: npm
summary: Actions core lib summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/master/packages/core homepage: https://github.com/actions/toolkit/tree/main/packages/core
license: mit license: mit
licenses: licenses:
- sources: Auto-generated MIT license text - sources: LICENSE.md
text: | text: |-
MIT License The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy Copyright 2019 GitHub
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: [] notices: []

View File

@ -1,6 +1,6 @@
--- ---
name: "@actions/http-client" name: "@actions/http-client"
version: 1.0.8 version: 1.0.9
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client
homepage: https://github.com/actions/http-client#readme homepage: https://github.com/actions/http-client#readme

View File

@ -199,6 +199,25 @@ Environment variables along with context expressions can also be used for input.
path: ${{ github.workspace }}/artifact/**/* path: ${{ github.workspace }}/artifact/**/*
``` ```
### Retention Period
Artifacts are retained for 90 days by default. You can specify a shorter retention period using the `retention-days` input:
```yaml
- name: 'Create a file'
run: echo "I won't live long" > my_file.txt
- name: 'Upload Artifact'
uses: actions/upload-artifact@v2
with:
name: my-artifact
path: my_file.txt
retention-days: 5
```
The retention period must be between 1 and 90 inclusive. For more information see [artifact and log retention policies](https://docs.github.com/en/free-pro-team@latest/actions/reference/usage-limits-billing-and-administration#artifact-and-log-retention-policy).
## Where does the upload go? ## Where does the upload go?
In the top right corner of a workflow run, once the run is over, if you used this action, there will be an `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like<br/> In the top right corner of a workflow run, once the run is over, if you used this action, there will be an `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like<br/>
<img src="https://user-images.githubusercontent.com/16109154/72556687-20235a80-386d-11ea-9e2a-b534faa77083.png" width="375" height="140"> <img src="https://user-images.githubusercontent.com/16109154/72556687-20235a80-386d-11ea-9e2a-b534faa77083.png" width="375" height="140">
@ -234,6 +253,7 @@ If file permissions and case sensitivity are required, you can `tar` all of your
path: my_files.tar path: my_files.tar
``` ```
## Additional Documentation ## Additional Documentation
See [persisting workflow data using artifacts](https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts) for additional examples and tips. See [persisting workflow data using artifacts](https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts) for additional examples and tips.

399
dist/index.js vendored
View File

@ -137,6 +137,32 @@ function onceStrict (fn) {
} }
/***/ }),
/***/ 82:
/***/ (function(__unusedmodule, exports) {
"use strict";
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
function toCommandValue(input) {
if (input === null || input === undefined) {
return '';
}
else if (typeof input === 'string' || input instanceof String) {
return input;
}
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
//# sourceMappingURL=utils.js.map
/***/ }), /***/ }),
/***/ 87: /***/ 87:
@ -1074,6 +1100,42 @@ function regExpEscape (s) {
} }
/***/ }),
/***/ 102:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
// For internal use, subject to change.
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 });
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__webpack_require__(747));
const os = __importStar(__webpack_require__(87));
const utils_1 = __webpack_require__(82);
function issueCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`];
if (!filePath) {
throw new Error(`Unable to find environment variable for file command ${command}`);
}
if (!fs.existsSync(filePath)) {
throw new Error(`Missing file at path: ${filePath}`);
}
fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, {
encoding: 'utf8'
});
}
exports.issueCommand = issueCommand;
//# sourceMappingURL=file-command.js.map
/***/ }), /***/ }),
/***/ 117: /***/ 117:
@ -4013,7 +4075,8 @@ function run() {
} }
} }
else { else {
core.info(`With the provided path, there will be ${searchResult.filesToUpload.length} file(s) uploaded`); const s = searchResult.filesToUpload.length === 1 ? '' : 's';
core.info(`With the provided path, there will be ${searchResult.filesToUpload.length} file${s} uploaded`);
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`); core.debug(`Root artifact directory is ${searchResult.rootDirectory}`);
const artifactClient = artifact_1.create(); const artifactClient = artifact_1.create();
const options = { const options = {
@ -4917,6 +4980,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const utils_1 = __webpack_require__(82);
/** /**
* Commands * Commands
* *
@ -4971,13 +5035,13 @@ class Command {
} }
} }
function escapeData(s) { function escapeData(s) {
return (s || '') return utils_1.toCommandValue(s)
.replace(/%/g, '%25') .replace(/%/g, '%25')
.replace(/\r/g, '%0D') .replace(/\r/g, '%0D')
.replace(/\n/g, '%0A'); .replace(/\n/g, '%0A');
} }
function escapeProperty(s) { function escapeProperty(s) {
return (s || '') return utils_1.toCommandValue(s)
.replace(/%/g, '%25') .replace(/%/g, '%25')
.replace(/\r/g, '%0D') .replace(/\r/g, '%0D')
.replace(/\n/g, '%0A') .replace(/\n/g, '%0A')
@ -5049,6 +5113,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = __webpack_require__(431); const command_1 = __webpack_require__(431);
const file_command_1 = __webpack_require__(102);
const utils_1 = __webpack_require__(82);
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
/** /**
@ -5071,11 +5137,21 @@ var ExitCode;
/** /**
* Sets env variable for this action and future actions in the job * Sets env variable for this action and future actions in the job
* @param name the name of the variable to set * @param name the name of the variable to set
* @param val the value of the variable * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function exportVariable(name, val) { function exportVariable(name, val) {
process.env[name] = val; const convertedVal = utils_1.toCommandValue(val);
command_1.issueCommand('set-env', { name }, val); process.env[name] = convertedVal;
const filePath = process.env['GITHUB_ENV'] || '';
if (filePath) {
const delimiter = '_GitHubActionsFileCommandDelimeter_';
const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`;
file_command_1.issueCommand('ENV', commandValue);
}
else {
command_1.issueCommand('set-env', { name }, convertedVal);
}
} }
exports.exportVariable = exportVariable; exports.exportVariable = exportVariable;
/** /**
@ -5091,7 +5167,13 @@ exports.setSecret = setSecret;
* @param inputPath * @param inputPath
*/ */
function addPath(inputPath) { function addPath(inputPath) {
command_1.issueCommand('add-path', {}, inputPath); const filePath = process.env['GITHUB_PATH'] || '';
if (filePath) {
file_command_1.issueCommand('PATH', inputPath);
}
else {
command_1.issueCommand('add-path', {}, inputPath);
}
process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
} }
exports.addPath = addPath; exports.addPath = addPath;
@ -5114,12 +5196,22 @@ exports.getInput = getInput;
* Sets the value of an output. * Sets the value of an output.
* *
* @param name name of the output to set * @param name name of the output to set
* @param value value to store * @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) { function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value); command_1.issueCommand('set-output', { name }, value);
} }
exports.setOutput = setOutput; exports.setOutput = setOutput;
/**
* Enables or disables the echoing of commands into stdout for the rest of the step.
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
*
*/
function setCommandEcho(enabled) {
command_1.issue('echo', enabled ? 'on' : 'off');
}
exports.setCommandEcho = setCommandEcho;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// Results // Results
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@ -5153,18 +5245,18 @@ function debug(message) {
exports.debug = debug; exports.debug = debug;
/** /**
* Adds an error issue * Adds an error issue
* @param message error issue message * @param message error issue message. Errors will be converted to string via toString()
*/ */
function error(message) { function error(message) {
command_1.issue('error', message); command_1.issue('error', message instanceof Error ? message.toString() : message);
} }
exports.error = error; exports.error = error;
/** /**
* Adds an warning issue * Adds an warning issue
* @param message warning issue message * @param message warning issue message. Errors will be converted to string via toString()
*/ */
function warning(message) { function warning(message) {
command_1.issue('warning', message); command_1.issue('warning', message instanceof Error ? message.toString() : message);
} }
exports.warning = warning; exports.warning = warning;
/** /**
@ -5222,8 +5314,9 @@ exports.group = group;
* Saves state for current action, the state can only be retrieved by this action's post job execution. * Saves state for current action, the state can only be retrieved by this action's post job execution.
* *
* @param name name of the state to store * @param name name of the state to store
* @param value value to store * @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function saveState(name, value) { function saveState(name, value) {
command_1.issueCommand('save-state', { name }, value); command_1.issueCommand('save-state', { name }, value);
} }
@ -5242,6 +5335,88 @@ exports.getState = getState;
/***/ }), /***/ }),
/***/ 489:
/***/ (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 utils_1 = __webpack_require__(870);
const core = __importStar(__webpack_require__(470));
const config_variables_1 = __webpack_require__(401);
function retry(name, operation, customErrorMessages, maxAttempts) {
return __awaiter(this, void 0, void 0, function* () {
let response = undefined;
let statusCode = undefined;
let isRetryable = false;
let errorMessage = '';
let customErrorInformation = undefined;
let attempt = 1;
while (attempt <= maxAttempts) {
try {
response = yield operation();
statusCode = response.message.statusCode;
if (utils_1.isSuccessStatusCode(statusCode)) {
return response;
}
// Extra error information that we want to display if a particular response code is hit
if (statusCode) {
customErrorInformation = customErrorMessages.get(statusCode);
}
isRetryable = utils_1.isRetryableStatusCode(statusCode);
errorMessage = `Artifact service responded with ${statusCode}`;
}
catch (error) {
isRetryable = true;
errorMessage = error.message;
}
if (!isRetryable) {
core.info(`${name} - Error is not retryable`);
if (response) {
utils_1.displayHttpDiagnostics(response);
}
break;
}
core.info(`${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}`);
yield utils_1.sleep(utils_1.getExponentialRetryTimeInMilliseconds(attempt));
attempt++;
}
if (response) {
utils_1.displayHttpDiagnostics(response);
}
if (customErrorInformation) {
throw Error(`${name} failed: ${customErrorInformation}`);
}
throw Error(`${name} failed: ${errorMessage}`);
});
}
exports.retry = retry;
function retryHttpClientRequest(name, method, customErrorMessages = new Map(), maxAttempts = config_variables_1.getRetryLimit()) {
return __awaiter(this, void 0, void 0, function* () {
return yield retry(name, method, customErrorMessages, maxAttempts);
});
}
exports.retryHttpClientRequest = retryHttpClientRequest;
//# sourceMappingURL=requestUtils.js.map
/***/ }),
/***/ 532: /***/ 532:
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports, __webpack_require__) {
@ -5316,7 +5491,6 @@ exports.getDownloadSpecification = getDownloadSpecification;
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const url = __webpack_require__(835);
const http = __webpack_require__(605); const http = __webpack_require__(605);
const https = __webpack_require__(211); const https = __webpack_require__(211);
const pm = __webpack_require__(950); const pm = __webpack_require__(950);
@ -5365,7 +5539,7 @@ var MediaTypes;
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
function getProxyUrl(serverUrl) { function getProxyUrl(serverUrl) {
let proxyUrl = pm.getProxyUrl(url.parse(serverUrl)); let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : ''; return proxyUrl ? proxyUrl.href : '';
} }
exports.getProxyUrl = getProxyUrl; exports.getProxyUrl = getProxyUrl;
@ -5384,6 +5558,15 @@ const HttpResponseRetryCodes = [
const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
const ExponentialBackoffCeiling = 10; const ExponentialBackoffCeiling = 10;
const ExponentialBackoffTimeSlice = 5; const ExponentialBackoffTimeSlice = 5;
class HttpClientError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'HttpClientError';
this.statusCode = statusCode;
Object.setPrototypeOf(this, HttpClientError.prototype);
}
}
exports.HttpClientError = HttpClientError;
class HttpClientResponse { class HttpClientResponse {
constructor(message) { constructor(message) {
this.message = message; this.message = message;
@ -5402,7 +5585,7 @@ class HttpClientResponse {
} }
exports.HttpClientResponse = HttpClientResponse; exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) { function isHttps(requestUrl) {
let parsedUrl = url.parse(requestUrl); let parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:'; return parsedUrl.protocol === 'https:';
} }
exports.isHttps = isHttps; exports.isHttps = isHttps;
@ -5507,7 +5690,7 @@ class HttpClient {
if (this._disposed) { if (this._disposed) {
throw new Error('Client has already been disposed.'); throw new Error('Client has already been disposed.');
} }
let parsedUrl = url.parse(requestUrl); let parsedUrl = new URL(requestUrl);
let info = this._prepareRequest(verb, parsedUrl, headers); let info = this._prepareRequest(verb, parsedUrl, headers);
// Only perform retries on reads since writes may not be idempotent. // Only perform retries on reads since writes may not be idempotent.
let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1 let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
@ -5546,7 +5729,7 @@ class HttpClient {
// if there's no location to redirect to, we won't // if there's no location to redirect to, we won't
break; break;
} }
let parsedRedirectUrl = url.parse(redirectUrl); let parsedRedirectUrl = new URL(redirectUrl);
if (parsedUrl.protocol == 'https:' && if (parsedUrl.protocol == 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol && parsedUrl.protocol != parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade) { !this._allowRedirectDowngrade) {
@ -5662,7 +5845,7 @@ class HttpClient {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
getAgent(serverUrl) { getAgent(serverUrl) {
let parsedUrl = url.parse(serverUrl); let parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl); return this._getAgent(parsedUrl);
} }
_prepareRequest(method, requestUrl, headers) { _prepareRequest(method, requestUrl, headers) {
@ -5735,7 +5918,7 @@ class HttpClient {
maxSockets: maxSockets, maxSockets: maxSockets,
keepAlive: this._keepAlive, keepAlive: this._keepAlive,
proxy: { proxy: {
proxyAuth: proxyUrl.auth, proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`,
host: proxyUrl.hostname, host: proxyUrl.hostname,
port: proxyUrl.port port: proxyUrl.port
} }
@ -5830,12 +6013,8 @@ class HttpClient {
else { else {
msg = 'Failed request: (' + statusCode + ')'; msg = 'Failed request: (' + statusCode + ')';
} }
let err = new Error(msg); let err = new HttpClientError(msg, statusCode);
// attach statusCode and body obj (if available) to the error object err.result = response.result;
err['statusCode'] = statusCode;
if (response.result) {
err['result'] = response.result;
}
reject(err); reject(err);
} }
else { else {
@ -6668,8 +6847,10 @@ const util_1 = __webpack_require__(669);
const url_1 = __webpack_require__(835); const url_1 = __webpack_require__(835);
const perf_hooks_1 = __webpack_require__(630); const perf_hooks_1 = __webpack_require__(630);
const status_reporter_1 = __webpack_require__(176); const status_reporter_1 = __webpack_require__(176);
const http_client_1 = __webpack_require__(539);
const http_manager_1 = __webpack_require__(452); const http_manager_1 = __webpack_require__(452);
const upload_gzip_1 = __webpack_require__(647); const upload_gzip_1 = __webpack_require__(647);
const requestUtils_1 = __webpack_require__(489);
const stat = util_1.promisify(fs.stat); const stat = util_1.promisify(fs.stat);
class UploadHttpClient { class UploadHttpClient {
constructor() { constructor() {
@ -6697,20 +6878,22 @@ class UploadHttpClient {
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately // use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
const client = this.uploadHttpManager.getClient(0); const client = this.uploadHttpManager.getClient(0);
const headers = utils_1.getUploadHeaders('application/json', false); const headers = utils_1.getUploadHeaders('application/json', false);
const rawResponse = yield client.post(artifactUrl, data, headers); // Extra information to display when a particular HTTP code is returned
const body = yield rawResponse.readBody(); // If a 403 is returned when trying to create a file container, the customer has exceeded
if (utils_1.isSuccessStatusCode(rawResponse.message.statusCode) && body) { // their storage quota so no new artifact containers can be created
return JSON.parse(body); const customErrorMessages = new Map([
} [
else if (utils_1.isForbiddenStatusCode(rawResponse.message.statusCode)) { http_client_1.HttpCodes.Forbidden,
// if a 403 is returned when trying to create a file container, the customer has exceeded 'Artifact storage quota has been hit. Unable to upload any new artifacts'
// their storage quota so no new artifact containers can be created ],
throw new Error(`Artifact storage quota has been hit. Unable to upload any new artifacts`); [
} http_client_1.HttpCodes.BadRequest,
else { `The artifact name ${artifactName} is not valid. Request URL ${artifactUrl}`
utils_1.displayHttpDiagnostics(rawResponse); ]
throw new Error(`Unable to create a container for the artifact ${artifactName} at ${artifactUrl}`); ]);
} const response = yield requestUtils_1.retryHttpClientRequest('Create Artifact Container', () => __awaiter(this, void 0, void 0, function* () { return client.post(artifactUrl, data, headers); }), customErrorMessages);
const body = yield response.readBody();
return JSON.parse(body);
}); });
} }
/** /**
@ -6934,12 +7117,12 @@ class UploadHttpClient {
this.uploadHttpManager.disposeAndReplaceClient(httpClientIndex); this.uploadHttpManager.disposeAndReplaceClient(httpClientIndex);
if (retryAfterValue) { if (retryAfterValue) {
core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the upload`); core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the upload`);
yield new Promise(resolve => setTimeout(resolve, retryAfterValue)); yield utils_1.sleep(retryAfterValue);
} }
else { else {
const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount); const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount);
core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the upload at offset ${start}`); core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the upload at offset ${start}`);
yield new Promise(resolve => setTimeout(resolve, backoffTime)); yield utils_1.sleep(backoffTime);
} }
core.info(`Finished backoff for retry #${retryCount}, continuing with upload`); core.info(`Finished backoff for retry #${retryCount}, continuing with upload`);
return; return;
@ -6991,7 +7174,6 @@ class UploadHttpClient {
*/ */
patchArtifactSize(size, artifactName) { patchArtifactSize(size, artifactName) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const headers = utils_1.getUploadHeaders('application/json', false);
const resourceUrl = new url_1.URL(utils_1.getArtifactUrl()); const resourceUrl = new url_1.URL(utils_1.getArtifactUrl());
resourceUrl.searchParams.append('artifactName', artifactName); resourceUrl.searchParams.append('artifactName', artifactName);
const parameters = { Size: size }; const parameters = { Size: size };
@ -6999,19 +7181,18 @@ class UploadHttpClient {
core.debug(`URL is ${resourceUrl.toString()}`); core.debug(`URL is ${resourceUrl.toString()}`);
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately // use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
const client = this.uploadHttpManager.getClient(0); const client = this.uploadHttpManager.getClient(0);
const response = yield client.patch(resourceUrl.toString(), data, headers); const headers = utils_1.getUploadHeaders('application/json', false);
const body = yield response.readBody(); // Extra information to display when a particular HTTP code is returned
if (utils_1.isSuccessStatusCode(response.message.statusCode)) { const customErrorMessages = new Map([
core.debug(`Artifact ${artifactName} has been successfully uploaded, total size in bytes: ${size}`); [
} http_client_1.HttpCodes.NotFound,
else if (response.message.statusCode === 404) { `An Artifact with the name ${artifactName} was not found`
throw new Error(`An Artifact with the name ${artifactName} was not found`); ]
} ]);
else { // TODO retry for all possible response codes, the artifact upload is pretty much complete so it at all costs we should try to finish this
utils_1.displayHttpDiagnostics(response); const response = yield requestUtils_1.retryHttpClientRequest('Finalize artifact upload', () => __awaiter(this, void 0, void 0, function* () { return client.patch(resourceUrl.toString(), data, headers); }), customErrorMessages);
core.info(body); yield response.readBody();
throw new Error(`Unable to finish uploading artifact ${artifactName} to ${resourceUrl}`); core.debug(`Artifact ${artifactName} has been successfully uploaded, total size in bytes: ${size}`);
}
}); });
} }
} }
@ -7431,6 +7612,7 @@ const status_reporter_1 = __webpack_require__(176);
const perf_hooks_1 = __webpack_require__(630); const perf_hooks_1 = __webpack_require__(630);
const http_manager_1 = __webpack_require__(452); const http_manager_1 = __webpack_require__(452);
const config_variables_1 = __webpack_require__(401); const config_variables_1 = __webpack_require__(401);
const requestUtils_1 = __webpack_require__(489);
class DownloadHttpClient { class DownloadHttpClient {
constructor() { constructor() {
this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), '@actions/artifact-download'); this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), '@actions/artifact-download');
@ -7446,13 +7628,9 @@ class DownloadHttpClient {
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately // use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
const client = this.downloadHttpManager.getClient(0); const client = this.downloadHttpManager.getClient(0);
const headers = utils_1.getDownloadHeaders('application/json'); const headers = utils_1.getDownloadHeaders('application/json');
const response = yield client.get(artifactUrl, headers); const response = yield requestUtils_1.retryHttpClientRequest('List Artifacts', () => __awaiter(this, void 0, void 0, function* () { return client.get(artifactUrl, headers); }));
const body = yield response.readBody(); const body = yield response.readBody();
if (utils_1.isSuccessStatusCode(response.message.statusCode) && body) { return JSON.parse(body);
return JSON.parse(body);
}
utils_1.displayHttpDiagnostics(response);
throw new Error(`Unable to list artifacts for the run. Resource Url ${artifactUrl}`);
}); });
} }
/** /**
@ -7468,13 +7646,9 @@ class DownloadHttpClient {
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately // use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
const client = this.downloadHttpManager.getClient(0); const client = this.downloadHttpManager.getClient(0);
const headers = utils_1.getDownloadHeaders('application/json'); const headers = utils_1.getDownloadHeaders('application/json');
const response = yield client.get(resourceUrl.toString(), headers); const response = yield requestUtils_1.retryHttpClientRequest('Get Container Items', () => __awaiter(this, void 0, void 0, function* () { return client.get(resourceUrl.toString(), headers); }));
const body = yield response.readBody(); const body = yield response.readBody();
if (utils_1.isSuccessStatusCode(response.message.statusCode) && body) { return JSON.parse(body);
return JSON.parse(body);
}
utils_1.displayHttpDiagnostics(response);
throw new Error(`Unable to get ContainersItems from ${resourceUrl}`);
}); });
} }
/** /**
@ -7524,7 +7698,7 @@ class DownloadHttpClient {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
let retryCount = 0; let retryCount = 0;
const retryLimit = config_variables_1.getRetryLimit(); const retryLimit = config_variables_1.getRetryLimit();
const destinationStream = fs.createWriteStream(downloadPath); let destinationStream = fs.createWriteStream(downloadPath);
const headers = utils_1.getDownloadHeaders('application/json', true, true); const headers = utils_1.getDownloadHeaders('application/json', true, true);
// a single GET request is used to download a file // a single GET request is used to download a file
const makeDownloadRequest = () => __awaiter(this, void 0, void 0, function* () { const makeDownloadRequest = () => __awaiter(this, void 0, void 0, function* () {
@ -7549,22 +7723,40 @@ class DownloadHttpClient {
if (retryAfterValue) { if (retryAfterValue) {
// Back off by waiting the specified time denoted by the retry-after header // Back off by waiting the specified time denoted by the retry-after header
core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the download`); core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the download`);
yield new Promise(resolve => setTimeout(resolve, retryAfterValue)); yield utils_1.sleep(retryAfterValue);
} }
else { else {
// Back off using an exponential value that depends on the retry count // Back off using an exponential value that depends on the retry count
const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount); const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount);
core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the download`); core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the download`);
yield new Promise(resolve => setTimeout(resolve, backoffTime)); yield utils_1.sleep(backoffTime);
} }
core.info(`Finished backoff for retry #${retryCount}, continuing with download`); core.info(`Finished backoff for retry #${retryCount}, continuing with download`);
} }
}); });
const isAllBytesReceived = (expected, received) => {
// be lenient, if any input is missing, assume success, i.e. not truncated
if (!expected ||
!received ||
process.env['ACTIONS_ARTIFACT_SKIP_DOWNLOAD_VALIDATION']) {
core.info('Skipping download validation.');
return true;
}
return parseInt(expected) === received;
};
const resetDestinationStream = (fileDownloadPath) => __awaiter(this, void 0, void 0, function* () {
destinationStream.close();
yield utils_1.rmFile(fileDownloadPath);
destinationStream = fs.createWriteStream(fileDownloadPath);
});
// keep trying to download a file until a retry limit has been reached // keep trying to download a file until a retry limit has been reached
while (retryCount <= retryLimit) { while (retryCount <= retryLimit) {
let response; let response;
try { try {
response = yield makeDownloadRequest(); response = yield makeDownloadRequest();
if (core.isDebug()) {
utils_1.displayHttpDiagnostics(response);
}
} }
catch (error) { catch (error) {
// if an error is caught, it is usually indicative of a timeout so retry the download // if an error is caught, it is usually indicative of a timeout so retry the download
@ -7575,14 +7767,30 @@ class DownloadHttpClient {
yield backOff(); yield backOff();
continue; continue;
} }
let forceRetry = false;
if (utils_1.isSuccessStatusCode(response.message.statusCode)) { if (utils_1.isSuccessStatusCode(response.message.statusCode)) {
// The body contains the contents of the file however calling response.readBody() causes all the content to be converted to a string // The body contains the contents of the file however calling response.readBody() causes all the content to be converted to a string
// which can cause some gzip encoded data to be lost // which can cause some gzip encoded data to be lost
// Instead of using response.readBody(), response.message is a readableStream that can be directly used to get the raw body contents // Instead of using response.readBody(), response.message is a readableStream that can be directly used to get the raw body contents
return this.pipeResponseToFile(response, destinationStream, isGzip(response.message.headers)); try {
const isGzipped = isGzip(response.message.headers);
yield this.pipeResponseToFile(response, destinationStream, isGzipped);
if (isGzipped ||
isAllBytesReceived(response.message.headers['content-length'], yield utils_1.getFileSize(downloadPath))) {
return;
}
else {
forceRetry = true;
}
}
catch (error) {
// retry on error, most likely streams were corrupted
forceRetry = true;
}
} }
else if (utils_1.isRetryableStatusCode(response.message.statusCode)) { if (forceRetry || utils_1.isRetryableStatusCode(response.message.statusCode)) {
core.info(`A ${response.message.statusCode} response code has been received while attempting to download an artifact`); core.info(`A ${response.message.statusCode} response code has been received while attempting to download an artifact`);
resetDestinationStream(downloadPath);
// if a throttled status code is received, try to get the retryAfter header value, else differ to standard exponential backoff // if a throttled status code is received, try to get the retryAfter header value, else differ to standard exponential backoff
utils_1.isThrottledStatusCode(response.message.statusCode) utils_1.isThrottledStatusCode(response.message.statusCode)
? yield backOff(utils_1.tryGetRetryAfterValueTimeInMilliseconds(response.message.headers)) ? yield backOff(utils_1.tryGetRetryAfterValueTimeInMilliseconds(response.message.headers))
@ -7608,24 +7816,40 @@ class DownloadHttpClient {
if (isGzip) { if (isGzip) {
const gunzip = zlib.createGunzip(); const gunzip = zlib.createGunzip();
response.message response.message
.on('error', error => {
core.error(`An error occurred while attempting to read the response stream`);
gunzip.close();
destinationStream.close();
reject(error);
})
.pipe(gunzip) .pipe(gunzip)
.on('error', error => {
core.error(`An error occurred while attempting to decompress the response stream`);
destinationStream.close();
reject(error);
})
.pipe(destinationStream) .pipe(destinationStream)
.on('close', () => { .on('close', () => {
resolve(); resolve();
}) })
.on('error', error => { .on('error', error => {
core.error(`An error has been encountered while decompressing and writing a downloaded file to ${destinationStream.path}`); core.error(`An error occurred while writing a downloaded file to ${destinationStream.path}`);
reject(error); reject(error);
}); });
} }
else { else {
response.message response.message
.on('error', error => {
core.error(`An error occurred while attempting to read the response stream`);
destinationStream.close();
reject(error);
})
.pipe(destinationStream) .pipe(destinationStream)
.on('close', () => { .on('close', () => {
resolve(); resolve();
}) })
.on('error', error => { .on('error', error => {
core.error(`An error has been encountered while writing a downloaded file to ${destinationStream.path}`); core.error(`An error occurred while writing a downloaded file to ${destinationStream.path}`);
reject(error); reject(error);
}); });
} }
@ -8158,6 +8382,20 @@ function createEmptyFilesForArtifact(emptyFilesToCreate) {
}); });
} }
exports.createEmptyFilesForArtifact = createEmptyFilesForArtifact; exports.createEmptyFilesForArtifact = createEmptyFilesForArtifact;
function getFileSize(filePath) {
return __awaiter(this, void 0, void 0, function* () {
const stats = yield fs_1.promises.stat(filePath);
core_1.debug(`${filePath} size:(${stats.size}) blksize:(${stats.blksize}) blocks:(${stats.blocks})`);
return stats.size;
});
}
exports.getFileSize = getFileSize;
function rmFile(filePath) {
return __awaiter(this, void 0, void 0, function* () {
yield fs_1.promises.unlink(filePath);
});
}
exports.rmFile = rmFile;
function getProperRetention(retentionInput, retentionSetting) { function getProperRetention(retentionInput, retentionSetting) {
if (retentionInput < 0) { if (retentionInput < 0) {
throw new Error('Invalid retention, minimum value is 1.'); throw new Error('Invalid retention, minimum value is 1.');
@ -8173,6 +8411,12 @@ function getProperRetention(retentionInput, retentionSetting) {
return retention; return retention;
} }
exports.getProperRetention = getProperRetention; exports.getProperRetention = getProperRetention;
function sleep(milliseconds) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise(resolve => setTimeout(resolve, milliseconds));
});
}
exports.sleep = sleep;
//# sourceMappingURL=utils.js.map //# sourceMappingURL=utils.js.map
/***/ }), /***/ }),
@ -8491,12 +8735,11 @@ exports.Pattern = Pattern;
/***/ }), /***/ }),
/***/ 950: /***/ 950:
/***/ (function(__unusedmodule, exports, __webpack_require__) { /***/ (function(__unusedmodule, exports) {
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const url = __webpack_require__(835);
function getProxyUrl(reqUrl) { function getProxyUrl(reqUrl) {
let usingSsl = reqUrl.protocol === 'https:'; let usingSsl = reqUrl.protocol === 'https:';
let proxyUrl; let proxyUrl;
@ -8511,7 +8754,7 @@ function getProxyUrl(reqUrl) {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY']; proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
} }
if (proxyVar) { if (proxyVar) {
proxyUrl = url.parse(proxyVar); proxyUrl = new URL(proxyVar);
} }
return proxyUrl; return proxyUrl;
} }

3417
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,8 @@
}, },
"homepage": "https://github.com/actions/upload-artifact#readme", "homepage": "https://github.com/actions/upload-artifact#readme",
"dependencies": { "dependencies": {
"@actions/artifact": "^0.4.0", "@actions/artifact": "^0.5.0",
"@actions/core": "^1.2.3", "@actions/core": "^1.2.6",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/io": "^1.0.2" "@actions/io": "^1.0.2"
}, },
@ -41,10 +41,10 @@
"@zeit/ncc": "^0.22.1", "@zeit/ncc": "^0.22.1",
"concurrently": "^5.1.0", "concurrently": "^5.1.0",
"eslint": "^7.4.0", "eslint": "^7.4.0",
"eslint-plugin-github": "^3.4.1", "eslint-plugin-github": "^4.1.1",
"eslint-plugin-jest": "^23.8.2", "eslint-plugin-jest": "^23.8.2",
"glob": "^7.1.6", "glob": "^7.1.6",
"jest": "^26.1.0", "jest": "^26.6.3",
"jest-circus": "^26.1.0", "jest-circus": "^26.1.0",
"prettier": "^2.0.4", "prettier": "^2.0.4",
"ts-jest": "^25.3.1", "ts-jest": "^25.3.1",

View File

@ -31,8 +31,9 @@ async function run(): Promise<void> {
} }
} }
} else { } else {
const s = searchResult.filesToUpload.length === 1 ? '' : 's'
core.info( core.info(
`With the provided path, there will be ${searchResult.filesToUpload.length} file(s) uploaded` `With the provided path, there will be ${searchResult.filesToUpload.length} file${s} uploaded`
) )
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`) core.debug(`Root artifact directory is ${searchResult.rootDirectory}`)