From 877a0ac37e5e947ff6857c0b93172832e9786d47 Mon Sep 17 00:00:00 2001 From: Isaac Levin <8878502+isaacrlevin@users.noreply.github.com> Date: Fri, 1 Jan 2021 22:57:53 +0000 Subject: [PATCH 1/2] feat: add SHELL input support --- .github/workflows/ci_cd.yml | 19 +++++++++++++++ README.md | 22 ++++++++++++++++++ action.yml | 9 +++++++- dist/index.js | 46 +++++++++++++++++++++++++++++++++++-- sample.env | 2 ++ src/index.ts | 46 ++++++++++++++++++++++++++++++++++++- 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index fa4d3f1..6361b5e 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -197,6 +197,25 @@ jobs: expected: failure actual: ${{ steps.sad_path_timeout.outcome }} + - name: sad-path (wrong shell) + 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 }} + + # runs on push to master only cd: name: Publish Action diff --git a/README.md b/README.md index b411c12..270c27d 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,20 @@ Retries an Action step on failure or timeout. This is currently intended to repl **Required** The command to run +### `OS` + +**Required** The OS passed from the runner (runner.OS) + ### `retry_wait_seconds` **Optional** Number of seconds to wait before attempting the next retry. Defaults to `10` +### `shell` + +**Optional** Shell to use to execute `command`. Defaults to `pwsh`. See [here](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell) for shell options + required: false + default: pwsh + ### `polling_interval_seconds` **Optional** Number of seconds to wait while polling for command result. Defaults to `1` @@ -52,6 +62,18 @@ The final error returned by the command ## Examples +### Shell + +```yaml +uses: nick-invision/retry@v2 +with: + timeout_minutes: 10 + max_attempts: 3 + os: ${{ runner.os }} + shell: pwsh + command: dir +``` + ### Timeout in minutes ```yaml diff --git a/action.yml b/action.yml index f1f4a04..2fa4197 100644 --- a/action.yml +++ b/action.yml @@ -18,6 +18,13 @@ inputs: description: Number of seconds to wait before attempting the next retry required: false default: 10 + OS: + description: OS Passed from Runner (runner.OS) + required: true + shell: + description: Shell to Use to retry (Default is pwsh) + required: false + default: pwsh polling_interval_seconds: description: Number of seconds to wait for each check that command has completed running required: false @@ -36,4 +43,4 @@ outputs: description: The final error returned by the command runs: using: 'node12' - main: 'dist/index.js' + main: 'dist/index.js' \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 5e2e99c..5493451 100644 --- a/dist/index.js +++ b/dist/index.js @@ -247,8 +247,10 @@ var util_1 = __webpack_require__(322); var TIMEOUT_MINUTES = getInputNumber('timeout_minutes', false); var TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false); var MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; +var OS = core_1.getInput('os', { required: true }); var COMMAND = core_1.getInput('command', { required: true }); var RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; +var SHELL = core_1.getInput('shell') || 'pwsh'; 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'; @@ -308,14 +310,54 @@ function getTimeout() { function runCmd() { var _a, _b; return __awaiter(this, void 0, void 0, function () { - var end_time, child; + var end_time, executable, child; return __generator(this, function (_c) { switch (_c.label) { case 0: end_time = Date.now() + getTimeout(); exit = 0; done = false; - child = child_process_1.exec(COMMAND); + executable = SHELL + ".exe"; + switch (SHELL) { + case "pwsh": { + executable = SHELL; + break; + } + case "bash": { + executable = SHELL; + break; + } + case "python": { + executable = SHELL; + break; + } + case "sh": { + if (OS === 'Windows') { + throw new Error("Shell " + SHELL + " not allowed on OS " + OS); + } + executable = SHELL; + break; + } + case "cmd": { + if (OS !== 'Windows') { + throw new Error("Shell " + SHELL + " not allowed on OS " + OS); + } + executable = SHELL + ".exe"; + break; + } + case "powershell": { + if (OS !== 'Windows') { + throw new Error("Shell " + SHELL + " not allowed on OS " + OS); + } + executable = SHELL + ".exe"; + break; + } + default: { + throw new Error("Shell " + SHELL + " required"); + break; + } + } + child = child_process_1.exec(COMMAND, { 'shell': executable }); (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) { process.stdout.write(data); }); diff --git a/sample.env b/sample.env index 0dbd99c..5409962 100644 --- a/sample.env +++ b/sample.env @@ -2,5 +2,7 @@ INPUT_TIMEOUT_MINUTES=1 INPUT_MAX_ATTEMPTS=3 INPUT_COMMAND="node -e 'process.exit(99)'" INPUT_RETRY_WAIT_SECONDS=10 +SHELL=pwsh +OS=Linux INPUT_POLLING_INTERVAL_SECONDS=1 INPUT_RETRY_ON=any diff --git a/src/index.ts b/src/index.ts index a9acda1..e6c7524 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,8 +9,10 @@ import { wait } from './util'; const TIMEOUT_MINUTES = getInputNumber('timeout_minutes', false); const TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false); const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; +const OS = getInput('os', { required: true }); const COMMAND = getInput('command', { required: true }); const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; +const SHELL = getInput('shell') || 'pwsh'; 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'; @@ -67,7 +69,49 @@ async function runCmd() { exit = 0; done = false; - var child = exec(COMMAND); + let executable: string = SHELL + ".exe"; + switch (SHELL) { + case "pwsh": { + executable = SHELL; + break; + } + case "bash": { + executable = SHELL; + break; + } + case "python": { + executable = SHELL; + break; + } + case "sh": { + if (OS === 'Windows') { + throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); + } + executable = SHELL; + break; + } + case "cmd": { + if (OS !== 'Windows') { + throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); + } + executable = SHELL + ".exe"; + break; + } + case "powershell": { + if (OS !== 'Windows') { + throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); + } + executable = SHELL + ".exe"; + break; + } + default: { + throw new Error(`Shell ${SHELL} required`); + break; + } + } + + + var child = exec(COMMAND, { 'shell': executable }); child.stdout?.on('data', (data) => { process.stdout.write(data); From d0aac3501cc580d4be1376d5755887d2df590d57 Mon Sep 17 00:00:00 2001 From: Nick Fields Date: Sat, 2 Jan 2021 10:20:16 -0500 Subject: [PATCH 2/2] fix: dont require OS input and use correct shell per os --- .github/workflows/ci_cd.yml | 36 ++++++++++++++++- README.md | 5 --- action.yml | 4 -- dist/index.js | 79 +++++++++++++++++-------------------- sample.env | 1 - src/index.ts | 47 ++++++++++------------ 6 files changed, 93 insertions(+), 79 deletions(-) diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 6361b5e..3de7608 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -197,7 +197,7 @@ jobs: expected: failure actual: ${{ steps.sad_path_timeout.outcome }} - - name: sad-path (wrong shell) + - name: sad-path (wrong shell for OS) id: wrong_shell uses: ./ continue-on-error: true @@ -215,6 +215,40 @@ jobs: expected: failure actual: ${{ steps.wrong_shell.outcome }} + 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: 12 + - 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 # runs on push to master only cd: diff --git a/README.md b/README.md index 270c27d..793817e 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,6 @@ Retries an Action step on failure or timeout. This is currently intended to repl **Required** The command to run -### `OS` - -**Required** The OS passed from the runner (runner.OS) - ### `retry_wait_seconds` **Optional** Number of seconds to wait before attempting the next retry. Defaults to `10` @@ -69,7 +65,6 @@ uses: nick-invision/retry@v2 with: timeout_minutes: 10 max_attempts: 3 - os: ${{ runner.os }} shell: pwsh command: dir ``` diff --git a/action.yml b/action.yml index 2fa4197..d9978b3 100644 --- a/action.yml +++ b/action.yml @@ -18,13 +18,9 @@ inputs: description: Number of seconds to wait before attempting the next retry required: false default: 10 - OS: - description: OS Passed from Runner (runner.OS) - required: true shell: description: Shell to Use to retry (Default is pwsh) required: false - default: pwsh polling_interval_seconds: description: Number of seconds to wait for each check that command has completed running required: false diff --git a/dist/index.js b/dist/index.js index 5493451..88822fb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -247,13 +247,13 @@ var util_1 = __webpack_require__(322); var TIMEOUT_MINUTES = getInputNumber('timeout_minutes', false); var TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false); var MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; -var OS = core_1.getInput('os', { required: true }); var COMMAND = core_1.getInput('command', { required: true }); var RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; -var SHELL = core_1.getInput('shell') || 'pwsh'; +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 OS = process.platform; var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts'; var OUTPUT_EXIT_CODE_KEY = 'exit_code'; var OUTPUT_EXIT_ERROR_KEY = 'exit_error'; @@ -307,6 +307,39 @@ function getTimeout() { } throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); } +function getExecutable() { + if (!SHELL) { + return OS === 'win32' ? 'powershell' : 'bash'; + } + var executable; + switch (SHELL) { + case "bash": + 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 + " required"); + } + } + return executable; +} function runCmd() { var _a, _b; return __awaiter(this, void 0, void 0, function () { @@ -315,48 +348,10 @@ function runCmd() { switch (_c.label) { case 0: end_time = Date.now() + getTimeout(); + executable = getExecutable(); exit = 0; done = false; - executable = SHELL + ".exe"; - switch (SHELL) { - case "pwsh": { - executable = SHELL; - break; - } - case "bash": { - executable = SHELL; - break; - } - case "python": { - executable = SHELL; - break; - } - case "sh": { - if (OS === 'Windows') { - throw new Error("Shell " + SHELL + " not allowed on OS " + OS); - } - executable = SHELL; - break; - } - case "cmd": { - if (OS !== 'Windows') { - throw new Error("Shell " + SHELL + " not allowed on OS " + OS); - } - executable = SHELL + ".exe"; - break; - } - case "powershell": { - if (OS !== 'Windows') { - throw new Error("Shell " + SHELL + " not allowed on OS " + OS); - } - executable = SHELL + ".exe"; - break; - } - default: { - throw new Error("Shell " + SHELL + " required"); - break; - } - } + core_1.debug("Running command " + COMMAND + " on " + OS + " using shell " + executable); child = child_process_1.exec(COMMAND, { 'shell': executable }); (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) { process.stdout.write(data); diff --git a/sample.env b/sample.env index 5409962..f69d5fd 100644 --- a/sample.env +++ b/sample.env @@ -3,6 +3,5 @@ INPUT_MAX_ATTEMPTS=3 INPUT_COMMAND="node -e 'process.exit(99)'" INPUT_RETRY_WAIT_SECONDS=10 SHELL=pwsh -OS=Linux INPUT_POLLING_INTERVAL_SECONDS=1 INPUT_RETRY_ON=any diff --git a/src/index.ts b/src/index.ts index e6c7524..d55da15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,14 +9,14 @@ import { wait } from './util'; const TIMEOUT_MINUTES = getInputNumber('timeout_minutes', false); const TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false); const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; -const OS = getInput('os', { required: true }); const COMMAND = getInput('command', { required: true }); const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; -const SHELL = getInput('shell') || 'pwsh'; +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 OS = process.platform; const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts'; const OUTPUT_EXIT_CODE_KEY = 'exit_code'; const OUTPUT_EXIT_ERROR_KEY = 'exit_error'; @@ -63,42 +63,29 @@ function getTimeout(): number { throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); } -async function runCmd() { - const end_time = Date.now() + getTimeout(); +function getExecutable(): string { + if (!SHELL) { + return OS === 'win32' ? 'powershell' : 'bash'; + } - exit = 0; - done = false; - - let executable: string = SHELL + ".exe"; + let executable: string; switch (SHELL) { + case "bash": + case "python": case "pwsh": { executable = SHELL; break; } - case "bash": { - executable = SHELL; - break; - } - case "python": { - executable = SHELL; - break; - } case "sh": { - if (OS === 'Windows') { + if (OS === 'win32') { throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); } executable = SHELL; break; } - case "cmd": { - if (OS !== 'Windows') { - throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); - } - executable = SHELL + ".exe"; - break; - } + case "cmd": case "powershell": { - if (OS !== 'Windows') { + if (OS !== 'win32') { throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`); } executable = SHELL + ".exe"; @@ -106,11 +93,19 @@ async function runCmd() { } default: { throw new Error(`Shell ${SHELL} required`); - break; } } + return executable +} +async function runCmd() { + const end_time = Date.now() + getTimeout(); + const executable = getExecutable(); + exit = 0; + done = false; + + debug(`Running command ${COMMAND} on ${OS} using shell ${executable}`) var child = exec(COMMAND, { 'shell': executable }); child.stdout?.on('data', (data) => {