Skip to content

Commit

Permalink
feat(all_but_latest): add ability to clear every run but latest
Browse files Browse the repository at this point in the history
This is an adaptation of styfle#35 from @thomwiggers - the logic is entirely
from that PR (thank you!)

A new workflow adds a 240-second sleep job on macos (limit 5 concurrent)
with manual dispatch available for testing
  • Loading branch information
mikehardy committed Apr 1, 2021
1 parent 12fef67 commit dbcb13a
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 5 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/cancel-all-but-latest.yml
@@ -0,0 +1,21 @@
name: Cancel All But Latest

on:
push:
workflow_dispatch:

jobs:
task:
# Use macOS because it has a 5 concurrent job limit: easier to test by manual enqueue
# https://docs.github.com/en/actions/reference/usage-limits-billing-and-administration#usage-limits
runs-on: macos-latest
name: Task
steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Test Step
uses: ./ # Uses an action in the root directory
with:
- all_but_latest: true
- run: echo 'Sleeping...'; sleep 240; echo 'Done.';
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -100,6 +100,21 @@ jobs:
workflow_id: 479426
```

### Advanced: Cancel All But Latest

In some cases, you may wish to cancel all workflow runs except the newest one. This can help if you have very deep workflow queues and you find that the newer runs are not even executing thus they cannot clear out of date runs. In this mode, the out-of-date workflow runs will cancel all but the latest run and themselves, freeing up the workflow queue.

```yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.8.0
with:
all_but_latest: true
```

## Contributing

- Clone this repo
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Expand Up @@ -12,6 +12,9 @@ inputs:
description: 'Optional - Allow canceling other workflows with the same SHA. Useful for the `pull_request.closed` event.'
required: false
default: 'false'
all_but_latest:
description: "Optional - Cancel all but the most recent action, can help with very deep queues"
required: false
access_token:
description: 'Your GitHub Access Token, defaults to: {{ github.token }}'
default: '${{ github.token }}'
Expand Down
20 changes: 19 additions & 1 deletion dist/index.js
Expand Up @@ -5864,6 +5864,7 @@ async function main() {
const token = core.getInput('access_token', { required: true });
const workflow_id_input = core.getInput('workflow_id', { required: false });
const ignore_sha = core.getInput('ignore_sha', { required: false }) === 'true';
const all_but_latest = core.getInput('all_but_latest', { required: false }) === 'true';
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids = [];
const octokit = github.getOctokit(token);
Expand Down Expand Up @@ -5891,14 +5892,31 @@ async function main() {
workflow_id,
branch
});
let cancel_before;
if (all_but_latest) {
cancel_before = new Date(data.workflow_runs.reduce((a, b) => parseInt(a.created_at, 10) > parseInt(b.created_at, 10) ? a : b).created_at);
}
else {
cancel_before = new Date(current_run.created_at);
}
const branchWorkflows = data.workflow_runs.filter(run => run.head_branch === branch);
console.log(`Found ${branchWorkflows.length} runs for workflow ${workflow_id} on branch ${branch}`);
console.log(branchWorkflows.map(run => `- ${run.html_url}`).join('\n'));
let runningWorkflows = branchWorkflows.filter(run => run.status !== 'completed');
runningWorkflows = runningWorkflows.filter(run => ignore_sha || run.head_sha !== headSha);
runningWorkflows = runningWorkflows.filter(run => new Date(run.created_at) < new Date(current_run.created_at));
runningWorkflows = runningWorkflows.filter(run => {
if (all_but_latest && run !== current_run) {
return new Date(run.created_at) < cancel_before;
}
else {
return new Date(run.created_at) < new Date(current_run.created_at);
}
});
console.log(`with ${runningWorkflows.length} runs to cancel.`);
await cancelWorkflowRuns(runningWorkflows, owner, repo, token);
if (all_but_latest && new Date(current_run.created_at) < cancel_before) {
await cancelWorkflowRuns([current_run], owner, repo, token);
}
}
catch (e) {
const msg = e.message || e;
Expand Down
31 changes: 27 additions & 4 deletions src/index.ts
Expand Up @@ -33,6 +33,7 @@ async function main(): Promise<void> {
const token = core.getInput('access_token', { required: true });
const workflow_id_input = core.getInput('workflow_id', { required: false });
const ignore_sha = core.getInput('ignore_sha', { required: false }) === 'true';
const all_but_latest = core.getInput('all_but_latest', { required: false }) === 'true';
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids: number[] = [];
const octokit = github.getOctokit(token);
Expand Down Expand Up @@ -67,6 +68,17 @@ async function main(): Promise<void> {
branch
});

let cancel_before: Date;
if (all_but_latest) {
cancel_before = new Date(
data.workflow_runs.reduce((a, b) =>
parseInt(a.created_at, 10) > parseInt(b.created_at, 10) ? a : b
).created_at
);
} else {
cancel_before = new Date(current_run.created_at);
}

const branchWorkflows = data.workflow_runs.filter(run => run.head_branch === branch);
console.log(
`Found ${branchWorkflows.length} runs for workflow ${workflow_id} on branch ${branch}`
Expand All @@ -79,13 +91,24 @@ async function main(): Promise<void> {
// Filter out for only our headSha unless ignoring it
runningWorkflows = runningWorkflows.filter(run => ignore_sha || run.head_sha !== headSha);

// Filter all workflow runs newer than ours
runningWorkflows = runningWorkflows.filter(
run => new Date(run.created_at) < new Date(current_run.created_at)
);
// Filter workflow runs over time - retain either all before us, or just the latest
runningWorkflows = runningWorkflows.filter(run => {
// In all_but_latest mode, we must not cancel ourselves until we have canceled the rest
if (all_but_latest && run !== current_run) {
return new Date(run.created_at) < cancel_before;
} else {
return new Date(run.created_at) < new Date(current_run.created_at);
}
});

console.log(`with ${runningWorkflows.length} runs to cancel.`);
await cancelWorkflowRuns(runningWorkflows, owner, repo, token);

// In all_but_latest_mode, we may need to cancel ourselves as well.
// We postponed canceling this run because otherwise we couldn't cancel the rest.
if (all_but_latest && new Date(current_run.created_at) < cancel_before) {
await cancelWorkflowRuns([current_run], owner, repo, token);
}
} catch (e) {
const msg = e.message || e;
console.log(`Error while canceling workflow_id ${workflow_id}: ${msg}`);
Expand Down

0 comments on commit dbcb13a

Please sign in to comment.