mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-30 07:09:35 +08:00
219 lines
6.4 KiB
Python
219 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import shutil
|
||
import sys
|
||
import tempfile
|
||
import urllib.error
|
||
import urllib.parse
|
||
import urllib.request
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
|
||
DRIVERS = [
|
||
"mariadb",
|
||
"oceanbase",
|
||
"doris",
|
||
"starrocks",
|
||
"sphinx",
|
||
"sqlserver",
|
||
"sqlite",
|
||
"duckdb",
|
||
"dameng",
|
||
"kingbase",
|
||
"highgo",
|
||
"vastbase",
|
||
"opengauss",
|
||
"mongodb",
|
||
"tdengine",
|
||
"clickhouse",
|
||
]
|
||
|
||
BUNDLE_NAME = "GoNavi-DriverAgents.zip"
|
||
|
||
|
||
def required_assets():
|
||
assets = []
|
||
for driver in DRIVERS:
|
||
assets.extend(
|
||
[
|
||
("Windows", f"{driver}-driver-agent-windows-amd64.exe"),
|
||
("MacOS", f"{driver}-driver-agent-darwin-amd64"),
|
||
("MacOS", f"{driver}-driver-agent-darwin-arm64"),
|
||
("Linux", f"{driver}-driver-agent-linux-amd64"),
|
||
]
|
||
)
|
||
if driver == "duckdb":
|
||
assets.append(("Windows", "duckdb.dll"))
|
||
else:
|
||
assets.append(("Windows", f"{driver}-driver-agent-windows-arm64.exe"))
|
||
return assets
|
||
|
||
|
||
def github_headers():
|
||
headers = {
|
||
"Accept": "application/vnd.github+json",
|
||
"User-Agent": "GoNavi-CI",
|
||
}
|
||
token = os.environ.get("DRIVER_RELEASE_TOKEN") or os.environ.get("GITHUB_TOKEN")
|
||
if token:
|
||
headers["Authorization"] = f"Bearer {token}"
|
||
return headers
|
||
|
||
|
||
def fetch_json(url):
|
||
req = urllib.request.Request(url, headers=github_headers())
|
||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||
return json.loads(resp.read().decode("utf-8"))
|
||
|
||
|
||
def download_asset(asset, destination):
|
||
headers = github_headers()
|
||
headers["Accept"] = "application/octet-stream"
|
||
req = urllib.request.Request(asset["url"], headers=headers)
|
||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||
with open(destination, "wb") as out:
|
||
shutil.copyfileobj(resp, out)
|
||
|
||
|
||
def load_release(repo, source):
|
||
owner_repo = repo.strip()
|
||
if not owner_repo:
|
||
raise ValueError("repo is required")
|
||
if source == "latest":
|
||
api_url = f"https://api.github.com/repos/{owner_repo}/releases/latest"
|
||
else:
|
||
api_url = f"https://api.github.com/repos/{owner_repo}/releases/tags/{urllib.parse.quote(source, safe='')}"
|
||
try:
|
||
return fetch_json(api_url)
|
||
except urllib.error.HTTPError as exc:
|
||
if exc.code == 404:
|
||
print(f"未找到上一版 driver release:{source}", file=sys.stderr)
|
||
return None
|
||
raise
|
||
|
||
|
||
def asset_map(release):
|
||
result = {}
|
||
for asset in release.get("assets", []):
|
||
name = str(asset.get("name", "")).strip()
|
||
if name:
|
||
result[name] = asset
|
||
return result
|
||
|
||
|
||
def copy_missing_from_bundle(bundle_path, target_root):
|
||
copied = 0
|
||
required = {
|
||
(Path(platform) / file_name).as_posix()
|
||
for platform, file_name in required_assets()
|
||
}
|
||
with zipfile.ZipFile(bundle_path) as zf:
|
||
members = {
|
||
Path(info.filename).as_posix(): info
|
||
for info in zf.infolist()
|
||
if not info.is_dir()
|
||
}
|
||
for item in sorted(required):
|
||
source = members.get(item)
|
||
if not source:
|
||
continue
|
||
relative = Path(item)
|
||
target = target_root / relative
|
||
if target.exists():
|
||
continue
|
||
target.parent.mkdir(parents=True, exist_ok=True)
|
||
with zf.open(source) as src, open(target, "wb") as out:
|
||
shutil.copyfileobj(src, out)
|
||
copied += 1
|
||
return copied
|
||
|
||
|
||
def copy_missing_standalone(release_assets, target_root):
|
||
copied = 0
|
||
with tempfile.TemporaryDirectory(prefix="gonavi-driver-assets-") as tmp:
|
||
tmp_root = Path(tmp)
|
||
for platform, file_name in required_assets():
|
||
target = target_root / platform / file_name
|
||
if target.exists():
|
||
continue
|
||
asset = release_assets.get(file_name)
|
||
if not asset:
|
||
continue
|
||
temp_path = tmp_root / file_name
|
||
download_asset(asset, temp_path)
|
||
target.parent.mkdir(parents=True, exist_ok=True)
|
||
shutil.copy2(temp_path, target)
|
||
copied += 1
|
||
return copied
|
||
|
||
|
||
def verify_complete(target_root):
|
||
missing = []
|
||
for platform, file_name in required_assets():
|
||
if not (target_root / platform / file_name).is_file():
|
||
missing.append(f"{platform}/{file_name}")
|
||
return missing
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--assets-dir", required=True)
|
||
parser.add_argument("--repo", default="Syngnat/GoNavi-DriverAgents")
|
||
parser.add_argument(
|
||
"--source",
|
||
action="append",
|
||
required=True,
|
||
help="latest or release tag such as dev-latest/v1.0.0; may be passed more than once",
|
||
)
|
||
parser.add_argument("--require-complete", action="store_true")
|
||
args = parser.parse_args()
|
||
|
||
assets_dir = Path(args.assets_dir)
|
||
driver_root = assets_dir / "drivers"
|
||
driver_root.mkdir(parents=True, exist_ok=True)
|
||
|
||
found_source = False
|
||
total_copied = 0
|
||
for source in args.source:
|
||
release = load_release(args.repo, source)
|
||
if release is None:
|
||
continue
|
||
found_source = True
|
||
|
||
releases_assets = asset_map(release)
|
||
copied = 0
|
||
bundle_asset = releases_assets.get(BUNDLE_NAME)
|
||
if bundle_asset:
|
||
with tempfile.TemporaryDirectory(prefix="gonavi-driver-release-") as tmp:
|
||
bundle_path = Path(tmp) / BUNDLE_NAME
|
||
download_asset(bundle_asset, bundle_path)
|
||
copied += copy_missing_from_bundle(bundle_path, driver_root)
|
||
copied += copy_missing_standalone(releases_assets, driver_root)
|
||
total_copied += copied
|
||
print(f"已从 {source} 补齐 driver assets:{copied} 个文件")
|
||
|
||
if not verify_complete(driver_root):
|
||
break
|
||
|
||
if not found_source:
|
||
print("未找到可补齐的上一版 driver release。", file=sys.stderr)
|
||
else:
|
||
print(f"driver assets 合计补齐:{total_copied} 个文件")
|
||
|
||
if args.require_complete:
|
||
missing = verify_complete(driver_root)
|
||
if missing:
|
||
print("driver assets 不完整:", file=sys.stderr)
|
||
for item in missing:
|
||
print(f" - {item}", file=sys.stderr)
|
||
return 1
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|