From 1b089110a9a9e58a8df39b6284a064811f281550 Mon Sep 17 00:00:00 2001 From: Marc Brannan Date: Sun, 22 Aug 2021 10:57:46 -0500 Subject: [PATCH 1/3] add disqualifying_jobs input and logic --- action.yml | 3 +++ dist/index.js | 55 ++++++++++++++++++++++++++++++++++++++------------- src/index.ts | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/action.yml b/action.yml index db1ff02a..c1439d96 100644 --- a/action.yml +++ b/action.yml @@ -20,6 +20,9 @@ inputs: description: "Cancel all actions but the last one" required: false default: 'false' + disqualifying_jobs: + description: "A JSON array of named jobs that will stop cancellation if in progress. Usefull for jobs like deployments where cancellation could risk downtime." + required: false runs: using: 'node12' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 3f613e47..ef7178a2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5893,7 +5893,7 @@ module.exports = eval("require")("encoding"); /***/ ((module) => { "use strict"; -module.exports = require("assert");; +module.exports = require("assert"); /***/ }), @@ -5901,7 +5901,7 @@ module.exports = require("assert");; /***/ ((module) => { "use strict"; -module.exports = require("events");; +module.exports = require("events"); /***/ }), @@ -5909,7 +5909,7 @@ module.exports = require("events");; /***/ ((module) => { "use strict"; -module.exports = require("fs");; +module.exports = require("fs"); /***/ }), @@ -5917,7 +5917,7 @@ module.exports = require("fs");; /***/ ((module) => { "use strict"; -module.exports = require("http");; +module.exports = require("http"); /***/ }), @@ -5925,7 +5925,7 @@ module.exports = require("http");; /***/ ((module) => { "use strict"; -module.exports = require("https");; +module.exports = require("https"); /***/ }), @@ -5933,7 +5933,7 @@ module.exports = require("https");; /***/ ((module) => { "use strict"; -module.exports = require("net");; +module.exports = require("net"); /***/ }), @@ -5941,7 +5941,7 @@ module.exports = require("net");; /***/ ((module) => { "use strict"; -module.exports = require("os");; +module.exports = require("os"); /***/ }), @@ -5949,7 +5949,7 @@ module.exports = require("os");; /***/ ((module) => { "use strict"; -module.exports = require("path");; +module.exports = require("path"); /***/ }), @@ -5957,7 +5957,7 @@ module.exports = require("path");; /***/ ((module) => { "use strict"; -module.exports = require("stream");; +module.exports = require("stream"); /***/ }), @@ -5965,7 +5965,7 @@ module.exports = require("stream");; /***/ ((module) => { "use strict"; -module.exports = require("tls");; +module.exports = require("tls"); /***/ }), @@ -5973,7 +5973,7 @@ module.exports = require("tls");; /***/ ((module) => { "use strict"; -module.exports = require("url");; +module.exports = require("url"); /***/ }), @@ -5981,7 +5981,7 @@ module.exports = require("url");; /***/ ((module) => { "use strict"; -module.exports = require("util");; +module.exports = require("util"); /***/ }), @@ -5989,7 +5989,7 @@ module.exports = require("util");; /***/ ((module) => { "use strict"; -module.exports = require("zlib");; +module.exports = require("zlib"); /***/ }) @@ -6068,7 +6068,9 @@ module.exports = require("zlib");; /******/ /******/ /* webpack/runtime/compat */ /******/ -/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";/************************************************************************/ +/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; +/******/ +/************************************************************************/ var __webpack_exports__ = {}; // This entry need to be wrapped in an IIFE because it need to be in strict mode. (() => { @@ -6102,9 +6104,14 @@ async function main() { console.log({ eventName, sha, headSha, branch, owner, repo, GITHUB_RUN_ID }); const token = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('access_token', { required: true }); const workflow_id = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('workflow_id', { required: false }); + const disqualifying_jobs_input = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('disqualifying_jobs', { required: false }); + const disqualifying_jobs = disqualifying_jobs_input ? JSON.parse(disqualifying_jobs_input) : null; const ignore_sha = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('ignore_sha', { required: false }); const all_but_latest = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('all_but_latest', { required: false }); console.log(`Found token: ${token ? 'yes' : 'no'}`); + console.log(disqualifying_jobs + ? `Skipping cancel if job in ${disqualifying_jobs}` + : 'No disqualifying jobs'); const workflow_ids = []; const octokit = _actions_github__WEBPACK_IMPORTED_MODULE_1__.getOctokit(token); const { data: current_run } = await octokit.actions.getWorkflowRun({ @@ -6140,6 +6147,26 @@ async function main() { .reduce((a, b) => Math.max(a, b), cancelBefore.getTime()); cancelBefore = new Date(n); } + if (disqualifying_jobs && !Array.isArray(disqualifying_jobs)) { + _actions_core__WEBPACK_IMPORTED_MODULE_0__.setFailed('Disqualifying jobs found but is not array'); + } + const workflow_jobs = (disqualifying_jobs && disqualifying_jobs.length > 0 + ? await Promise.all(workflow_runs.map(async ({ id, jobs_url }) => { + const { data: { jobs }, } = await octokit.request(`GET ${jobs_url}`, { + owner, + repo, + run_id: id, + }); + return { + workflow_run_id: id, + jobs: jobs.filter(({ status, name }) => status === 'in_progress' && disqualifying_jobs.includes(name)), + }; + })) + : []).filter(workflow => workflow.jobs.length > 0); + if (workflow_jobs.length) { + console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); + workflow_runs.length = 0; + } const runningWorkflows = workflow_runs.filter(run => run.head_repository.id === trigger_repo_id && run.id !== current_run.id && (ignore_sha || run.head_sha !== headSha) && diff --git a/src/index.ts b/src/index.ts index cfbb846e..9d668800 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,9 +32,16 @@ async function main() { console.log({ eventName, sha, headSha, branch, owner, repo, GITHUB_RUN_ID }); const token = core.getInput('access_token', { required: true }); const workflow_id = core.getInput('workflow_id', { required: false }); + const disqualifying_jobs_input = core.getInput('disqualifying_jobs', { required: false }); + const disqualifying_jobs = disqualifying_jobs_input ? JSON.parse(disqualifying_jobs_input) : null; const ignore_sha = core.getBooleanInput('ignore_sha', { required: false }); const all_but_latest = core.getBooleanInput('all_but_latest', { required: false }); console.log(`Found token: ${token ? 'yes' : 'no'}`); + console.log( + disqualifying_jobs + ? `Skipping cancel if job in ${disqualifying_jobs}` + : 'No disqualifying jobs', + ); const workflow_ids: string[] = []; const octokit = github.getOctokit(token); @@ -77,6 +84,39 @@ async function main() { .reduce((a, b) => Math.max(a, b), cancelBefore.getTime()); cancelBefore = new Date(n); } + + if (disqualifying_jobs && !Array.isArray(disqualifying_jobs)) { + core.setFailed('Disqualifying jobs found but is not array'); + } + + const workflow_jobs = ( + disqualifying_jobs && disqualifying_jobs.length > 0 + ? await Promise.all( + workflow_runs.map(async ({ id, jobs_url }) => { + const { + data: { jobs }, + } = await octokit.request(`GET ${jobs_url}`, { + owner, + repo, + run_id: id, + }); + return { + workflow_run_id: id, + jobs: jobs.filter( + ({ status, name }: any) => + status === 'in_progress' && disqualifying_jobs.includes(name), + ), + }; + }), + ) + : [] + ).filter(workflow => workflow.jobs.length > 0); + + if (workflow_jobs.length) { + console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); + workflow_runs.length = 0; + } + const runningWorkflows = workflow_runs.filter( run => run.head_repository.id === trigger_repo_id && From a155645ec1afe4c961dbb0d806105c5e61dd3ecd Mon Sep 17 00:00:00 2001 From: Marc Brannan Date: Sun, 22 Aug 2021 14:06:46 -0500 Subject: [PATCH 2/3] only omit found jobs --- README.md | 21 +++++++++++++++++++++ dist/index.js | 5 +++-- src/index.ts | 9 +++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f6ba7c99..39e21063 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,27 @@ jobs: _Note_ : This is typical when global access is set to be restrictive. Only this job will elevate those permissions. +### Advanced: Disqualifying Jobs + +In the case where you may want for an `in_progress` job to stop the cancellation of a workflow you may pass a JSON Array as input to `disqualifying_jobs`. If a job is named in the array and its `status` is `in_progress` the workflow will be removed from the list of jobs to cancel and skipped. + +This is useful for operations such as static site deployment where two jobs have the ability to read/write files simultaneously which could cause downtime or runtime errors. + +```yml +name: Cancel +on: [push] +jobs: + cancel: + name: 'Cancel Previous Runs' + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + disqualifying_jobs: '["deploy"]' +``` + ## Contributing - Clone this repo diff --git a/dist/index.js b/dist/index.js index ef7178a2..4744f312 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6163,11 +6163,12 @@ async function main() { }; })) : []).filter(workflow => workflow.jobs.length > 0); + let workflow_runs_to_cancel = [...workflow_runs]; if (workflow_jobs.length) { console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); - workflow_runs.length = 0; + workflow_runs_to_cancel = workflow_runs.filter(({ id }) => !workflow_jobs.map(({ workflow_run_id }) => workflow_run_id).includes(id)); } - const runningWorkflows = workflow_runs.filter(run => run.head_repository.id === trigger_repo_id && + const runningWorkflows = workflow_runs_to_cancel.filter(run => run.head_repository.id === trigger_repo_id && run.id !== current_run.id && (ignore_sha || run.head_sha !== headSha) && run.status !== 'completed' && diff --git a/src/index.ts b/src/index.ts index 9d668800..7006681f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -112,12 +112,17 @@ async function main() { : [] ).filter(workflow => workflow.jobs.length > 0); + let workflow_runs_to_cancel = [...workflow_runs]; + if (workflow_jobs.length) { console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); - workflow_runs.length = 0; + workflow_runs_to_cancel = workflow_runs.filter( + ({ id }: any) => + !workflow_jobs.map(({ workflow_run_id }) => workflow_run_id).includes(id), + ); } - const runningWorkflows = workflow_runs.filter( + const runningWorkflows = workflow_runs_to_cancel.filter( run => run.head_repository.id === trigger_repo_id && run.id !== current_run.id && From fe2e6d94b9e6de0c4d55b8327a33600968866a64 Mon Sep 17 00:00:00 2001 From: Marc Brannan Date: Sun, 22 Aug 2021 14:08:00 -0500 Subject: [PATCH 3/3] only omit found jobs --- dist/index.js | 3 ++- src/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 4744f312..1ef727ea 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6166,7 +6166,8 @@ async function main() { let workflow_runs_to_cancel = [...workflow_runs]; if (workflow_jobs.length) { console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); - workflow_runs_to_cancel = workflow_runs.filter(({ id }) => !workflow_jobs.map(({ workflow_run_id }) => workflow_run_id).includes(id)); + const workflows_to_skip = workflow_jobs.map(({ workflow_run_id }) => workflow_run_id); + workflow_runs_to_cancel = workflow_runs.filter(({ id }) => !workflows_to_skip.includes(id)); } const runningWorkflows = workflow_runs_to_cancel.filter(run => run.head_repository.id === trigger_repo_id && run.id !== current_run.id && diff --git a/src/index.ts b/src/index.ts index 7006681f..618eae09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -116,9 +116,9 @@ async function main() { if (workflow_jobs.length) { console.log('Found disqualifying jobs running, skipping cancel', workflow_jobs); + const workflows_to_skip = workflow_jobs.map(({ workflow_run_id }) => workflow_run_id); workflow_runs_to_cancel = workflow_runs.filter( - ({ id }: any) => - !workflow_jobs.map(({ workflow_run_id }) => workflow_run_id).includes(id), + ({ id }: any) => !workflows_to_skip.includes(id), ); }