diff --git a/tests/ci/release.py b/tests/ci/release.py index fd4bda3eae4..8024091e300 100755 --- a/tests/ci/release.py +++ b/tests/ci/release.py @@ -1,5 +1,14 @@ #!/usr/bin/env python +""" +script to create releases for ClickHouse + +The `gh` CLI prefered over the PyGithub to have an easy way to rollback bad +release in command line by simple execution giving rollback commands + +On another hand, PyGithub is used for convenient getting commit's status from API +""" + from contextlib import contextmanager from typing import List, Optional @@ -8,6 +17,8 @@ import logging import subprocess from git_helper import commit, release_branch +from github_helper import GitHub +from mark_release_ready import RELEASE_READY_STATUS from version_helper import ( FILE_WITH_VERSION_PATH, GENERATED_CONTRIBUTORS, @@ -67,12 +78,12 @@ class Release: self._release_branch = "" self._rollback_stack = [] # type: List[str] - def run(self, cmd: str, cwd: Optional[str] = None) -> str: + def run(self, cmd: str, cwd: Optional[str] = None, **kwargs) -> str: cwd_text = "" if cwd: cwd_text = f" (CWD='{cwd}')" logging.info("Running command%s:\n %s", cwd_text, cmd) - return self._git.run(cmd, cwd) + return self._git.run(cmd, cwd, **kwargs) def set_release_branch(self): # Fetch release commit in case it does not exist locally @@ -94,6 +105,38 @@ class Release: return VersionType.LTS return VersionType.STABLE + def check_commit_release_ready(self): + # First, get the auth token from gh cli + auth_status = self.run( + "gh auth status -t", stderr=subprocess.STDOUT + ).splitlines() + token = "" + for line in auth_status: + if "✓ Token:" in line: + token = line.split()[-1] + if not token: + logging.error("Can not extract token from `gh auth`") + raise subprocess.SubprocessError("Can not extract token from `gh auth`") + gh = GitHub(token, per_page=100) + repo = gh.get_repo(str(self.repo)) + + # Statuses are ordered by descending updated_at, so the first necessary + # status in the list is the most recent + statuses = repo.get_commit(self.release_commit).get_statuses() + for status in statuses: + if status.context == RELEASE_READY_STATUS: + if status.state == "success": + return + + raise Exception( + f"the status {RELEASE_READY_STATUS} is {status.state}, not success" + ) + + raise Exception( + f"the status {RELEASE_READY_STATUS} " + f"is not found for commit {self.release_commit}" + ) + def check_prerequisites(self): """ Check tooling installed in the system, `git` is checked by Git() init @@ -108,6 +151,8 @@ class Release: ) raise + self.check_commit_release_ready() + def do(self, check_dirty: bool, check_branch: bool, with_release_branch: bool): self.check_prerequisites()