feat(ci): enhance publishing targets and update configuration

This update introduces a new mechanism for handling multiple publish
targets when uploading artifacts. It refines the configuration by
parsing legacy targets from environment variables and allows for
dynamic bucket and directory management during the publishing process.

- Added support for multiple publish targets from environment variables
- Refactored artifact upload logic to improve maintainability
- Updated tauri configuration to include new endpoint for latest.json
This commit is contained in:
Christoph Brandau
2026-07-08 21:02:55 +02:00
parent e0a77607f5
commit 784f133abd
2 changed files with 87 additions and 27 deletions
+85 -26
View File
@@ -4,7 +4,7 @@ import json
import os import os
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Any, Iterable, List, Optional from typing import Any, Iterable, List, NamedTuple, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from minio import Minio from minio import Minio
@@ -20,6 +20,11 @@ class ConfigurationError(RuntimeError):
"""Raised when required configuration values are missing or invalid.""" """Raised when required configuration values are missing or invalid."""
class PublishTarget(NamedTuple):
bucket_name: str
app_dir: str
def _resolve_version(version_arg: Optional[str]) -> str: def _resolve_version(version_arg: Optional[str]) -> str:
"""Resolve the version to publish, falling back to tauri.conf.json.""" """Resolve the version to publish, falling back to tauri.conf.json."""
if version_arg and version_arg.lower() not in {"auto"}: if version_arg and version_arg.lower() not in {"auto"}:
@@ -259,6 +264,71 @@ def _build_latest_payload(
} }
def _parse_publish_targets(primary_bucket: str, primary_app_dir: str) -> list[PublishTarget]:
targets = [PublishTarget(primary_bucket, primary_app_dir)]
raw_legacy_targets = os.environ.get("LEGACY_UPDATE_TARGETS", "gitlite:gitlite")
for raw_target in raw_legacy_targets.split(","):
value = raw_target.strip()
if not value:
continue
if ":" in value:
bucket_name, app_dir = value.split(":", 1)
else:
bucket_name = value
app_dir = value
target = PublishTarget(bucket_name.strip().lower(), app_dir.strip("/"))
if target.bucket_name and target not in targets:
targets.append(target)
return targets
def _publish_target(
client: Minio,
target: PublishTarget,
version: str,
platform: str,
artifacts: Iterable[Path],
primary_artifact: Path,
signature: str,
artifact_base_url: str,
) -> None:
_ensure_bucket(client, target.bucket_name)
existing_latest_json = _read_existing_latest_json(client, target.bucket_name)
latest_payload = _build_latest_payload(
version=version,
platform=platform,
primary_artifact=primary_artifact,
signature=signature,
artifact_base_url=artifact_base_url,
app_dir=target.app_dir,
existing_payload=existing_latest_json,
)
latest_json = json.dumps(latest_payload, separators=(",", ":"))
for artifact in artifacts:
object_segments = [part for part in (version, artifact.name) if part]
object_name = "/".join(object_segments)
client.fput_object(target.bucket_name, object_name, str(artifact))
print(f"Uploaded {artifact.name} -> s3://{target.bucket_name}/{object_name}")
latest_bytes = latest_json.encode("utf-8")
client.put_object(
bucket_name=target.bucket_name,
object_name="latest.json",
data=io.BytesIO(latest_bytes),
length=len(latest_bytes),
content_type="application/json",
)
print(
f"Uploaded latest.json for {platform} -> "
f"s3://{target.bucket_name}/latest.json"
)
def main(version: Optional[str] = None, app_dir: Optional[str] = None) -> None: def main(version: Optional[str] = None, app_dir: Optional[str] = None) -> None:
resolved_version = _resolve_version(version) resolved_version = _resolve_version(version)
artifact_base_url = os.environ.get("ARTIFACT_BASE_URL", "") artifact_base_url = os.environ.get("ARTIFACT_BASE_URL", "")
@@ -269,6 +339,7 @@ def main(version: Optional[str] = None, app_dir: Optional[str] = None) -> None:
# S3_BUCKET variable is cased (e.g. "Gitty" -> "gitty"). # S3_BUCKET variable is cased (e.g. "Gitty" -> "gitty").
bucket_name = (os.environ.get("S3_BUCKET") or "gitty").lower() bucket_name = (os.environ.get("S3_BUCKET") or "gitty").lower()
bucket_prefix = (app_dir or os.environ.get("APP_DIR") or "gitty").strip("/") bucket_prefix = (app_dir or os.environ.get("APP_DIR") or "gitty").strip("/")
publish_targets = _parse_publish_targets(bucket_name, bucket_prefix)
artifacts = _collect_artifacts() artifacts = _collect_artifacts()
if not artifacts: if not artifacts:
@@ -287,9 +358,10 @@ def main(version: Optional[str] = None, app_dir: Optional[str] = None) -> None:
signature = _read_signature(primary) signature = _read_signature(primary)
client = _create_minio_client() client = _create_minio_client()
_ensure_bucket(client, bucket_name)
existing_latest_json = _read_existing_latest_json(client, bucket_name)
local_dist_dir = PROJECT_DIR / "dist"
local_dist_dir.mkdir(parents=True, exist_ok=True)
latest_json_path = local_dist_dir / "latest.json"
latest_payload = _build_latest_payload( latest_payload = _build_latest_payload(
version=resolved_version, version=resolved_version,
platform=platform, platform=platform,
@@ -297,33 +369,20 @@ def main(version: Optional[str] = None, app_dir: Optional[str] = None) -> None:
signature=signature, signature=signature,
artifact_base_url=artifact_base_url, artifact_base_url=artifact_base_url,
app_dir=bucket_prefix, app_dir=bucket_prefix,
existing_payload=existing_latest_json,
) )
latest_json = json.dumps(latest_payload, separators=(",", ":")) latest_json = json.dumps(latest_payload, separators=(",", ":"))
local_dist_dir = PROJECT_DIR / "dist"
local_dist_dir.mkdir(parents=True, exist_ok=True)
latest_json_path = local_dist_dir / "latest.json"
latest_json_path.write_text(latest_json + "\n", encoding="utf-8") latest_json_path.write_text(latest_json + "\n", encoding="utf-8")
for artifact in artifacts: for target in publish_targets:
object_segments = [part for part in (resolved_version, artifact.name) if part] _publish_target(
object_name = "/".join(object_segments) client=client,
client.fput_object(bucket_name, object_name, str(artifact)) target=target,
print(f"Uploaded {artifact.name} -> s3://{bucket_name}/{object_name}") version=resolved_version,
platform=platform,
latest_object_name = "latest.json" artifacts=artifacts,
latest_bytes = latest_json.encode("utf-8") primary_artifact=primary,
client.put_object( signature=signature,
bucket_name=bucket_name, artifact_base_url=artifact_base_url,
object_name=latest_object_name,
data=io.BytesIO(latest_bytes),
length=len(latest_bytes),
content_type="application/json",
)
print(
f"Uploaded latest.json for {platform} -> "
f"s3://{bucket_name}/{latest_object_name}"
) )
+1
View File
@@ -64,6 +64,7 @@
"updater": { "updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDNEJGRTZERTREOTc5QjkKUldTNWVkbmtiZjVMZk02YzdpenVLOVltREJpRkFrc2F4dUwzaTV5M3pacFRVR0tmd25xSGtjK1MK", "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDNEJGRTZERTREOTc5QjkKUldTNWVkbmtiZjVMZk02YzdpenVLOVltREJpRkFrc2F4dUwzaTV5M3pacFRVR0tmd25xSGtjK1MK",
"endpoints": [ "endpoints": [
"https://cdn.cbsk-tech.de/gitty/latest.json",
"https://cdn.cbsk-tech.de/gitlite/latest.json" "https://cdn.cbsk-tech.de/gitlite/latest.json"
] ]
} }