From c8f97623cad52bb485791e0b0096f8fdcf44eaad Mon Sep 17 00:00:00 2001 From: Tim Jaacks <tim.jaacks@seco.com> Date: Wed, 21 Dec 2022 11:40:52 +0100 Subject: [PATCH] Add job to cancel outdated pipelines in merge requests Everytime we push a new commit to a branch, a new pipeline is created. Often the pipeline for the previous commit has not finished yet, and GitLab does not automatically cancel it. It consumes valuable build time, even if we don't need the results anymore. Adding a MR pipeline job to search for previous pipelines on the same branch and cancel them explicitly. --- manifest-integration.yml | 14 ++++++ scripts/cancel_pipelines.py | 96 +++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100755 scripts/cancel_pipelines.py diff --git a/manifest-integration.yml b/manifest-integration.yml index 53691afd..1fc1b1cb 100644 --- a/manifest-integration.yml +++ b/manifest-integration.yml @@ -115,6 +115,20 @@ check: fi; done <<< "$INTEGRATION" +cancel-previous-pipelines: + extends: + - .infrastructure + - .skip-for-gitlab-ci-integrations + stage: manifest-integration + allow_failure: true + script: + - .gitlab-ci/scripts/cancel_pipelines.py + --gitlab-url=${CI_SERVER_URL} + --token=${GITBOT_TOKEN} + --project=${CI_PROJECT_PATH} + --ref=${CI_MERGE_REQUEST_REF_PATH} + --below-pipeline-id=${CI_PIPELINE_ID} + yamllint: extends: .yamllint stage: manifest-integration diff --git a/scripts/cancel_pipelines.py b/scripts/cancel_pipelines.py new file mode 100755 index 00000000..437dc880 --- /dev/null +++ b/scripts/cancel_pipelines.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +import argparse +import sys + +from gitlab.client import Gitlab +from gitlab.v4.objects import Project, ProjectPipeline + +import common + + +def cancel_pipelines( + project: Project, + ref: str = "", + below_pipeline_id: int = sys.maxsize, +) -> list[ProjectPipeline]: + """Cancel currently running pipelines. + + Args: + project: GitLab project the pipeline belongs to + ref: Git ref (branch or tag) the pipeline is running on + below_pipeline_id: cancel only pipelines with ID below this + + Returns: + List of cancelled pipelines + """ + + pipelines = project.pipelines.list( + ref=ref, status="running", retry_transient_errors=True + ) + + cancelled_pipelines = [] + for pipeline in pipelines: + if pipeline.id < below_pipeline_id: + pipeline.cancel() + cancelled_pipelines.append(pipeline) + + return cancelled_pipelines + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--gitlab-url", + help="""URL to the GitLab instance""", + dest="gitlab_url", + default=common.GITLAB_URL, + ) + parser.add_argument( + "--token", + help="""GitLab REST API private access token""", + dest="token", + required=True, + ) + parser.add_argument( + "--project", + help="""name of the GitLab project""", + dest="project", + required=True, + ) + parser.add_argument( + "--ref", + help="""Git reference (branch or tag)""", + dest="ref", + default="", + ) + parser.add_argument( + "--below-pipeline-id", + help="""Cancel only pipelines with IDs lower than this""", + dest="below_pipeline_id", + type=int, + default=sys.maxsize, + ) + + args, _ = parser.parse_known_args() + + gitlab = Gitlab(args.gitlab_url, private_token=args.token) + project = common.get_project(gitlab, args.project) + + print( + "Searching for pipelines in project '%s' on ref '%s' with IDs below %d" + % (args.project, args.ref, args.below_pipeline_id) + ) + + cancelled_pipelines = cancel_pipelines(project, args.ref, args.below_pipeline_id) + + if not cancelled_pipelines: + print("No running pipelines found.") + sys.exit(0) + + print("Cancelled pipelines:") + for pipeline in cancelled_pipelines: + print(pipeline.web_url) + + +if __name__ == "__main__": + main() -- GitLab