Skip to content

Commit 4a7a711

Browse files
authored
Add retries to all HTTP calls + fix dependabot alerts (#80)
* Update @actions/artifact package to version 0.5.0 * bump eslint-plugin-github to version 4.1.1 * Update artifact.dep.yml
1 parent f144d3c commit 4a7a711

4 files changed

Lines changed: 579 additions & 965 deletions

File tree

.licenses/npm/@actions/artifact.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.

dist/index.js

Lines changed: 127 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4881,6 +4881,88 @@ exports.getState = getState;
48814881

48824882
/***/ }),
48834883

4884+
/***/ 489:
4885+
/***/ (function(__unusedmodule, exports, __webpack_require__) {
4886+
4887+
"use strict";
4888+
4889+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4890+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4891+
return new (P || (P = Promise))(function (resolve, reject) {
4892+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
4893+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
4894+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
4895+
step((generator = generator.apply(thisArg, _arguments || [])).next());
4896+
});
4897+
};
4898+
var __importStar = (this && this.__importStar) || function (mod) {
4899+
if (mod && mod.__esModule) return mod;
4900+
var result = {};
4901+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
4902+
result["default"] = mod;
4903+
return result;
4904+
};
4905+
Object.defineProperty(exports, "__esModule", { value: true });
4906+
const utils_1 = __webpack_require__(870);
4907+
const core = __importStar(__webpack_require__(470));
4908+
const config_variables_1 = __webpack_require__(401);
4909+
function retry(name, operation, customErrorMessages, maxAttempts) {
4910+
return __awaiter(this, void 0, void 0, function* () {
4911+
let response = undefined;
4912+
let statusCode = undefined;
4913+
let isRetryable = false;
4914+
let errorMessage = '';
4915+
let customErrorInformation = undefined;
4916+
let attempt = 1;
4917+
while (attempt <= maxAttempts) {
4918+
try {
4919+
response = yield operation();
4920+
statusCode = response.message.statusCode;
4921+
if (utils_1.isSuccessStatusCode(statusCode)) {
4922+
return response;
4923+
}
4924+
// Extra error information that we want to display if a particular response code is hit
4925+
if (statusCode) {
4926+
customErrorInformation = customErrorMessages.get(statusCode);
4927+
}
4928+
isRetryable = utils_1.isRetryableStatusCode(statusCode);
4929+
errorMessage = `Artifact service responded with ${statusCode}`;
4930+
}
4931+
catch (error) {
4932+
isRetryable = true;
4933+
errorMessage = error.message;
4934+
}
4935+
if (!isRetryable) {
4936+
core.info(`${name} - Error is not retryable`);
4937+
if (response) {
4938+
utils_1.displayHttpDiagnostics(response);
4939+
}
4940+
break;
4941+
}
4942+
core.info(`${name} - Attempt ${attempt} of ${maxAttempts} failed with error: ${errorMessage}`);
4943+
yield utils_1.sleep(utils_1.getExponentialRetryTimeInMilliseconds(attempt));
4944+
attempt++;
4945+
}
4946+
if (response) {
4947+
utils_1.displayHttpDiagnostics(response);
4948+
}
4949+
if (customErrorInformation) {
4950+
throw Error(`${name} failed: ${customErrorInformation}`);
4951+
}
4952+
throw Error(`${name} failed: ${errorMessage}`);
4953+
});
4954+
}
4955+
exports.retry = retry;
4956+
function retryHttpClientRequest(name, method, customErrorMessages = new Map(), maxAttempts = config_variables_1.getRetryLimit()) {
4957+
return __awaiter(this, void 0, void 0, function* () {
4958+
return yield retry(name, method, customErrorMessages, maxAttempts);
4959+
});
4960+
}
4961+
exports.retryHttpClientRequest = retryHttpClientRequest;
4962+
//# sourceMappingURL=requestUtils.js.map
4963+
4964+
/***/ }),
4965+
48844966
/***/ 532:
48854967
/***/ (function(__unusedmodule, exports, __webpack_require__) {
48864968

@@ -5997,8 +6079,10 @@ const util_1 = __webpack_require__(669);
59976079
const url_1 = __webpack_require__(835);
59986080
const perf_hooks_1 = __webpack_require__(630);
59996081
const status_reporter_1 = __webpack_require__(176);
6082+
const http_client_1 = __webpack_require__(539);
60006083
const http_manager_1 = __webpack_require__(452);
60016084
const upload_gzip_1 = __webpack_require__(647);
6085+
const requestUtils_1 = __webpack_require__(489);
60026086
const stat = util_1.promisify(fs.stat);
60036087
class UploadHttpClient {
60046088
constructor() {
@@ -6026,20 +6110,22 @@ class UploadHttpClient {
60266110
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
60276111
const client = this.uploadHttpManager.getClient(0);
60286112
const headers = utils_1.getUploadHeaders('application/json', false);
6029-
const rawResponse = yield client.post(artifactUrl, data, headers);
6030-
const body = yield rawResponse.readBody();
6031-
if (utils_1.isSuccessStatusCode(rawResponse.message.statusCode) && body) {
6032-
return JSON.parse(body);
6033-
}
6034-
else if (utils_1.isForbiddenStatusCode(rawResponse.message.statusCode)) {
6035-
// if a 403 is returned when trying to create a file container, the customer has exceeded
6036-
// their storage quota so no new artifact containers can be created
6037-
throw new Error(`Artifact storage quota has been hit. Unable to upload any new artifacts`);
6038-
}
6039-
else {
6040-
utils_1.displayHttpDiagnostics(rawResponse);
6041-
throw new Error(`Unable to create a container for the artifact ${artifactName} at ${artifactUrl}`);
6042-
}
6113+
// Extra information to display when a particular HTTP code is returned
6114+
// If a 403 is returned when trying to create a file container, the customer has exceeded
6115+
// their storage quota so no new artifact containers can be created
6116+
const customErrorMessages = new Map([
6117+
[
6118+
http_client_1.HttpCodes.Forbidden,
6119+
'Artifact storage quota has been hit. Unable to upload any new artifacts'
6120+
],
6121+
[
6122+
http_client_1.HttpCodes.BadRequest,
6123+
`The artifact name ${artifactName} is not valid. Request URL ${artifactUrl}`
6124+
]
6125+
]);
6126+
const response = yield requestUtils_1.retryHttpClientRequest('Create Artifact Container', () => __awaiter(this, void 0, void 0, function* () { return client.post(artifactUrl, data, headers); }), customErrorMessages);
6127+
const body = yield response.readBody();
6128+
return JSON.parse(body);
60436129
});
60446130
}
60456131
/**
@@ -6263,12 +6349,12 @@ class UploadHttpClient {
62636349
this.uploadHttpManager.disposeAndReplaceClient(httpClientIndex);
62646350
if (retryAfterValue) {
62656351
core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the upload`);
6266-
yield new Promise(resolve => setTimeout(resolve, retryAfterValue));
6352+
yield utils_1.sleep(retryAfterValue);
62676353
}
62686354
else {
62696355
const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount);
62706356
core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the upload at offset ${start}`);
6271-
yield new Promise(resolve => setTimeout(resolve, backoffTime));
6357+
yield utils_1.sleep(backoffTime);
62726358
}
62736359
core.info(`Finished backoff for retry #${retryCount}, continuing with upload`);
62746360
return;
@@ -6320,27 +6406,25 @@ class UploadHttpClient {
63206406
*/
63216407
patchArtifactSize(size, artifactName) {
63226408
return __awaiter(this, void 0, void 0, function* () {
6323-
const headers = utils_1.getUploadHeaders('application/json', false);
63246409
const resourceUrl = new url_1.URL(utils_1.getArtifactUrl());
63256410
resourceUrl.searchParams.append('artifactName', artifactName);
63266411
const parameters = { Size: size };
63276412
const data = JSON.stringify(parameters, null, 2);
63286413
core.debug(`URL is ${resourceUrl.toString()}`);
63296414
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
63306415
const client = this.uploadHttpManager.getClient(0);
6331-
const response = yield client.patch(resourceUrl.toString(), data, headers);
6332-
const body = yield response.readBody();
6333-
if (utils_1.isSuccessStatusCode(response.message.statusCode)) {
6334-
core.debug(`Artifact ${artifactName} has been successfully uploaded, total size in bytes: ${size}`);
6335-
}
6336-
else if (response.message.statusCode === 404) {
6337-
throw new Error(`An Artifact with the name ${artifactName} was not found`);
6338-
}
6339-
else {
6340-
utils_1.displayHttpDiagnostics(response);
6341-
core.info(body);
6342-
throw new Error(`Unable to finish uploading artifact ${artifactName} to ${resourceUrl}`);
6343-
}
6416+
const headers = utils_1.getUploadHeaders('application/json', false);
6417+
// Extra information to display when a particular HTTP code is returned
6418+
const customErrorMessages = new Map([
6419+
[
6420+
http_client_1.HttpCodes.NotFound,
6421+
`An Artifact with the name ${artifactName} was not found`
6422+
]
6423+
]);
6424+
// 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
6425+
const response = yield requestUtils_1.retryHttpClientRequest('Finalize artifact upload', () => __awaiter(this, void 0, void 0, function* () { return client.patch(resourceUrl.toString(), data, headers); }), customErrorMessages);
6426+
yield response.readBody();
6427+
core.debug(`Artifact ${artifactName} has been successfully uploaded, total size in bytes: ${size}`);
63446428
});
63456429
}
63466430
}
@@ -6806,6 +6890,7 @@ const status_reporter_1 = __webpack_require__(176);
68066890
const perf_hooks_1 = __webpack_require__(630);
68076891
const http_manager_1 = __webpack_require__(452);
68086892
const config_variables_1 = __webpack_require__(401);
6893+
const requestUtils_1 = __webpack_require__(489);
68096894
class DownloadHttpClient {
68106895
constructor() {
68116896
this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), '@actions/artifact-download');
@@ -6821,13 +6906,9 @@ class DownloadHttpClient {
68216906
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
68226907
const client = this.downloadHttpManager.getClient(0);
68236908
const headers = utils_1.getDownloadHeaders('application/json');
6824-
const response = yield client.get(artifactUrl, headers);
6909+
const response = yield requestUtils_1.retryHttpClientRequest('List Artifacts', () => __awaiter(this, void 0, void 0, function* () { return client.get(artifactUrl, headers); }));
68256910
const body = yield response.readBody();
6826-
if (utils_1.isSuccessStatusCode(response.message.statusCode) && body) {
6827-
return JSON.parse(body);
6828-
}
6829-
utils_1.displayHttpDiagnostics(response);
6830-
throw new Error(`Unable to list artifacts for the run. Resource Url ${artifactUrl}`);
6911+
return JSON.parse(body);
68316912
});
68326913
}
68336914
/**
@@ -6843,13 +6924,9 @@ class DownloadHttpClient {
68436924
// use the first client from the httpManager, `keep-alive` is not used so the connection will close immediately
68446925
const client = this.downloadHttpManager.getClient(0);
68456926
const headers = utils_1.getDownloadHeaders('application/json');
6846-
const response = yield client.get(resourceUrl.toString(), headers);
6927+
const response = yield requestUtils_1.retryHttpClientRequest('Get Container Items', () => __awaiter(this, void 0, void 0, function* () { return client.get(resourceUrl.toString(), headers); }));
68476928
const body = yield response.readBody();
6848-
if (utils_1.isSuccessStatusCode(response.message.statusCode) && body) {
6849-
return JSON.parse(body);
6850-
}
6851-
utils_1.displayHttpDiagnostics(response);
6852-
throw new Error(`Unable to get ContainersItems from ${resourceUrl}`);
6929+
return JSON.parse(body);
68536930
});
68546931
}
68556932
/**
@@ -6924,13 +7001,13 @@ class DownloadHttpClient {
69247001
if (retryAfterValue) {
69257002
// Back off by waiting the specified time denoted by the retry-after header
69267003
core.info(`Backoff due to too many requests, retry #${retryCount}. Waiting for ${retryAfterValue} milliseconds before continuing the download`);
6927-
yield new Promise(resolve => setTimeout(resolve, retryAfterValue));
7004+
yield utils_1.sleep(retryAfterValue);
69287005
}
69297006
else {
69307007
// Back off using an exponential value that depends on the retry count
69317008
const backoffTime = utils_1.getExponentialRetryTimeInMilliseconds(retryCount);
69327009
core.info(`Exponential backoff for retry #${retryCount}. Waiting for ${backoffTime} milliseconds before continuing the download`);
6933-
yield new Promise(resolve => setTimeout(resolve, backoffTime));
7010+
yield utils_1.sleep(backoffTime);
69347011
}
69357012
core.info(`Finished backoff for retry #${retryCount}, continuing with download`);
69367013
}
@@ -7612,6 +7689,12 @@ function getProperRetention(retentionInput, retentionSetting) {
76127689
return retention;
76137690
}
76147691
exports.getProperRetention = getProperRetention;
7692+
function sleep(milliseconds) {
7693+
return __awaiter(this, void 0, void 0, function* () {
7694+
return new Promise(resolve => setTimeout(resolve, milliseconds));
7695+
});
7696+
}
7697+
exports.sleep = sleep;
76157698
//# sourceMappingURL=utils.js.map
76167699

76177700
/***/ }),

0 commit comments

Comments
 (0)