Browse Source

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

tags/v1.67.0rc1
reivilibre 1 year ago
committed by GitHub
parent
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. +1
    -0
      changelog.d/13483.misc
  2. +142
    -4
      scripts-dev/release.py

+ 1
- 0
changelog.d/13483.misc 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.

+ 142
- 4
scripts-dev/release.py View File

@@ -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
@@ -71,18 +73,21 @@ 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.
"""
@@ -90,6 +95,10 @@ def cli() -> None:

@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.
"""
@@ -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.
@@ -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.
@@ -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()
@@ -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."""
@@ -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()
@@ -548,10 +640,56 @@ Announce the release in
- #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()

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:
version_string = subprocess.check_output(["poetry", "version", "--short"]).decode(
"utf-8"


Loading…
Cancel
Save