Files
broker/reference_tools/proxmox_readonly_reference.py
T
coreci 7f7868f7cf docs(milestone): complete coreci-broker
---ci---
phase: 5
milestone: v0.1
status: complete
requirements:
  covered: [REQ-01, REQ-02, REQ-03, REQ-04, REQ-05, REQ-06, REQ-07, REQ-08, REQ-09, REQ-10, REQ-11, REQ-12, REQ-13, REQ-14, REQ-15, REQ-16, REQ-17, REQ-18, REQ-19, REQ-20]
  partial: []
---/ci---
2026-10-10 15:30:14 +00:00

104 lines
4.0 KiB
Python

"""title: Proxmox Read-Only Reference [CoreCI]
author: coreci
description: Open WebUI tool template for the CoreCI Broker flow (spec section 11) - register, persist agent state, read coreci/proxmox, then act; downstream Proxmox calls default to a safe dry-run.
version: 0.1.0
requirements: []
licence: MIT
"""
import datetime
import coreci_broker_client
SECRET_PATH = "coreci/proxmox"
DRY_RUN_NOTE = (
"reference dry-run: the broker credential was fetched but stays unused; "
"replace this block with a real read-only Proxmox call to go live"
)
class Tools:
class Valves:
base_url = ""
agent_id = ""
app_uuid = ""
user_id = ""
def __init__(self):
self.valves = self.Valves()
self._state = {}
def _client(self):
return coreci_broker_client.BrokerClient(
self.valves.base_url, self.valves.agent_id,
self.valves.app_uuid, self.valves.user_id, ["proxmox", "readonly"],
)
def _persist(self, client):
now = datetime.datetime.now(datetime.timezone.utc).isoformat()
self._state.setdefault("first_registered_at", now)
self._state["last_seen_at"] = now
client.put_state(self._state)
def _load_persisted(self, client):
document = client.get_state(self._state)
if document is not None:
for key in document:
self._state.setdefault(key, document[key])
def _credential(self):
client = self._client()
first_call = self._state.get("current_token") is None
fingerprint = (self._state.get("current_token"),
self._state.get("token_expires_at"))
if first_call:
client.ensure_registered(self._state)
self._persist(client)
else:
self._load_persisted(client)
document = client.read_secret(SECRET_PATH, self._state)
current = (self._state.get("current_token"),
self._state.get("token_expires_at"))
if not first_call and current != fingerprint:
self._persist(client)
return document
def _dry_run(self, action, document):
return {
"dry_run": True,
"action": action,
"credential_present": isinstance(document, dict) and "data" in document,
"note": DRY_RUN_NOTE,
}
def list_nodes(self, context=None) -> dict:
"""List the Proxmox cluster nodes as a safe dry-run by default.
First call registers with the broker (idempotent) and persists the
agent state; subsequent calls load the persisted token and reuse
it. The credential is fetched from coreci/proxmox before any
downstream action, and on a 401 the client re-registers with a
single retry after which the new token is persisted again. The
reference template then returns a dry-run marker and performs no
live Proxmox call: replace the dry-run block with a real read-only
API call when wiring live infrastructure; this template ships no
live-credential use path.
:param context: optional Open WebUI call context, unused
:return: dry-run response dict with credential presence
"""
return self._dry_run("list_nodes", self._credential())
def vm_status(self, vm_id: str, context=None) -> dict:
"""Report one Proxmox VM status as a safe dry-run by default.
Follows the same broker flow as list_nodes: register and persist
the agent state on the first call, load the persisted token on
later calls, fetch the coreci/proxmox credential, then act. The
dry-run block is the default and no live Proxmox client is wired
in - replace it with a real read-only status call for live use.
:param vm_id: Proxmox VM identifier to look up
:param context: optional Open WebUI call context, unused
:return: dry-run response dict with credential presence
"""
return self._dry_run("vm_status", self._credential())