rein-aharness/deploy/scripts/rein-aharness-claim
tegwick 94a15d67aa feat: k8s:// ClusterIP claim env + complete payload target_repo
Resolve llm-connect / actcore-api / edge-relay without port-forward.
Include target_repo on FI and Binky complete payloads for ops UI artefacts.
2026-08-05 17:23:03 +02:00

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()