Initial scaffold: FastAPI backend + Vue 3 frontend + Docker setup
Backend: config/db/security/logging core, SQLAlchemy models (Server, Credential, UpdateJob, UpdateLog, AuditLog, User), services (winrm, ssh, cau, audit, job_runner), REST API (auth, servers, updates, audit), Socket.io WebSocket layer. Frontend: Vue 3 + TS + Pinia + Tailwind, Views (Dashboard, Servers, Updates, Audit, Login), axios + socket.io-client, nginx prod config.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""CAU service: Cluster-Aware Updating via PowerShell remoting (WinRM)."""
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from app.core.exceptions import CAUError
|
||||
from app.core.logging import get_logger
|
||||
from app.services.winrm import WinRMCredentials, WinRMService
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class CAUService:
|
||||
"""Orchestrates Invoke-CauRun against a Windows failover cluster."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cluster_name: str,
|
||||
access_node: str,
|
||||
port: int = 5985,
|
||||
credentials: WinRMCredentials | None = None,
|
||||
) -> None:
|
||||
self.cluster_name = cluster_name
|
||||
self.winrm = WinRMService(access_node, port=port, credentials=credentials)
|
||||
|
||||
async def get_cluster_nodes(self) -> list[dict[str, str]]:
|
||||
"""List cluster nodes and their state."""
|
||||
script = f"""
|
||||
Import-Module FailoverClusters
|
||||
Get-ClusterNode -Cluster {self.cluster_name} |
|
||||
Select-Object Name, State |
|
||||
ForEach-Object {{ "$($_.Name)|$($_.State)" }}
|
||||
"""
|
||||
output = await self.winrm.run_powershell(script)
|
||||
nodes = []
|
||||
for line in output.splitlines():
|
||||
if "|" in line:
|
||||
name, state = line.split("|", 1)
|
||||
nodes.append({"name": name.strip(), "state": state.strip()})
|
||||
return nodes
|
||||
|
||||
async def get_cau_status(self) -> str:
|
||||
"""Return the last CAU run summary."""
|
||||
script = f"""
|
||||
Import-Module ClusterAwareUpdating
|
||||
Get-CauRun -ClusterName {self.cluster_name} |
|
||||
Select-Object -First 1 |
|
||||
Format-List | Out-String
|
||||
"""
|
||||
return await self.winrm.run_powershell(script)
|
||||
|
||||
async def test_cluster(self) -> tuple[bool, str]:
|
||||
"""Verify the cluster is reachable and CAU module present."""
|
||||
try:
|
||||
nodes = await self.get_cluster_nodes()
|
||||
if not nodes:
|
||||
return False, "Keine Cluster-Knoten gefunden"
|
||||
return True, f"Cluster OK — {len(nodes)} Knoten"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return False, str(exc)
|
||||
|
||||
async def invoke_cau_run(self) -> AsyncIterator[str]:
|
||||
"""Start a CAU run and stream per-node progress lines.
|
||||
|
||||
Scaffold implementation: starts Invoke-CauRun and polls Get-CauReport.
|
||||
TODO: true live streaming of per-node phases via event log polling.
|
||||
"""
|
||||
script = f"""
|
||||
Import-Module ClusterAwareUpdating
|
||||
Invoke-CauRun -ClusterName {self.cluster_name} `
|
||||
-Force -Confirm:$false -WaitForCompletion |
|
||||
Out-String
|
||||
"""
|
||||
try:
|
||||
async for line in self.winrm.stream_powershell(script):
|
||||
yield line
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise CAUError(f"CAU-Lauf fehlgeschlagen: {exc}") from exc
|
||||
Reference in New Issue
Block a user