sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #04921
[Merge] ~alexsander-souza/maas/+git/maas-release-tools:improve_help into ~maas-committers/maas/+git/maas-release-tools:main
Alexsander de Souza has proposed merging ~alexsander-souza/maas/+git/maas-release-tools:improve_help into ~maas-committers/maas/+git/maas-release-tools:main.
Commit message:
Add fix instructions
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~alexsander-souza/maas/+git/maas-release-tools/+merge/436480
--
Your team MAAS Committers is requested to review the proposed merge of ~alexsander-souza/maas/+git/maas-release-tools:improve_help into ~maas-committers/maas/+git/maas-release-tools:main.
diff --git a/maas_release_tools/git.py b/maas_release_tools/git.py
index 5e377c0..14420d7 100644
--- a/maas_release_tools/git.py
+++ b/maas_release_tools/git.py
@@ -69,6 +69,20 @@ class Git:
output = self._run(*run_args).output
return output.split("\n")
+ def get_commit_count(self, ref: str) -> str:
+ result = self._run("rev-list", "--count", ref)
+ return not result.succeeded
+
+ def list_changes(self) -> list[str]:
+ result = self._run("status", "-suno").output
+ return result.split("\n")
+
+ def get_user(self) -> str:
+ return self._run("config", "user.name").output
+
+ def get_email(self) -> str:
+ return self._run("config", "user.email").output
+
def _run(self, *args) -> GitCommandResult:
proc = subprocess.run(
["git", *args],
diff --git a/maas_release_tools/launchpad.py b/maas_release_tools/launchpad.py
index f070d9a..7ea3191 100644
--- a/maas_release_tools/launchpad.py
+++ b/maas_release_tools/launchpad.py
@@ -8,10 +8,13 @@ from pathlib import Path
from typing import List, Optional, Sequence
from launchpadlib.launchpad import Launchpad
+from lazr.restfulclient.errors import NotFound
DONE_BUGS = ("Invalid", "Won't Fix", "Fix Committed", "Fix Released")
UNFINISHED_BUGS = ("New", "Confirmed", "Triaged", "In Progress", "Incomplete")
+MAAS_USER = "maas-committers"
+
class UnknownLaunchpadEntry(Exception):
def __init__(self, entry_type: str, identifier: str):
@@ -42,6 +45,19 @@ class LaunchpadActions:
"""Return the logged in user user from LP."""
return self.lp.me
+ @cached_property
+ def maas(self):
+ """Return the MAAS user from LP."""
+ return self.lp.people[MAAS_USER]
+
+ def snap_builder_exist(self, builder_name: str) -> bool:
+ try:
+ _ = self.lp.snaps.getByName(name=builder_name, owner=self.maas)
+ except NotFound:
+ return False
+ else:
+ return True
+
def move_done_bugs(
self,
origin_milestone: str,
diff --git a/maas_release_tools/scripts/release_status.py b/maas_release_tools/scripts/release_status.py
index ca722d0..42ca459 100644
--- a/maas_release_tools/scripts/release_status.py
+++ b/maas_release_tools/scripts/release_status.py
@@ -18,8 +18,9 @@ import base64
from functools import lru_cache
import glob
import json
+import os
import sys
-from typing import Iterable, Optional
+from typing import Iterable, Optional, Tuple
from debian.changelog import Changelog
from lazr.restfulclient.errors import NotFound
@@ -28,7 +29,12 @@ import requests
from . import convert_file_descriptors_to_path
from ..git import Git
-from ..launchpad import DONE_BUGS, LaunchpadActions, UnknownLaunchpadEntry
+from ..launchpad import (
+ DONE_BUGS,
+ LaunchpadActions,
+ MAAS_USER,
+ UnknownLaunchpadEntry,
+)
from ..maasci import (
JenkinsActions,
JenkinsConnectionFailed,
@@ -99,28 +105,40 @@ class ReleasePreparer:
snapstore_auth,
launchpad: LaunchpadActions,
jenkins: JenkinsActions,
+ keep_going: bool,
):
self.launchpad = launchpad
self.jenkins = jenkins
self.version = version
+ self.keep_going = keep_going
self.snapstore_auth = snapstore_auth
self.git_short_rev = Git().get_short_rev("HEAD")
def run(self, args):
all_good = True
for step in self.steps:
+ how_to_fix = None
if step.skip():
continue
print(step.title, end=": ")
success, message = step.check()
if not success:
+ success, how_to_fix = step.fix()
+
+ if not success:
all_good = False
print("\N{Cross Mark}")
else:
print("\N{White Heavy Check Mark}")
+
if message:
for line in message.splitlines():
print(" " + str(line))
+ if how_to_fix:
+ for line in how_to_fix:
+ print(" \N{Radio Button} " + f"{line}")
+ if not (all_good or self.keep_going):
+ break
print()
if all_good:
@@ -144,7 +162,7 @@ class ReleaseStep(ABC):
return False
@abstractmethod
- def check(self):
+ def check(self) -> Tuple[bool, str]:
"""Return whether the step has already been performed.
It returns a tuple of (succeeded, message), where result is a
@@ -152,58 +170,87 @@ class ReleaseStep(ABC):
multi-line message to be displayed.
"""
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ """Return instructions to fix the issue.
+
+ When `doit` is True and this ReleaseStep can be fixed automatically, this
+ method should execute the procedure.
+
+ Returns a tuple of (fixed, how_to_fix), where fixed is True if the fix was
+ sucessful, and False when this failure could not be recovered. how_to_fix is
+ an array with manual steps the user should do to fix this.
+ """
+ return (
+ False,
+ [
+ "I don't know how to fix this \U0001F979",
+ "Please improve maas-release-tools",
+ ],
+ )
+
class NoUncommittedChanges(ReleaseStep):
@property
def title(self):
return "No uncommitted changes"
- def check(self):
+ def check(self) -> Tuple[bool, str]:
if self.git.has_uncommited_changes():
- return False, "Commit and push all changes before releasing."
+ return False, "Your local repository is not clean"
else:
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ return False, [
+ "Either commit or stash your changes:",
+ *self.git.list_changes(),
+ ]
+
class CommitInRemoteBranch(ReleaseStep):
+ def __init__(self, preparer):
+ super().__init__(preparer)
+ self.release_branch_name = self.preparer.version.major
+ if self.preparer.version.grade in ("alpha", "beta"):
+ # alpha and beta releases are released from master
+ self.release_branch_name = "master"
+ self.official_maas_remote = get_official_maas_remote(self.git)
+
@property
def title(self):
return "Release commit in remote branch"
- def check(self):
- release_branch_name = self.preparer.version.major
- if self.preparer.version.grade in ("alpha", "beta"):
- # alpha and beta releases are released from master
- release_branch_name = "master"
- official_maas_remote = get_official_maas_remote(self.git)
- if not official_maas_remote:
+ def check(self) -> Tuple[bool, str]:
+ if not self.official_maas_remote:
return False, "Official MAAS remote not found"
remote_branches = self.git.get_remote_branches_containing("HEAD")
for remote, branch_name in remote_branches:
if (
- remote == official_maas_remote
- and branch_name == release_branch_name
+ remote == self.official_maas_remote
+ and branch_name == self.release_branch_name
):
return True, None
else:
error_message = (
"Current HEAD is not in "
- f"{official_maas_remote}/{release_branch_name}"
+ f"{self.official_maas_remote}/{self.release_branch_name}"
)
return False, error_message
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ steps = [f"git checkout {self.release_branch_name}"]
+ return False, steps
+
class MAASVersion(ReleaseStep):
@property
def title(self):
return "MAAS version set in branch"
- def check(self):
+ def check(self) -> Tuple[bool, str]:
setup_version = get_branch_setup_version()
if setup_version != self.preparer.version.python_version:
- error_message = (
- f"setup.cfg has {setup_version}, run 'release-prepare'"
- )
+ error_message = f"setup.cfg has {setup_version} (expected {self.preparer.version.python_version})"
return False, error_message
deb_ver = self.preparer.version.deb_version
with open("debian/changelog", "r") as fh:
@@ -215,6 +262,22 @@ class MAASVersion(ReleaseStep):
)
return True, None
+ def fix(self, doit=False):
+ if self.preparer.version.grade == "beta":
+ branch = "master"
+ else:
+ branch = self.preparer.version.major
+ return False, [
+ "Go to http://maas-ci.internal:8080/job/maas-version-bump/build",
+ f"Set 'LP_BRANCH' to '{branch}'",
+ f"Set 'RELEASE_VERSION' to '{self.preparer.version.python_version}'",
+ f"Set 'DEBFULLNAME' to '{self.git.get_user()}' *",
+ f"Set 'DEBEMAIL' to '{self.git.get_email()}' *",
+ " * Your user and email should match your GPG key",
+ "Press 'Build'",
+ "run 'git pull --recurse-submodules' to update the local repository",
+ ]
+
class SnapTrack(ReleaseStep):
def __init__(self, preparer, snap_name):
@@ -243,6 +306,45 @@ class SnapTrack(ReleaseStep):
auth_error = get_macaroon_auth_error(res, self.snap_name)
return res.status_code == 200, auth_error
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ procedure = [
+ "Go to the snapstore discourse forum (https://forum.snapcraft.io/).",
+ "Post a message requesting a new track, use this template: https://forum.snapcraft.io/t/3-3-tracks-for-maas-and-maas-test-db/32141.",
+ "Be *sure* to update the version number and associated regex, and set the tag to `store-requests`.",
+ "Pay attention to the forum for a response. It may take some time, in some cases.",
+ ]
+ return False, procedure
+
+
+class PPACopyMixin:
+ def check_packages_copied(self, source_ppa, target_ppa):
+ target_packages = list(
+ (package.source_package_name, package.source_package_version)
+ for package in target_ppa.getPublishedSources(
+ status="Published", distro_series=self.current_series
+ )
+ )
+ missing_packages = set()
+ for package in source_ppa.getPublishedSources(
+ status="Published", distro_series=self.current_series
+ ):
+ name, version = (
+ package.source_package_name,
+ package.source_package_version,
+ )
+ if (name, version) not in target_packages:
+ missing_packages.add((name, version))
+
+ if missing_packages:
+ error_message = "\n".join(
+ f"{name} {version} has not been copied"
+ for name, version in sorted(missing_packages)
+ )
+ # error_message += f"\nGo to {source_ppa.web_link}/+copy-packages"
+ return False, error_message
+ else:
+ return True, None
+
class MAASPPA(ReleaseStep):
def __init__(self, preparer, ppa_type):
@@ -261,56 +363,51 @@ class MAASPPA(ReleaseStep):
self.current_series = ubuntu.getSeries(
name_or_version=get_ubuntu_series()
)
+ self.ppa = None
@property
def title(self):
return f"MAAS {self.ppa_type} PPA ({self.ppa_path})"
- def check(self):
+ def load_ppa(self):
try:
- ppa = self.ppa_owner.getPPAByName(name=self.ppa_name)
+ self.ppa = self.ppa_owner.getPPAByName(name=self.ppa_name)
except NotFound:
+ return False
+ else:
+ return True
+
+ def check(self):
+ if not self.load_ppa():
return (
False,
f"ppa:{self.ppa_owner.name}/{self.ppa_name} couldn't be found.",
)
- else:
- ppa_archs = set(processor.name for processor in ppa.processors)
- missing_archs = sorted(set(BUILD_ARCHS).difference(ppa_archs))
- if missing_archs:
- return False, (
- f"Missing build architectures: {', '.join(missing_archs)}"
- )
- return True, None
-
- def _check_packages_copied(self, source_ppa, target_ppa):
- target_packages = list(
- (package.source_package_name, package.source_package_version)
- for package in target_ppa.getPublishedSources(
- status="Published", distro_series=self.current_series
- )
- )
- missing_packages = set()
- for package in source_ppa.getPublishedSources(
- status="Published", distro_series=self.current_series
- ):
- name, version = (
- package.source_package_name,
- package.source_package_version,
+ ppa_archs = set(processor.name for processor in self.ppa.processors)
+ missing_archs = sorted(set(BUILD_ARCHS).difference(ppa_archs))
+ if missing_archs:
+ return False, (
+ f"Missing build architectures: {', '.join(missing_archs)}"
)
- if (name, version) not in target_packages:
- missing_packages.add((name, version))
+ return True, None
- if missing_packages:
- error_message = "\n".join(
- f"{name} {version} has not been copied"
- for name, version in sorted(missing_packages)
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ steps = []
+ if self.ppa is None:
+ steps.append(
+ f"Go to https://launchpad.net/~{self.ppa_owner.name}/+activate-ppa"
)
- error_message += f"\nGo to {source_ppa.web_link}/+copy-packages"
- return False, error_message
+ steps.append(f"Create '{self.ppa_name}' PPA")
else:
- return True, None
+ steps.append(
+ f"Go to https://launchpad.net/~{self.ppa_owner.name}/+archive/ubuntu/{self.ppa_name}/"
+ )
+ steps.append("Click `Change Details`")
+ steps.append(
+ f"Enable the following processors: {', '.join(sorted(set(BUILD_ARCHS)))}"
+ )
+ return False, steps
class MAASPackagePublished(MAASPPA):
@@ -322,56 +419,54 @@ class MAASPackagePublished(MAASPPA):
return f"MAAS package published in ({self.ppa_path})"
def check(self):
- try:
- ppa = self.ppa_owner.getPPAByName(name=self.ppa_name)
- except NotFound:
+ if not self.load_ppa():
return (
False,
f"ppa:{self.ppa_path} couldn't be found.",
)
- else:
- sources = list(
- ppa.getPublishedSources(
- source_name="maas",
- status="Published",
- distro_series=self.current_series,
- )
+
+ sources = list(
+ self.ppa.getPublishedSources(
+ source_name="maas",
+ status="Published",
+ distro_series=self.current_series,
)
- if not sources:
- return False, (
- "Source package hasn't been published or uploaded yet."
- )
- [package] = sources
- if not self._check_version(package.source_package_version):
- expected = self.preparer.version.deb_version
- return False, (
- f"Currently published source version is {package.source_package_version}. Expected {expected}"
- )
- binaries = list(
- ppa.getPublishedBinaries(
- binary_name="maas",
- exact_match=True,
- status="Published",
- )
+ )
+ if not sources:
+ return False, (
+ "Source package hasn't been published or uploaded yet."
)
- if not binaries:
- return False, "Binary packages haven't been published yet."
- published_architectures = set()
- for binary in binaries:
- arch = binary.distro_arch_series_link.split("/")[-1]
- if self._check_version(binary.binary_package_version):
- published_architectures.add(arch)
-
- non_published_architectures = sorted(
- set(BUILD_ARCHS).difference(published_architectures)
+ [package] = sources
+ if not self._check_version(package.source_package_version):
+ expected = self.preparer.version.deb_version
+ return False, (
+ f"Currently published source version is {package.source_package_version}. Expected {expected}"
+ )
+ binaries = list(
+ self.ppa.getPublishedBinaries(
+ binary_name="maas",
+ exact_match=True,
+ status="Published",
+ )
+ )
+ if not binaries:
+ return False, "Binary packages haven't been published yet."
+ published_architectures = set()
+ for binary in binaries:
+ arch = binary.distro_arch_series_link.split("/")[-1]
+ if self._check_version(binary.binary_package_version):
+ published_architectures.add(arch)
+
+ non_published_architectures = sorted(
+ set(BUILD_ARCHS).difference(published_architectures)
+ )
+ if non_published_architectures:
+ return False, (
+ "Binary package hasn't been published for: "
+ f"{non_published_architectures}"
)
- if non_published_architectures:
- return False, (
- "Binary package hasn't been published for: "
- f"{non_published_architectures}"
- )
- return True, None
+ return True, None
def _check_version(self, package_version):
expected_package_version = self.preparer.version.deb_version
@@ -383,41 +478,53 @@ class MAASPackagePublished(MAASPPA):
and version_parts[2] == f"g.{self.preparer.git_short_rev}"
)
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ if self.ppa is None:
+ return super().fix(doit)
+ return False, [
+ f"Check https://launchpad.net/~/+archive/ubuntu/{self.ppa_name}/+packages",
+ ]
+
-class PackagesCopiedFromDeps(MAASPPA):
+class PackagesCopiedFromDeps(MAASPPA, PPACopyMixin):
def __init__(self, preparer):
super().__init__(preparer, "release-preparation")
+ self.source_ppa = None
+ self.target_ppa = None
@property
def title(self):
- return "Packages copied from ppa:maas-committers/latest-deps"
+ return f"Packages copied from ppa:{MAAS_USER}/latest-deps"
def check(self):
try:
- source_ppa = self.preparer.launchpad.lp.people[
- "maas-committers"
- ].getPPAByName(name="latest-deps")
+ self.source_ppa = self.preparer.launchpad.maas.getPPAByName(
+ name="latest-deps"
+ )
except NotFound:
- return False, "ppa:maas-committers/latest-deps couldn't be found."
+ return False, f"ppa:{MAAS_USER}/latest-deps couldn't be found."
try:
- target_ppa = self.ppa_owner.getPPAByName(name=self.ppa_name)
+ self.target_ppa = self.ppa_owner.getPPAByName(name=self.ppa_name)
except NotFound:
return (
False,
f"ppa:{self.ppa_path} couldn't be found.",
)
else:
- return self._check_packages_copied(source_ppa, target_ppa)
+ return self.check_packages_copied(self.source_ppa, self.target_ppa)
+ # def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
-class PackagesCopiedToReleasePPA(MAASPPA):
+
+class PackagesCopiedToReleasePPA(MAASPPA, PPACopyMixin):
@property
def title(self):
return f"Packages copied to ppa:{self.ppa_path}"
def skip(self):
return (
- self.preparer.version.grade == "beta" and self.ppa_type == "stable"
+ self.preparer.version.grade != "stable"
+ and self.ppa_type == "stable"
)
def check(self):
@@ -441,7 +548,7 @@ class PackagesCopiedToReleasePPA(MAASPPA):
f"ppa:{self.ppa_path} couldn't be found.",
)
else:
- return self._check_packages_copied(source_ppa, target_ppa)
+ return self.check_packages_copied(source_ppa, target_ppa)
def macaroon_auth(macaroons):
@@ -472,11 +579,19 @@ class PackageBuilt(ReleaseStep):
if len(tar_gzs) == 0:
return False, (
"No orig.tar.gz could be found for the current revision.\n"
- "Run release-build."
)
[orig_tgz] = tar_gzs
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ return False, [
+ f"export DEBFULLNAME='{self.git.get_user()}'",
+ f"export DEBEMAIL='{self.git.get_email()}'",
+ "make install-dependencies",
+ f"{os.path.dirname(sys.argv[0])}/release-build {get_ubuntu_series()}",
+ f"dput ppa:{self.preparer.launchpad.me.name}/maas-{self.preparer.version.major}-next build_pkg/maas_{self.preparer.version.deb_version}-*_source.changes",
+ ]
+
class SnapsUploaded(ReleaseStep):
@@ -486,6 +601,7 @@ class SnapsUploaded(ReleaseStep):
def title(self):
return "Snaps have been built and uploaded to the store."
+ @lru_cache(maxsize=1)
def _get_revisisions(self):
# XXX: This considers only the last 500 uploaded revisions. That's fine
# if you're currently working on the release, but it will
@@ -536,6 +652,28 @@ class SnapsUploaded(ReleaseStep):
return True, "\n".join(revision_info)
+ def fix(self, doit=False):
+ builder = f"maas-{self.preparer.version.major}"
+ steps = []
+ if self.preparer.launchpad.snap_builder_exist(builder):
+ steps.append(
+ f"check the snap package status at https://launchpad.net/~{MAAS_USER}/+snap/{builder}"
+ )
+ else:
+ steps.extend(
+ [
+ f" go to https://code.launchpad.net/~{MAAS_USER}/maas/+git/maas/+ref/{self.preparer.version.major}/+new-snap",
+ f" * snap recipe name: maas-{self.preparer.version.major}",
+ " * owner: maas",
+ f" * processors: {', '.join(BUILD_ARCHS)}",
+ " * Automatically build when branch changes",
+ f" * Source archive for automatic builds: ~maas/ubuntu/{self.preparer.version.major}-next",
+ " * Automatically upload to store",
+ f" * Track: {self.preparer.version.major} Risk: Edge",
+ ]
+ )
+ return False, steps
+
class SnapsInChannel(SnapsUploaded):
@@ -544,6 +682,7 @@ class SnapsInChannel(SnapsUploaded):
def __init__(self, preparer, channel):
super().__init__(preparer)
self.channel = channel
+ self.missing_archs = []
@property
def title(self):
@@ -561,11 +700,32 @@ class SnapsInChannel(SnapsUploaded):
released_archs.add(arch)
break
- missing_archs = sorted(set(BUILD_ARCHS).difference(released_archs))
- if missing_archs:
- return False, (f"Missing releases for: {', '.join(missing_archs)}")
+ self.missing_archs = sorted(
+ set(BUILD_ARCHS).difference(released_archs)
+ )
+ if self.missing_archs:
+ return False, (
+ f"Missing releases for: {', '.join(self.missing_archs)}"
+ )
return True, None
+ def fix(self, doit=False):
+ steps = []
+ revision_map, _ = self._get_revisisions()
+
+ for arch in self.missing_archs:
+ if revision_map is None or len(revision_map.get(arch, [])) == 0:
+ steps.append(f"Release for {arch} not built yet, check above.")
+ else:
+ latest_revision = max(
+ revision["revision"] for revision in revision_map[arch]
+ )
+ steps.append(
+ f"snapcraft release maas {latest_revision} {self.channel}"
+ )
+
+ return False, steps
+
class ReleaseTagged(ReleaseStep):
@property
@@ -598,6 +758,12 @@ class ReleaseTagged(ReleaseStep):
return True, None
+ def fix(self, doit=False):
+ return False, [
+ f"git tag {self.preparer.version.version}",
+ "git push --tags",
+ ]
+
class VersionBranch(ReleaseStep):
def __init__(
@@ -617,7 +783,7 @@ class VersionBranch(ReleaseStep):
@property
def _branch_version(self) -> str:
drop_idx = self.preparer.version.version.rfind(".")
- return self.preparer.version.version[:drop_idx]
+ return str(self.preparer.version.version[:drop_idx])
@property
def _ref_version(self) -> str:
@@ -654,21 +820,32 @@ class MilestoneExist(ReleaseStep):
else:
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, Optional[list[str]]]:
+ return False, [
+ f"go to https://launchpad.net/maas/{self.preparer.version.major}/+addmilestone",
+ f"Create {self.preparer.version.version} milestone",
+ ]
+
class BugMovedToMilestone(ReleaseStep):
+ def __init__(self, preparer):
+ super().__init__(preparer)
+ self._ms_found = False
+
@property
def title(self):
return "Bugs moved to Milestone on Launchpad"
def check(self):
- tag_name = self.preparer.version.version
+ tag_name = self.preparer.version.final_version
try:
ms = self.preparer.launchpad._get_milestone(tag_name)
+ self._ms_found = True
bug_tasks = ms.searchTasks(status=DONE_BUGS)
- if len(bug_tasks) == 0:
+ if len(bug_tasks) > 0:
return (
False,
- f"Bugs not copied to milestone {tag_name}, use 'release-manage move-done-bugs' to fix this",
+ "Bugs not copied to milestone.",
)
except UnknownLaunchpadEntry:
return (
@@ -678,8 +855,19 @@ class BugMovedToMilestone(ReleaseStep):
else:
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, Optional[list[str]]]:
+ if not self._ms_found:
+ return False, ["Pre-requesites check has failed, check above"]
+ return False, [
+ f"run {os.path.dirname(sys.argv[0])}/release-manage maas move-done-bugs {self.preparer.version.final_version} {self.preparer.version.version}"
+ ]
+
class MilestoneReleased(ReleaseStep):
+ def __init__(self, preparer):
+ super().__init__(preparer)
+ self._ms_found = False
+
@property
def title(self):
return "Milestone released on Launchpad"
@@ -688,10 +876,11 @@ class MilestoneReleased(ReleaseStep):
tag_name = self.preparer.version.version
try:
ms = self.preparer.launchpad._get_milestone(tag_name)
- if ms.is_active or len(ms.searchTasks(status="Fix Committed")) > 0:
+ self._ms_found = True
+ if ms.is_active:
return (
False,
- "Milestone not released, use 'release-manage release-milestone' to fix this",
+ "Milestone not released",
)
except UnknownLaunchpadEntry:
return (
@@ -701,6 +890,13 @@ class MilestoneReleased(ReleaseStep):
else:
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, Optional[list[str]]]:
+ if not self._ms_found:
+ return False, ["Pre-requesites check has failed, check above"]
+ return False, [
+ f"run {os.path.dirname(sys.argv[0])}/release-manage maas release-milestone {self.preparer.version.version}'"
+ ]
+
class SystemIntegrationTests(ReleaseStep):
def __init__(
@@ -710,6 +906,7 @@ class SystemIntegrationTests(ReleaseStep):
):
super().__init__(preparer)
self._job = job_name
+ self._url = None
@property
def title(self):
@@ -717,13 +914,16 @@ class SystemIntegrationTests(ReleaseStep):
def check(self):
try:
- result, url = self.preparer.jenkins.get_last_build_result_for_rev(
+ (
+ result,
+ self._url,
+ ) = self.preparer.jenkins.get_last_build_result_for_rev(
self._job, self.preparer.git_short_rev
)
if result == JJB_FAILURE:
return (
False,
- f"Last build has failed, check {url}",
+ "Last build has failed (this is not a fatal error)",
)
except JenkinsConnectionFailed:
return (
@@ -733,11 +933,21 @@ class SystemIntegrationTests(ReleaseStep):
else:
return True, None
+ def fix(self, doit: bool = False) -> Tuple[bool, list[str]]:
+ # Not fatal
+ return True, [f"check {self._url}"]
+
def parse_args():
parser = ArgumentParser(description=__doc__)
parser.add_argument("version", help="The version of MAAS to be released")
parser.add_argument(
+ "--keep-going",
+ action="store_true",
+ dest="keep_going",
+ help="Don't stop on first error",
+ )
+ parser.add_argument(
"--dry-run",
action="store_true",
dest="dry_run",
@@ -793,6 +1003,7 @@ def main():
macaroon_auth(macaroons),
launchpad=launchpad,
jenkins=jenkins,
+ keep_going=args.keep_going,
)
preparer.steps = [
MAASVersion(preparer),
@@ -805,7 +1016,7 @@ def main():
),
VersionBranch(
preparer,
- f"git+ssh://{launchpad.lp.me.name}@git.launchpad.net/~maas-committers/maas/+git/maas-test-db",
+ f"git+ssh://{launchpad.lp.me.name}@git.launchpad.net/~{MAAS_USER}/maas/+git/maas-test-db",
),
SnapTrack(preparer, "maas"),
SnapTrack(preparer, "maas-test-db"),
diff --git a/maas_release_tools/version.py b/maas_release_tools/version.py
index deb29bf..cd5fbca 100644
--- a/maas_release_tools/version.py
+++ b/maas_release_tools/version.py
@@ -29,6 +29,7 @@ class ReleaseVersion:
self.grade = self._grade()
self.snap_channels = self._snap_channels()
self.deb_version = self.version.replace("-", "~")
+ self.final_version = self._final()
def _python_version(self) -> Version:
string_version = (
@@ -48,6 +49,11 @@ class ReleaseVersion:
else:
raise InvalidReleaseVersion(f"Unknown version suffix: {suffix}")
+ def _final(self) -> str:
+ if "-" not in self.version:
+ return self.version
+ return self.version.split("-")[0]
+
def _snap_channels(self) -> Iterable[str]:
grade_map = {
"final": "stable",
Follow ups