Compare commits

..

5 Commits

Author SHA1 Message Date
Nick Fields
39da88d5f7 Merge pull request #6 from nick-invision/nrf/issue-5
Enforce retry_wait_seconds both when command fails and times out
2020-06-17 14:09:20 -04:00
Nick Fields
6a380b501f fix: fixed debug logging 2020-06-17 13:57:10 -04:00
Nick Fields
3ded872743 fix: enforce RETRY_WAIT_SECONDS on both command timeout and error 2020-06-17 13:52:49 -04:00
Nick Fields
88ea919f23 patch: added debugging for issue #5 2020-06-17 13:48:21 -04:00
Nick Fields
21d303ab46 fix: fix tag push in publish step 2020-06-17 13:18:10 -04:00
3 changed files with 80 additions and 29 deletions

View File

@@ -18,19 +18,20 @@ jobs:
node-version: 12 node-version: 12
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Test
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
command: npm install this-isnt-a-real-package-name-zzz
- name: happy-path - name: happy-path
uses: ./ uses: ./
with: with:
timeout_minutes: 1 timeout_minutes: 1
max_attempts: 2 max_attempts: 2
command: npm -v command: npm -v
- name: sad-path (retry_wait_seconds)
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
retry_wait_seconds: 15
command: npm install this-isnt-a-real-package-name-zzz
- name: sad-path (error) - name: sad-path (error)
uses: ./ uses: ./
continue-on-error: true continue-on-error: true
@@ -67,7 +68,7 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Tag - name: Tag
run: git tag -f v${MAJOR_VERSION} && git push origin v${MAJOR_VERSION} run: git tag -f v${MAJOR_VERSION} && git push -f origin v${MAJOR_VERSION}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAJOR_VERSION: ${{ steps.semantic.outputs.new_release_major_version }} MAJOR_VERSION: ${{ steps.semantic.outputs.new_release_major_version }}

74
dist/index.js vendored
View File

