From d5d87e865a880491daadecf35f753c637af577f0 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Tue, 30 Sep 2025 13:40:29 +0200 Subject: [PATCH] Gitea: Create release pipeline --- .gitea/workflows/build_and_test.yml | 47 ++++++++++- gitea_create_release.py | 122 +++++++++++++++++++++++++++ gitea_upload_file.py | 125 ++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 gitea_create_release.py create mode 100644 gitea_upload_file.py diff --git a/.gitea/workflows/build_and_test.yml b/.gitea/workflows/build_and_test.yml index 6b327c09..76a14d8b 100644 --- a/.gitea/workflows/build_and_test.yml +++ b/.gitea/workflows/build_and_test.yml @@ -8,10 +8,17 @@ on: - '**' pull_request: workflow_dispatch: + inputs: + create_release: + type: boolean + description: 'Create release' + required: false + default: false jobs: build-rpm: name: build:rpm (${{ matrix.distro }}) + if: github.ref_type != 'workflow_dispatch' runs-on: ${{ matrix.runner }} strategy: fail-fast: false @@ -88,8 +95,19 @@ jobs: fi for file in "${files[@]}"; do echo "Uploading $file -> ${{ matrix.upload_url }}" - curl --fail --user __token__:"$TOKEN" --upload-file "$file" "${{ matrix.upload_url }}" + curl --fail --user __token__:"$TOKEN" --upload-file "$file" "${{ matrix.upload_url }}" + python gitea_upload_file.py done + + if [ "${{ matrix.distro }}" = "rocky8_nocuda" ]; then + for file in jfjoch-viewer*.rpm; do + python gitea_upload_file.py "$file" + done + elif [ "${{ matrix.distro }}" = "ubuntu2204_nocuda" ]; then + for file in jfjoch*viewer*.deb; do + python gitea_upload_file.py "$file" + done + fi python-client: name: Generate python client runs-on: jfjoch_rocky8 @@ -103,7 +121,6 @@ jobs: run: | twine upload -u __token__ -p ${{ secrets.CI_PYPI_TOKEN }} --skip-existing dist/* twine upload -u __token__ -p ${{ secrets.PIP_REPOSITORY_API_TOKEN }} --repository-url https://gitea.psi.ch/api/packages/mx/pypi dist/* - documentation: name: Build documentation runs-on: jfjoch_rocky8 @@ -133,6 +150,7 @@ jobs: unit-tests: name: Unit tests runs-on: jfjoch_rocky8 + if: github.ref_type != 'tag' && github.ref_type != 'workflow_dispatch' container: image: gitea.psi.ch/leonarski_f/jfjoch_rocky8:2509 options: --gpus all @@ -158,3 +176,28 @@ jobs: run: | cd build/tools ./jfjoch_hdf5_test ../../tests/test_data/compression_benchmark.h5 + + create-release: + name: Create release + runs-on: jfjoch_rocky8 + if: github.event_name == 'workflow_dispatch' && (github.event.inputs.create_release == 'true' || github.event.inputs.create_release == true) + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: 'true' # Optional; should be the default + - name: Configure auth and fetch LFS + shell: bash + env: + GITEA_TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }} + run: | + git lfs install --local + AUTH=$(git config --local http.${{ github.server_url }}/.extraheader) + git config --local --unset http.${{ github.server_url }}/.extraheader + git config --local http.${{ github.server_url }}/${{ github.repository }}.git/info/lfs/objects/batch.extraheader "$AUTH" + git lfs pull + - name: Create release + env: + GITEA_TOKEN: ${{ secrets.PIP_REPOSITORY_API_TOKEN }} + shell: bash + run: | + python3 gitea_create_release.py \ No newline at end of file diff --git a/gitea_create_release.py b/gitea_create_release.py new file mode 100644 index 00000000..aa4ebe37 --- /dev/null +++ b/gitea_create_release.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import os +import sys +from typing import Optional +try: + import requests +except ModuleNotFoundError: + print("ERROR: Python 'requests' package is required. Please install it (e.g., pip install requests).") + sys.exit(1) + +gitea_api_url = "https://gitea.psi.ch/api/v1/" +repo_owner = "mx" +repo_name = "jungfraujoch" + +gitea_token = os.getenv('GITEA_TOKEN') +if gitea_token is None: + print("ERROR: Required environment variables not set") + sys.exit(1) + +def read_version(): + try: + with open('VERSION', 'r') as f: + return f.readline().strip() + except FileNotFoundError: + print("ERROR: VERSION file not found") + sys.exit(1) + +def create_release(version): + headers = { + 'Authorization': f'token {gitea_token}', + 'Content-Type': 'application/json', + } + + data = { + 'tag_name': version, + 'name': f'Release {version}', + 'draft': False, + 'prerelease': '-rc.' in version or '-alpha.' in version or '-beta.' in version + } + + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases' + + response = requests.post(url, headers=headers, json=data) + if response.status_code != 201: + print(f"ERROR: Failed to create release. Status code: {response.status_code}") + print(response.text) + sys.exit(1) + + release = response.json() + print(f"Successfully created release {version}") + return release + +def get_release_by_tag(version: str): + """ + Fetch release information by tag name. + Returns the release object (dict) or exits on error. + """ + headers = { + 'Authorization': f'token {gitea_token}', + 'Content-Type': 'application/json', + } + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/tags/{version}' + resp = requests.get(url, headers=headers) + if resp.status_code != 200: + print(f"ERROR: Failed to fetch release {version}. Status code: {resp.status_code}") + print(resp.text) + sys.exit(1) + return resp.json() + +def upload_file_to_release(version: str, file_path: str, name: Optional[str] = None, label: Optional[str] = None): + """ + Upload a file as an asset to the release identified by the given version tag. + + :param version: Tag name of the release (e.g., '1.2.3' or '1.2.3-rc.1') + :param file_path: Local file path to upload + :param name: Optional asset name to display in release (defaults to filename) + :param label: Optional asset label (display name) + """ + if not os.path.isfile(file_path): + print(f"ERROR: File not found: {file_path}") + sys.exit(1) + + # Ensure the release exists and get its ID + release = get_release_by_tag(version) + release_id = release.get('id') + if release_id is None: + print("ERROR: Release ID not found in response") + sys.exit(1) + + headers = { + 'Authorization': f'token {gitea_token}', + } + + asset_name = name if name else os.path.basename(file_path) + data = {'name': asset_name} + if label: + data['label'] = label + + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/{release_id}/assets' + + with open(file_path, 'rb') as f: + files = { + 'attachment': (asset_name, f, 'application/octet-stream'), + } + resp = requests.post(url, headers=headers, data=data, files=files) + + if resp.status_code not in (201, 200): + print(f"ERROR: Failed to upload asset. Status code: {resp.status_code}") + print(resp.text) + sys.exit(1) + + asset = resp.json() + browser_download_url = asset.get('browser_download_url') or asset.get('browser_url') or asset.get('url') + print(f"Uploaded asset '{asset_name}' to release {version}: {browser_download_url}") + +if __name__ == '__main__': + version = read_version() + release = create_release(version) + + upload_file_to_release(version, 'fpga/images/jfjoch_fpga_pcie_8x10g.mcs.gz', 'jfjoch_fpga_pcie_8x10g.mcs.gz', '8x10G FPGA image') + upload_file_to_release(version, 'fpga/images/jfjoch_fpga_pcie_100g.mcs.gz', 'jfjoch_fpga_pcie_100g.mcs.gz', '100G FPGA image') diff --git a/gitea_upload_file.py b/gitea_upload_file.py new file mode 100644 index 00000000..8fde9c9e --- /dev/null +++ b/gitea_upload_file.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +import os +import sys +from typing import Optional +try: + import requests +except ModuleNotFoundError: + print("ERROR: Python 'requests' package is required. Please install it (e.g., pip install requests).") + sys.exit(1) + +gitea_api_url = "https://gitea.psi.ch/api/v1/" +repo_owner = "mx" +repo_name = "jungfraujoch" + +gitea_token = os.getenv('GITEA_TOKEN') +if gitea_token is None: + print("ERROR: Required environment variables not set") + sys.exit(1) + +def read_version(): + try: + with open('VERSION', 'r') as f: + return f.readline().strip() + except FileNotFoundError: + print("ERROR: VERSION file not found") + sys.exit(1) + +def create_release(version): + headers = { + 'Authorization': f'token {gitea_token}', + 'Content-Type': 'application/json', + } + + data = { + 'tag_name': version, + 'name': f'Release {version}', + 'draft': False, + 'prerelease': '-rc.' in version or '-alpha.' in version or '-beta.' in version + } + + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases' + + response = requests.post(url, headers=headers, json=data) + if response.status_code != 201: + print(f"ERROR: Failed to create release. Status code: {response.status_code}") + print(response.text) + sys.exit(1) + + release = response.json() + print(f"Successfully created release {version}") + return release + +def get_release_by_tag(version: str): + """ + Fetch release information by tag name. + Returns the release object (dict) or exits on error. + """ + headers = { + 'Authorization': f'token {gitea_token}', + 'Content-Type': 'application/json', + } + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/tags/{version}' + resp = requests.get(url, headers=headers) + if resp.status_code != 200: + print(f"ERROR: Failed to fetch release {version}. Status code: {resp.status_code}") + print(resp.text) + sys.exit(1) + return resp.json() + +def upload_file_to_release(version: str, file_path: str, name: Optional[str] = None, label: Optional[str] = None): + """ + Upload a file as an asset to the release identified by the given version tag. + + :param version: Tag name of the release (e.g., '1.2.3' or '1.2.3-rc.1') + :param file_path: Local file path to upload + :param name: Optional asset name to display in release (defaults to filename) + :param label: Optional asset label (display name) + """ + if not os.path.isfile(file_path): + print(f"ERROR: File not found: {file_path}") + sys.exit(1) + + asset_name = name if name else os.path.basename(file_path) + print(f"Uploading file '{file_path}' to release {version} as {asset_name}") + + # Ensure the release exists and get its ID + release = get_release_by_tag(version) + release_id = release.get('id') + if release_id is None: + print("ERROR: Release ID not found in response") + sys.exit(1) + + headers = { + 'Authorization': f'token {gitea_token}', + } + + data = {'name': asset_name} + if label: + data['label'] = label + + url = f'{gitea_api_url}/repos/{repo_owner}/{repo_name}/releases/{release_id}/assets' + + with open(file_path, 'rb') as f: + files = { + 'attachment': (asset_name, f, 'application/octet-stream'), + } + resp = requests.post(url, headers=headers, data=data, files=files) + + if resp.status_code not in (201, 200): + print(f"ERROR: Failed to upload asset. Status code: {resp.status_code}") + print(resp.text) + sys.exit(1) + + asset = resp.json() + browser_download_url = asset.get('browser_download_url') or asset.get('browser_url') or asset.get('url') + print(f"Uploaded asset '{asset_name}' to release {version}: {browser_download_url}") + +if __name__ == '__main__': + version = read_version() + if len(sys.argv) != 2: + exit(1) + + upload_file_to_release(version, sys.argv[1]) +