diff --git a/get_pipelines.py b/get_pipelines.py
new file mode 100755
index 0000000000000000000000000000000000000000..b3c2012a9e1f9b6394ea30f145b77bb2fa19b8e8
--- /dev/null
+++ b/get_pipelines.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+import common
+
+import argparse
+import sys
+from gitlab import Gitlab
+from gitlab.v4.objects import Project
+
+
+def get_pipelines(project: Project, commit, ref: str):
+    """
+    Get all pipelines for a given commit and ref.
+    The ref can be negated with a preceding '^', e.g. '^master' finds pipelines on any
+    ref different from 'master'.
+    """
+
+    # Find pipelines for commit
+    pipelines = project.pipelines.list(sha=commit, retry_transient_errors=True)
+
+    # Remove pipelines not matching the ref condition
+    if ref:
+        for p in pipelines[:]:
+            if (
+                ref.startswith("^")
+                and p.ref == ref[1:]
+                or not ref.startswith("^")
+                and p.ref != ref
+            ):
+                pipelines.remove(p)
+
+    if not pipelines:
+        raise LookupError(
+            "No pipeline for commit '%s'%s found"
+            % (commit, " on ref '%s'" % ref if ref else "")
+        )
+
+    return pipelines
+
+
+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(
+        "--commit",
+        help="""sha of the project commit to check""",
+        dest="commit",
+        required=True,
+    )
+    parser.add_argument(
+        "--ref",
+        help="""ref the pipeline ran on (can be negated with preceding '^')""",
+        dest="ref",
+        default="",
+        required=False,
+    )
+
+    args, _ = parser.parse_known_args()
+
+    gitlab = Gitlab(args.gitlab_url, private_token=args.token)
+    project = common.get_project(gitlab, args.project)
+
+    try:
+        pipelines = get_pipelines(project, args.commit, args.ref)
+    except LookupError as e:
+        sys.exit(e)
+
+    for p in pipelines:
+        print(p.id)
+
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/check_pipeline_status.py b/mirror_pipeline_result.py
similarity index 62%
rename from check_pipeline_status.py
rename to mirror_pipeline_result.py
index fa3b8175c102c807860eafd8a4c48a285537d6e4..c2973691108ecff7cbdc24e92855fe000fd26e66 100755
--- a/check_pipeline_status.py
+++ b/mirror_pipeline_result.py
@@ -9,31 +9,14 @@ from gitlab import Gitlab, GitlabGetError
 from gitlab.v4.objects import Project
 
 
-def check_pipeline_status(project: Project, commit, ref: str):
+def mirror_pipeline_result(project: Project, pipeline_id, job=None):
     """
     Get latest pipeline status for a given commit and ref.
     The ref can be negated with a preceding '^', e.g. '^master' finds a pipeline on any
     ref different from 'master'.
     """
 
-    # Find pipeline for commit
-    pipelines = project.pipelines.list(sha=commit, retry_transient_errors=True)
-
-    # Remove pipelines not matching the ref condition
-    if ref:
-        for p in pipelines[:]:
-            if (
-                ref.startswith("^")
-                and p.ref == ref[1:]
-                or not ref.startswith("^")
-                and p.ref != ref
-            ):
-                pipelines.remove(p)
-
-    if not pipelines:
-        print("ERROR: no pipeline for commit '%s' with ref '%s' found" % (commit, ref))
-        sys.exit(1)
-    pipeline = pipelines[0]
+    pipeline = project.pipelines.get(pipeline_id, retry_transient_errors=True)
 
     # Wait for pipeline termination
     print("Waiting for pipeline %s" % pipeline.web_url)
@@ -45,6 +28,13 @@ def check_pipeline_status(project: Project, commit, ref: str):
     print("")
     print("Result: %s" % pipeline.status, flush=True)
 
+    # If we are running in a job environment and the upstream status is canceled,
+    # explicitly cancel this job as well
+    if job and pipeline.status == "canceled":
+        time.sleep(5)
+        job.cancel()
+
+    # Else just reflect job status in return value
     return pipeline.status
 
 
@@ -69,43 +59,31 @@ def main():
         required=True,
     )
     parser.add_argument(
-        "--commit",
-        help="""sha of the project commit to check""",
-        dest="commit",
+        "--pipeline",
+        help="""id of the pipeline to mirror the result of""",
+        dest="pipeline",
         required=True,
     )
-    parser.add_argument(
-        "--ref",
-        help="""ref the pipeline ran on (can be negated with preceding '^')""",
-        dest="ref",
-        default="",
-        required=False,
-    )
 
     args, _ = parser.parse_known_args()
 
     gitlab = Gitlab(args.gitlab_url, private_token=args.token)
     project = common.get_project(gitlab, args.project)
 
-    status = check_pipeline_status(project, args.commit, args.ref)
-
-    # If we are running in a job environment and the upstream status is canceled,
-    # explicitly cancel this job as well
+    # Check if we are running in a GitLab job context
     job_project_path = os.environ.get("CI_PROJECT_PATH")
     job_id = os.environ.get("CI_JOB_ID")
-    if job_project_path and job_id and status == "canceled":
+    if job_project_path and job_id:
         try:
             job_project = common.get_project(gitlab, job_project_path)
             job = job_project.jobs.get(id=job_id, retry_transient_errors=True)
-            # Wait some time until Gitlab has flushed the job log, otherwise it will be
-            # truncated before all lines are visible
-            time.sleep(5)
-            job.cancel()
         except GitlabGetError:
             print("WARNING: job %s not found in project %s" % (args.job, args.project))
+            job = None
 
-    # Else just reflect job status in return value
-    elif status != "success":
+    status = mirror_pipeline_result(project, args.pipeline, job)
+
+    if status != "success":
         sys.exit(1)
 
     sys.exit(0)
diff --git a/trigger_pipeline.py b/trigger_pipeline.py
new file mode 100755
index 0000000000000000000000000000000000000000..c0d0bada90119c59e3ba4ca91f2f06c9f345d0aa
--- /dev/null
+++ b/trigger_pipeline.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+import common
+
+import argparse
+import sys
+from gitlab import Gitlab, GitlabCreateError
+
+
+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(
+        "--ref",
+        help="""branch or tag ref to trigger the pipeline for""",
+        dest="ref",
+        required=True,
+    )
+
+    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.create({"ref": args.ref})
+    except GitlabCreateError as e:
+        sys.exit("ERROR: %s" % e)
+
+    print("Created new pipeline:")
+    print(pipeline.web_url)
+
+    sys.exit(0)
+
+
+if __name__ == "__main__":
+    main()