@@ -143,14 +143,28 @@ class Command {
return cmdStr; return cmdStr;
} }
} }
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
function toCommandValue(input) {
if (input === null || input === undefined) {
return '';
}
else if (typeof input === 'string' || input instanceof String) {
return input;
}
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
function escapeData(s) { function escapeData(s) {
return (s || '') return toCommandValue(s)
.replace(/%/g, '%25') .replace(/%/g, '%25')
.replace(/\r/g, '%0D') .replace(/\r/g, '%0D')
.replace(/\n/g, '%0A'); .replace(/\n/g, '%0A');
} }
function escapeProperty(s) { function escapeProperty(s) {
return (s || '') return toCommandValue(s)
.replace(/%/g, '%25') .replace(/%/g, '%25')
.replace(/\r/g, '%0D') .replace(/\r/g, '%0D')
.replace(/\n/g, '%0A') .replace(/\n/g, '%0A')
@@ -206,11 +220,13 @@ var ExitCode;
/** /**
* Sets env variable for this action and future actions in the job * Sets env variable for this action and future actions in the job
* @param name the name of the variable to set * @param name the name of the variable to set
* @param val the value of the variable * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function exportVariable(name, val) { function exportVariable(name, val) {
process.env[name] = val; const convertedVal = command_1.toCommandValue(val);
command_1.issueCommand('set-env', { name }, val); process.env[name] = convertedVal;
command_1.issueCommand('set-env', { name }, convertedVal);
} }
exports.exportVariable = exportVariable; exports.exportVariable = exportVariable;
/** /**
@@ -249,12 +265,22 @@ exports.getInput = getInput;
* Sets the value of an output. * Sets the value of an output.
* *
* @param name name of the output to set * @param name name of the output to set
* @param value value to store * @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) { function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value); command_1.issueCommand('set-output', { name }, value);
} }
exports.setOutput = setOutput; exports.setOutput = setOutput;
/**
* Enables or disables the echoing of commands into stdout for the rest of the step.
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
*
*/
function setCommandEcho(enabled) {
command_1.issue('echo', enabled ? 'on' : 'off');
}
exports.setCommandEcho = setCommandEcho;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// Results // Results
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
@@ -271,6 +297,13 @@ exports.setFailed = setFailed;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
// Logging Commands // Logging Commands
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/**
* Gets whether Actions Step Debug is on or not
*/
function isDebug() {
return process.env['RUNNER_DEBUG'] === '1';
}
exports.isDebug = isDebug;
/** /**
* Writes debug message to user log * Writes debug message to user log
* @param message debug message * @param message debug message
@@ -281,18 +314,18 @@ function debug(message) {
exports.debug = debug; exports.debug = debug;
/** /**
* Adds an error issue * Adds an error issue
* @param message error issue message * @param message error issue message. Errors will be converted to string via toString()
*/ */
function error(message) { function error(message) {
command_1.issue('error', message); command_1.issue('error', message instanceof Error ? message.toString() : message);
} }
exports.error = error; exports.error = error;
/** /**
* Adds an warning issue * Adds an warning issue
* @param message warning issue message * @param message warning issue message. Errors will be converted to string via toString()
*/ */
function warning(message) { function warning(message) {
command_1.issue('warning', message); command_1.issue('warning', message instanceof Error ? message.toString() : message);
} }
exports.warning = warning; exports.warning = warning;
/** /**
@@ -350,8 +383,9 @@ exports.group = group;
* Saves state for current action, the state can only be retrieved by this action's post job execution. * Saves state for current action, the state can only be retrieved by this action's post job execution.
* *
* @param name name of the state to store * @param name name of the state to store
* @param value value to store * @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function saveState(name, value) { function saveState(name, value) {
command_1.issueCommand('save-state', { name }, value); command_1.issueCommand('save-state', { name }, value);
} }
@@ -380,7 +414,7 @@ module.exports = require("path");
/***/ 676: /***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const { getInput, error, warning, info } = __webpack_require__(470); const { getInput, error, warning, info, debug } = __webpack_require__(470);
const { spawn } = __webpack_require__(129); const { spawn } = __webpack_require__(129);
const { join } = __webpack_require__(622); const { join } = __webpack_require__(622);
const ms = __webpack_require__(156); const ms = __webpack_require__(156);
@@ -405,7 +439,7 @@ const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false);
const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false); const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false);
async function wait(ms) { async function wait(ms) {
return new Promise(r => setTimeout(r, ms)); return new Promise((r) => setTimeout(r, ms));
} }
async function runCmd() { async function runCmd() {
@@ -414,7 +448,7 @@ async function runCmd() {
var child = spawn('node', [__webpack_require__.ab + "exec.js", COMMAND], { stdio: 'inherit' }); var child = spawn('node', [__webpack_require__.ab + "exec.js", COMMAND], { stdio: 'inherit' });
child.on('exit', code => { child.on('exit', (code) => {
if (code > 0) { if (code > 0) {
exit = code; exit = code;
} }
@@ -427,15 +461,23 @@ async function runCmd() {
if (!done) { if (!done) {
kill(child.pid); kill(child.pid);
await wait(ms.seconds(RETRY_WAIT_SECONDS)); await retryWait();
throw new Error(`Timeout of ${TIMEOUT_MINUTES}m hit`); throw new Error(`Timeout of ${TIMEOUT_MINUTES}m hit`);
} else if (exit > 0) { } else if (exit > 0) {
await retryWait();
throw new Error(`Child_process exited with error`); throw new Error(`Child_process exited with error`);
} else { } else {
return; return;
} }
} }
async function retryWait() {
const waitStart = Date.now();
await wait(ms.seconds(RETRY_WAIT_SECONDS));
debug(`Waited ${Date.now() - waitStart}ms`);
debug(`Configured wait: ${ms.seconds(RETRY_WAIT_SECONDS)}ms`);
}
async function runAction() { async function runAction() {
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try { try {
@@ -452,7 +494,7 @@ async function runAction() {
} }
} }
runAction().catch(err => { runAction().catch((err) => {
error(err.message); error(err.message);
process.exit(1); process.exit(1);
}); });

View File

@@ -1,4 +1,4 @@
const { getInput, error, warning, info } = require('@actions/core'); const { getInput, error, warning, info, debug } = require('@actions/core');
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const { join } = require('path'); const { join } = require('path');
const ms = require('milliseconds'); const ms = require('milliseconds');
@@ -23,7 +23,7 @@ const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false);
const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false); const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false);
async function wait(ms) { async function wait(ms) {
return new Promise(r => setTimeout(r, ms)); return new Promise((r) => setTimeout(r, ms));
} }
async function runCmd() { async function runCmd() {
@@ -32,7 +32,7 @@ async function runCmd() {
var child = spawn('node', [join(__dirname, 'exec.js'), COMMAND], { stdio: 'inherit' }); var child = spawn('node', [join(__dirname, 'exec.js'), COMMAND], { stdio: 'inherit' });
child.on('exit', code => { child.on('exit', (code) => {
if (code > 0) { if (code > 0) {
exit = code; exit = code;
} }
@@ -45,15 +45,23 @@ async function runCmd() {
if (!done) { if (!done) {
kill(child.pid); kill(child.pid);
await wait(ms.seconds(RETRY_WAIT_SECONDS)); await retryWait();
throw new Error(`Timeout of ${TIMEOUT_MINUTES}m hit`); throw new Error(`Timeout of ${TIMEOUT_MINUTES}m hit`);
} else if (exit > 0) { } else if (exit > 0) {
await retryWait();
throw new Error(`Child_process exited with error`); throw new Error(`Child_process exited with error`);
} else { } else {
return; return;
} }
} }
async function retryWait() {
const waitStart = Date.now();
await wait(ms.seconds(RETRY_WAIT_SECONDS));
debug(`Waited ${Date.now() - waitStart}ms`);
debug(`Configured wait: ${ms.seconds(RETRY_WAIT_SECONDS)}ms`);
}
async function runAction() { async function runAction() {
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try { try {
@@ -70,7 +78,7 @@ async function runAction() {
} }
} }
runAction().catch(err => { runAction().catch((err) => {
error(err.message); error(err.message);
process.exit(1); process.exit(1);
}); });