Extend the release script to wait for GitHub Actions to finish and to be usable as a guide for the whole process. (#13483)

This commit is contained in:
reivilibre 2022-09-05 11:16:59 +00:00 committed by GitHub
parent 8cb9261598
commit c7b18d9d44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 143 additions and 4 deletions

1
changelog.d/13483.misc Normal file
View File

@ -0,0 +1 @@
Extend the release script to wait for GitHub Actions to finish and to be usable as a guide for the whole process.

View File

@ -18,10 +18,12 @@
""" """
import glob import glob
import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import time
import urllib.request import urllib.request
from os import path from os import path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
@ -71,18 +73,21 @@ def cli() -> None:
./scripts-dev/release.py tag ./scripts-dev/release.py tag
# ... wait for assets to build ... # wait for assets to build, either manually or with:
./scripts-dev/release.py wait-for-actions
./scripts-dev/release.py publish ./scripts-dev/release.py publish
./scripts-dev/release.py upload ./scripts-dev/release.py upload
# Optional: generate some nice links for the announcement
./scripts-dev/release.py merge-back ./scripts-dev/release.py merge-back
# Optional: generate some nice links for the announcement
./scripts-dev/release.py announce ./scripts-dev/release.py announce
Alternatively, `./scripts-dev/release.py full` will do all the above
as well as guiding you through the manual steps.
If the env var GH_TOKEN (or GITHUB_TOKEN) is set, or passed into the If the env var GH_TOKEN (or GITHUB_TOKEN) is set, or passed into the
`tag`/`publish` command, then a new draft release will be created/published. `tag`/`publish` command, then a new draft release will be created/published.
""" """
@ -90,6 +95,10 @@ def cli() -> None:
@cli.command() @cli.command()
def prepare() -> None: def prepare() -> None:
_prepare()
def _prepare() -> None:
"""Do the initial stages of creating a release, including creating release """Do the initial stages of creating a release, including creating release
branch, updating changelog and pushing to GitHub. branch, updating changelog and pushing to GitHub.
""" """
@ -284,6 +293,10 @@ def prepare() -> None:
@cli.command() @cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"]) @click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"])
def tag(gh_token: Optional[str]) -> None: def tag(gh_token: Optional[str]) -> None:
_tag(gh_token)
def _tag(gh_token: Optional[str]) -> None:
"""Tags the release and generates a draft GitHub release""" """Tags the release and generates a draft GitHub release"""
# Make sure we're in a git repo. # Make sure we're in a git repo.
@ -374,6 +387,10 @@ def tag(gh_token: Optional[str]) -> None:
@cli.command() @cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True) @click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True)
def publish(gh_token: str) -> None: def publish(gh_token: str) -> None:
_publish(gh_token)
def _publish(gh_token: str) -> None:
"""Publish release on GitHub.""" """Publish release on GitHub."""
# Make sure we're in a git repo. # Make sure we're in a git repo.
@ -411,6 +428,10 @@ def publish(gh_token: str) -> None:
@cli.command() @cli.command()
def upload() -> None: def upload() -> None:
_upload()
def _upload() -> None:
"""Upload release to pypi.""" """Upload release to pypi."""
current_version = get_package_version() current_version = get_package_version()
@ -479,8 +500,75 @@ def _merge_into(repo: Repo, source: str, target: str) -> None:
repo.remote().push() repo.remote().push()
@cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=False)
def wait_for_actions(gh_token: Optional[str]) -> None:
_wait_for_actions(gh_token)
def _wait_for_actions(gh_token: Optional[str]) -> None:
# Find out the version and tag name.
current_version = get_package_version()
tag_name = f"v{current_version}"
# Authentication is optional on this endpoint,
# but use a token if we have one to reduce the chance of being rate-limited.
url = f"https://api.github.com/repos/matrix-org/synapse/actions/runs?branch={tag_name}"
headers = {"Accept": "application/vnd.github+json"}
if gh_token is not None:
headers["authorization"] = f"token {gh_token}"
req = urllib.request.Request(url, headers=headers)
time.sleep(10 * 60)
while True:
time.sleep(5 * 60)
response = urllib.request.urlopen(req)
resp = json.loads(response.read())
if len(resp["workflow_runs"]) == 0:
continue
if all(
workflow["status"] != "in_progress" for workflow in resp["workflow_runs"]
):
success = (
workflow["status"] == "completed" for workflow in resp["workflow_runs"]
)
if success:
_notify("Workflows successful. You can now continue the release.")
else:
_notify("Workflows failed.")
click.confirm("Continue anyway?", abort=True)
break
def _notify(message: str) -> None:
# Send a bell character. Most terminals will play a sound or show a notification
# for this.
click.echo(f"\a{message}")
# Try and run notify-send, but don't raise an Exception if this fails
# (This is best-effort)
# TODO Support other platforms?
subprocess.run(
[
"notify-send",
"--app-name",
"Synapse Release Script",
"--expire-time",
"3600000",
message,
]
)
@cli.command() @cli.command()
def merge_back() -> None: def merge_back() -> None:
_merge_back()
def _merge_back() -> None:
"""Merge the release branch back into the appropriate branches. """Merge the release branch back into the appropriate branches.
All branches will be automatically pulled from the remote and the results All branches will be automatically pulled from the remote and the results
will be pushed to the remote.""" will be pushed to the remote."""
@ -519,6 +607,10 @@ def merge_back() -> None:
@cli.command() @cli.command()
def announce() -> None: def announce() -> None:
_announce()
def _announce() -> None:
"""Generate markdown to announce the release.""" """Generate markdown to announce the release."""
current_version = get_package_version() current_version = get_package_version()
@ -548,10 +640,56 @@ Announce the release in
- #homeowners:matrix.org (Synapse Announcements), bumping the version in the topic - #homeowners:matrix.org (Synapse Announcements), bumping the version in the topic
- #synapse:matrix.org (Synapse Admins), bumping the version in the topic - #synapse:matrix.org (Synapse Admins), bumping the version in the topic
- #synapse-dev:matrix.org - #synapse-dev:matrix.org
- #synapse-package-maintainers:matrix.org""" - #synapse-package-maintainers:matrix.org
Ask the designated people to do the blog and tweets."""
) )
@cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True)
def full(gh_token: str) -> None:
click.echo("1. If this is a security release, read the security wiki page.")
click.echo("2. Check for any release blockers before proceeding.")
click.echo(" https://github.com/matrix-org/synapse/labels/X-Release-Blocker")
click.confirm("Ready?", abort=True)
click.echo("\n*** prepare ***")
_prepare()
click.echo("Deploy to matrix.org and ensure that it hasn't fallen over.")
click.echo("Remember to silence the alerts to prevent alert spam.")
click.confirm("Deployed?", abort=True)
click.echo("\n*** tag ***")
_tag(gh_token)
click.echo("\n*** wait for actions ***")
_wait_for_actions(gh_token)
click.echo("\n*** publish ***")
_publish(gh_token)
click.echo("\n*** upload ***")
_upload()
click.echo("\n*** merge back ***")
_merge_back()
click.echo("\nUpdate the Debian repository")
click.confirm("Started updating Debian repository?", abort=True)
click.echo("\nWait for all release methods to be ready.")
# Docker should be ready because it was done by the workflows earlier
# PyPI should be ready because we just ran upload().
# TODO Automatically poll until the Debs have made it to packages.matrix.org
click.confirm("Debs ready?", abort=True)
click.echo("\n*** announce ***")
_announce()
def get_package_version() -> version.Version: def get_package_version() -> version.Version:
version_string = subprocess.check_output(["poetry", "version", "--short"]).decode( version_string = subprocess.check_output(["poetry", "version", "--short"]).decode(
"utf-8" "utf-8"