File size: 3,187 Bytes
2e00b5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#!/usr/bin/env python3
"""
Utility to push Hugging Face Space secrets from a local env file.

Usage:
    export HF_TOKEN=hf_xxx   # token with write access to the Space
    python backend/scripts/update_hf_space_secrets.py \
        --space davidttran999/hue-portal-backendDocker \
        --secrets-file ops/hf.secrets.env
"""

from __future__ import annotations

import argparse
import os
import sys
from pathlib import Path
from typing import Dict

import requests


def parse_env_file(path: Path) -> Dict[str, str]:
    """
    Load KEY=VALUE pairs from the provided file.

    Blank lines and comments starting with `#` are ignored.
    """
    if not path.exists():
        raise FileNotFoundError(f"Secrets file not found: {path}")

    secrets: Dict[str, str] = {}
    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            raise ValueError(f"Invalid secret line (missing '='): {raw_line}")
        key, value = line.split("=", 1)
        secrets[key.strip()] = value.strip()

    if not secrets:
        raise ValueError(f"No secrets detected in {path}")
    return secrets


def upsert_secret(space_id: str, token: str, key: str, value: str) -> None:
    """Create or update a secret for the given Hugging Face Space."""
    url = f"https://huggingface.co/api/spaces/{space_id}/secrets"
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    response = requests.post(url, headers=headers, json={"key": key, "value": value}, timeout=30)
    if response.status_code != 200:
        raise RuntimeError(
            f"Failed to upsert secret '{key}'. "
            f"Status {response.status_code}: {response.text}"
        )


def build_parser() -> argparse.ArgumentParser:
    """Configure CLI options."""
    parser = argparse.ArgumentParser(description="Sync secrets to a Hugging Face Space.")
    parser.add_argument(
        "--space",
        required=True,
        help="Space identifier in the form owner/space (e.g. davidttran999/hue-portal-backendDocker).",
    )
    parser.add_argument(
        "--secrets-file",
        default="ops/hf.secrets.env",
        help="Path to file containing KEY=VALUE entries (default: %(default)s).",
    )
    parser.add_argument(
        "--token-env",
        default="HF_TOKEN",
        help="Environment variable that stores the Hugging Face access token (default: %(default)s).",
    )
    return parser


def main() -> None:
    """CLI entry point."""
    parser = build_parser()
    args = parser.parse_args()

    token = os.environ.get(args.token_env)
    if not token:
        parser.error(f"Environment variable {args.token_env} is not set.")

    secrets = parse_env_file(Path(args.secrets_file).expanduser())
    for key, value in secrets.items():
        upsert_secret(args.space, token, key, value)
        print(f"✅ Synced secret '{key}' to {args.space}")


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:  # pylint: disable=broad-except
        print(f"❌ {exc}", file=sys.stderr)
        sys.exit(1)