...

Text file src/github.com/emissary-ingress/emissary/v3/releng/00-release-start

Documentation: github.com/emissary-ingress/emissary/v3/releng

     1#!/usr/bin/env python3
     2"""Updates version.yml and CHANGELOG.md for next version, then
     3launch the `git citool` GUI to create a commit of it.
     4"""
     5
     6import os
     7import re
     8import sys
     9from contextlib import contextmanager
    10from typing import Generator
    11from shutil import which
    12import argparse
    13import subprocess
    14
    15from lib import re_ga, re_ea, get_gh_repo, vX, vY
    16from lib.uiutil import Checker, CheckResult, run, check_command
    17from lib.uiutil import run_txtcapture as run_capture
    18from lib.start_release_updates import update_changelog_date
    19from lib.gitutil import branch_exists, has_open_pr
    20
    21
    22PR_MESSAGE = """
    23All commits that are going out in {next_ver} release **must** be on {pr_branch}.
    24This will allow the appropriate CI to run.
    25
    26Reviewers, please review the changelog and commits in this branch to make sure everything that needs to go out in the release is on this branch.
    27"""
    28
    29
    30def main(next_ver: str, commit: bool = True) -> int:
    31    """Starts a release train.
    32    This script will create a changelog entry and create PRs of applicable.
    33    """
    34    m = re_ga.match(next_ver)
    35    if not m:
    36        m = re_ea.match(next_ver)
    37    assert m
    38
    39    if which("gh") is None:
    40        print("gh tool is not installed.")
    41        print("Please install the tool and rerun this script:")
    42        print("https://github.com/cli/cli#installation")
    43        return 1
    44
    45    if not os.getenv("GITHUB_TOKEN"):
    46        run(["gh", "auth", "login"])
    47
    48    print()
    49    print(f'Doing basic updates for v{next_ver}...')
    50    print()
    51
    52    # This context manager and check function are pretty much just to produce
    53    # a nice list of steps...
    54
    55    checker = Checker()
    56
    57    @contextmanager
    58    def check(name: str) -> Generator[CheckResult, None, None]:
    59        with checker.check(name) as subcheck:
    60            yield subcheck
    61    workbranch = ''
    62    base_branch = ''
    63    current_release_branch = f"rel/v{next_ver}"
    64    release_branch = f"release/v{m[vX]}.{m[vY]}"
    65    check_command(["git", "fetch", "--all"])
    66
    67    workbranch = current_release_branch
    68    base_branch = release_branch
    69    if branch_exists(current_release_branch):
    70        with check(f"Checking out {current_release_branch}"):
    71            run(["git", "checkout", current_release_branch])
    72
    73        if branch_exists("origin/" + current_release_branch):
    74            with check(f"Bringing our branch it up to date with origin/{release_branch}"):
    75                run(["git", "pull", "origin", current_release_branch])
    76    else:
    77        with check(f"Creating {current_release_branch}"):
    78            run(["git", "checkout", "-b", current_release_branch])
    79    if not checker.ok:
    80        return 1
    81    run(["git", "merge", f"origin/{release_branch}"])
    82    with check(f"Checking for clean branch with no conflicts..."):
    83        out = run_capture(["git", "status", "--porcelain"])
    84        if out:
    85            raise AssertionError(f"Merge conflicts on {current_release_branch}. Resolve these, then rerun this scrip")
    86    if not checker.ok:
    87        return 1
    88
    89    with check(f"Updating CHANGELOG.md with {next_ver}..."):
    90        update_changelog_date(next_ver)
    91    if not checker.ok:
    92        return 1
    93    if not commit:
    94        print('not committing')
    95        return 0
    96
    97    out = run_capture(["git", "status", "--porcelain"])
    98    if not out:
    99        print("No changes needed, all good.")
   100        return 0
   101
   102    with check(f"Committing changes..."):
   103        gitdir = run_capture(['git', 'rev-parse', '--git-dir'])
   104        with open(os.path.join(gitdir, 'GITGUI_MSG'), 'w') as msgfile:
   105            msgfile.write(f"Update for v{next_ver}\n")
   106        if os.getenv("AMBASSADOR_RELENG_NO_GUI"):
   107            run(['git', 'commit', '-am', f'Update for v{next_ver}'])
   108        else:
   109            run(['git', 'citool'])
   110        run(["git", "push", "-u", "origin", workbranch])
   111    if not checker.ok:
   112        return 1
   113    if has_open_pr(get_gh_repo(), base_branch, current_release_branch):
   114        run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo()])
   115        return 0
   116    pr_message = f"[v{next_ver}] Release Branch"
   117    pr_body = PR_MESSAGE.format(next_ver=next_ver, pr_branch=current_release_branch)
   118    if not checker.ok:
   119        return 1
   120
   121    with check(f"Creating PR for {workbranch}"):
   122        run(["gh", "pr", "create",
   123                "--repo", get_gh_repo(),
   124                "--base", base_branch,
   125                "--title", pr_message,
   126                "--body", pr_body])
   127
   128    if checker.ok:
   129        if commit:
   130            print(f'Update complete. CI triggered on the PR will generate RC artifacts to test')
   131            print(f'Pushes to this branch will generate new RCs')
   132            if os.getenv("AMBASSADOR_RELENG_NO_GUI"):
   133                run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo()])
   134            else:
   135                run(["gh", "pr", "view", workbranch, "--repo", get_gh_repo(), "--web"])
   136        return 0
   137    else:
   138        return 1
   139
   140
   141if __name__ == '__main__':
   142    parser = argparse.ArgumentParser(description='Start a release train')
   143
   144    parser.add_argument('--next-version', help='Next version to prep for', required=True)
   145    parser.add_argument('--no-commit', dest='commit', action='store_false', default=True)
   146
   147    args = parser.parse_args()
   148
   149    if not re_ga.match(args.next_version) and not re_ea.match(args.next_version):
   150        sys.stderr.write(f"{os.path.basename(sys.argv[0])}: Version must match X.Y.Z(-ea)?\n")
   151        sys.exit(2)
   152
   153    sys.exit(main(
   154        next_ver=args.next_version,
   155        commit=args.commit
   156    ))

View as plain text