mirror of
https://github.com/nick-fields/retry.git
synced 2026-02-10 15:15:30 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
002ef572db | ||
|
|
e80198a9da | ||
|
|
c77dc43532 | ||
|
|
0019811846 | ||
|
|
a63662d5a7 | ||
|
|
67e1bdfd8d |
35
.github/workflows/ci_cd.yml
vendored
35
.github/workflows/ci_cd.yml
vendored
@@ -97,6 +97,41 @@ jobs:
|
|||||||
expected: failure
|
expected: failure
|
||||||
actual: ${{ steps.sad_path_error.outcome }}
|
actual: ${{ steps.sad_path_error.outcome }}
|
||||||
|
|
||||||
|
- name: happy-path (continue_on_error)
|
||||||
|
id: happy_path_continue_on_error
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
command: node -e "process.exit(0)"
|
||||||
|
timeout_minutes: 1
|
||||||
|
continue_on_error: true
|
||||||
|
- name: sad-path (continue_on_error)
|
||||||
|
id: sad_path_continue_on_error
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
command: node -e "process.exit(33)"
|
||||||
|
timeout_minutes: 1
|
||||||
|
continue_on_error: true
|
||||||
|
- name: Verify continue_on_error returns correct exit code on success
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: 0
|
||||||
|
actual: ${{ steps.happy_path_continue_on_error.outputs.exit_code }}
|
||||||
|
- name: Verify continue_on_error exits with correct outcome on success
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: success
|
||||||
|
actual: ${{ steps.happy_path_continue_on_error.outcome }}
|
||||||
|
- name: Verify continue_on_error returns correct exit code on error
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: 33
|
||||||
|
actual: ${{ steps.sad_path_continue_on_error.outputs.exit_code }}
|
||||||
|
- name: Verify continue_on_error exits with successful outcome when an error occurs
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: success
|
||||||
|
actual: ${{ steps.sad_path_continue_on_error.outcome }}
|
||||||
|
|
||||||
- name: retry_on (timeout) fails early if error encountered
|
- name: retry_on (timeout) fails early if error encountered
|
||||||
id: retry_on_timeout_fail
|
id: retry_on_timeout_fail
|
||||||
uses: ./
|
uses: ./
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -44,6 +44,10 @@ Retries an Action step on failure or timeout. This is currently intended to repl
|
|||||||
|
|
||||||
**Optional** Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
|
**Optional** Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
|
||||||
|
|
||||||
|
### `continue_on_error`
|
||||||
|
|
||||||
|
**Optional** Exit successfully even if an error occurs. Same as native continue-on-error behavior, but for use in composite actions. Defaults to `false`
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
### `total_attempts`
|
### `total_attempts`
|
||||||
@@ -113,7 +117,29 @@ with:
|
|||||||
command: npm run some-typically-fast-script
|
command: npm run some-typically-fast-script
|
||||||
```
|
```
|
||||||
|
|
||||||
### Retry but allow failure and do something with output
|
### Retry using continue_on_error input (in composite action) but allow failure and do something with output
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: nick-invision/retry@v2
|
||||||
|
id: retry
|
||||||
|
with:
|
||||||
|
timeout_seconds: 15
|
||||||
|
max_attempts: 3
|
||||||
|
continue_on_error: true
|
||||||
|
command: node -e 'process.exit(99);'
|
||||||
|
- name: Assert that step succeeded (despite failing command)
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: success
|
||||||
|
actual: ${{ steps.retry.outcome }}
|
||||||
|
- name: Assert that action exited with expected exit code
|
||||||
|
uses: nick-invision/assert-action@v1
|
||||||
|
with:
|
||||||
|
expected: 99
|
||||||
|
actual: ${{ steps.retry.outputs.exit_code }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retry using continue-on-error built-in command (in workflow action) but allow failure and do something with output
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: nick-invision/retry@v2
|
- uses: nick-invision/retry@v2
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ inputs:
|
|||||||
on_retry_command:
|
on_retry_command:
|
||||||
description: Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
|
description: Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
|
||||||
required: false
|
required: false
|
||||||
|
continue_on_error:
|
||||||
|
description: Exits successfully even if an error occurs. Same as native continue-on-error behavior, but for use in composite actions. Default is false
|
||||||
|
default: false
|
||||||
outputs:
|
outputs:
|
||||||
total_attempts:
|
total_attempts:
|
||||||
description: The final number of attempts made
|
description: The final number of attempts made
|
||||||
|
|||||||
66
dist/index.js
vendored
66
dist/index.js
vendored
@@ -51,7 +51,7 @@ module.exports =
|
|||||||
// We use any as a valid input type
|
// We use any as a valid input type
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.toCommandValue = void 0;
|
exports.toCommandProperties = exports.toCommandValue = void 0;
|
||||||
/**
|
/**
|
||||||
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
* Sanitizes an input into a string so it can be passed into issueCommand safely
|
||||||
* @param input input to sanitize into a string
|
* @param input input to sanitize into a string
|
||||||
@@ -66,6 +66,25 @@ function toCommandValue(input) {
|
|||||||
return JSON.stringify(input);
|
return JSON.stringify(input);
|
||||||
}
|
}
|
||||||
exports.toCommandValue = toCommandValue;
|
exports.toCommandValue = toCommandValue;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param annotationProperties
|
||||||
|
* @returns The command properties to send with the actual annotation command
|
||||||
|
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
|
||||||
|
*/
|
||||||
|
function toCommandProperties(annotationProperties) {
|
||||||
|
if (!Object.keys(annotationProperties).length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: annotationProperties.title,
|
||||||
|
line: annotationProperties.startLine,
|
||||||
|
endLine: annotationProperties.endLine,
|
||||||
|
col: annotationProperties.startColumn,
|
||||||
|
endColumn: annotationProperties.endColumn
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exports.toCommandProperties = toCommandProperties;
|
||||||
//# sourceMappingURL=utils.js.map
|
//# sourceMappingURL=utils.js.map
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -268,6 +287,7 @@ var POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false)
|
|||||||
var RETRY_ON = core_1.getInput('retry_on') || 'any';
|
var RETRY_ON = core_1.getInput('retry_on') || 'any';
|
||||||
var WARNING_ON_RETRY = core_1.getInput('warning_on_retry').toLowerCase() === 'true';
|
var WARNING_ON_RETRY = core_1.getInput('warning_on_retry').toLowerCase() === 'true';
|
||||||
var ON_RETRY_COMMAND = core_1.getInput('on_retry_command');
|
var ON_RETRY_COMMAND = core_1.getInput('on_retry_command');
|
||||||
|
var CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');
|
||||||
var OS = process.platform;
|
var OS = process.platform;
|
||||||
var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
|
var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
|
||||||
var OUTPUT_EXIT_CODE_KEY = 'exit_code';
|
var OUTPUT_EXIT_CODE_KEY = 'exit_code';
|
||||||
@@ -286,6 +306,13 @@ function getInputNumber(id, required) {
|
|||||||
}
|
}
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
function getInputBoolean(id) {
|
||||||
|
var input = core_1.getInput(id);
|
||||||
|
if (!['true', 'false'].includes(input.toLowerCase())) {
|
||||||
|
throw "Input " + id + " only accepts boolean values. Received " + input;
|
||||||
|
}
|
||||||
|
return input.toLowerCase() === 'true';
|
||||||
|
}
|
||||||
function retryWait() {
|
function retryWait() {
|
||||||
return __awaiter(this, void 0, void 0, function () {
|
return __awaiter(this, void 0, void 0, function () {
|
||||||
var waitStart;
|
var waitStart;
|
||||||
@@ -497,12 +524,20 @@ runAction()
|
|||||||
process.exit(0); // success
|
process.exit(0); // success
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
|
// exact error code if available, otherwise just 1
|
||||||
|
var exitCode = exit > 0 ? exit : 1;
|
||||||
|
if (CONTINUE_ON_ERROR) {
|
||||||
|
core_1.warning(err.message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
core_1.error(err.message);
|
core_1.error(err.message);
|
||||||
|
}
|
||||||
// these can be helpful to know if continue-on-error is true
|
// these can be helpful to know if continue-on-error is true
|
||||||
core_1.setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
|
core_1.setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
|
||||||
core_1.setOutput(OUTPUT_EXIT_CODE_KEY, exit > 0 ? exit : 1);
|
core_1.setOutput(OUTPUT_EXIT_CODE_KEY, exitCode);
|
||||||
// exit with exact error code if available, otherwise just exit with 1
|
// if continue_on_error, exit with exact error code else exit gracefully
|
||||||
process.exit(exit > 0 ? exit : 1);
|
// mimics native continue-on-error that is not supported in composite actions
|
||||||
|
process.exit(CONTINUE_ON_ERROR ? 0 : exitCode);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -641,7 +676,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
|
exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
|
||||||
const command_1 = __webpack_require__(431);
|
const command_1 = __webpack_require__(431);
|
||||||
const file_command_1 = __webpack_require__(102);
|
const file_command_1 = __webpack_require__(102);
|
||||||
const utils_1 = __webpack_require__(82);
|
const utils_1 = __webpack_require__(82);
|
||||||
@@ -819,19 +854,30 @@ exports.debug = debug;
|
|||||||
/**
|
/**
|
||||||
* Adds an error issue
|
* Adds an error issue
|
||||||
* @param message error issue message. Errors will be converted to string via toString()
|
* @param message error issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
function error(message) {
|
function error(message, properties = {}) {
|
||||||
command_1.issue('error', message instanceof Error ? message.toString() : message);
|
command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
|
||||||
}
|
}
|
||||||
exports.error = error;
|
exports.error = error;
|
||||||
/**
|
/**
|
||||||
* Adds an warning issue
|
* Adds a warning issue
|
||||||
* @param message warning issue message. Errors will be converted to string via toString()
|
* @param message warning issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
*/
|
*/
|
||||||
function warning(message) {
|
function warning(message, properties = {}) {
|
||||||
command_1.issue('warning', message instanceof Error ? message.toString() : message);
|
command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
|
||||||
}
|
}
|
||||||
exports.warning = warning;
|
exports.warning = warning;
|
||||||
|
/**
|
||||||
|
* Adds a notice issue
|
||||||
|
* @param message notice issue message. Errors will be converted to string via toString()
|
||||||
|
* @param properties optional properties to add to the annotation.
|
||||||
|
*/
|
||||||
|
function notice(message, properties = {}) {
|
||||||
|
command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
|
||||||
|
}
|
||||||
|
exports.notice = notice;
|
||||||
/**
|
/**
|
||||||
* Writes info to log with console.log.
|
* Writes info to log with console.log.
|
||||||
* @param message info message
|
* @param message info message
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -5,9 +5,9 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.4.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
|
||||||
"integrity": "sha512-CGx2ilGq5i7zSLgiiGUtBCxhRRxibJYU6Fim0Q1Wg2aQL2LTnF27zbqZOrxfvFQ55eSBW0L8uVStgtKMpa0Qlg=="
|
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
|
||||||
},
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.8.3",
|
"version": "7.8.3",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/nick-invision/retry#readme",
|
"homepage": "https://github.com/nick-invision/retry#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.4.0",
|
"@actions/core": "^1.5.0",
|
||||||
"milliseconds": "^1.0.3",
|
"milliseconds": "^1.0.3",
|
||||||
"tree-kill": "^1.2.2"
|
"tree-kill": "^1.2.2"
|
||||||
},
|
},
|
||||||
|
|||||||
24
src/index.ts
24
src/index.ts
@@ -16,6 +16,7 @@ const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', fals
|
|||||||
const RETRY_ON = getInput('retry_on') || 'any';
|
const RETRY_ON = getInput('retry_on') || 'any';
|
||||||
const WARNING_ON_RETRY = getInput('warning_on_retry').toLowerCase() === 'true';
|
const WARNING_ON_RETRY = getInput('warning_on_retry').toLowerCase() === 'true';
|
||||||
const ON_RETRY_COMMAND = getInput('on_retry_command');
|
const ON_RETRY_COMMAND = getInput('on_retry_command');
|
||||||
|
const CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');
|
||||||
|
|
||||||
const OS = process.platform;
|
const OS = process.platform;
|
||||||
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
|
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
|
||||||
@@ -41,6 +42,15 @@ function getInputNumber(id: string, required: boolean): number | undefined {
|
|||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInputBoolean(id: string): Boolean {
|
||||||
|
const input = getInput(id);
|
||||||
|
|
||||||
|
if (!['true','false'].includes(input.toLowerCase())) {
|
||||||
|
throw `Input ${id} only accepts boolean values. Received ${input}`;
|
||||||
|
}
|
||||||
|
return input.toLowerCase() === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
async function retryWait() {
|
async function retryWait() {
|
||||||
const waitStart = Date.now();
|
const waitStart = Date.now();
|
||||||
await wait(ms.seconds(RETRY_WAIT_SECONDS));
|
await wait(ms.seconds(RETRY_WAIT_SECONDS));
|
||||||
@@ -195,12 +205,20 @@ runAction()
|
|||||||
process.exit(0); // success
|
process.exit(0); // success
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
// exact error code if available, otherwise just 1
|
||||||
|
const exitCode = exit > 0 ? exit : 1;
|
||||||
|
|
||||||
|
if (CONTINUE_ON_ERROR) {
|
||||||
|
warning(err.message);
|
||||||
|
} else {
|
||||||
error(err.message);
|
error(err.message);
|
||||||
|
}
|
||||||
|
|
||||||
// these can be helpful to know if continue-on-error is true
|
// these can be helpful to know if continue-on-error is true
|
||||||
setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
|
setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
|
||||||
setOutput(OUTPUT_EXIT_CODE_KEY, exit > 0 ? exit : 1);
|
setOutput(OUTPUT_EXIT_CODE_KEY, exitCode);
|
||||||
|
|
||||||
// exit with exact error code if available, otherwise just exit with 1
|
// if continue_on_error, exit with exact error code else exit gracefully
|
||||||
process.exit(exit > 0 ? exit : 1);
|
// mimics native continue-on-error that is not supported in composite actions
|
||||||
|
process.exit(CONTINUE_ON_ERROR ? 0 : exitCode);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user