chore: initial project setup with backend, frontend, CI/CD, and documentation
Add complete NexaFabric project structure including: - FastAPI backend with SQLAlchemy models, JWT auth, RBAC, audit logging, and provider interfaces - React + TypeScript frontend with Vite, Tailwind CSS, TanStack Query, and Zustand - Docker Compose configuration for PostgreSQL, Redis, API, worker, frontend, and nginx - GitHub Actions and GitLab CI workflows for testing, linting, building, and security scanning - Environment
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProviderConnection:
|
||||
api_url: str
|
||||
token: str
|
||||
verify_tls: bool = True
|
||||
read_only: bool = True
|
||||
|
||||
|
||||
class HypervisorProvider(ABC):
|
||||
@abstractmethod
|
||||
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class InventoryProvider(ABC):
|
||||
@abstractmethod
|
||||
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NetworkProvider(ABC):
|
||||
@abstractmethod
|
||||
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FirewallProvider(ABC):
|
||||
@abstractmethod
|
||||
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Provider(
|
||||
HypervisorProvider,
|
||||
InventoryProvider,
|
||||
NetworkProvider,
|
||||
FirewallProvider,
|
||||
ABC,
|
||||
):
|
||||
name: str
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from typing import Any
|
||||
|
||||
from app.services.providers.base import Provider, ProviderConnection
|
||||
|
||||
|
||||
class DemoProvider(Provider):
|
||||
name = "demo"
|
||||
|
||||
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
|
||||
return {"version": "demo", "api_url": connection.api_url, "mode": "offline"}
|
||||
|
||||
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
|
||||
return {
|
||||
"nodes": [
|
||||
{"node": "demo-pve-01", "status": "online", "maxcpu": 16, "maxmem": 68719476736},
|
||||
{"node": "demo-pve-02", "status": "online", "maxcpu": 16, "maxmem": 68719476736},
|
||||
],
|
||||
"workloads": [
|
||||
{"vmid": 201, "name": "demo-web-01", "type": "qemu", "status": "running", "node": "demo-pve-01"},
|
||||
{"vmid": 202, "name": "demo-db-01", "type": "qemu", "status": "running", "node": "demo-pve-02"},
|
||||
],
|
||||
"networks": [
|
||||
{"name": "vmbr0", "type": "bridge", "vlan": 10},
|
||||
{"name": "vnet-prod", "type": "vnet", "vlan": 120},
|
||||
],
|
||||
}
|
||||
|
||||
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
|
||||
return (await self.sync_inventory(connection))["networks"]
|
||||
|
||||
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return {"provider": self.name, "generated": rules, "warnings": ["Demo provider preview."]}
|
||||
|
||||
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return {"applied": False, "reason": "Demo provider never applies firewall changes.", "rules": rules}
|
||||
@@ -0,0 +1,56 @@
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.services.providers.base import Provider, ProviderConnection
|
||||
|
||||
|
||||
class ProxmoxProvider(Provider):
|
||||
name = "proxmox"
|
||||
|
||||
async def test_connection(self, connection: ProviderConnection) -> dict[str, Any]:
|
||||
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=10) as client:
|
||||
response = await client.get(
|
||||
f"{connection.api_url.rstrip('/')}/api2/json/version",
|
||||
headers={"Authorization": connection.token},
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json().get("data", {})
|
||||
|
||||
async def sync_inventory(self, connection: ProviderConnection) -> dict[str, list[dict[str, Any]]]:
|
||||
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=20) as client:
|
||||
resources = await client.get(
|
||||
f"{connection.api_url.rstrip('/')}/api2/json/cluster/resources",
|
||||
headers={"Authorization": connection.token},
|
||||
)
|
||||
resources.raise_for_status()
|
||||
data = resources.json().get("data", [])
|
||||
|
||||
nodes = [item for item in data if item.get("type") == "node"]
|
||||
workloads = [item for item in data if item.get("type") in {"qemu", "lxc"}]
|
||||
networks = await self.list_networks(connection)
|
||||
return {"nodes": nodes, "workloads": workloads, "networks": networks}
|
||||
|
||||
async def list_networks(self, connection: ProviderConnection) -> list[dict[str, Any]]:
|
||||
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=20) as client:
|
||||
resources = await client.get(
|
||||
f"{connection.api_url.rstrip('/')}/api2/json/cluster/resources",
|
||||
headers={"Authorization": connection.token},
|
||||
)
|
||||
resources.raise_for_status()
|
||||
data = resources.json().get("data", [])
|
||||
return [item for item in data if item.get("type") in {"network", "sdn"}]
|
||||
|
||||
async def preview_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return {
|
||||
"provider": self.name,
|
||||
"read_only": connection.read_only,
|
||||
"generated": rules,
|
||||
"warnings": ["Preview only. No Proxmox firewall changes were sent."],
|
||||
}
|
||||
|
||||
async def apply_rules(self, connection: ProviderConnection, rules: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
if connection.read_only:
|
||||
return {"applied": False, "reason": "Cluster is read-only", "rules": rules}
|
||||
return {"applied": False, "reason": "Apply adapter intentionally requires explicit implementation", "rules": rules}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from app.services.providers.base import Provider
|
||||
from app.services.providers.demo import DemoProvider
|
||||
from app.services.providers.proxmox import ProxmoxProvider
|
||||
|
||||
|
||||
_providers: dict[str, Provider] = {"proxmox": ProxmoxProvider(), "demo": DemoProvider()}
|
||||
|
||||
|
||||
def get_provider(name: str) -> Provider:
|
||||
return _providers[name]
|
||||
Reference in New Issue
Block a user