mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
refactor(ci): remove Bunny Storage upload steps and associated script from release workflow
This commit is contained in:
parent
e4a1b54e98
commit
c1e196ed90
2 changed files with 0 additions and 232 deletions
53
.github/workflows/build-release.yml
vendored
53
.github/workflows/build-release.yml
vendored
|
|
@ -1,9 +1,6 @@
|
|||
# Single tagged-release pipeline: Linux release assets, Windows + macOS Electron
|
||||
# builds, Flatpak, Android APKs (dev/master track tags via android-apk-tag.yml), SLSA
|
||||
# provenance (generic generator: Linux, desktop, optional Android+Flatpak), optional cosign bundles, and one draft GitHub release.
|
||||
# Optional: same draft upload/ tree is mirrored to bunny.net Edge Storage for master/dev
|
||||
# tags when BUNNY_STORAGE_ACCESS_KEY is set (see draft job). -rc tags always use the
|
||||
# dev/ prefix; previous release folders under master/ and dev/ are pruned after each upload.
|
||||
# One workflow run per tag keeps the release graph immutable.
|
||||
#
|
||||
# Pinned first-party actions (bump tag and SHA together when upgrading):
|
||||
|
|
@ -569,33 +566,6 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve tag release track
|
||||
id: release_track
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin dev master
|
||||
sha="$(git rev-parse HEAD)"
|
||||
on_m=false
|
||||
on_d=false
|
||||
while IFS= read -r line; do
|
||||
ref="$(printf '%s' "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
case "$ref" in
|
||||
origin/master) on_m=true ;;
|
||||
origin/dev) on_d=true ;;
|
||||
esac
|
||||
done < <(git branch -r --contains "${sha}")
|
||||
if [[ "${on_m}" == true ]]; then
|
||||
track=master
|
||||
elif [[ "${on_d}" == true ]]; then
|
||||
track=dev
|
||||
else
|
||||
track=none
|
||||
fi
|
||||
echo "track=${track}" >> "${GITHUB_OUTPUT}"
|
||||
echo "Resolved tag ${GITHUB_REF_NAME} -> track=${track}"
|
||||
|
||||
- name: Download Linux release assets (x64)
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
|
|
@ -692,29 +662,6 @@ jobs:
|
|||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash scripts/ci/github-draft-release-upload-assets.sh upload
|
||||
|
||||
- name: Upload release assets to Bunny Storage
|
||||
if: >-
|
||||
!startsWith(github.ref_name, 'preview-') &&
|
||||
(steps.release_track.outputs.track == 'master' || steps.release_track.outputs.track == 'dev')
|
||||
env:
|
||||
BUNNY_STORAGE_ACCESS_KEY: ${{ secrets.BUNNY_STORAGE_ACCESS_KEY }}
|
||||
BUNNY_STORAGE_BASE_URL: ${{ vars.BUNNY_STORAGE_BASE_URL }}
|
||||
BUNNY_RELEASE_TRACK: ${{ steps.release_track.outputs.track }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${BUNNY_STORAGE_ACCESS_KEY:-}" ]]; then
|
||||
echo "::notice::Skipping Bunny Storage (set repository secret BUNNY_STORAGE_ACCESS_KEY to enable)."
|
||||
exit 0
|
||||
fi
|
||||
export BUNNY_STORAGE_BASE_URL="${BUNNY_STORAGE_BASE_URL:-https://la.storage.bunnycdn.com/meshchatx}"
|
||||
_ref_lc="${GITHUB_REF_NAME,,}"
|
||||
if [[ "${_ref_lc}" == *"-rc"* ]]; then
|
||||
export BUNNY_STORAGE_OBJECT_PREFIX="dev/${GITHUB_REF_NAME}"
|
||||
else
|
||||
export BUNNY_STORAGE_OBJECT_PREFIX="${BUNNY_RELEASE_TRACK}/${GITHUB_REF_NAME}"
|
||||
fi
|
||||
python3 scripts/ci/github-upload-bunny-storage-release-assets.py upload
|
||||
|
||||
release-artifact-gate:
|
||||
name: Verify required tagged release artifacts
|
||||
if: >-
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Upload a directory tree to bunny.net Edge Storage (HTTP PUT per object)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
def encode_object_rel(rel: str) -> str:
|
||||
return "/".join(quote(part, safe="") for part in rel.split("/"))
|
||||
|
||||
|
||||
def mime_for(path: Path) -> str:
|
||||
if path.suffix.lower() == ".wasm":
|
||||
return "application/wasm"
|
||||
guessed, _enc = mimetypes.guess_type(path.name)
|
||||
return guessed or "application/octet-stream"
|
||||
|
||||
|
||||
def get_json(url: str, access_key: str, timeout: int = 120) -> object:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
method="GET",
|
||||
headers={"AccessKey": access_key},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
body = resp.read()
|
||||
return json.loads(body.decode("utf-8"))
|
||||
|
||||
|
||||
def delete_path(url: str, access_key: str, timeout: int = 120) -> None:
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
method="DELETE",
|
||||
headers={"AccessKey": access_key},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
code = resp.getcode()
|
||||
except urllib.error.HTTPError as e:
|
||||
code = e.code
|
||||
if code == 404:
|
||||
return
|
||||
e.read(500)
|
||||
raise SystemExit(f"HTTP {code} DELETE {url}") from e
|
||||
else:
|
||||
if code not in (200, 201, 204):
|
||||
raise SystemExit(f"unexpected DELETE status {code} for {url}")
|
||||
|
||||
|
||||
def prune_other_versions(
|
||||
base: str,
|
||||
access_key: str,
|
||||
track: str,
|
||||
keep_version: str,
|
||||
) -> None:
|
||||
"""Remove every version directory under ``track/`` except ``keep_version``."""
|
||||
base = base.rstrip("/")
|
||||
list_url = f"{base}/{encode_object_rel(track)}/"
|
||||
try:
|
||||
listing = get_json(list_url, access_key)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
return
|
||||
raise
|
||||
if not isinstance(listing, list):
|
||||
return
|
||||
seen: set[str] = set()
|
||||
for item in listing:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if not item.get("IsDirectory"):
|
||||
continue
|
||||
name = item.get("ObjectName")
|
||||
if not name or not isinstance(name, str):
|
||||
continue
|
||||
if name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
if name == keep_version:
|
||||
continue
|
||||
rel = f"{track}/{name}"
|
||||
delete_url = f"{base}/{encode_object_rel(rel)}"
|
||||
print(f"prune: DELETE {delete_url}", file=sys.stderr)
|
||||
delete_path(delete_url, access_key)
|
||||
|
||||
|
||||
def put_file(
|
||||
url: str,
|
||||
body: bytes,
|
||||
access_key: str,
|
||||
content_type: str,
|
||||
max_attempts: int = 4,
|
||||
) -> None:
|
||||
checksum = hashlib.sha256(body).hexdigest().upper()
|
||||
timeout = 600 if len(body) > 50_000_000 else 120
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=body,
|
||||
method="PUT",
|
||||
headers={
|
||||
"AccessKey": access_key,
|
||||
"Content-Type": content_type,
|
||||
"Checksum": checksum,
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
code = resp.getcode()
|
||||
except urllib.error.HTTPError as e:
|
||||
code = e.code
|
||||
err_body = e.read(500)
|
||||
if code in (200, 201):
|
||||
return
|
||||
if 500 <= code < 600 and attempt < max_attempts:
|
||||
time.sleep(0.5 * (2 ** (attempt - 1)))
|
||||
continue
|
||||
raise SystemExit(
|
||||
f"HTTP {code} for {url}: {err_body!r}",
|
||||
) from e
|
||||
except (urllib.error.URLError, TimeoutError) as e:
|
||||
if attempt < max_attempts:
|
||||
time.sleep(0.5 * (2 ** (attempt - 1)))
|
||||
continue
|
||||
raise SystemExit(f"request failed for {url}: {e}") from e
|
||||
else:
|
||||
if code in (200, 201):
|
||||
return
|
||||
raise SystemExit(f"unexpected status {code} for {url}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
base = os.environ.get("BUNNY_STORAGE_BASE_URL", "").rstrip("/")
|
||||
key = os.environ.get("BUNNY_STORAGE_ACCESS_KEY", "")
|
||||
prefix = os.environ.get("BUNNY_STORAGE_OBJECT_PREFIX", "").strip("/")
|
||||
if not base or not key:
|
||||
print(
|
||||
"BUNNY_STORAGE_BASE_URL and BUNNY_STORAGE_ACCESS_KEY must be set",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
if prefix:
|
||||
seg = prefix.split("/", 1)
|
||||
if len(seg) == 2 and seg[0] in ("master", "dev"):
|
||||
prune_other_versions(base, key, seg[0], seg[1])
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
if not root.is_dir():
|
||||
print(f"not a directory: {root}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
files = sorted(p for p in root.rglob("*") if p.is_file())
|
||||
if not files:
|
||||
print(f"no files under {root}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
for path in files:
|
||||
rel = path.relative_to(root).as_posix()
|
||||
if prefix:
|
||||
object_rel = f"{prefix}/{rel}"
|
||||
else:
|
||||
object_rel = rel
|
||||
url = f"{base}/{encode_object_rel(object_rel)}"
|
||||
body = path.read_bytes()
|
||||
put_file(url, body, key, mime_for(path))
|
||||
print(url)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue