diff options
author | Oliver Smith <osmith@sysmocom.de> | 2022-10-07 10:16:24 +0200 |
---|---|---|
committer | Oliver Smith <osmith@sysmocom.de> | 2022-10-11 12:07:40 +0200 |
commit | 4e679c8f2ef0315c00c1cf6df6141efe8181fc70 (patch) | |
tree | aa9cbcdf05342b6d0a835d747c717d36f822b52f | |
parent | d0ef24c3c6547cae90a5f7ea62c1b0a28cc7d04c (diff) |
jobs/gerrit-verifications: write summary comment
Instead of having multiple mails from jenkins per submitted gerrit
patch, only have one at the end of the pipeline that writes a summary
with all relevant links and votes +1/-1 for verified.
This also resolves the problem we had for a long time, that the link for
the build job didn't directly point to the relevant console output.
Instead it pointed to a matrix job that didn't have a direct link to the
relevant job.
The summary looks like this:
1 failed:
[build] https://jenkins.osmocom.org/jenkins/job/gerrit-osmo-bsc-nat-build/a1=default,a2=default,a3=default,a4=default,label=osmocom-gerrit-debian9/11/consoleFull
3 passed:
[rpm] https://jenkins.osmocom.org/jenkins/job/gerrit-binpkgs-rpm/5/consoleFull
[deb] https://jenkins.osmocom.org/jenkins/job/gerrit-binpkgs-deb/5/consoleFull
[lint] https://jenkins.osmocom.org/jenkins/job/gerrit-lint/11/consoleFull
Build Failed
Related: OS#2385
Change-Id: Idcab969e1b5ca4e0f1383bee8f36f2d1aac4f624
-rw-r--r-- | jobs/gerrit-pipeline-result.yml | 68 | ||||
-rw-r--r-- | jobs/gerrit-verifications.yml | 15 | ||||
-rwxr-xr-x | scripts/jenkins-gerrit/pipeline_summary.py | 185 | ||||
-rwxr-xr-x | scripts/jenkins-gerrit/pipeline_summary_send.sh | 13 |
4 files changed, 280 insertions, 1 deletions
diff --git a/jobs/gerrit-pipeline-result.yml b/jobs/gerrit-pipeline-result.yml new file mode 100644 index 0000000..ac54cee --- /dev/null +++ b/jobs/gerrit-pipeline-result.yml @@ -0,0 +1,68 @@ +# This job runs at the end of the pipeline in gerrit-verififactions.yml, to +# post a list of failed/successful job links to gerrit and to vote +V/-V. + +- project: + name: gerrit-pipeline-result + jobs: + - 'gerrit-pipeline-result' + +- job: + name: 'gerrit-pipeline-result' + project-type: freestyle + node: osmocom-gerrit-debian10 || osmocom-gerrit-debian11 + retry-count: 3 # scm checkout + properties: + - build-discarder: + days-to-keep: 30 + num-to-keep: 120 + artifact-days-to-keep: -1 + artifact-num-to-keep: -1 + description: | + Result job of CI for patches sent to <a href="https://gerrit.osmocom.org">gerrit</a>. + </br></br> + Related issue: <a href="https://osmocom.org/issues/2385">OS#2385</a> + + parameters: + - string: + name: BRANCH_CI + description: | + osmo-ci.git branch + default: 'master' + - string: + name: GERRIT_BRANCH + description: set by gerrit verification pipeline job + - string: + name: GERRIT_HOST + description: set by gerrit verification pipeline job + - string: + name: GERRIT_PATCHSET_REVISION + description: set by gerrit verification pipeline job + - string: + name: GERRIT_PORT + description: set by gerrit verification pipeline job + - string: + name: GERRIT_REFSPEC + description: set by gerrit verification pipeline job + - string: + name: PIPELINE_BUILD_URL + description: set by gerrit verification pipeline job + + scm: + - git: + url: 'https://gerrit.osmocom.org/osmo-ci' + credentials-id: d5eda5e9-b59d-44ba-88d2-43473cb6e42d + branches: + - '$BRANCH_CI' + wipe-workspace: true + + builders: + - shell: 'cd scripts/jenkins-gerrit && ./pipeline_summary_send.sh' + + wrappers: + - ansicolor: + colormap: xterm + - ssh-agent-credentials: + users: + - d5eda5e9-b59d-44ba-88d2-43473cb6e42d + +# vim: expandtab tabstop=2 shiftwidth=2 diff --git a/jobs/gerrit-verifications.yml b/jobs/gerrit-verifications.yml index 5235525..0e64ff3 100644 --- a/jobs/gerrit-verifications.yml +++ b/jobs/gerrit-verifications.yml @@ -458,6 +458,19 @@ echo "PIPELINE_LINT_PASSED=${{env.PIPELINE_LINT_PASSED}}" echo "PIPELINE_DEB_PASSED=${{env.PIPELINE_DEB_PASSED}}" echo "PIPELINE_RPM_PASSED=${{env.PIPELINE_RPM_PASSED}}" + + // Run the result job to get successful/failed links and add a + // comment + vote to gerrit + script {{ + build job: 'gerrit-pipeline-result', parameters: [ + string(name: "GERRIT_BRANCH", value: "${{env.GERRIT_BRANCH}}"), + string(name: "GERRIT_HOST", value: "${{env.GERRIT_HOST}}"), + string(name: "GERRIT_PATCHSET_REVISION", value: "${{env.GERRIT_PATCHSET_REVISION}}"), + string(name: "GERRIT_PORT", value: "${{env.GERRIT_PORT}}"), + string(name: "GERRIT_REFSPEC", value: "${{env.GERRIT_REFSPEC}}"), + string(name: "PIPELINE_BUILD_URL", value: "${{env.BUILD_URL}}"), + ] + }} }} }} // The end result is success if all started jobs were successful, @@ -509,7 +522,7 @@ failed: false unstable: false notbuilt: false - silent: false + silent: true # comment + vote is done in gerrit-pipeline-result.yml escape-quotes: false no-name-and-email: false trigger-for-unreviewed-patches: true diff --git a/scripts/jenkins-gerrit/pipeline_summary.py b/scripts/jenkins-gerrit/pipeline_summary.py new file mode 100755 index 0000000..c12ad64 --- /dev/null +++ b/scripts/jenkins-gerrit/pipeline_summary.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright 2022 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> +import argparse +import io +import json +import re +import urllib.request + +jenkins_url = "https://jenkins.osmocom.org" +re_start_build = re.compile("Starting building: gerrit-[a-zA-Z-_]* #[0-9]*") +re_result = re.compile("^PIPELINE_[A-Z]*_PASSED=[01]$") + +def parse_args(): + parser = argparse.ArgumentParser( + description="Get a summary of failed / successful builds from the CI" + " pipeline we run for patches submitted to gerrit.") + parser.add_argument("build_url", + help="$BUILD_URL of the pipeline job, e.g." + " https://jenkins.osmocom.org/jenkins/job/gerrit-osmo-bsc-nat/17/") + parser.add_argument("-o", "--output", help="output json file") + return parser.parse_args() + + +def stage_from_job_name(job_name): + if job_name == "gerrit-pipeline-result": + # The job that runs this script. Don't include it in the summary. + return None + if job_name == "gerrit-lint": + return "lint" + if job_name == "gerrit-binpkgs-deb": + return "deb" + if job_name == "gerrit-binpkgs-rpm": + return "rpm" + if job_name.endswith("-build"): + return "build" + assert False, f"couldn't figure out stage from job_name: {job_name}" + + +def parse_pipeline(build_url): + """ Parse started jobs and result from the pipeline log. + :returns: a dict that looks like: + {"build": {"name": "gerrit-osmo-bsc-nat-build", id=7, + "passed": True, "url": "https://..."}, + "lint": {...}, + "deb": {...}, + "rpm: {...}} """ + global re_start_build + global re_result + global jenkins_url + ret = {} + + url = f"{build_url}/consoleText" + with urllib.request.urlopen(url) as response: + for line in io.TextIOWrapper(response, encoding='utf-8'): + # Parse start build lines + for match in re_start_build.findall(line): + job_name = match.split(" ")[2] + job_id = int(match.split(" ")[3].replace("#", "")) + job_url = f"{jenkins_url}/jenkins/job/{job_name}/{job_id}" + stage = stage_from_job_name(job_name) + if stage: + ret[stage] = {"url": job_url, "name": job_name, "id": job_id} + + # Parse result lines + if re_result.match(line): + stage = line.split("_")[1].lower() + passed = line.split("=")[1].rstrip() == "1" + ret[stage]["passed"] = passed + + return ret + + +def parse_build_matrix(job): + """ Parse started jobs and result from the matrix of the build job. Usually + it is only one job, but for some projects we build for multiple arches + (x86_64, arm) or build multiple times with different configure flags. + :param job: "build" dict from parse_pipeline() + :returns: a list of jobs in the matrix, looks like: + [{"passed": True, "url": "https://..."}, ...] + """ + global jenkins_url + + ret = [] + url = f"{job['url']}/consoleFull" + with urllib.request.urlopen(url) as response: + for line in io.TextIOWrapper(response, encoding='utf-8'): + if " completed with result " in line: + url = line.split("<a href='", 1)[1].split("'", 1)[0] + url = f"{jenkins_url}{url}{job['id']}" + result = line.split(" completed with result ")[1].rstrip() + passed = result == "SUCCESS" + ret += [{"passed": passed, "url": url}] + return ret + + +def jobs_for_summary(pipeline, build_matrix): + """ Sort the jobs from pipeline and build matrix into passed/failed lists. + :returns: a dict that looks like: + {"passed": [{"stage": "build", "url": "https://..."}, ...], + "failed": [...]} """ + ret = {"passed": [], "failed": []} + + # Build errors are most interesting, display them first + for job in build_matrix: + category = "passed" if job["passed"] else "failed" + ret[category] += [{"stage": "build", "url": job["url"]}] + + # Hide the build matrix job (we show the jobs started by it instead), as + # long as there is at least one failed started job when the matrix failed + matrix_failed = "build" in pipeline and not pipeline["build"]["passed"] + show_build_matrix_job = matrix_failed and not ret["failed"] + + # Add jobs from the pipeline + for stage, job in pipeline.items(): + if stage == "build" and not show_build_matrix_job: + continue + category = "passed" if job["passed"] else "failed" + ret[category] += [{"stage": stage, "url": job["url"]}] + + return ret + + +def get_jobs_list_str(jobs): + ret = "" + for job in jobs: + ret += f" [{job['stage']}] {job['url']}/consoleFull\n" + return ret + + +def get_pipeline_summary(build_url): + """ Generate a summary of failed and successful builds for gerrit. + :returns: a dict that is expected by gerrit's set-review api, e.g. + {"tag": "jenkins", + "message": "...", + "labels": {"Code-Review": -1}} """ + summary = "" + pipeline = parse_pipeline(build_url) + + build_matrix = [] + if "build" in pipeline: + build_matrix = parse_build_matrix(pipeline["build"]) + + jobs = jobs_for_summary(pipeline, build_matrix) + + if jobs["failed"]: + summary += f"{len(jobs['failed'])} failed:\n" + summary += get_jobs_list_str(jobs["failed"]) + summary += "\n" + + summary += f"{len(jobs['passed'])} passed:\n" + summary += get_jobs_list_str(jobs["passed"]) + + if "lint" in pipeline and not pipeline["lint"]["passed"]: + summary += "\n" + summary += "Please fix the linting errors. More information:\n" + summary += "https://osmocom.org/projects/cellular-infrastructure/wiki/Linting\n" + + summary += "\n" + if jobs["failed"]: + summary += "Build Failed\n" + vote = -1 + else: + summary += "Build Successful\n" + vote = 1 + + # Reference: + # https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review + return {"tag": "jenkins", + "message": summary, + "labels": {"Verified": vote}} + + +def main(): + args = parse_args() + summary = get_pipeline_summary(args.build_url) + + print(summary["message"]) + + if args.output: + with open(args.output, "w") as handle: + json.dump(summary, handle, indent=4) + +if __name__ == "__main__": + main() diff --git a/scripts/jenkins-gerrit/pipeline_summary_send.sh b/scripts/jenkins-gerrit/pipeline_summary_send.sh new file mode 100755 index 0000000..d52ad9f --- /dev/null +++ b/scripts/jenkins-gerrit/pipeline_summary_send.sh @@ -0,0 +1,13 @@ +#!/bin/sh -ex + +./pipeline_summary.py "$PIPELINE_BUILD_URL" -o gerrit_report.json + +ssh \ + -p "$GERRIT_PORT" \ + -l jenkins \ + "$GERRIT_HOST" \ + gerrit \ + review \ + "$GERRIT_PATCHSET_REVISION" \ + --json \ + < gerrit_report.json |