From 02683cc1dc898a138634b11d03b7d6e65155148f Mon Sep 17 00:00:00 2001
From: Tim Jaacks <tim.jaacks@garz-fricke.com>
Date: Tue, 16 Aug 2022 09:35:46 +0200
Subject: [PATCH] Recurse to child pipelines on retrigger

This makes it possible to retrigger jobs that are not part of the
given pipeline but in any nested child pipeline of it.
---
 manifest-pipeline.yml                 |   1 +
 scripts/retrigger_mr_pipeline_job.py  |  89 -------------------
 scripts/retrigger_mr_pipeline_jobs.py |  23 ++++-
 scripts/retrigger_pipeline_jobs.py    | 122 ++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 91 deletions(-)
 delete mode 100755 scripts/retrigger_mr_pipeline_job.py
 create mode 100755 scripts/retrigger_pipeline_jobs.py

diff --git a/manifest-pipeline.yml b/manifest-pipeline.yml
index 77877579..ae394457 100644
--- a/manifest-pipeline.yml
+++ b/manifest-pipeline.yml
@@ -79,6 +79,7 @@ retrigger:
           --state=opened
           --target-branch=${MASTER_BRANCH}
           --job=check
+          --include-children
         ;
       done
 
diff --git a/scripts/retrigger_mr_pipeline_job.py b/scripts/retrigger_mr_pipeline_job.py
deleted file mode 100755
index b4ac6827..00000000
--- a/scripts/retrigger_mr_pipeline_job.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-import common
-
-import argparse
-import sys
-from gitlab import Gitlab, GitlabGetError
-
-
-def retrigger_mr_pipeline_job(project, mr, job_name, status_list):
-    # Get pipeline
-    if not mr.pipeline:
-        print("No pipeline in !%s" % mr.iid)
-        return None
-    pipeline = project.pipelines.get(mr.pipeline.get("id"), retry_transient_errors=True)
-
-    # Find job
-    job = None
-    for pipelinejob in pipeline.jobs.list():
-        if pipelinejob.name == job_name:
-            job = project.jobs.get(pipelinejob.id, retry_transient_errors=True)
-    if not job:
-        print("Could not find job '%s' in pipeline of !%s" % (job_name, mr.iid))
-        return None
-
-    # Only retrigger if job is in certain status
-    if job.status not in status_list:
-        return None
-
-    # Retrigger job
-    job.retry()
-    print("Retrigger job '%s' of pipeline #%s:" % (job_name, pipeline.id))
-    job = project.jobs.get(job.id, retry_transient_errors=True)
-    print(job.web_url)
-
-    return job
-
-
-def main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument(
-        "--gitlab-url",
-        help="""URL to the GitLab instance""",
-        dest="gitlab_url",
-        required=True,
-    )
-    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(
-        "--iid",
-        help="""iid of the merge request""",
-        dest="iid",
-        required=True,
-    )
-    parser.add_argument(
-        "--job",
-        help="""name of the job""",
-        dest="job",
-        required=True,
-    )
-
-    args, _ = parser.parse_known_args()
-
-    gitlab = Gitlab(args.gitlab_url, private_token=args.token)
-    project = common.get_project(gitlab, args.project)
-    try:
-        mr = project.mergerequests.get(args.iid, retry_transient_errors=True)
-    except GitlabGetError as e:
-        sys.exit(
-            "ERROR: could not get merge request %s:!%s: %s"
-            % (project.name, args.iid, e)
-        )
-    if not mr:
-        sys.exit("ERROR: could not find merge request %s!%s" % (project.name, args.iid))
-
-    retrigger_mr_pipeline_job(project, mr, args.job, ["success", "running"])
-
-
-if __name__ == "__main__":
-    main()
diff --git a/scripts/retrigger_mr_pipeline_jobs.py b/scripts/retrigger_mr_pipeline_jobs.py
index 24019bbf..8879b2d1 100755
--- a/scripts/retrigger_mr_pipeline_jobs.py
+++ b/scripts/retrigger_mr_pipeline_jobs.py
@@ -6,7 +6,7 @@ import sys
 from gitlab import Gitlab, GitlabJobRetryError
 
 from get_merge_requests import get_merge_requests
