mirror of
https://github.com/nick-fields/retry.git
synced 2026-02-10 07:05:29 +00:00
Compare commits
61 Commits
v2.2.0
...
anthonyc/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e4fe2b191 | ||
|
|
676683ec60 | ||
|
|
772a768088 | ||
|
|
6e8635d64a | ||
|
|
2d8f74008e | ||
|
|
4d604b1776 | ||
|
|
29e1861bc1 | ||
|
|
f2eb0f4f8a | ||
|
|
2762157955 | ||
|
|
ce44dab6c9 | ||
|
|
40cf3886b8 | ||
|
|
db59b2620d | ||
|
|
02a3f09f15 | ||
|
|
6b1204d918 | ||
|
|
8629cc7c0b | ||
|
|
e88a9994b0 | ||
|
|
e4acf08f18 | ||
|
|
51e448da7c | ||
|
|
5f63400863 | ||
|
|
c0687a0dcd | ||
|
|
102f21a736 | ||
|
|
752366eac8 | ||
|
|
a3da592761 | ||
|
|
7c5cca7536 | ||
|
|
f227091f2e | ||
|
|
6183d5c3dd | ||
|
|
71062288b7 | ||
|
|
afe1ef9058 | ||
|
|
e53cf64f16 | ||
|
|
7f8f3d9f0f | ||
|
|
bf1736e338 | ||
|
|
f7cf641580 | ||
|
|
002ef572db | ||
|
|
e80198a9da | ||
|
|
c77dc43532 | ||
|
|
0019811846 | ||
|
|
a63662d5a7 | ||
|
|
67e1bdfd8d | ||
|
|
45ba062d35 | ||
|
|
0470ad1628 | ||
|
|
2750220347 | ||
|
|
b00fd808da | ||
|
|
bebba89192 | ||
|
|
52b3fdbcaa | ||
|
|
f3f0bb1a3c | ||
|
|
7c68161adf | ||
|
|
025c480d85 | ||
|
|
3073a9f1e1 | ||
|
|
9e6dab8302 | ||
|
|
850bd83fba | ||
|
|
88ed4273a8 | ||
|
|
bee86ddb77 | ||
|
|
f865f2ade8 | ||
|
|
8310ca5ae8 | ||
|
|
e48877fb9c | ||
|
|
4af9664183 | ||
|
|
d0aac3501c | ||
|
|
877a0ac37e | ||
|
|
7463808b4e | ||
|
|
0aeb89504c | ||
|
|
b2ee390b23 |
286
.github/workflows/ci_cd.yml
vendored
286
.github/workflows/ci_cd.yml
vendored
@@ -1,8 +1,7 @@
|
||||
name: CI/CD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
# runs on branch pushes only
|
||||
ci:
|
||||
@@ -15,7 +14,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -60,6 +59,71 @@ jobs:
|
||||
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
|
||||
comparison: contains
|
||||
|
||||
- name: new-command-on-retry
|
||||
id: new-command-on-retry
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 3
|
||||
command: node -e "process.exit(1)"
|
||||
new_command_on_retry: node -e "console.log('this is the new command on retry')"
|
||||
|
||||
- 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: retry_on_exit_code (with expected error code)
|
||||
id: retry_on_exit_code_expected
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
retry_on_exit_code: 2
|
||||
max_attempts: 3
|
||||
command: node -e "process.exit(2)"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: failure
|
||||
actual: ${{ steps.retry_on_exit_code_expected.outcome }}
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 3
|
||||
actual: ${{ steps.retry_on_exit_code_expected.outputs.total_attempts }}
|
||||
|
||||
- name: retry_on_exit_code (with unexpected error code)
|
||||
id: retry_on_exit_code_unexpected
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
retry_on_exit_code: 2
|
||||
max_attempts: 3
|
||||
command: node -e "process.exit(1)"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: failure
|
||||
actual: ${{ steps.retry_on_exit_code_unexpected.outcome }}
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 1
|
||||
actual: ${{ steps.retry_on_exit_code_unexpected.outputs.total_attempts }}
|
||||
|
||||
- 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: ./
|
||||
@@ -77,6 +141,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: ./
|
||||
@@ -121,7 +220,6 @@ jobs:
|
||||
expected: 2
|
||||
actual: ${{ steps.retry_on_error.outputs.exit_code }}
|
||||
|
||||
|
||||
# timeout tests (takes longer to run so run last)
|
||||
- name: sad-path (timeout)
|
||||
id: sad_path_timeout
|
||||
@@ -197,6 +295,184 @@ jobs:
|
||||
expected: failure
|
||||
actual: ${{ steps.sad_path_timeout.outcome }}
|
||||
|
||||
- name: sad-path (wrong shell for OS)
|
||||
id: wrong_shell
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: cmd
|
||||
command: 'dir'
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 2
|
||||
actual: ${{ steps.wrong_shell.outputs.total_attempts }}
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: failure
|
||||
actual: ${{ steps.wrong_shell.outcome }}
|
||||
|
||||
ci_multiline_tests:
|
||||
name: Run Tests (Multiline)
|
||||
if: startsWith(github.ref, 'refs/heads')
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Multi-line 2 commands non existent first command
|
||||
id: multi_line_2_commands_non_existent_first_command
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
shell: bash
|
||||
timeout_seconds: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
i-do-not-exist && \
|
||||
echo "i-exist"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 2
|
||||
actual: ${{ steps.multi_line_2_commands_non_existent_first_command.outputs.total_attempts }}
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 'Final attempt failed'
|
||||
actual: ${{ steps.multi_line_2_commands_non_existent_first_command.outputs.exit_error }}
|
||||
comparison: contains
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
# The 127 error code indicates “command not found”.
|
||||
expected: '127'
|
||||
actual: ${{ steps.multi_line_2_commands_non_existent_first_command.outputs.exit_code }}
|
||||
comparison: contains
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 'i-exist'
|
||||
actual: ${{ steps.multi_line_2_commands_non_existent_first_command.outputs.exit_error }}
|
||||
comparison: notContains
|
||||
|
||||
- name: Multi-line 2 commands happy path test
|
||||
id: multi_line_2_commands_happy_path
|
||||
uses: ./
|
||||
with:
|
||||
shell: bash
|
||||
timeout_seconds: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
echo "foo" && \
|
||||
echo "bar"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 1
|
||||
actual: ${{ steps.multi_line_2_commands_happy_path.outputs.total_attempts }}
|
||||
|
||||
- name: Conventional multi-line non existent first command
|
||||
id: conventional_multi_line_non_existent_first_command
|
||||
uses: ./
|
||||
continue-on-error: true
|
||||
with:
|
||||
shell: bash
|
||||
timeout_seconds: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
i-do-not-exist
|
||||
echo "i-exist"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 2
|
||||
actual: ${{ steps.conventional_multi_line_non_existent_first_command.outputs.total_attempts }}
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 'Final attempt failed'
|
||||
actual: ${{ steps.conventional_multi_line_non_existent_first_command.outputs.exit_error }}
|
||||
comparison: contains
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
# The 127 error code indicates “command not found”.
|
||||
expected: '127'
|
||||
actual: ${{ steps.conventional_multi_line_non_existent_first_command.outputs.exit_code }}
|
||||
comparison: contains
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 'i-exist'
|
||||
actual: ${{ steps.conventional_multi_line_non_existent_first_command.outputs.exit_error }}
|
||||
comparison: notContains
|
||||
|
||||
- name: Conventional multi-line happy path test
|
||||
id: conventional_multi_line_happy_path
|
||||
uses: ./
|
||||
with:
|
||||
shell: bash
|
||||
timeout_seconds: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
echo "foo"
|
||||
echo "bar"
|
||||
- uses: nick-invision/assert-action@v1
|
||||
with:
|
||||
expected: 1
|
||||
actual: ${{ steps.conventional_multi_line_happy_path.outputs.total_attempts }}
|
||||
|
||||
ci_windows:
|
||||
name: Run Windows Tests
|
||||
if: startsWith(github.ref, 'refs/heads')
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Powershell test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: powershell
|
||||
command: Get-ComputerInfo
|
||||
- name: CMD.exe test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: cmd
|
||||
command: echo %PATH%
|
||||
- name: Python test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: python
|
||||
command: print('1', '2', '3')
|
||||
- name: Multi-line multi-command Test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
Get-ComputerInfo
|
||||
Get-Date
|
||||
- name: Multi-line single-command Test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: cmd
|
||||
command: >-
|
||||
echo "this is
|
||||
a test"
|
||||
|
||||
# runs on push to master only
|
||||
cd:
|
||||
name: Publish Action
|
||||
@@ -209,7 +485,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Release
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
},
|
||||
|
||||
132
README.md
132
README.md
@@ -2,6 +2,10 @@
|
||||
|
||||
Retries an Action step on failure or timeout. This is currently intended to replace the `run` step for moody commands.
|
||||
|
||||
**NOTE:** Ownership of this project was transferred to my personal account `nick-fields` from my work account `nick-invision`. Details [here](#Ownership)
|
||||
|
||||
---
|
||||
|
||||
## Inputs
|
||||
|
||||
### `timeout_minutes`
|
||||
@@ -24,6 +28,10 @@ Retries an Action step on failure or timeout. This is currently intended to repl
|
||||
|
||||
**Optional** Number of seconds to wait before attempting the next retry. Defaults to `10`
|
||||
|
||||
### `shell`
|
||||
|
||||
**Optional** Shell to use to execute `command`. Defaults to `powershell` on Windows, `bash` otherwise. Supports bash, python, pwsh, sh, cmd, and powershell per [docs](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell)
|
||||
|
||||
### `polling_interval_seconds`
|
||||
|
||||
**Optional** Number of seconds to wait while polling for command result. Defaults to `1`
|
||||
@@ -36,6 +44,22 @@ 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.
|
||||
|
||||
### `new_command_on_retry`
|
||||
|
||||
**Optional** Command to run if the first attempt fails. This command will be called on all subsequent attempts.
|
||||
|
||||
### `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`
|
||||
|
||||
### `retry_on_exit_code`
|
||||
|
||||
**Optional** Specific exit code to retry on. This will only retry for the given error code and fail immediately other error codes.
|
||||
|
||||
## Outputs
|
||||
|
||||
### `total_attempts`
|
||||
@@ -52,10 +76,21 @@ The final error returned by the command
|
||||
|
||||
## Examples
|
||||
|
||||
### Shell
|
||||
|
||||
```yaml
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
shell: pwsh
|
||||
command: dir
|
||||
```
|
||||
|
||||
### Timeout in minutes
|
||||
|
||||
```yaml
|
||||
uses: nick-invision/retry@v2
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
@@ -65,7 +100,7 @@ with:
|
||||
### Timeout in seconds
|
||||
|
||||
```yaml
|
||||
uses: nick-invision/retry@v2
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_seconds: 15
|
||||
max_attempts: 3
|
||||
@@ -75,7 +110,7 @@ with:
|
||||
### Only retry after timeout
|
||||
|
||||
```yaml
|
||||
uses: nick-invision/retry@v2
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_seconds: 15
|
||||
max_attempts: 3
|
||||
@@ -86,7 +121,7 @@ with:
|
||||
### Only retry after error
|
||||
|
||||
```yaml
|
||||
uses: nick-invision/retry@v2
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_seconds: 15
|
||||
max_attempts: 3
|
||||
@@ -94,10 +129,32 @@ 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
|
||||
- uses: nick-fields/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-fields/assert-action@v1
|
||||
with:
|
||||
expected: success
|
||||
actual: ${{ steps.retry.outcome }}
|
||||
- name: Assert that action exited with expected exit code
|
||||
uses: nick-fields/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-fields/retry@v2
|
||||
id: retry
|
||||
# see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error
|
||||
continue-on-error: true
|
||||
@@ -107,22 +164,79 @@ with:
|
||||
retry_on: error
|
||||
command: node -e 'process.exit(99);'
|
||||
- name: Assert that action failed
|
||||
uses: nick-invision/assert-action@v1
|
||||
uses: nick-fields/assert-action@v1
|
||||
with:
|
||||
expected: failure
|
||||
actual: ${{ steps.retry.outcome }}
|
||||
- name: Assert that action exited with expected exit code
|
||||
uses: nick-invision/assert-action@v1
|
||||
uses: nick-fields/assert-action@v1
|
||||
with:
|
||||
expected: 99
|
||||
actual: ${{ steps.retry.outputs.exit_code }}
|
||||
- name: Assert that action made expected number of attempts
|
||||
uses: nick-invision/assert-action@v1
|
||||
uses: nick-fields/assert-action@v1
|
||||
with:
|
||||
expected: 3
|
||||
actual: ${{ steps.retry.outputs.total_attempts }}
|
||||
```
|
||||
|
||||
### Run script after failure but before retry
|
||||
|
||||
```yaml
|
||||
uses: nick-fields/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
|
||||
```
|
||||
|
||||
### Run different command after first failure
|
||||
|
||||
```yaml
|
||||
uses: nick-fields/retry@v2
|
||||
with:
|
||||
timeout_seconds: 15
|
||||
max_attempts: 3
|
||||
command: npx jest
|
||||
new_command_on_retry: npx jest --onlyFailures
|
||||
```
|
||||
|
||||
### Run multi-line, multi-command script
|
||||
|
||||
```yaml
|
||||
name: Multi-line multi-command Test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
command: |
|
||||
Get-ComputerInfo
|
||||
Get-Date
|
||||
```
|
||||
|
||||
### Run multi-line, single-command script
|
||||
|
||||
```yaml
|
||||
name: Multi-line single-command Test
|
||||
uses: ./
|
||||
with:
|
||||
timeout_minutes: 1
|
||||
max_attempts: 2
|
||||
shell: cmd
|
||||
command: >-
|
||||
echo "this is
|
||||
a test"
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## **Ownership**
|
||||
|
||||
As of 2022/02/15 ownership of this project has been transferred to my personal account `nick-fields` from my work account `nick-invision` due to me leaving InVision. I am the author and have been the primary maintainer since day one and will continue to maintain this as needed.
|
||||
|
||||
Existing workflow references to `nick-invision/retry@<whatever>` no longer work and must be updated to `nick-fields/retry@<whatever>`.
|
||||
|
||||
15
action.yml
15
action.yml
@@ -18,6 +18,9 @@ inputs:
|
||||
description: Number of seconds to wait before attempting the next retry
|
||||
required: false
|
||||
default: 10
|
||||
shell:
|
||||
description: Alternate shell to use (defaults to powershell on windows, bash otherwise). Supports bash, python, pwsh, sh, cmd, and powershell
|
||||
required: false
|
||||
polling_interval_seconds:
|
||||
description: Number of seconds to wait for each check that command has completed running
|
||||
required: false
|
||||
@@ -27,6 +30,18 @@ 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
|
||||
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
|
||||
new_command_on_retry:
|
||||
description: Command to run if the first attempt fails. This command will be called on all subsequent attempts.
|
||||
required: false
|
||||
retry_on_exit_code:
|
||||
description: Specific exit code to retry on. This will only retry for the given error code and fail immediately other error codes.
|
||||
required: false
|
||||
outputs:
|
||||
total_attempts:
|
||||
description: The final number of attempts made
|
||||
|
||||
1792
dist/index.js
vendored
1792
dist/index.js
vendored
File diff suppressed because it is too large
Load Diff
15194
package-lock.json
generated
15194
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -11,28 +11,28 @@
|
||||
"url": "git+https://github.com/nick-invision/retry.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "Nick Fields",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nick-invision/retry/issues"
|
||||
},
|
||||
"homepage": "https://github.com/nick-invision/retry#readme",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/core": "^1.8.2",
|
||||
"milliseconds": "^1.0.3",
|
||||
"tree-kill": "^1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "11.0.0",
|
||||
"@commitlint/config-conventional": "11.0.0",
|
||||
"@semantic-release/changelog": "5.0.1",
|
||||
"@semantic-release/git": "9.0.0",
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@semantic-release/changelog": "^6.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/milliseconds": "0.0.30",
|
||||
"@types/node": "14.14.7",
|
||||
"@zeit/ncc": "^0.20.5",
|
||||
"dotenv": "8.2.0",
|
||||
"husky": "4.3.0",
|
||||
"semantic-release": "17.2.2",
|
||||
"husky": "^4.3.8",
|
||||
"semantic-release": "19.0.3",
|
||||
"ts-node": "9.0.0",
|
||||
"typescript": "4.0.5"
|
||||
},
|
||||
|
||||
@@ -2,5 +2,6 @@ INPUT_TIMEOUT_MINUTES=1
|
||||
INPUT_MAX_ATTEMPTS=3
|
||||
INPUT_COMMAND="node -e 'process.exit(99)'"
|
||||
INPUT_RETRY_WAIT_SECONDS=10
|
||||
SHELL=pwsh
|
||||
INPUT_POLLING_INTERVAL_SECONDS=1
|
||||
INPUT_RETRY_ON=any
|
||||
|
||||
98
src/index.ts
98
src/index.ts
@@ -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';
|
||||
|
||||
@@ -11,10 +11,16 @@ const TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false);
|
||||
const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3;
|
||||
const COMMAND = getInput('command', { required: true });
|
||||
const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10;
|
||||
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 CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');
|
||||
const NEW_COMMAND_ON_RETRY = getInput('new_command_on_retry');
|
||||
const RETRY_ON_EXIT_CODE = getInputNumber('retry_on_exit_code', false);
|
||||
|
||||
const OS = process.platform;
|
||||
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
|
||||
const OUTPUT_EXIT_CODE_KEY = 'exit_code';
|
||||
const OUTPUT_EXIT_ERROR_KEY = 'exit_error';
|
||||
@@ -38,6 +44,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));
|
||||
@@ -61,13 +76,69 @@ function getTimeout(): number {
|
||||
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
|
||||
}
|
||||
|
||||
async function runCmd() {
|
||||
function getExecutable(): string {
|
||||
if (!SHELL) {
|
||||
return OS === 'win32' ? 'powershell' : 'bash -e';
|
||||
}
|
||||
|
||||
let executable: string;
|
||||
switch (SHELL) {
|
||||
case "bash": {
|
||||
// -e to not ignore errors, but exit with non-zero code.
|
||||
executable = "bash -e";
|
||||
break;
|
||||
}
|
||||
case "python":
|
||||
case "pwsh": {
|
||||
executable = SHELL;
|
||||
break;
|
||||
}
|
||||
case "sh": {
|
||||
if (OS === 'win32') {
|
||||
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
|
||||
}
|
||||
executable = SHELL;
|
||||
break;
|
||||
}
|
||||
case "cmd":
|
||||
case "powershell": {
|
||||
if (OS !== 'win32') {
|
||||
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
|
||||
}
|
||||
executable = SHELL + ".exe";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Shell ${SHELL} not supported. See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell for supported shells`);
|
||||
}
|
||||
}
|
||||
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(attempt: number) {
|
||||
const end_time = Date.now() + getTimeout();
|
||||
const executable = getExecutable();
|
||||
|
||||
exit = 0;
|
||||
done = false;
|
||||
|
||||
var child = exec(COMMAND);
|
||||
debug(`Running command ${COMMAND} on ${OS} using shell "${executable}"`)
|
||||
var child = attempt > 1 && NEW_COMMAND_ON_RETRY
|
||||
? exec(NEW_COMMAND_ON_RETRY, { 'shell': executable })
|
||||
: exec(COMMAND, { 'shell': executable });
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
process.stdout.write(data);
|
||||
@@ -112,7 +183,7 @@ async function runAction() {
|
||||
try {
|
||||
// just keep overwriting attempts output
|
||||
setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt);
|
||||
await runCmd();
|
||||
await runCmd(attempt);
|
||||
info(`Command completed after ${attempt} attempt(s).`);
|
||||
break;
|
||||
} catch (error) {
|
||||
@@ -121,10 +192,13 @@ async function runAction() {
|
||||
} else if (!done && RETRY_ON === 'error') {
|
||||
// error: timeout
|
||||
throw error;
|
||||
} else if (RETRY_ON_EXIT_CODE && RETRY_ON_EXIT_CODE !== exit){
|
||||
throw error;
|
||||
} else if (exit > 0 && RETRY_ON === 'timeout') {
|
||||
// error: error
|
||||
throw error;
|
||||
} else {
|
||||
await runRetryCmd();
|
||||
if (WARNING_ON_RETRY) {
|
||||
warning(`Attempt ${attempt} failed. Reason: ${error.message}`);
|
||||
} else {
|
||||
@@ -141,12 +215,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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user