Skip to content

Commit

Permalink
Add on_retry_command input to optionally run cmd before a retry (#33)
Browse files Browse the repository at this point in the history
* minor: add on_retry_command input to optionally run cmd before a retry

* test: add test for on-retry-command failure
  • Loading branch information
nick-invision authored Jan 5, 2021
1 parent 025c480 commit 7c68161
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 25 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ jobs:
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
comparison: contains

- name: on-retry-cmd
id: on-retry-cmd
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
command: node -e "process.exit(1)"
on_retry_command: node -e "console.log('this is a retry command')"

- name: on-retry-cmd (on-retry fails)
id: on-retry-cmd-fails
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
command: node -e "process.exit(1)"
on_retry_command: node -e "throw new Error('This is an on-retry command error')"

- name: sad-path (error)
id: sad_path_error
uses: ./
Expand Down
1 change: 1 addition & 0 deletions .releaserc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
{ type: 'minor', release: 'minor' },
{ type: 'major', release: 'major' },
{ type: 'patch', release: 'patch' },
{ type: 'test', release: false },
{ scope: 'no-release', release: false },
],
},
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ Retries an Action step on failure or timeout. This is currently intended to repl

**Optional** Whether to output a warning on retry, or just output to info. Defaults to `true`.

### `on_retry_command`

**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.

## Outputs

### `total_attempts`
Expand Down Expand Up @@ -138,6 +142,17 @@ with:
actual: ${{ steps.retry.outputs.total_attempts }}
```
### Run script after failure but before retry
```yaml
uses: nick-invision/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
command: npm run some-flaky-script-that-outputs-something
on_retry_command: npm run cleanup-flaky-script-output
```
## Requirements
NodeJS is required for this action to run. This runs without issue on all GitHub hosted runners but if you are running into issues with this on self hosted runners ensure NodeJS is installed.
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ inputs:
warning_on_retry:
description: Whether to output a warning on retry, or just output to info. Defaults to true
default: true
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
outputs:
total_attempts:
description: The final number of attempts made
Expand Down
76 changes: 52 additions & 24 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ var SHELL = core_1.getInput('shell');
var POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1;
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 OS = process.platform;
var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
var OUTPUT_EXIT_CODE_KEY = 'exit_code';
Expand Down Expand Up @@ -340,6 +341,32 @@ function getExecutable() {
}
return executable;
}
function runRetryCmd() {
return __awaiter(this, void 0, void 0, function () {
var error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// if no retry script, just continue
if (!ON_RETRY_COMMAND) {
return [2 /*return*/];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, child_process_1.execSync(ON_RETRY_COMMAND, { stdio: 'inherit' })];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
error_1 = _a.sent();
core_1.info("WARNING: Retry command threw the error " + error_1.message);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
}
function runCmd() {
var _a, _b;
return __awaiter(this, void 0, void 0, function () {
Expand Down Expand Up @@ -399,7 +426,7 @@ function runCmd() {
}
function runAction() {
return __awaiter(this, void 0, void 0, function () {
var attempt, error_1;
var attempt, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, validateInputs()];
Expand All @@ -408,43 +435,44 @@ function runAction() {
attempt = 1;
_a.label = 2;
case 2:
if (!(attempt <= MAX_ATTEMPTS)) return [3 /*break*/, 7];
if (!(attempt <= MAX_ATTEMPTS)) return [3 /*break*/, 12];
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
_a.trys.push([3, 5, , 11]);
// just keep overwriting attempts output
core_1.setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt);
return [4 /*yield*/, runCmd()];
case 4:
_a.sent();
core_1.info("Command completed after " + attempt + " attempt(s).");
return [3 /*break*/, 7];
return [3 /*break*/, 12];
case 5:
error_1 = _a.sent();
if (attempt === MAX_ATTEMPTS) {
throw new Error("Final attempt failed. " + error_1.message);
}
else if (!done && RETRY_ON === 'error') {
// error: timeout
throw error_1;
}
else if (exit > 0 && RETRY_ON === 'timeout') {
// error: error
throw error_1;
error_2 = _a.sent();
if (!(attempt === MAX_ATTEMPTS)) return [3 /*break*/, 6];
throw new Error("Final attempt failed. " + error_2.message);
case 6:
if (!(!done && RETRY_ON === 'error')) return [3 /*break*/, 7];
// error: timeout
throw error_2;
case 7:
if (!(exit > 0 && RETRY_ON === 'timeout')) return [3 /*break*/, 8];
// error: error
throw error_2;
case 8: return [4 /*yield*/, runRetryCmd()];
case 9:
_a.sent();
if (WARNING_ON_RETRY) {
core_1.warning("Attempt " + attempt + " failed. Reason: " + error_2.message);
}
else {
if (WARNING_ON_RETRY) {
core_1.warning("Attempt " + attempt + " failed. Reason: " + error_1.message);
}
else {
core_1.info("Attempt " + attempt + " failed. Reason: " + error_1.message);
}
core_1.info("Attempt " + attempt + " failed. Reason: " + error_2.message);
}
return [3 /*break*/, 6];
case 6:
_a.label = 10;
case 10: return [3 /*break*/, 11];
case 11:
attempt++;
return [3 /*break*/, 2];
case 7: return [2 /*return*/];
case 12: return [2 /*return*/];
}
});
});
Expand Down
17 changes: 16 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getInput, error, warning, info, debug, setOutput } from '@actions/core';
import { exec } from 'child_process';
import { exec, execSync } from 'child_process';
import ms from 'milliseconds';
import kill from 'tree-kill';

Expand All @@ -15,6 +15,7 @@ const SHELL = getInput('shell');
const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1;
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 OS = process.platform;
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
Expand Down Expand Up @@ -98,6 +99,19 @@ function getExecutable(): string {
return executable
}

async function runRetryCmd(): Promise<void> {
// if no retry script, just continue
if (!ON_RETRY_COMMAND) {
return;
}

try {
await execSync(ON_RETRY_COMMAND, { stdio: 'inherit' });
} catch (error) {
info(`WARNING: Retry command threw the error ${error.message}`)
}
}

async function runCmd() {
const end_time = Date.now() + getTimeout();
const executable = getExecutable();
Expand Down Expand Up @@ -164,6 +178,7 @@ async function runAction() {
// error: error
throw error;
} else {
await runRetryCmd();
if (WARNING_ON_RETRY) {
warning(`Attempt ${attempt} failed. Reason: ${error.message}`);
} else {
Expand Down

0 comments on commit 7c68161

Please sign in to comment.