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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow cancelling newer builds with cancel_newer #59

Closed
wants to merge 6 commits into from
Closed
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
23 changes: 23 additions & 0 deletions README.md
Expand Up @@ -100,6 +100,29 @@ jobs:
workflow_id: 479426
```

### Advanced: Cancel newer builds

Under certain circumstances it could be beneficial to cancel _newer_ workflows. For example, if there are no code
changes, but you are pulling data from another source, there may be no need for subsequent builds that are currently
queued. Using the `cancel_newer` option will cancel _ALL_ other builds for the same workflow.

```yml
on:
pull_request:
types: [closed]
jobs:
cleanup:
name: 'Clean up ALL other builds'
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- name: Cancel ALL build runs
uses: styfle/cancel-workflow-action@0.9.0
with:
ignore_sha: true
cancel_newer: true
```

## Contributing

- Clone this repo
Expand Down
7 changes: 6 additions & 1 deletion action.yml
Expand Up @@ -11,10 +11,15 @@ inputs:
ignore_sha:
description: 'Optional - Allow canceling other workflows with the same SHA. Useful for the `pull_request.closed` event.'
required: false
default: false
default: 'false'
Copy link
Owner

Choose a reason for hiding this comment

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

Why was this changed from false to 'false'?

Copy link
Author

Choose a reason for hiding this comment

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

In the specification, default is a string value. In YAML true and false are booleans, so adding the 's turns this it into the string "false".

The way YAML handles strings causes a number of common gotchas, especially when it comes to booleans.

cancel_newer:
Copy link
Owner

@styfle styfle Mar 7, 2021

Choose a reason for hiding this comment

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

Let's call this ignore_date to match the ignore_sha prop

description: 'Cancel all queued and in progress workflows, even if they are newer'
required: false
default: 'false'
access_token:
description: 'Your GitHub Access Token, defaults to: {{ github.token }}'
default: '${{ github.token }}'
required: true
Copy link
Owner

Choose a reason for hiding this comment

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

Is it actually required if it has a default? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

Good point, not sure why I added that 😅

runs:
using: 'node12'
main: 'dist/index.js'
13 changes: 10 additions & 3 deletions dist/index.js
Expand Up @@ -5864,6 +5864,7 @@ async function main() {
const token = core.getInput('access_token', { required: true });
const workflow_id = core.getInput('workflow_id', { required: false });
const ignore_sha = core.getInput('ignore_sha', { required: false }) === 'true';
const cancel_newer = core.getInput('cancel_newer', { required: false }) === 'true';
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids = [];
const octokit = github.getOctokit(token);
Expand All @@ -5884,6 +5885,7 @@ async function main() {
await Promise.all(workflow_ids.map(async (workflow_id) => {
try {
const { data } = await octokit.actions.listWorkflowRuns({
per_page: 100,
owner,
repo,
workflow_id,
Expand All @@ -5894,17 +5896,22 @@ async function main() {
console.log(branchWorkflows.map(run => `- ${run.html_url}`).join('\n'));
const runningWorkflows = branchWorkflows.filter(run => (ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
new Date(run.created_at) < new Date(current_run.created_at));
run.id !== current_run.id &&
(cancel_newer || new Date(run.created_at) < new Date(current_run.created_at)));
console.log(`with ${runningWorkflows.length} runs to cancel.`);
const promises = [];
for (const { id, head_sha, status, html_url } of runningWorkflows) {
console.log('Canceling run: ', { id, head_sha, status, html_url });
const res = await octokit.actions.cancelWorkflowRun({
const current_promise = octokit.actions.cancelWorkflowRun({
owner,
repo,
run_id: id
}).then((res) => {
console.log(`Cancel run ${id} responded with status ${res.status}`);
});
console.log(`Cancel run ${id} responded with status ${res.status}`);
promises.push(current_promise);
}
await Promise.all(promises);
}
catch (e) {
const msg = e.message || e;
Expand Down
14 changes: 11 additions & 3 deletions src/index.ts
Expand Up @@ -27,6 +27,7 @@ async function main() {
const token = core.getInput('access_token', { required: true });
const workflow_id = core.getInput('workflow_id', { required: false });
const ignore_sha = core.getInput('ignore_sha', { required: false }) === 'true';
const cancel_newer = core.getInput('cancel_newer', { required: false }) === 'true';
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids: string[] = [];
const octokit = github.getOctokit(token);
Expand All @@ -52,6 +53,7 @@ async function main() {
await Promise.all(workflow_ids.map(async (workflow_id) => {
try {
const { data } = await octokit.actions.listWorkflowRuns({
per_page: 100,
Copy link
Owner

Choose a reason for hiding this comment

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

I like the idea of increasing this number. Do you want to submit another PR with just this one change?

owner,
repo,
// @ts-ignore
Expand All @@ -66,19 +68,25 @@ async function main() {
const runningWorkflows = branchWorkflows.filter(run =>
(ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
new Date(run.created_at) < new Date(current_run.created_at)
run.id !== current_run.id && // Do not cancel yourself
(cancel_newer || new Date(run.created_at) < new Date(current_run.created_at))
);
console.log(`with ${runningWorkflows.length} runs to cancel.`);

const promises = [];
for (const {id, head_sha, status, html_url} of runningWorkflows) {
console.log('Canceling run: ', {id, head_sha, status, html_url});
const res = await octokit.actions.cancelWorkflowRun({
const current_promise = octokit.actions.cancelWorkflowRun({
owner,
repo,
run_id: id
}).then((res) => {
console.log(`Cancel run ${id} responded with status ${res.status}`);
});
console.log(`Cancel run ${id} responded with status ${res.status}`);
promises.push(current_promise);
}
await Promise.all(promises);

} catch (e) {
const msg = e.message || e;
console.log(`Error while canceling workflow_id ${workflow_id}: ${msg}`);
Expand Down