Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add continue_on_error action input #48

Merged
merged 3 commits into from Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci_cd.yml
Expand Up @@ -97,6 +97,41 @@ jobs:
expected: failure
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
id: retry_on_timeout_fail
uses: ./
Expand Down
28 changes: 27 additions & 1 deletion README.md
Expand Up @@ -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.

### `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

### `total_attempts`
Expand Down Expand Up @@ -113,7 +117,29 @@ with:
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
- uses: nick-invision/retry@v2
Expand Down
5 changes: 4 additions & 1 deletion action.yml
Expand Up @@ -33,6 +33,9 @@ inputs:
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.
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:
total_attempts:
description: The final number of attempts made
Expand All @@ -42,4 +45,4 @@ outputs:
description: The final error returned by the command
runs:
using: 'node12'
main: 'dist/index.js'
main: 'dist/index.js'
68 changes: 57 additions & 11 deletions dist/index.js
Expand Up @@ -51,7 +51,7 @@ module.exports =
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
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
* @param input input to sanitize into a string
Expand All @@ -66,6 +66,25 @@ function toCommandValue(input) {
return JSON.stringify(input);
}
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

/***/ }),
Expand Down Expand Up @@ -268,6 +287,7 @@ var POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false)
var RETRY_ON = core_1.getInput('retry_on') || 'any';
var WARNING_ON_RETRY = core_1.getInput('warning_on_retry').toLowerCase() === 'true';
var ON_RETRY_COMMAND = core_1.getInput('on_retry_command');
var CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');
var OS = process.platform;
var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
var OUTPUT_EXIT_CODE_KEY = 'exit_code';
Expand All @@ -286,6 +306,13 @@ function getInputNumber(id, required) {
}
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() {
return __awaiter(this, void 0, void 0, function () {
var waitStart;
Expand Down Expand Up @@ -497,12 +524,20 @@ runAction()
process.exit(0); // success
})
.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
core_1.setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
core_1.setOutput(OUTPUT_EXIT_CODE_KEY, exit > 0 ? exit : 1);
// exit with exact error code if available, otherwise just exit with 1
process.exit(exit > 0 ? exit : 1);
core_1.setOutput(OUTPUT_EXIT_CODE_KEY, exitCode);
// if continue_on_error, exit with exact error code else exit gracefully
// mimics native continue-on-error that is not supported in composite actions
process.exit(CONTINUE_ON_ERROR ? 0 : exitCode);
});


Expand Down Expand Up @@ -641,7 +676,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
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 file_command_1 = __webpack_require__(102);
const utils_1 = __webpack_require__(82);
Expand Down Expand Up @@ -819,19 +854,30 @@ exports.debug = debug;
/**
* Adds an error issue
* @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) {
command_1.issue('error', message instanceof Error ? message.toString() : message);
function error(message, properties = {}) {
command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
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 properties optional properties to add to the annotation.
*/
function warning(message) {
command_1.issue('warning', message instanceof Error ? message.toString() : message);
function warning(message, properties = {}) {
command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
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.
* @param message info message
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -18,7 +18,7 @@
},
"homepage": "https://github.com/nick-invision/retry#readme",
"dependencies": {
"@actions/core": "^1.4.0",
"@actions/core": "^1.5.0",
"milliseconds": "^1.0.3",
"tree-kill": "^1.2.2"
},
Expand Down
26 changes: 22 additions & 4 deletions src/index.ts
Expand Up @@ -16,6 +16,7 @@ const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', fals
const RETRY_ON = getInput('retry_on') || 'any';
const WARNING_ON_RETRY = getInput('warning_on_retry').toLowerCase() === 'true';
const ON_RETRY_COMMAND = getInput('on_retry_command');
const CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');

const OS = process.platform;
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
Expand All @@ -41,6 +42,15 @@ function getInputNumber(id: string, required: boolean): number | undefined {
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() {
const waitStart = Date.now();
await wait(ms.seconds(RETRY_WAIT_SECONDS));
Expand Down Expand Up @@ -195,12 +205,20 @@ runAction()
process.exit(0); // success
})
.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
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
process.exit(exit > 0 ? exit : 1);
// if continue_on_error, exit with exact error code else exit gracefully
// mimics native continue-on-error that is not supported in composite actions
process.exit(CONTINUE_ON_ERROR ? 0 : exitCode);
});