863f482f9c
New scripts/render_pptx.py parses the consolidated -marp.md and produces a structured, editable, S&P-themed PPTX via python-pptx. 16:9; title slide black bg + red top bar; content slides with red H2 titles, bullets, blockquotes, embedded PNGs, native tables, benefit callouts. Added python-pptx>=0.6.23 to pyproject [slides] optional-dep. render_slides.sh Step 4 produces it; attach_release_asset.py extended for dual PPTX. Output: nova-autonomous-cloud-delivery-python.pptx. ---ci--- project: acdl phase: 3 milestone: v1.23 status: execute phase_role: execution ---/ci---
96 lines
3.7 KiB
Python
Executable File
96 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""scripts/attach_release_asset.py — upload one or more files as Gitea release
|
|
attachments.
|
|
|
|
REQ-228 (v1.18): PPTX (and any deck artifact) is attached to the phase's
|
|
Gitea release. Uses the Gitea API:
|
|
POST /api/v1/repos/{owner}/{repo}/releases/{id}/assets
|
|
multipart form: name=<filename>, attachment=<file bytes>
|
|
|
|
REQ-270 (v1.23): supports dual PPTX attachment — the MARP PPTX (primary,
|
|
attached first) and the python-pptx PPTX (comparison artifact). Multiple
|
|
file paths are accepted; the first is the primary attachment.
|
|
|
|
Usage:
|
|
python3 scripts/attach_release_asset.py <file-path> <release-id>
|
|
python3 scripts/attach_release_asset.py <file-path> <file-path-2>... <release-id>
|
|
python3 scripts/attach_release_asset.py docs/presentations/nova-autonomous-cloud-delivery.pptx 522
|
|
python3 scripts/attach_release_asset.py \
|
|
docs/presentations/nova-autonomous-cloud-delivery.pptx \
|
|
docs/presentations/nova-autonomous-cloud-delivery-python.pptx 522
|
|
|
|
The last positional argument is always the release id; every preceding
|
|
argument is an asset path (backward compatible with the single-asset call).
|
|
|
|
Token resolution: reads NOVA_GITEA_TOKEN (or ACDL_GITEA_TOKEN) from .env.secrets
|
|
/ .env, matching the ship_phase.sh pattern. Never uses shell env tokens.
|
|
"""
|
|
import os
|
|
import sys
|
|
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
from pathlib import Path
|
|
|
|
GITEA_BASE = "https://git.cloudinit.dev"
|
|
OWNER = "continuous-intelligence"
|
|
REPO = "acdl"
|
|
|
|
|
|
def resolve_token() -> str:
|
|
for fn in (".env.secrets", ".env"):
|
|
try:
|
|
for line in Path(fn).read_text().splitlines():
|
|
if line.startswith("NOVA_GITEA_TOKEN=") or line.startswith("ACDL_GITEA_TOKEN="):
|
|
return line.split("=", 1)[1].strip()
|
|
except (FileNotFoundError, PermissionError):
|
|
continue
|
|
raise RuntimeError("No Gitea token found in .env.secrets or .env (NOVA_GITEA_TOKEN/ACDL_GITEA_TOKEN)")
|
|
|
|
|
|
def attach_asset(file_path: str, release_id: str) -> dict:
|
|
token = resolve_token()
|
|
p = Path(file_path)
|
|
if not p.is_file():
|
|
raise FileNotFoundError(f"Asset file not found: {file_path}")
|
|
|
|
url = f"{GITEA_BASE}/api/v1/repos/{OWNER}/{REPO}/releases/{release_id}/assets"
|
|
filename = p.name
|
|
|
|
boundary = "----NovaBoundary7MAgYbk"
|
|
body = (
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="name"\r\n\r\n'
|
|
f"{filename}\r\n"
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="attachment"; filename="{filename}"\r\n'
|
|
f"Content-Type: application/octet-stream\r\n\r\n"
|
|
).encode() + p.read_bytes() + f"\r\n--{boundary}--\r\n".encode()
|
|
|
|
req = urllib.request.Request(
|
|
url,
|
|
data=body,
|
|
headers={
|
|
"Authorization": f"token {token}",
|
|
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
},
|
|
method="POST",
|
|
)
|
|
try:
|
|
resp = urllib.request.urlopen(req, timeout=60)
|
|
return json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
err = e.read().decode()[:300]
|
|
raise RuntimeError(f"HTTP {e.code} attaching {filename} to release {release_id}: {err}") from e
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 3:
|
|
print("Usage: attach_release_asset.py <file-path> [<file-path-2>...] <release-id>")
|
|
sys.exit(1)
|
|
asset_paths = sys.argv[1:-1]
|
|
release_id = sys.argv[-1]
|
|
for idx, path in enumerate(asset_paths):
|
|
result = attach_asset(path, release_id)
|
|
primary = " (primary)" if idx == 0 and len(asset_paths) > 1 else ""
|
|
print(f"Attached{primary}: {result.get('name')} → release {release_id} (asset id {result.get('id')})") |