7f7868f7cf
---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---
104 lines
4.0 KiB
Python
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()) |