-from retrigger_mr_pipeline_job import retrigger_mr_pipeline_job
+from retrigger_pipeline_jobs import retrigger_pipeline_jobs
 
 
 def main():
@@ -75,8 +75,27 @@ def main():
 
     failed = 0
     for mr in mrs:
+        # Get pipeline
+        if not mr.pipeline:
+            print("No pipeline in !%s" % mr.iid)
+            continue
+        pipeline = project.pipelines.get(
+            mr.pipeline.get("id"),
+            retry_transient_errors=True,
+        )
         try:
-            retrigger_mr_pipeline_job(project, mr, args.job, ["success", "running"])
+            jobs = retrigger_pipeline_jobs(
+                project,
+                pipeline,
+                args.job,
+                ["success", "running"],
+                include_children=True,
+            )
+            if not jobs:
+                print(
+                    "Could not find any jobs named '%s' in pipeline of !%s"
+                    % (args.job, mr.iid)
+                )
         except GitlabJobRetryError as e:
             print(
                 "ERROR: Could not retrigger job '%s' of %s!%s: %s"
diff --git a/scripts/retrigger_pipeline_jobs.py b/scripts/retrigger_pipeline_jobs.py
new file mode 100755
index 00000000..21b9e263
--- /dev/null
+++ b/scripts/retrigger_pipeline_jobs.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+import common
+
+import argparse
+import sys
+from gitlab import Gitlab, GitlabGetError
+from gitlab.v4.objects import Project, ProjectPipeline
+from typing import List
+
+
+def retrigger_pipeline_jobs(
+    project: Project,
+    pipeline: ProjectPipeline,
+    job_name: str,
+    status_list: List[str],
+    include_children: bool = False,
+):
+    jobs = []
+
+    # Recurse to child pipelines
+    if include_children:
+        for bridge in pipeline.bridges.list():
+            if bridge.downstream_pipeline["project_id"] == project.id:
+                child_pipeline = project.pipelines.get(
+                    bridge.downstream_pipeline["id"],
+                    retry_transient_errors=True,
+                )
+                jobs += retrigger_pipeline_jobs(
+                    project,
+                    child_pipeline,
+                    job_name,
+                    status_list,
+                    include_children=True,
+                )
+
+    # Find job
+    job = None
+    for pipelinejob in pipeline.jobs.list():
+        if pipelinejob.name == job_name:
+            job = project.jobs.get(pipelinejob.id, retry_transient_errors=True)
+    if not job:
+        return jobs
+
+    # Only retrigger if job is in certain status
+    if job.status not in status_list:
+        return jobs
+
+    # Retrigger job
+    job.retry()
+    print("Retrigger job '%s' of pipeline #%s:" % (job_name, pipeline.id))
+    job = project.jobs.get(job.id, retry_transient_errors=True)
+    print(job.web_url)
+    jobs.append(job)
+
+    return jobs
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--gitlab-url",
+        help="""URL to the GitLab instance""",
+        dest="gitlab_url",
+        required=True,
+    )
+    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(
+        "--pipeline",
+        help="""pipeline id""",
+        dest="pipeline",
+        required=True,
+    )
+    parser.add_argument(
+        "--job",
+        help="""name of the job""",
+        dest="job",
+        required=True,
+    )
+
+    parser.add_argument(
+        "--include-children",
+        help="""search for job in child pipelines""",
+        dest="include_children",
+        action="store_true",
+        default=False,
+    )
+
+    args, _ = parser.parse_known_args()
+
+    gitlab = Gitlab(args.gitlab_url, private_token=args.token)
+    project = common.get_project(gitlab, args.project)
+    try:
+        pipeline = project.pipelines.get(args.pipeline, retry_transient_errors=True)
+    except GitlabGetError as e:
+        sys.exit("ERROR: could not get pipeline %s: %s" % (args.pipeline, e))
+    if not pipeline:
+        sys.exit("ERROR: could not find pipeline %s" % args.pipeline)
+
+    jobs = retrigger_pipeline_jobs(
+        project,
+        pipeline,
+        args.job,
+        ["success", "running"],
+        args.include_children,
+    )
+
+    print("Retriggered %d jobs for pipeline #%s" % (len(jobs), args.pipeline))
+
+
+if __name__ == "__main__":
+    main()
-- 
GitLab