fix(CI): properly configure cancel duplicates (#12625)
Updating the "Cancel Duplicates" job to use our own Python script, which will try to cancel all duplicate jobs of all workflows: For each workflow, we keep only the latest run for a given branch + event type (pull_request or push). Older runs will be cancelled even if they are already running.
This commit is contained in:
parent
9b007e65c2
commit
9c5ec3d72a
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit ce177499ccf9fd2aded3b0426c97e5434c2e8a73
|
|
||||||
|
|
@ -1,25 +1,40 @@
|
||||||
name: Cancel Duplicates
|
name: Cancel Duplicates
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: ["CI"]
|
workflows:
|
||||||
types: ["requested"]
|
- "Miscellaneous"
|
||||||
|
types:
|
||||||
|
- requested
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cancel-duplicate-workflow-runs:
|
cancel-duplicate-runs:
|
||||||
name: "Cancel duplicate workflow runs"
|
name: Cancel duplicate workflow runs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check number of queued tasks
|
||||||
|
id: check_queued
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GITHUB_REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
get_count() {
|
||||||
|
echo $(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/$GITHUB_REPO/actions/runs?status=$1" | \
|
||||||
|
jq ".total_count")
|
||||||
|
}
|
||||||
|
count=$(( `get_count queued` + `get_count in_progress` ))
|
||||||
|
echo "Found $count unfinished jobs."
|
||||||
|
echo "::set-output name=count::$count"
|
||||||
|
|
||||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||||
|
if: steps.check_queued.outputs.count >= 20
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
persist-credentials: false
|
- name: Cancel duplicate workflow runs
|
||||||
submodules: recursive
|
if: steps.check_queued.outputs.count >= 20
|
||||||
- uses: ./.github/actions/cancel-workflow-runs/
|
env:
|
||||||
name: "Cancel duplicate workflow runs"
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
cancelMode: duplicates
|
run: |
|
||||||
cancelFutureDuplicates: true
|
pip install click requests typing_extensions python-dateutil
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
python ./scripts/cancel_github_workflows.py
|
||||||
sourceRunId: ${{ github.event.workflow_run.id }}
|
|
||||||
notifyPRCancel: true
|
|
||||||
skipEventTypes: '["push", "pull_request", "pull_request_target"]'
|
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,6 @@
|
||||||
[submodule ".github/actions/file-changes-action"]
|
[submodule ".github/actions/file-changes-action"]
|
||||||
path = .github/actions/file-changes-action
|
path = .github/actions/file-changes-action
|
||||||
url = https://github.com/trilom/file-changes-action
|
url = https://github.com/trilom/file-changes-action
|
||||||
[submodule ".github/actions/cancel-workflow-action"]
|
|
||||||
path = .github/actions/cancel-workflow-action
|
|
||||||
url = https://github.com/styfle/cancel-workflow-action
|
|
||||||
[submodule ".github/actions/cached-dependencies"]
|
[submodule ".github/actions/cached-dependencies"]
|
||||||
path = .github/actions/cached-dependencies
|
path = .github/actions/cached-dependencies
|
||||||
url = https://github.com/apache-superset/cached-dependencies
|
url = https://github.com/apache-superset/cached-dependencies
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ Example:
|
||||||
export GITHUB_TOKEN=394ba3b48494ab8f930fbc93
|
export GITHUB_TOKEN=394ba3b48494ab8f930fbc93
|
||||||
export GITHUB_REPOSITORY=apache/superset
|
export GITHUB_REPOSITORY=apache/superset
|
||||||
|
|
||||||
# cancel previous jobs for a PR
|
# cancel previous jobs for a PR, will even cancel the running ones
|
||||||
./cancel_github_workflows.py 1042
|
./cancel_github_workflows.py 1042
|
||||||
|
|
||||||
# cancel previous jobs for a branch
|
# cancel previous jobs for a branch
|
||||||
./cancel_github_workflows.py my-branch
|
./cancel_github_workflows.py my-branch
|
||||||
|
|
||||||
# cancel all jobs
|
# cancel all jobs of a PR, including the latest runs
|
||||||
./cancel_github_workflows.py 1024 --include-last
|
./cancel_github_workflows.py 1024 --include-last
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
@ -58,7 +58,21 @@ def request(method: Literal["GET", "POST", "DELETE", "PUT"], endpoint: str, **kw
|
||||||
|
|
||||||
|
|
||||||
def list_runs(repo: str, params=None):
|
def list_runs(repo: str, params=None):
|
||||||
return request("GET", f"/repos/{repo}/actions/runs", params=params)
|
"""List all github workflow runs.
|
||||||
|
Returns:
|
||||||
|
An iterator that will iterate through all pages of matching runs."""
|
||||||
|
page = 1
|
||||||
|
total_count = 10000
|
||||||
|
while page * 100 < total_count:
|
||||||
|
result = request(
|
||||||
|
"GET",
|
||||||
|
f"/repos/{repo}/actions/runs",
|
||||||
|
params={**params, "per_page": 100, "page": page},
|
||||||
|
)
|
||||||
|
total_count = result["total_count"]
|
||||||
|
for item in result["workflow_runs"]:
|
||||||
|
yield item
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
|
||||||
def cancel_run(repo: str, run_id: Union[str, int]):
|
def cancel_run(repo: str, run_id: Union[str, int]):
|
||||||
|
|
@ -69,9 +83,9 @@ def get_pull_request(repo: str, pull_number: Union[str, int]):
|
||||||
return request("GET", f"/repos/{repo}/pulls/{pull_number}")
|
return request("GET", f"/repos/{repo}/pulls/{pull_number}")
|
||||||
|
|
||||||
|
|
||||||
def get_runs_by_branch(
|
def get_runs(
|
||||||
repo: str,
|
repo: str,
|
||||||
branch: str,
|
branch: Optional[str] = None,
|
||||||
user: Optional[str] = None,
|
user: Optional[str] = None,
|
||||||
statuses: Iterable[str] = ("queued", "in_progress"),
|
statuses: Iterable[str] = ("queued", "in_progress"),
|
||||||
events: Iterable[str] = ("pull_request", "push"),
|
events: Iterable[str] = ("pull_request", "push"),
|
||||||
|
|
@ -81,15 +95,13 @@ def get_runs_by_branch(
|
||||||
item
|
item
|
||||||
for event in events
|
for event in events
|
||||||
for status in statuses
|
for status in statuses
|
||||||
for item in list_runs(
|
for item in list_runs(repo, {"event": event, "status": status})
|
||||||
repo, {"event": event, "status": status, "per_page": 100}
|
if (branch is None or (branch == item["head_branch"]))
|
||||||
)["workflow_runs"]
|
|
||||||
if item["head_branch"] == branch
|
|
||||||
and (user is None or (user == item["head_repository"]["owner"]["login"]))
|
and (user is None or (user == item["head_repository"]["owner"]["login"]))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def print_commit(commit):
|
def print_commit(commit, branch):
|
||||||
"""Print out commit message for verification"""
|
"""Print out commit message for verification"""
|
||||||
indented_message = " \n".join(commit["message"].split("\n"))
|
indented_message = " \n".join(commit["message"].split("\n"))
|
||||||
date_str = (
|
date_str = (
|
||||||
|
|
@ -98,7 +110,7 @@ def print_commit(commit):
|
||||||
.strftime("%a, %d %b %Y %H:%M:%S")
|
.strftime("%a, %d %b %Y %H:%M:%S")
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
f"""HEAD {commit["id"]}
|
f"""HEAD {commit["id"]} ({branch})
|
||||||
Author: {commit["author"]["name"]} <{commit["author"]["email"]}>
|
Author: {commit["author"]["name"]} <{commit["author"]["email"]}>
|
||||||
Date: {date_str}
|
Date: {date_str}
|
||||||
|
|
||||||
|
|
@ -132,10 +144,10 @@ Date: {date_str}
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Whether to also cancel running workflows.",
|
help="Whether to also cancel running workflows.",
|
||||||
)
|
)
|
||||||
@click.argument("branch_or_pull")
|
@click.argument("branch_or_pull", required=False)
|
||||||
def cancel_github_workflows(
|
def cancel_github_workflows(
|
||||||
branch_or_pull: str,
|
branch_or_pull: Optional[str],
|
||||||
repo,
|
repo: str,
|
||||||
event: List[str],
|
event: List[str],
|
||||||
include_last: bool,
|
include_last: bool,
|
||||||
include_running: bool,
|
include_running: bool,
|
||||||
|
|
@ -145,24 +157,24 @@ def cancel_github_workflows(
|
||||||
raise ClickException("Please provide GITHUB_TOKEN as an env variable")
|
raise ClickException("Please provide GITHUB_TOKEN as an env variable")
|
||||||
|
|
||||||
statuses = ("queued", "in_progress") if include_running else ("queued",)
|
statuses = ("queued", "in_progress") if include_running else ("queued",)
|
||||||
|
events = event
|
||||||
pr = None
|
pr = None
|
||||||
|
|
||||||
if branch_or_pull.isdigit():
|
if branch_or_pull is None:
|
||||||
|
title = "all jobs" if include_last else "all duplicate jobs"
|
||||||
|
elif branch_or_pull.isdigit():
|
||||||
pr = get_pull_request(repo, pull_number=branch_or_pull)
|
pr = get_pull_request(repo, pull_number=branch_or_pull)
|
||||||
target_type = "pull request"
|
title = f"pull request #{pr['number']} - {pr['title']}"
|
||||||
title = f"#{pr['number']} - {pr['title']}"
|
|
||||||
else:
|
else:
|
||||||
target_type = "branch"
|
title = f"branch [{branch_or_pull}]"
|
||||||
title = branch_or_pull
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"\nCancel {'active' if include_running else 'previous'} "
|
f"\nCancel {'active' if include_running else 'previous'} "
|
||||||
f"workflow runs for {target_type}\n\n {title}\n"
|
f"workflow runs for {title}\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
if pr:
|
if pr:
|
||||||
# full branch name
|
runs = get_runs(
|
||||||
runs = get_runs_by_branch(
|
|
||||||
repo,
|
repo,
|
||||||
statuses=statuses,
|
statuses=statuses,
|
||||||
events=event,
|
events=event,
|
||||||
|
|
@ -172,26 +184,33 @@ def cancel_github_workflows(
|
||||||
else:
|
else:
|
||||||
user = None
|
user = None
|
||||||
branch = branch_or_pull
|
branch = branch_or_pull
|
||||||
if ":" in branch:
|
if branch and ":" in branch:
|
||||||
[user, branch] = branch.split(":", 2)
|
[user, branch] = branch.split(":", 2)
|
||||||
runs = get_runs_by_branch(
|
runs = get_runs(
|
||||||
repo, statuses=statuses, events=event, branch=branch_or_pull, user=user
|
repo, branch=branch, user=user, statuses=statuses, events=events,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# sort old jobs to the front, so to cancel older jobs first
|
||||||
runs = sorted(runs, key=lambda x: x["created_at"])
|
runs = sorted(runs, key=lambda x: x["created_at"])
|
||||||
if not runs:
|
if runs:
|
||||||
|
print(
|
||||||
|
f"Found {len(runs)} potential runs of\n"
|
||||||
|
f" status: {statuses}\n event: {events}\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
print(f"No {' or '.join(statuses)} workflow runs found.\n")
|
print(f"No {' or '.join(statuses)} workflow runs found.\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not include_last:
|
if not include_last:
|
||||||
# Only keep one item for each workflow
|
# Keep the latest run for each workflow and cancel all others
|
||||||
seen = set()
|
seen = set()
|
||||||
dups = []
|
dups = []
|
||||||
for item in reversed(runs):
|
for item in reversed(runs):
|
||||||
if item["workflow_id"] in seen:
|
key = f'{item["event"]}_{item["head_branch"]}_{item["workflow_id"]}'
|
||||||
|
if key in seen:
|
||||||
dups.append(item)
|
dups.append(item)
|
||||||
else:
|
else:
|
||||||
seen.add(item["workflow_id"])
|
seen.add(key)
|
||||||
if not dups:
|
if not dups:
|
||||||
print(
|
print(
|
||||||
"Only the latest runs are in queue. "
|
"Only the latest runs are in queue. "
|
||||||
|
|
@ -207,7 +226,8 @@ def cancel_github_workflows(
|
||||||
head_commit = entry["head_commit"]
|
head_commit = entry["head_commit"]
|
||||||
if head_commit["id"] != last_sha:
|
if head_commit["id"] != last_sha:
|
||||||
last_sha = head_commit["id"]
|
last_sha = head_commit["id"]
|
||||||
print_commit(head_commit)
|
print("")
|
||||||
|
print_commit(head_commit, entry["head_branch"])
|
||||||
try:
|
try:
|
||||||
print(f"[{entry['status']}] {entry['name']}", end="\r")
|
print(f"[{entry['status']}] {entry['name']}", end="\r")
|
||||||
cancel_run(repo, entry["id"])
|
cancel_run(repo, entry["id"])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue