Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

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

Merged
merged 11 commits into from
Sep 5, 2022
1 change: 1 addition & 0 deletions changelog.d/13483.misc
Original file line number Diff line number Diff line change
@@ -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.
146 changes: 142 additions & 4 deletions scripts-dev/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
"""

import glob
import json
import os
import re
import subprocess
import sys
import time
import urllib.request
from os import path
from tempfile import TemporaryDirectory
Expand Down Expand Up @@ -71,25 +73,32 @@ def cli() -> None:

./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 upload

# Optional: generate some nice links for the announcement

./scripts-dev/release.py merge-back

# Optional: generate some nice links for the announcement
./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
`tag`/`publish` command, then a new draft release will be created/published.
"""


@cli.command()
def prepare() -> None:
_prepare()


def _prepare() -> None:
"""Do the initial stages of creating a release, including creating release
branch, updating changelog and pushing to GitHub.
"""
Expand Down Expand Up @@ -284,6 +293,10 @@ def prepare() -> None:
@cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"])
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"""

# Make sure we're in a git repo.
Expand Down Expand Up @@ -374,6 +387,10 @@ def tag(gh_token: Optional[str]) -> None:
@cli.command()
@click.option("--gh-token", envvar=["GH_TOKEN", "GITHUB_TOKEN"], required=True)
def publish(gh_token: str) -> None:
_publish(gh_token)


def _publish(gh_token: str) -> None:
"""Publish release on GitHub."""

# Make sure we're in a git repo.
Expand Down Expand Up @@ -411,6 +428,10 @@ def publish(gh_token: str) -> None:

@cli.command()
def upload() -> None:
_upload()


def _upload() -> None:
"""Upload release to pypi."""

current_version = get_package_version()
Expand Down Expand Up @@ -479,8 +500,75 @@ def _merge_into(repo: Repo, source: str, target: str) -> None:
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()
def merge_back() -> None:
_merge_back()


def _merge_back() -> None:
"""Merge the release branch back into the appropriate branches.
All branches will be automatically pulled from the remote and the results
will be pushed to the remote."""
Expand Down Expand Up @@ -519,6 +607,10 @@ def merge_back() -> None:

@cli.command()
def announce() -> None:
_announce()


def _announce() -> None:
"""Generate markdown to announce the release."""

current_version = get_package_version()
Expand Down Expand Up @@ -548,10 +640,56 @@ def announce() -> None:
- #homeowners:matrix.org (Synapse Announcements), bumping the version in the topic
- #synapse:matrix.org (Synapse Admins), bumping the version in the topic
- #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()
reivilibre marked this conversation as resolved.
Show resolved Hide resolved

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to poll for the debs too

Copy link
Contributor Author

@reivilibre reivilibre Sep 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we can hit e.g. https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.66.0%2Bbuster1.dsc etc until it exists if that's what you mean?


click.echo("\n*** announce ***")
_announce()


def get_package_version() -> version.Version:
version_string = subprocess.check_output(["poetry", "version", "--short"]).decode(
"utf-8"
Expand Down