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

Cleanup all_but_latest #67

Merged
merged 7 commits into from Apr 11, 2021
Merged
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
9 changes: 4 additions & 5 deletions README.md
Expand Up @@ -6,7 +6,7 @@ This includes runs with a [status](https://docs.github.com/en/rest/reference/che

## How does it work?

When you `git push`, this GitHub Action will capture the current Branch and SHA. It will query GitHub's API to find previous workflow runs that match the Branch but do not match the SHA. These in-progress runs will be canceled leaving only this run.
When you `git push`, this GitHub Action will capture the current Branch and SHA. It will query GitHub's API to find previous workflow runs that match the Branch but do not match the SHA. These in-progress runs will be canceled leaving only this run, or the [latest run](#advanced-all-but-latest).

Read more about the [Workflow Runs API](https://docs.github.com/en/rest/reference/actions#workflow-runs).

Expand Down Expand Up @@ -100,10 +100,11 @@ jobs:
workflow_id: 479426
```

## Advanced: Cancel more recent workflows
## Advanced: All But Latest

Because this action can only cancel workflows if it is actually being run, it only helps if the pipeline isn't saturated and there are still runners available to schedule the workflow.
By default, this action does not cancel any workflows older than itself. The optional flag ``all_but_latest`` switches to a mode where the action also cancels itself and all later-scheduled workflows but the last one.

By default, this action does not cancel any workflows created after itself. The `all_but_latest` flags allows the action to cancel itself and all later-scheduled workflows, leaving only the latest.

```yml
name: Cancel
Expand All @@ -120,8 +121,6 @@ jobs:
access_token: ${{ github.token }}
```

At the time of writing `0.8.0` is the latest release but you can select any [release](https://github.com/styfle/cancel-workflow-action/releases).

## Contributing

- Clone this repo
Expand Down
23 changes: 16 additions & 7 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 all_but_latest = core.getInput('all_but_latest', { required: false });
console.log(`Found token: ${token ? 'yes' : 'no'}`);
const workflow_ids = [];
const octokit = github.getOctokit(token);
Expand All @@ -5883,19 +5884,27 @@ async function main() {
console.log(`Found workflow_id: ${JSON.stringify(workflow_ids)}`);
await Promise.all(workflow_ids.map(async (workflow_id) => {
try {
const { data } = await octokit.actions.listWorkflowRuns({
const { data: { total_count, workflow_runs } } = await octokit.actions.listWorkflowRuns({
per_page: 100,
owner,
repo,
workflow_id,
branch,
});
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'));
const runningWorkflows = branchWorkflows.filter(run => (ignore_sha || run.head_sha !== headSha) &&
console.log(`Found ${total_count} runs total.`);
let cancelBefore = new Date(current_run.created_at);
if (all_but_latest) {
const n = workflow_runs.map(run => new Date(run.created_at).getTime()).reduce((a, b) => Math.max(a, b), cancelBefore.getTime());
cancelBefore = new Date(n);
}
const runningWorkflows = workflow_runs.filter(run => run.id !== current_run.id &&
(ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
new Date(run.created_at) < new Date(current_run.created_at));
console.log(`with ${runningWorkflows.length} runs to cancel.`);
new Date(run.created_at) < cancelBefore);
if (all_but_latest && new Date(current_run.created_at) < cancelBefore) {
runningWorkflows.push(current_run);
}
console.log(`Found ${runningWorkflows.length} runs to cancel.`);
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({
Expand Down
44 changes: 18 additions & 26 deletions src/index.ts
Expand Up @@ -47,34 +47,36 @@ async function main() {
// The user did not provide workflow id so derive from current run
workflow_ids.push(String(current_run.workflow_id));
}

console.log(`Found workflow_id: ${JSON.stringify(workflow_ids)}`);

await Promise.all(workflow_ids.map(async (workflow_id) => {
try {
const { data } = await octokit.actions.listWorkflowRuns({
const { data: { total_count, workflow_runs } } = await octokit.actions.listWorkflowRuns({
per_page: 100,
owner,
repo,
// @ts-ignore
workflow_id,
branch,
});
console.log(`Found ${data.total_count} runs total.`);

let cancel_before;
console.log(`Found ${total_count} runs total.`);
let cancelBefore = new Date(current_run.created_at);
if (all_but_latest) {
cancel_before = new Date(data.workflow_runs.reduce((a, b) => Math.max(a.created_at, b.created_at)));
} else {
cancel_before = new Date(current_run.created_at);
const n = workflow_runs.map(run => new Date(run.created_at).getTime()).reduce((a, b) => Math.max(a, b), cancelBefore.getTime());
cancelBefore = new Date(n);
}

const runningWorkflows = data.workflow_runs.filter(
run => run.head_branch === branch && (ignore_sha || run.head_sha !== headSha) && run.status !== 'completed' &&
run != current_run &&
new Date(run.created_at) < cancel_before
const runningWorkflows = workflow_runs.filter(run =>
run.id !== current_run.id &&
(ignore_sha || run.head_sha !== headSha) &&
run.status !== 'completed' &&
new Date(run.created_at) < cancelBefore
);
if (all_but_latest && new Date(current_run.created_at) < cancelBefore) {
// Make sure we cancel this run itself if it's out-of-date.
// We must cancel this run last so we can cancel the others first.
runningWorkflows.push(current_run);
}
console.log(`Found ${runningWorkflows.length} runs to cancel.`);
for (const {id, head_sha, status} of runningWorkflows) {
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({
Copy link
Contributor

Choose a reason for hiding this comment

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

missed opportunity to incorporate Promise.all performance optimization - that seemed useful based on other poster's experience

Copy link
Contributor

Choose a reason for hiding this comment

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

definitely would have to do 2 batches at that point though, while this implementation allows a simple loop

owner,
Expand All @@ -83,17 +85,7 @@ async function main() {
});
console.log(`Cancel run ${id} responded with status ${res.status}`);
}
// Make sure we cancel this run itself if it's out-of-date.
// 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) {
const id = current_run.id;
const res = await octokit.actions.cancelWorkflowRun({
owner,
repo,
run_id: id
});
console.log(`Cancel run ${id} responded with status ${res.status}`);
}

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