Resolve llm-connect / actcore-api / edge-relay without port-forward. Include target_repo on FI and Binky complete payloads for ops UI artefacts.
110 lines
3.1 KiB
Python
Executable file
110 lines
3.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Claim-loop launcher: load claim-loop.env and resolve k8s:// Service URLs.
|
|
|
|
Used as ~/bin/rein-aharness-claim on railiance01 so shell never sources JSON
|
|
values (AGENT_HARNESS_REPO_MAP). Supports durable ClusterIP access without
|
|
kubectl port-forward (ACTIVITY-WP-0027 / docs in activity-core).
|
|
|
|
Env file keys may use:
|
|
LLM_CONNECT_URL=k8s://activity-core/llm-connect:8080
|
|
ACTIVITY_CORE_URL=k8s://activity-core/actcore-api:8010
|
|
STATE_HUB_URL=k8s://activity-core/actcore-statehub-edge-relay:8000
|
|
|
|
Resolution uses: kubectl -n <ns> get svc <name> -o jsonpath={.spec.clusterIP}
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
_K8S_URL = re.compile(
|
|
r"^k8s://(?P<ns>[a-z0-9-]+)/(?P<svc>[a-z0-9-]+)(?::(?P<port>\d+))?/?$"
|
|
)
|
|
|
|
|
|
def _load_env_file(path: Path) -> dict[str, str]:
|
|
out: dict[str, str] = {}
|
|
if not path.is_file():
|
|
return out
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
out[key.strip()] = value.strip()
|
|
return out
|
|
|
|
|
|
def _resolve_k8s_url(value: str) -> str:
|
|
m = _K8S_URL.match(value.strip())
|
|
if not m:
|
|
return value
|
|
ns = m.group("ns")
|
|
svc = m.group("svc")
|
|
port = m.group("port") or "80"
|
|
kubeconfig = os.environ.get("KUBECONFIG", "/etc/rancher/k3s/k3s.yaml")
|
|
cmd = [
|
|
"kubectl",
|
|
f"--kubeconfig={kubeconfig}",
|
|
"-n",
|
|
ns,
|
|
"get",
|
|
"svc",
|
|
svc,
|
|
"-o",
|
|
"jsonpath={.spec.clusterIP}",
|
|
]
|
|
try:
|
|
ip = subprocess.check_output(cmd, text=True, timeout=30).strip()
|
|
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as exc:
|
|
print(
|
|
f"rein-aharness-claim: failed to resolve {value!r}: {exc}",
|
|
file=sys.stderr,
|
|
)
|
|
raise SystemExit(2) from exc
|
|
if not ip or ip == "None":
|
|
print(
|
|
f"rein-aharness-claim: empty ClusterIP for {ns}/{svc}",
|
|
file=sys.stderr,
|
|
)
|
|
raise SystemExit(2)
|
|
return f"http://{ip}:{port}"
|
|
|
|
|
|
def _resolve_env(env: dict[str, str]) -> dict[str, str]:
|
|
keys = (
|
|
"LLM_CONNECT_URL",
|
|
"ACTIVITY_CORE_URL",
|
|
"STATE_HUB_URL",
|
|
"STATEHUB_URL",
|
|
)
|
|
for key in keys:
|
|
if key in env and env[key].startswith("k8s://"):
|
|
env[key] = _resolve_k8s_url(env[key])
|
|
return env
|
|
|
|
|
|
def main() -> None:
|
|
home = Path.home()
|
|
env = os.environ.copy()
|
|
# Default kubeconfig for k3s host users
|
|
env.setdefault("KUBECONFIG", "/etc/rancher/k3s/k3s.yaml")
|
|
file_env = _load_env_file(home / ".config/rein-aharness/claim-loop.env")
|
|
env.update(file_env)
|
|
env = _resolve_env(env)
|
|
|
|
rein = home / "rein-aharness" / ".venv" / "bin" / "rein-aharness"
|
|
if not rein.is_file():
|
|
# Fall back to PATH
|
|
cmd = ["rein-aharness", *sys.argv[1:]]
|
|
else:
|
|
cmd = [str(rein), *sys.argv[1:]]
|
|
raise SystemExit(subprocess.call(cmd, env=env))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|