From 6252541628f1c64588dee9190e183283f407e7bb Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 9 Oct 2024 14:23:36 -0400 Subject: [PATCH] Add script and workflow that is able to "cancel all CI runs for a specific PR/commit" (#35975) * Add a script and workflow for 'cancel running workflows'. The intent for this is to allow failing workflows to trigger a "cancel all the rest of the runs" for a current PR. * Restyle * Rename parameter to make it clear it is a github token * Update scripts/tools/cancel_workflows_for_pr.py Co-authored-by: C Freeman --------- Co-authored-by: Andrei Litvin Co-authored-by: C Freeman --- .../workflows/cancel_workflows_for_pr.yaml | 55 ++++++++++++ scripts/tools/cancel_workflows_for_pr.py | 87 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 .github/workflows/cancel_workflows_for_pr.yaml create mode 100755 scripts/tools/cancel_workflows_for_pr.py diff --git a/.github/workflows/cancel_workflows_for_pr.yaml b/.github/workflows/cancel_workflows_for_pr.yaml new file mode 100644 index 00000000000000..c8b23b6de70ba5 --- /dev/null +++ b/.github/workflows/cancel_workflows_for_pr.yaml @@ -0,0 +1,55 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Cancel workflow on PR +on: + workflow_dispatch: + inputs: + pull_request_id: + description: 'PR number to consider' + required: true + type: number + commit_sha: + description: 'Cancel runs for this specific SHA' + required: true + type: string + +jobs: + cancel_workflow: + name: Report on pull requests + + runs-on: ubuntu-latest + + # Don't run on forked repos + if: github.repository_owner == 'project-chip' + + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Setup pip modules we use + run: | + pip install \ + click \ + coloredlogs \ + pygithub \ + && echo "DONE installint python prerequisites" + - name: Cancel runs + run: | + scripts/tools/cancel_workflows_for_pr.py \ + --pull-request ${{ inputs.pull_request_id }} \ + --commit-sha "${{ inputs.commit_sha }}" \ + --gh-api-token "${{ secrets.GITHUB_TOKEN }}" diff --git a/scripts/tools/cancel_workflows_for_pr.py b/scripts/tools/cancel_workflows_for_pr.py new file mode 100755 index 00000000000000..bd4b85374cc24f --- /dev/null +++ b/scripts/tools/cancel_workflows_for_pr.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging + +import click +import coloredlogs +from github import Github + +__LOG_LEVELS__ = { + "debug": logging.DEBUG, + "info": logging.INFO, + "warn": logging.WARN, + "fatal": logging.FATAL, +} + +REPOSITORY = "project-chip/connectedhomeip" + + +class Canceller: + def __init__(self, token): + self.api = Github(token) + self.repo = self.api.get_repo(REPOSITORY) + + def cancel_all_runs(self, pr_number, commit_sha, dry_run): + pr = self.repo.get_pull(pr_number) + logging.info("Examining PR '%s'", pr.title) + for commit in pr.get_commits(): + if commit.sha != commit_sha: + logging.info("Skipping SHA '%s' as it was not selected", commit.sha) + continue + + for check_suite in commit.get_check_suites(): + for run in check_suite.get_check_runs(): + if run.status in {"in_progress", "queued"}: + if dry_run: + logging.warning("DRY RUN: Will not stop run %s", run.name) + else: + logging.warning("Stopping run %s", run.name) + self.repo.get_workflow_run(run.id).cancel() + else: + logging.info("Skip over run %s (%s)", run.name, run.status) + + +@click.command() +@click.option( + "--log-level", + default="INFO", + type=click.Choice(list(__LOG_LEVELS__.keys()), case_sensitive=False), + help="Determines the verbosity of script output.", +) +@click.option("--pull-request", type=int, help="Pull request number to consider") +@click.option("--commit-sha", help="Commit to look at when cancelling pull requests") +@click.option("--gh-api-token", help="Github token to use") +@click.option("--token-file", help="Read github token from the given file") +@click.option("--dry-run", default=False, is_flag=True, help="Actually cancel or not") +def main(log_level, pull_request, commit_sha, gh_api_token, token_file, dry_run): + coloredlogs.install( + level=__LOG_LEVELS__[log_level], fmt="%(asctime)s %(levelname)-7s %(message)s" + ) + + if gh_api_token: + gh_token = gh_api_token + elif token_file: + gh_token = open(token_file, "rt").read().strip() + else: + raise Exception("Require a --gh-api-token or --token-file to access github") + + Canceller(gh_token).cancel_all_runs(pull_request, commit_sha, dry_run) + + +if __name__ == "__main__": + main()