From a56d6a7a26e54a6ba76f04cf2d1c1df126e812cc Mon Sep 17 00:00:00 2001 From: Johanna Bahl Date: Tue, 12 Nov 2024 14:10:23 +0100 Subject: [PATCH] switch the release script to python this now allows selecting which phases to run. needs to be ported to 24.05 and 23.11. Re PL-132115 --- changelog.d/scriv.ini | 2 +- fc-release.py | 173 ++++++++++++++++++++++++++++++++++++++++++ fc-release.sh | 71 ----------------- 3 files changed, 174 insertions(+), 72 deletions(-) create mode 100755 fc-release.py delete mode 100755 fc-release.sh diff --git a/changelog.d/scriv.ini b/changelog.d/scriv.ini index dedf2e54f..dbde72b91 100644 --- a/changelog.d/scriv.ini +++ b/changelog.d/scriv.ini @@ -1,6 +1,6 @@ [scriv] format = md entry_title_template = -categories = Impact, NixOS platform +categories = Impact, NixOS XX.XX platform output_file = changelog.d/CHANGELOG.md.tmp skip_fragments = CHANGELOG.* diff --git a/fc-release.py b/fc-release.py new file mode 100755 index 000000000..aed641c5b --- /dev/null +++ b/fc-release.py @@ -0,0 +1,173 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i python3 -p scriv + +import argparse +import re +import sys +from pathlib import Path +from subprocess import CalledProcessError, run + +ORIGIN_REMOTE_PATTERN = re.compile( + r"^origin\s.*github.com.flyingcircusio/fc-nixos" +) +DOCS_REMOTE_PATTERN = re.compile(r"^origin\s.*github.com.flyingcircusio/doc") +STEPS = ["prepare", "changelog", "merge", "backmerge", "push"] +TEMP_CHANGELOG = Path("changelog.d/CHANGELOG.md.tmp") +CHANGELOG = Path("changelog.d/CHANGELOG.md") + + +def release_id_type(arg_value): + if not re.compile("^[0-9]{4}_[0-9]{3}$").match(arg_value): + raise argparse.ArgumentTypeError( + "invalid release id format. Expected: YYYY_NNN" + ) + return arg_value + + +def git_remote(repo=Path(".")): + return run( + ["git", "-C", str(repo), "remote", "-v"], capture_output=True + ).stdout.decode() + + +class Release: + def __init__(self, release_id: str): + self.release_id = release_id + with Path("nixos-version").open() as f: + self.nixos_version = f.read().strip() + + self.branch_dev = f"fc-{self.nixos_version}-dev" + self.branch_stag = f"fc-{self.nixos_version}-staging" + self.branch_prod = f"fc-{self.nixos_version}-production" + + def prepare(self): + remotes = git_remote() + if not ORIGIN_REMOTE_PATTERN.match(remotes): + print( + "please perform release in a clean checkout with proper origin" + ) + sys.exit(64) + + run(["git", "fetch", "origin", "--tags", "--prune"], check=True) + run(["git", "checkout", self.branch_dev], check=True) + run( + ["git", "merge", "--ff-only"], check=True + ) # expected to fail on unclean/unpushed workdirs + run(["git", "checkout", self.branch_stag], check=True) + run(["git", "merge", "--ff-only"], check=True) + + def changelog(self): + doc_repo = Path("../doc") + if not (doc_repo / "changelog.d").is_dir(): + print( + "please ensure that you have a checkout of " + "`flyingcircusio/doc` next to this repo" + ) + sys.exit(64) + + doc_remotes = git_remote(doc_repo) + if not DOCS_REMOTE_PATTERN.match(doc_remotes): + print("doc repo has unexpected origin") + sys.exit(64) + + doc_fragment_path = ( + doc_repo / "changelog.d" / f"{self.nixos_version}.md" + ) + if doc_fragment_path.exists(): + print( + f"the changelog fragment '{doc_fragment_path}' already exists\n" + + "Remove it or skip changelog generation" + ) + sys.exit(64) + + TEMP_CHANGELOG.open("w").close() # truncate + try: + run(["scriv", "collect", "--add"], check=True) + except CalledProcessError: + TEMP_CHANGELOG.unlink() + print( + "Failed to collect Changelog. You can skip this by omitting the `changelog` stage." + ) + raise + + new_fragment = TEMP_CHANGELOG.read_text() + doc_fragment = new_fragment.replace( + "\n## Impact", f"\n## Impact\n### {self.nixos_version}" + ) + doc_fragment = doc_fragment.replace( + "\n## NixOS XX.XX platform", + f"\n## NixOS {self.nixos_version} platform", + ) + doc_fragment_path.write_text(doc_fragment) + + new_changelog = f"# Release {self.release_id}\n\n" + new_fragment + if CHANGELOG.exists(): + new_changelog += "\n" + CHANGELOG.read_text() + CHANGELOG.write_text(new_changelog) + + TEMP_CHANGELOG.unlink() + + try: + run(["git", "add", str(TEMP_CHANGELOG), str(CHANGELOG)], check=True) + run( + ["git", "commit", "-m", "Collect changelog fragments"], + check=True, + ) + except CalledProcessError: + print( + "Failed to commit Changelog. Commit it manually and continue after the `changelog` stage" + ) + raise + + def merge(self): + # Ensure the upstream/tracked production branch is clean compared + # to our local branch. + run(["git", "checkout", self.branch_prod], check=True) + run(["git", "merge", "--ff-only"], check=True) + + msg = ( + f"Merge branch '{self.branch_stag}' into " + f"'{self.branch_prod}' for release {self.release_id}" + ) + run(["git", "merge", "-m", msg, self.branch_stag], check=True) + + def backmerge(self): + run(["git", "checkout", self.branch_dev], check=True) + msg = f"Backmerge branch '{self.branch_prod}' into '{self.branch_dev}'' for release {self.release_id}" + run(["git", "merge", "-m", msg, self.branch_prod], check=True) + + def push(self): + print("Committed changes:") + run( + ["git", "log", "--graph", "--decorate", "--format=short", "-n3"], + env={"PAGER": ""}, + check=True, + ) + cmd = f"git push origin {self.branch_dev} {self.branch_stag} {self.branch_prod}" + print( + "If this looks correct, press Enter to push (or use ^C to abort)." + ) + print(f"This will issue: `{cmd}`") + input() + run(cmd, shell=True, check=True) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("release_id", type=release_id_type) + parser.add_argument( + "steps", choices=["all"] + STEPS, default="all", nargs="*" + ) + args = parser.parse_args() + if args.steps == "all" or "all" in args.steps: + args.steps = STEPS + + release = Release(args.release_id) + print(f"Performing release for {args.release_id}") + for step_name in args.steps: + print(f"Release step: {step_name}") + getattr(release, step_name)() + + +if __name__ == "__main__": + main() diff --git a/fc-release.sh b/fc-release.sh deleted file mode 100755 index 571dae6ab..000000000 --- a/fc-release.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env nix-shell -#!nix-shell -i bash -set -e - -releaseid="${1:?no release id given}" - -if ! echo "$releaseid" | egrep -q '^[0-9]{4}_[0-9]{3}$'; then - echo "$0: release id must be of the form YYYY_NNN" >&2 - exit 64 -fi - -nixos_version=$(< nixos-version) -dev="fc-${nixos_version}-dev" -stag="fc-${nixos_version}-staging" -prod="fc-${nixos_version}-production" - -echo "$0: performing release based on $stag" - -if ! git remote -v | egrep -q "^origin\s.*github.com.flyingcircusio/fc-nixos" -then - echo "$0: please perform release in a clean checkout with proper origin" >&2 - exit 64 -fi - -if [[ ! -d ../doc/changelog.d ]] || ! git -C ../doc remote -v | grep -Eq "^origin\s.*github.com.flyingcircusio/doc"; then - echo "$0: please ensure that you have a checkout of flyingcircusio/doc next to this repo" - exit 64 -fi -if [[ -e ../doc/changelog.d/"$nixos_version".md ]]; then - echo "$0: the changelog fragment '../doc/changelog.d/$nixos_version.md' already exists" - exit 64 -fi - -git fetch origin --tags --prune -git checkout $dev -git merge --ff-only # expected to fail on unclean/unpushed workdirs - -git checkout $stag -git merge --ff-only - -TEMP_CHANGELOG=changelog.d/CHANGELOG.md.tmp -CHANGELOG=changelog.d/CHANGELOG.md -truncate -s 0 $TEMP_CHANGELOG -scriv collect --add -sed -e "s/^## Impact/## Impact\n### $nixos_version/" \ - -e "s/^## NixOS XX.XX platform/## NixOS $nixos_version platform/" $TEMP_CHANGELOG > ../doc/changelog.d/"$nixos_version".md -echo -e "\n" >> $TEMP_CHANGELOG -cat $CHANGELOG >> $TEMP_CHANGELOG -(echo "# Release $releaseid"; cat $TEMP_CHANGELOG) > $CHANGELOG -rm $TEMP_CHANGELOG -git add $TEMP_CHANGELOG $CHANGELOG -git commit -m "Collect changelog fragments" - -git checkout "$prod" -git merge --ff-only - -msg="Merge branch '$stag' into $prod for release $releaseid" -git merge -m "$msg" $stag - -git checkout $dev -msg="Backmerge branch '$prod' into $dev for release $releaseid" -git merge -m "$msg" $prod - -echo "$0: committed changes:" -PAGER= git log --graph --decorate --format=short -n3 - -cmd="git push origin $dev $stag $prod" -echo "$0: If this looks correct, press Enter to push (or use ^C to abort)." -echo "$0: This will issue: $cmd" -read -eval $cmd