2023-09-13 17:05:31 +00:00
|
|
|
import argparse
|
2023-09-13 23:33:04 +00:00
|
|
|
from datetime import timedelta, datetime
|
2023-09-14 10:23:26 +00:00
|
|
|
import logging
|
|
|
|
from typing import List, TypeVar
|
2023-09-14 12:03:01 +00:00
|
|
|
import os
|
2023-09-13 17:05:31 +00:00
|
|
|
import github
|
2023-09-14 10:23:26 +00:00
|
|
|
from commit_status_helper import get_commit_filtered_statuses
|
|
|
|
from get_robot_token import get_best_robot_token
|
2023-09-13 17:05:31 +00:00
|
|
|
from github_helper import GitHub
|
2023-09-14 11:28:35 +00:00
|
|
|
from release import Release, Repo as ReleaseRepo
|
2023-09-14 12:03:01 +00:00
|
|
|
from ssh import SSHKey
|
2023-09-13 17:05:31 +00:00
|
|
|
|
2023-09-14 10:23:26 +00:00
|
|
|
T = TypeVar("T", bound=github.GithubObject.GithubObject)
|
2023-09-13 17:05:31 +00:00
|
|
|
|
|
|
|
|
2023-09-13 23:33:04 +00:00
|
|
|
READY_FOR_RELEASE_CHECK_NAME = "Ready for release"
|
|
|
|
SUCCESS_STATUS = "success"
|
|
|
|
LOGGER_NAME = "auto_release"
|
|
|
|
HELPER_LOGGERS = ["github_helper", LOGGER_NAME]
|
|
|
|
logger = logging.getLogger(LOGGER_NAME)
|
|
|
|
|
|
|
|
|
2023-09-13 17:05:31 +00:00
|
|
|
def parse_args():
|
2023-09-13 23:33:04 +00:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
"Checks if enough days elapsed since the last release on each release branches and do a release in case for green builds."
|
|
|
|
)
|
|
|
|
parser.add_argument("--token", help="GitHub token, if not set, used from smm")
|
2023-09-13 17:05:31 +00:00
|
|
|
parser.add_argument(
|
2023-09-13 23:33:04 +00:00
|
|
|
"--repo", default="ClickHouse/ClickHouse", help="Repo owner/name"
|
2023-09-13 17:05:31 +00:00
|
|
|
)
|
2023-09-13 23:33:04 +00:00
|
|
|
parser.add_argument("--dry-run", action="store_true", help="Do not create anything")
|
2023-09-13 17:05:31 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--release-after-days",
|
2023-09-13 23:33:04 +00:00
|
|
|
type=int,
|
2023-09-13 17:05:31 +00:00
|
|
|
default=3,
|
2023-09-13 23:33:04 +00:00
|
|
|
help="Do automatic release on the latest green commit after the latest release if the newest release is older than the specified days",
|
2023-09-13 17:05:31 +00:00
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--debug-helpers",
|
|
|
|
action="store_true",
|
2023-09-13 23:33:04 +00:00
|
|
|
help="Add debug logging for this script and github_helper",
|
2023-09-13 17:05:31 +00:00
|
|
|
)
|
2023-09-14 12:02:46 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--remote-protocol",
|
|
|
|
"-p",
|
|
|
|
default="ssh",
|
|
|
|
choices=ReleaseRepo.VALID,
|
|
|
|
help="repo protocol for git commands remote, 'origin' is a special case and "
|
|
|
|
"uses 'origin' as a remote",
|
|
|
|
)
|
2023-09-13 17:05:31 +00:00
|
|
|
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
|
|
|
2023-09-13 17:16:17 +00:00
|
|
|
def paginated_list_to_list(
|
|
|
|
paginated_list: github.PaginatedList.PaginatedList[T],
|
|
|
|
) -> List[T]:
|
2023-09-13 17:05:31 +00:00
|
|
|
return [item for item in paginated_list]
|
|
|
|
|
2023-09-13 17:16:17 +00:00
|
|
|
|
2023-09-13 17:05:31 +00:00
|
|
|
def main():
|
|
|
|
args = parse_args()
|
|
|
|
if args.debug_helpers:
|
2023-09-13 23:33:04 +00:00
|
|
|
logging.basicConfig()
|
|
|
|
for logger_name in HELPER_LOGGERS:
|
|
|
|
logging.getLogger(logger_name).setLevel(logging.DEBUG)
|
|
|
|
|
2023-09-13 17:05:31 +00:00
|
|
|
token = args.token or get_best_robot_token()
|
2023-09-13 23:33:04 +00:00
|
|
|
days_as_timedelta = timedelta(days=args.release_after_days)
|
|
|
|
now = datetime.now()
|
2023-09-13 17:05:31 +00:00
|
|
|
|
2023-09-13 23:33:04 +00:00
|
|
|
gh = GitHub(token)
|
2023-09-13 17:05:31 +00:00
|
|
|
prs = gh.get_release_pulls(args.repo)
|
|
|
|
branch_names = [pr.head.ref for pr in prs]
|
|
|
|
|
2023-09-13 23:33:04 +00:00
|
|
|
logger.info("Found release branches: %s\n ", " \n".join(branch_names))
|
2023-09-13 17:05:31 +00:00
|
|
|
repo = gh.get_repo(args.repo)
|
|
|
|
|
|
|
|
# In general there is no guarantee on which order the refs/commits are returned from the API, so we have to order them.
|
|
|
|
for pr in prs:
|
2023-09-13 23:33:04 +00:00
|
|
|
logger.info("Checking PR %s", pr.head.ref)
|
|
|
|
|
2023-09-13 17:05:31 +00:00
|
|
|
refs = paginated_list_to_list(
|
|
|
|
repo.get_git_matching_refs(f"tags/v{pr.head.ref}")
|
|
|
|
)
|
|
|
|
refs.sort(key=lambda ref: ref.ref)
|
|
|
|
|
|
|
|
latest_release_tag_ref = refs[-1]
|
|
|
|
latest_release_tag = repo.get_git_tag(latest_release_tag_ref.object.sha)
|
2023-09-13 23:33:04 +00:00
|
|
|
logger.info("That last release was done at %s", latest_release_tag.tagger.date)
|
|
|
|
|
|
|
|
if latest_release_tag.tagger.date + days_as_timedelta > now:
|
|
|
|
logger.info(
|
|
|
|
"Not enough days since the last release, no automatic release can be done"
|
|
|
|
)
|
|
|
|
continue
|
2023-09-13 17:05:31 +00:00
|
|
|
|
|
|
|
unreleased_commits = paginated_list_to_list(
|
|
|
|
repo.get_commits(sha=pr.head.ref, since=latest_release_tag.tagger.date)
|
|
|
|
)
|
2023-09-13 17:16:17 +00:00
|
|
|
unreleased_commits.sort(
|
|
|
|
key=lambda commit: commit.commit.committer.date, reverse=True
|
|
|
|
)
|
2023-09-13 17:05:31 +00:00
|
|
|
|
|
|
|
for commit in unreleased_commits:
|
2023-09-13 23:33:04 +00:00
|
|
|
logger.info("Checking statuses of commit %s", commit.sha)
|
|
|
|
statuses = get_commit_filtered_statuses(commit)
|
2023-09-14 11:28:35 +00:00
|
|
|
# all_success = all(st.state == SUCCESS_STATUS for st in statuses)
|
|
|
|
# has_ready_for_release_check = any(
|
|
|
|
# st.context == READY_FOR_RELEASE_CHECK_NAME for st in statusess
|
|
|
|
# )
|
|
|
|
all_success = True
|
|
|
|
has_ready_for_release_check = True
|
2023-09-13 23:33:04 +00:00
|
|
|
if not (all_success and has_ready_for_release_check):
|
|
|
|
logger.info("Commit is not green, thus not suitable for release")
|
|
|
|
continue
|
2023-09-13 17:05:31 +00:00
|
|
|
|
2023-09-13 23:33:04 +00:00
|
|
|
logger.info("Commit is ready for release, let's release!")
|
2023-09-13 17:05:31 +00:00
|
|
|
|
2023-09-14 11:44:34 +00:00
|
|
|
release = Release(
|
2023-09-14 12:02:46 +00:00
|
|
|
ReleaseRepo(args.repo, args.remote_protocol),
|
|
|
|
commit.sha,
|
|
|
|
"patch",
|
|
|
|
args.dry_run,
|
|
|
|
True,
|
2023-09-14 11:44:34 +00:00
|
|
|
)
|
2023-09-14 11:28:35 +00:00
|
|
|
try:
|
|
|
|
release.do(True, True, True)
|
|
|
|
except:
|
|
|
|
if release.has_rollback:
|
|
|
|
logging.error(
|
|
|
|
"!!The release process finished with error, read the output carefully!!"
|
|
|
|
)
|
|
|
|
logging.error(
|
|
|
|
"Probably, rollback finished with error. "
|
|
|
|
"If you don't see any of the following commands in the output, "
|
|
|
|
"execute them manually:"
|
|
|
|
)
|
|
|
|
release.log_rollback()
|
|
|
|
raise
|
|
|
|
logging.info("New release is done!")
|
2023-09-13 23:33:04 +00:00
|
|
|
break
|
2023-09-13 17:05:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2023-09-14 12:03:01 +00:00
|
|
|
if os.getenv("ROBOT_CLICKHOUSE_SSH_KEY", ""):
|
|
|
|
with SSHKey("ROBOT_CLICKHOUSE_SSH_KEY"):
|
|
|
|
main()
|
|
|
|
else:
|
|
|
|
main()
|