Merge pull request #48 from nick-invision/nrf/continue-on-error

Add continue_on_error action input
This commit is contained in:
Nick Fields
2021-10-06 20:02:05 -04:00
committed by GitHub
7 changed files with 149 additions and 21 deletions

View File

@@ -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: ./

View File

@@ -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

View File

@@ -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
@@ -42,4 +45,4 @@ outputs:
description: The final error returned by the command description: The final error returned by the command
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'

68
dist/index.js vendored
View File

@@ -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) {
core_1.error(err.message); // 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);
}
// 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
View File

@@ -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",

View File

@@ -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"
}, },

View File

@@ -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) => {
error(err.message); // 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);
}
// 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);
}); });