← Back to team overview

sts-sponsors team mailing list archive

[Merge] ~alexsander-souza/maas/+git/maas-release-tools:add_jenkins into ~maas-committers/maas/+git/maas-release-tools:main

 

Alexsander de Souza has proposed merging ~alexsander-souza/maas/+git/maas-release-tools:add_jenkins into ~maas-committers/maas/+git/maas-release-tools:main.

Commit message:
add checks for MAAS system integration tests

Requested reviews:
  MAAS Committers (maas-committers)

For more details, see:
https://code.launchpad.net/~alexsander-souza/maas/+git/maas-release-tools/+merge/433614
-- 
Your team MAAS Committers is requested to review the proposed merge of ~alexsander-souza/maas/+git/maas-release-tools:add_jenkins into ~maas-committers/maas/+git/maas-release-tools:main.
diff --git a/maas_release_tools/maasci.py b/maas_release_tools/maasci.py
new file mode 100644
index 0000000..099df77
--- /dev/null
+++ b/maas_release_tools/maasci.py
@@ -0,0 +1,53 @@
+"""Interact with Jenkins API"""
+import configparser
+from functools import cached_property
+import logging
+from os.path import expanduser
+from pathlib import Path
+from typing import Optional, Tuple
+
+from jenkins import Jenkins
+
+JJB_CONFIG = Path("~/.config/jenkins_jobs/jenkins_jobs.ini")
+JJB_SECTION = "maas-integration-ci"
+
+
+class JenkinsActions:
+    def __init__(
+        self,
+        server_section: Optional[str] = None,
+        jenkins_config: Optional[Path] = None,
+        dry_run: bool = False,
+    ):
+        self._jenkins = self._get_client(
+            server_section=server_section, jenkins_config=jenkins_config
+        )
+        self.logger = logging.getLogger("jenkins")
+        self.dry_run = dry_run
+
+    def _get_client(
+        self,
+        server_section: Optional[str] = None,
+        jenkins_config: Optional[Path] = None,
+    ) -> Jenkins:
+        """Return a Jenkins API client."""
+        jenkins_config = jenkins_config or Path(expanduser(JJB_CONFIG))
+        server_section = server_section or JJB_SECTION
+        config = configparser.ConfigParser()
+        config.read(jenkins_config)
+        url = config[server_section]["url"]
+        kwargs = {
+            "username": config[server_section]["user"],
+            "password": config[server_section]["password"],
+        }
+        return Jenkins(url, **kwargs)
+
+    @cached_property
+    def me(self) -> str:
+        return self._jenkins.get_whoami()["id"]
+
+    def get_last_build_result(self, job_name: str) -> Tuple[str, str]:
+        job = self._jenkins.get_job_info(job_name)
+        last_build = job["lastCompletedBuild"]["number"]
+        build_info = self._jenkins.get_build_info(job_name, last_build)
+        return str(build_info["result"]), str(build_info["url"])
diff --git a/maas_release_tools/scripts/release_status.py b/maas_release_tools/scripts/release_status.py
index bcd7a75..ba2a9b0 100644
--- a/maas_release_tools/scripts/release_status.py
+++ b/maas_release_tools/scripts/release_status.py
@@ -23,6 +23,7 @@ import sys
 from typing import Iterable, Optional
 
 from debian.changelog import Changelog
+from jenkins import JenkinsException
 from lazr.restfulclient.errors import NotFound
 from pymacaroons import Macaroon
 import requests
@@ -30,6 +31,7 @@ import requests
 from . import convert_file_descriptors_to_path
 from ..git import Git
 from ..launchpad import DONE_BUGS, LaunchpadActions, UnknownLaunchpadEntry
+from ..maasci import JenkinsActions
 from ..version import get_branch_setup_version, ReleaseVersion
 
 MAAS_SNAP_ID = "shY22YTZ3RhJJDOj0MfmShTNZTEb1Jiq"
@@ -92,8 +94,10 @@ class ReleasePreparer:
         version: ReleaseVersion,
         snapstore_auth,
         launchpad: LaunchpadActions,
+        jenkins: JenkinsActions,
     ):
         self.launchpad = launchpad
+        self.jenkins = jenkins
         self.version = version
         self.snapstore_auth = snapstore_auth
         self.git_short_rev = Git().get_short_rev("HEAD")
@@ -416,7 +420,9 @@ class PackagesCopiedToReleasePPA(MAASPPA):
         return f"Packages copied to ppa:{self.ppa_path}"
 
     def skip(self):
-        return self.preparer.version.grade == "beta" and self.ppa_type == "stable"
+        return (
+            self.preparer.version.grade == "beta" and self.ppa_type == "stable"
+        )
 
     def check(self):
         sources = {
@@ -717,15 +723,64 @@ class DebianChangelogUpdated(ReleaseStep):
         )
 
 
+class SystemIntegrationTests(ReleaseStep):
+    def __init__(
+        self,
+        preparer: ReleasePreparer,
+        job_name: str,
+    ):
+        super().__init__(preparer)
+        self._job = job_name
+
+    @property
+    def title(self):
+        return f"System Integration '{self._job}' result"
+
+    def check(self):
+        try:
+            result, url = self.preparer.jenkins.get_last_build_result(
+                self._job
+            )
+            if result == "FAILURE":
+                return (
+                    False,
+                    f"Last build has failed, check {url}",
+                )
+        except JenkinsException:
+            return (
+                False,
+                "Failed to communicate with Jenkins, check your credentials",
+            )
+        else:
+            return True, None
+
+
 def parse_args():
     parser = ArgumentParser(description=__doc__)
     parser.add_argument("version", help="The version of MAAS to be released")
     parser.add_argument(
+        "--dry-run",
+        action="store_true",
+        dest="dry_run",
+        help="Don't execute actions",
+    )
+    parser.add_argument(
         "--launchpad-credentials",
         default=None,
         type=FileType(),
         help="Launchpad credentials file",
     )
+    parser.add_argument(
+        "--jenkins-config",
+        default=None,
+        type=FileType(),
+        help="Jenkins configuration file",
+    )
+    parser.add_argument(
+        "--jenkins-section",
+        default=None,
+        help="Jenkins server section name",
+    )
 
     ns = parser.parse_args()
     convert_file_descriptors_to_path(ns)
@@ -744,13 +799,21 @@ def main():
         return 1
 
     launchpad = LaunchpadActions(
-        "maas", credentials_file=args.launchpad_credentials
+        "maas",
+        credentials_file=args.launchpad_credentials,
+        dry_run=args.dry_run,
+    )
+    jenkins = JenkinsActions(
+        dry_run=args.dry_run,
+        jenkins_config=args.jenkins_config,
+        server_section=args.jenkins_section,
     )
     release_version = ReleaseVersion(args.version)
     preparer = ReleasePreparer(
         release_version,
         macaroon_auth(macaroons),
         launchpad=launchpad,
+        jenkins=jenkins,
     )
     preparer.steps = [
         MAASVersion(preparer),
@@ -779,6 +842,8 @@ def main():
             preparer,
             release_version.snap_channels[0] + "/release-prep",
         ),
+        SystemIntegrationTests(preparer, "maas-system-tests"),
+        SystemIntegrationTests(preparer, "maas-system-tests-snap"),
         PackagesCopiedToReleasePPA(preparer, "candidate"),
         *[
             SnapsInChannel(preparer, snap_channel)

Follow ups