diff --git a/RELEASE.md b/RELEASE.md index be9bd11af..e5ac2106f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -369,7 +369,10 @@ repo. To publish the release: - [ ] Create a new release on GitHub, put the changelog in the description of the release, and upload the macOS and Windows installers + * You can use `./dev_scripts/upload-asset.py`, if you want to upload an asset + using an access token. - [ ] Upload the `container.tar.gz` i686 image that was created in the previous step + **Important:** Make sure that it's the same container image as the ones that are shipped in other platforms (see our [Pre-release](#Pre-release) section) diff --git a/dev_scripts/env.py b/dev_scripts/env.py index c7c11145b..206640ef3 100755 --- a/dev_scripts/env.py +++ b/dev_scripts/env.py @@ -89,7 +89,7 @@ && rm -rf /var/lib/apt/lists/* RUN apt-get update \ && apt-get install -y --no-install-recommends dh-python make build-essential \ - fakeroot {qt_deps} pipx python3 python3-dev python3-venv python3-stdeb \ + git fakeroot {qt_deps} pipx python3 python3-dev python3-venv python3-stdeb \ python3-all \ && rm -rf /var/lib/apt/lists/* # NOTE: `pipx install poetry` fails on Ubuntu Focal, when installed through APT. By @@ -108,7 +108,7 @@ # FIXME: Install Poetry on Fedora via package manager. DOCKERFILE_BUILD_DEV_FEDORA_DEPS = r""" -RUN dnf install -y rpm-build podman python3 python3-devel python3-poetry-core \ +RUN dnf install -y git rpm-build podman python3 python3-devel python3-poetry-core \ pipx make qt6-qtbase-gui \ && dnf clean all diff --git a/dev_scripts/upload-asset.py b/dev_scripts/upload-asset.py new file mode 100755 index 000000000..8cf771278 --- /dev/null +++ b/dev_scripts/upload-asset.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 + +import argparse +import getpass +import inspect +import logging +import os +import pathlib +import sys +import urllib + +import requests + +log = logging.getLogger(__name__) + + +DEFAULT_HEADERS = { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", +} + + +def get_auth_header(token): + return {"Authorization": f"Bearer {token}"} + + +def get_latest_draft_release(token): + url = "https://api.github.com/repos/freedomofpress/dangerzone/releases" + headers = DEFAULT_HEADERS.copy() + headers.update(get_auth_header(token)) + + r = requests.get(url, headers=headers) + r.raise_for_status() + + draft_releases = [release["id"] for release in r.json() if release["draft"]] + if len(draft_releases) > 1: + raise RuntimeError("Found more than one draft releases") + elif len(draft_releases) == 0: + raise RuntimeError("No draft releases have been found") + + return draft_releases[0] + + +def get_release_from_tag(token, tag): + url = f"https://api.github.com/repos/freedomofpress/dangerzone/releases/tags/v{tag}" + headers = DEFAULT_HEADERS.copy() + headers.update(get_auth_header(token)) + + r = requests.get(url, headers=headers) + r.raise_for_status() + + return r.json()["id"] + + +def upload_asset(token, release_id, path): + filename = os.path.basename(path) + url = f"https://uploads.github.com/repos/freedomofpress/dangerzone/releases/{release_id}/assets?name={filename}" + headers = DEFAULT_HEADERS.copy() + headers.update(get_auth_header(token)) + headers["Content-Type"] = "application/octet-stream" + + with open(path, "rb") as f: + data = f.read() + # XXX: We have to load the data in-memory. Another solution is to use multipart + # encoding, but this doesn't work for GitHub. + r = requests.post(url, headers=headers, data=data) + r.raise_for_status() + + +def setup_logging(): + logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + +def main(): + parser = argparse.ArgumentParser( + prog=sys.argv[0], + description="Dev script for uploading assets to a GitHub release", + ) + parser.add_argument( + "--token", + help="the file path to the GitHub token we will use for uploading assets", + ) + parser.add_argument( + "--tag", + help=f"use the release with this tag", + ) + parser.add_argument( + "--release-id", + help=f"use the release with this ID", + ) + parser.add_argument( + "--draft", + action="store_true", + help=f"use the latest draft release", + ) + parser.add_argument( + "file", + help="the file path to the asset we want to upload", + ) + args = parser.parse_args() + + setup_logging() + + if args.token: + log.debug(f"Reading token from {args.token}") + with open(args.token) as f: + token = f.read().strip() + else: + token = getpass.getpass("Token: ") + + if args.tag: + log.debug(f"Getting the ID of the {args.tag} release") + release_id = get_release_from_tag(token, args.tag) + log.debug(f"The {args.tag} release has ID '{release_id}'") + elif args.release_id: + release_id = args.release_id + else: + log.debug(f"Getting the ID of the latest draft release") + release_id = get_latest_draft_release(token) + log.debug(f"The latest draft release has ID '{release_id}'") + + log.info(f"Uploading file '{args.file}' to GitHub release '{release_id}'") + upload_asset(token, release_id, args.file) + log.info( + f"Successfully uploaded file '{args.file}' to GitHub release '{release_id}'" + ) + + +if __name__ == "__main__": + sys.exit(main())