Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow skip cancel if named job in progress #107

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Expand Up @@ -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'
59 changes: 44 additions & 15 deletions dist/index.js
Expand Up @@ -5893,103 +5893,103 @@ module.exports = eval("require")("encoding");
/***/ ((module) => {

"use strict";
module.exports = require("assert");;
module.exports = require("assert");

/***/ }),

/***/ 614:
/***/ ((module) => {

"use strict";
module.exports = require("events");;
module.exports = require("events");

/***/ }),

/***/ 747:
/***/ ((module) => {

"use strict";
module.exports = require("fs");;
module.exports = require("fs");

/***/ }),

/***/ 605:
/***/ ((module) => {

"use strict";
module.exports = require("http");;
module.exports = require("http");

/***/ }),

/***/ 211:
/***/ ((module) => {

"use strict";
module.exports = require("https");;
module.exports = require("https");

/***/ }),

/***/ 631:
/***/ ((module) => {

"use strict";
module.exports = require("net");;
module.exports = require("net");

/***/ }),

/***/ 87:
/***/ ((module) => {

"use strict";
module.exports = require("os");;
module.exports = require("os");

/***/ }),

/***/ 622:
/***/ ((module) => {

"use strict";
module.exports = require("path");;
module.exports = require("path");

/***/ }),

/***/ 413:
/***/ ((module) => {

"use strict";
module.exports = require("stream");;
module.exports = require("stream");

/***/ }),

/***/ 16:
/***/ ((module) => {

"use strict";
module.exports = require("tls");;
module.exports = require("tls");

/***/ }),

/***/ 835:
/***/ ((module) => {

"use strict";
module.exports = require("url");;
module.exports = require("url");

/***/ }),

/***/ 669:
/***/ ((module) => {

"use strict";
module.exports = require("util");;
module.exports = require("util");

/***/ }),

/***/ 761:
/***/ ((module) => {

"use strict";
module.exports = require("zlib");;
module.exports = require("zlib");

/***/ })

Expand Down Expand Up @@ -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.
(() => {
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -6140,7 +6147,29 @@ async function main() {
.reduce((a, b) => Math.max(a, b), cancelBefore.getTime());
cancelBefore = new Date(n);
}
const runningWorkflows = workflow_runs.filter(run => run.head_repository.id === trigger_repo_id &&
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);
let workflow_runs_to_cancel = [...workflow_runs];
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 }) => !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 &&
(ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
Expand Down
47 changes: 46 additions & 1 deletion src/index.ts
Expand Up @@ -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);

Expand Down Expand Up @@ -77,7 +84,45 @@ async function main() {
.reduce((a, b) => Math.max(a, b), cancelBefore.getTime());
cancelBefore = new Date(n);
}
const runningWorkflows = workflow_runs.filter(

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),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use Job ID instead of Job Name? Or at least make both work like we do for workflow name/id?

),
};
}),
)
: []
).filter(workflow => workflow.jobs.length > 0);

let workflow_runs_to_cancel = [...workflow_runs];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this cloned?


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) => !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 &&
Expand Down