From d2e97d4161df6aa405073b314526d0fa6e68774d Mon Sep 17 00:00:00 2001 From: Mary Date: Sat, 14 May 2022 14:37:16 +0200 Subject: [PATCH] gh-actions: Add automatic archiving to Internet Archive --- .github/workflows/internet_archive.yaml | 28 ++++++ scripts/archiver/README.md | 3 + scripts/archiver/ia_update_release.py | 116 ++++++++++++++++++++++++ scripts/archiver/requirements.txt | 2 + 4 files changed, 149 insertions(+) create mode 100644 .github/workflows/internet_archive.yaml create mode 100644 scripts/archiver/README.md create mode 100644 scripts/archiver/ia_update_release.py create mode 100644 scripts/archiver/requirements.txt diff --git a/.github/workflows/internet_archive.yaml b/.github/workflows/internet_archive.yaml new file mode 100644 index 0000000..22ef5e4 --- /dev/null +++ b/.github/workflows/internet_archive.yaml @@ -0,0 +1,28 @@ +name: Upload release to Internet Archive + +on: + workflow_dispatch: + inputs: + release: + description: 'The version to archive on Internet Archive' + required: false + default: 'latest' + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: "Checkout" + uses: actions/checkout@v2 + + - name: "Install dependencies" + run: pip install -r scripts/archiver/requirements.txt + + - name: "Handle release event" + run: python3 scripts/archiver/ia_update_release.py + env: + IA_S3_ACCESS_KEY: ${{ secrets.IA_S3_ACCESS_KEY }} + IA_S3_SECRET_KEY: ${{ secrets.IA_S3_SECRET_KEY }} diff --git a/scripts/archiver/README.md b/scripts/archiver/README.md new file mode 100644 index 0000000..88ef582 --- /dev/null +++ b/scripts/archiver/README.md @@ -0,0 +1,3 @@ +# Archiver + +This directory contains the script used by the [Internet Archive workflow](https://github.com/Ryujinx/release-channel-master/.github/workflows/internet_archive.yaml) to perform automatic archiving of Ryujinx releases. diff --git a/scripts/archiver/ia_update_release.py b/scripts/archiver/ia_update_release.py new file mode 100644 index 0000000..65ad753 --- /dev/null +++ b/scripts/archiver/ia_update_release.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 + +import hashlib +import json +import os +from typing import Dict, Optional +import requests +import internetarchive + + +def get_env_variable(key: str, default_value: Optional[str] = None) -> str: + if key in os.environ: + return os.environ[key] + elif default_value is not None: + return default_value + else: + print(f"ERROR: Missing required env variable {key}") + exit(1) + + +def download_file( + url: str, path: str, headers: Optional[Dict[str, str]] = None +) -> bytes: + with requests.get(url, headers=headers, stream=True) as r: + r.raise_for_status() + with open(path, "wb") as f: + file_hash = hashlib.sha256() + for chunk in r.iter_content(chunk_size=8192): + file_hash.update(chunk) + f.write(chunk) + return file_hash.hexdigest() + + +GITHUB_EVENT_NAME = get_env_variable("GITHUB_EVENT_NAME") +GITHUB_EVENT_PATH = get_env_variable("GITHUB_EVENT_PATH") + +IA_S3_ACCESS_KEY = get_env_variable("IA_S3_ACCESS_KEY") +IA_S3_SECRET_KEY = get_env_variable("IA_S3_SECRET_KEY") + +RELEASE_INPUT_KEY = "release" + +GITHUB_API_LATEST_RELEASE_ENDPOINT = "releases/latest" +GITHUB_API_TAG_RELEASE_ENDPOINT = "releases/tags" + +with open(GITHUB_EVENT_PATH, "r") as f: + github_event = json.load(f) + +if GITHUB_EVENT_NAME == "release" and github_event["action"] == "published": + release_object = github_event["release"] +elif GITHUB_EVENT_NAME == "workflow_dispatch": + inputs_object = github_event["inputs"] + + if RELEASE_INPUT_KEY not in inputs_object: + print( + "ERROR: {GITHUB_EVENT_NAME} require the {RELEASE_INPUT_KEY} key to be present" + ) + exit(1) + + release_input = inputs_object[RELEASE_INPUT_KEY] + target_repository_url = github_event["repository"]["url"] + + # TODO: pass token when present + if release_input == 'latest': + response = requests.get(f"{target_repository_url}/{GITHUB_API_LATEST_RELEASE_ENDPOINT}") + else: + response = requests.get(f"{target_repository_url}/{GITHUB_API_TAG_RELEASE_ENDPOINT}/{release_input}") + + if response.status_code != 200: + print(f"ERROR: GitHub API returned {response.status_code}") + exit(1) + + release_object = response.json() +else: + print(f"ERROR: Unsupported event received ({GITHUB_EVENT_NAME})") + exit(1) + +download_path = "./" + +files = {} + +# First we grab all assets +for asset_object in release_object["assets"]: + asset_name = asset_object["name"] + target_file_path = os.path.join(download_path, asset_name) + browser_download_url = asset_object["browser_download_url"] + + # TODO: Release should expose hashes to ensure integrity. + print(f"Downloading {asset_name} ({browser_download_url})") + download_file(browser_download_url, target_file_path) + + files[asset_name] = target_file_path + +version = release_object["name"] +identifier = f"ryujinx-{version}" + +metadata = dict( + title=f"Ryujinx {version}", + mediatype="software", + description="This is an automatic archived version of Ryujinx.\nFor more informations about this release please check out the official Changelog https://github.com/Ryujinx/Ryujinx/wiki/Changelog.", + date=release_object["created_at"], +) + +# TODO: Check API response status. +response = internetarchive.upload( + identifier, + files, + metadata, + delete=True, + verify=True, + verbose=True, + validate_identifier=True, + access_key=IA_S3_ACCESS_KEY, + secret_key=IA_S3_SECRET_KEY, +) + +print(f"Version {version} archived at https://archive.org/details/{identifier}") diff --git a/scripts/archiver/requirements.txt b/scripts/archiver/requirements.txt new file mode 100644 index 0000000..654c3bb --- /dev/null +++ b/scripts/archiver/requirements.txt @@ -0,0 +1,2 @@ +internetarchive==3.0.0 +requests==2.26.0