feat: add Proxmox VE API authentication and real data collection
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been skipped
CI/CD Pipeline / Security Scanning (push) Has been skipped
CI/CD Pipeline / Lint Code (push) Failing after 7m32s
CI/CD Pipeline / Build and Push Docker Images (api) (push) Has been skipped
CI/CD Pipeline / Build and Push Docker Images (chat) (push) Has been skipped
CI/CD Pipeline / Build and Push Docker Images (frontend) (push) Has been skipped
CI/CD Pipeline / Generate Documentation (push) Failing after 7m37s
CI/CD Pipeline / Build and Push Docker Images (worker) (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been skipped
CI/CD Pipeline / Security Scanning (push) Has been skipped
CI/CD Pipeline / Lint Code (push) Failing after 7m32s
CI/CD Pipeline / Build and Push Docker Images (api) (push) Has been skipped
CI/CD Pipeline / Build and Push Docker Images (chat) (push) Has been skipped
CI/CD Pipeline / Build and Push Docker Images (frontend) (push) Has been skipped
CI/CD Pipeline / Generate Documentation (push) Failing after 7m37s
CI/CD Pipeline / Build and Push Docker Images (worker) (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
Implement complete Proxmox API integration with support for both password and API token authentication, replacing mock data with real infrastructure data. **Authentication Features:** 1. **Dual Authentication Support** - API Token authentication (recommended, more secure) - Username + Password authentication (fallback) - Automatic fallback to mock data if not configured 2. **Configuration** (`src/datacenter_docs/utils/config.py`) - PROXMOX_HOST: Server hostname/IP - PROXMOX_PORT: API port (default 8006) - PROXMOX_USER: Username with realm (e.g., root@pam) - PROXMOX_PASSWORD: Password authentication - PROXMOX_TOKEN_NAME: API token name - PROXMOX_TOKEN_VALUE: API token secret - PROXMOX_VERIFY_SSL: SSL certificate verification - PROXMOX_TIMEOUT: API request timeout 3. **Real Data Collection** (`src/datacenter_docs/collectors/proxmox_collector.py`) - VMs: Iterate all nodes, collect QEMU VMs with full details - Containers: Collect LXC containers from all nodes - Nodes: Cluster node information and status - Cluster: Cluster configuration and quorum status - Storage: Storage pools with usage statistics - Networks: Network interfaces from all nodes - Automatic fallback to mock data on errors 4. **Comprehensive Documentation** (`docs/PROXMOX_SETUP.md`) - Step-by-step API token creation guide - Permission setup (PVEAuditor role) - Security best practices - Troubleshooting guide - Example configurations 5. **Environment Template** (`.env.example`) - Detailed Proxmox configuration section - Inline documentation for both auth methods - Security recommendations **Security Features:** - Supports read-only PVEAuditor role - API tokens preferred over passwords - SSL verification configurable - Graceful degradation (uses mock data if API unavailable) - No credentials in code (environment variables only) **How to Configure:** ```bash # Method 1: API Token (Recommended) PROXMOX_HOST=proxmox.company.com PROXMOX_USER=automation@pam PROXMOX_TOKEN_NAME=docs-collector PROXMOX_TOKEN_VALUE=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # Method 2: Password PROXMOX_HOST=proxmox.company.com PROXMOX_USER=root@pam PROXMOX_PASSWORD=your-secure-password ``` See `docs/PROXMOX_SETUP.md` for complete setup instructions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -38,31 +38,85 @@ class ProxmoxCollector(BaseCollector):
|
||||
"""
|
||||
Connect to Proxmox VE via API
|
||||
|
||||
Supports two authentication methods:
|
||||
1. Username + Password (PROXMOX_USER + PROXMOX_PASSWORD)
|
||||
2. API Token (PROXMOX_USER + PROXMOX_TOKEN_NAME + PROXMOX_TOKEN_VALUE)
|
||||
|
||||
Returns:
|
||||
True if connection successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
self.logger.info("Connecting to Proxmox VE...")
|
||||
|
||||
# Try to connect via MCP client first
|
||||
# Check if we have real Proxmox credentials configured
|
||||
if not settings.PROXMOX_HOST or settings.PROXMOX_HOST == "proxmox.example.com":
|
||||
self.logger.warning(
|
||||
"Proxmox host not configured, using mock data for development"
|
||||
)
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
# Import proxmoxer (only when needed)
|
||||
try:
|
||||
from proxmoxer import ProxmoxAPI
|
||||
except ImportError:
|
||||
self.logger.error(
|
||||
"proxmoxer library not installed. Install with: pip install proxmoxer"
|
||||
)
|
||||
self.logger.info("Falling back to mock data")
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
self.logger.info("Connecting to Proxmox via MCP...")
|
||||
# MCP client would handle Proxmox connection
|
||||
# For now, fall back to direct connection
|
||||
raise NotImplementedError("MCP Proxmox integration pending")
|
||||
# Determine authentication method
|
||||
auth_params = {
|
||||
"host": settings.PROXMOX_HOST,
|
||||
"port": settings.PROXMOX_PORT,
|
||||
"verify_ssl": settings.PROXMOX_VERIFY_SSL,
|
||||
"timeout": settings.PROXMOX_TIMEOUT,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.info(f"MCP connection not available: {e}, will use mock data")
|
||||
# API Token authentication (preferred, more secure)
|
||||
if settings.PROXMOX_TOKEN_NAME and settings.PROXMOX_TOKEN_VALUE:
|
||||
self.logger.info(
|
||||
f"Connecting to Proxmox at {settings.PROXMOX_HOST}:{settings.PROXMOX_PORT} "
|
||||
f"using API token authentication"
|
||||
)
|
||||
auth_params["user"] = settings.PROXMOX_USER
|
||||
auth_params["token_name"] = settings.PROXMOX_TOKEN_NAME
|
||||
auth_params["token_value"] = settings.PROXMOX_TOKEN_VALUE
|
||||
|
||||
# Password authentication (fallback)
|
||||
elif settings.PROXMOX_PASSWORD:
|
||||
self.logger.info(
|
||||
f"Connecting to Proxmox at {settings.PROXMOX_HOST}:{settings.PROXMOX_PORT} "
|
||||
f"using password authentication"
|
||||
)
|
||||
auth_params["user"] = settings.PROXMOX_USER
|
||||
auth_params["password"] = settings.PROXMOX_PASSWORD
|
||||
|
||||
else:
|
||||
self.logger.warning(
|
||||
"No Proxmox credentials configured (neither token nor password). "
|
||||
"Using mock data."
|
||||
)
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
# Create Proxmox API connection
|
||||
self.proxmox_client = ProxmoxAPI(**auth_params)
|
||||
|
||||
# Test connection by getting version
|
||||
version = self.proxmox_client.version.get()
|
||||
self.logger.info(f"✓ Connected to Proxmox VE version {version.get('version')}")
|
||||
|
||||
# For development: use mock data
|
||||
self.logger.info("Will use mock data for development")
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Connection failed: {e}", exc_info=True)
|
||||
return False
|
||||
self.logger.info("Falling back to mock data for this run")
|
||||
self.connected = True # Continue with mock data
|
||||
return True
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
"""Disconnect from Proxmox VE"""
|
||||
@@ -123,14 +177,27 @@ class ProxmoxCollector(BaseCollector):
|
||||
|
||||
try:
|
||||
vms = []
|
||||
# In production: iterate through nodes and get VMs
|
||||
# for node in self.proxmox_client.nodes.get():
|
||||
# node_vms = self.proxmox_client.nodes(node['node']).qemu.get()
|
||||
# vms.extend(node_vms)
|
||||
# Iterate through all nodes and collect VMs
|
||||
nodes = self.proxmox_client.nodes.get()
|
||||
for node in nodes:
|
||||
node_name = node["node"]
|
||||
try:
|
||||
node_vms = self.proxmox_client.nodes(node_name).qemu.get()
|
||||
# Add node name to each VM for reference
|
||||
for vm in node_vms:
|
||||
vm["node"] = node_name
|
||||
vms.extend(node_vms)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to get VMs from node {node_name}: {e}")
|
||||
continue
|
||||
|
||||
self.logger.info(f"Collected {len(vms)} VMs from {len(nodes)} nodes")
|
||||
return vms
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect VMs: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_vms()
|
||||
|
||||
async def collect_containers(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -147,14 +214,27 @@ class ProxmoxCollector(BaseCollector):
|
||||
|
||||
try:
|
||||
containers = []
|
||||
# In production: iterate through nodes and get containers
|
||||
# for node in self.proxmox_client.nodes.get():
|
||||
# node_containers = self.proxmox_client.nodes(node['node']).lxc.get()
|
||||
# containers.extend(node_containers)
|
||||
# Iterate through all nodes and collect containers
|
||||
nodes = self.proxmox_client.nodes.get()
|
||||
for node in nodes:
|
||||
node_name = node["node"]
|
||||
try:
|
||||
node_containers = self.proxmox_client.nodes(node_name).lxc.get()
|
||||
# Add node name to each container for reference
|
||||
for container in node_containers:
|
||||
container["node"] = node_name
|
||||
containers.extend(node_containers)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to get containers from node {node_name}: {e}")
|
||||
continue
|
||||
|
||||
self.logger.info(f"Collected {len(containers)} containers from {len(nodes)} nodes")
|
||||
return containers
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect containers: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_containers()
|
||||
|
||||
async def collect_nodes(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -170,13 +250,13 @@ class ProxmoxCollector(BaseCollector):
|
||||
return self._get_mock_nodes()
|
||||
|
||||
try:
|
||||
# In production:
|
||||
# nodes = self.proxmox_client.nodes.get()
|
||||
# return nodes
|
||||
return []
|
||||
nodes = self.proxmox_client.nodes.get()
|
||||
self.logger.info(f"Collected {len(nodes)} nodes")
|
||||
return nodes
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect nodes: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_nodes()
|
||||
|
||||
async def collect_cluster_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -192,13 +272,21 @@ class ProxmoxCollector(BaseCollector):
|
||||
return self._get_mock_cluster()
|
||||
|
||||
try:
|
||||
# In production:
|
||||
# cluster_status = self.proxmox_client.cluster.status.get()
|
||||
# return cluster_status
|
||||
return {}
|
||||
cluster_status = self.proxmox_client.cluster.status.get()
|
||||
# Extract cluster info from status
|
||||
cluster_info = {}
|
||||
for item in cluster_status:
|
||||
if item.get("type") == "cluster":
|
||||
cluster_info = item
|
||||
break
|
||||
|
||||
self.logger.info(f"Collected cluster info: {cluster_info.get('name', 'unknown')}")
|
||||
return cluster_info if cluster_info else self._get_mock_cluster()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect cluster info: {e}", exc_info=True)
|
||||
return {}
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_cluster()
|
||||
|
||||
async def collect_storage(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -214,13 +302,28 @@ class ProxmoxCollector(BaseCollector):
|
||||
return self._get_mock_storage()
|
||||
|
||||
try:
|
||||
# In production:
|
||||
# storage = self.proxmox_client.storage.get()
|
||||
# return storage
|
||||
return []
|
||||
storage = self.proxmox_client.storage.get()
|
||||
# Get detailed stats for each storage
|
||||
for store in storage:
|
||||
storage_id = store.get("storage")
|
||||
try:
|
||||
# Get storage status with usage information
|
||||
# Note: This requires iterating through nodes
|
||||
nodes = self.proxmox_client.nodes.get()
|
||||
if nodes:
|
||||
node_name = nodes[0]["node"] # Use first node for storage info
|
||||
status = self.proxmox_client.nodes(node_name).storage(storage_id).status.get()
|
||||
store.update(status)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to get detailed info for storage {storage_id}: {e}")
|
||||
|
||||
self.logger.info(f"Collected {len(storage)} storage pools")
|
||||
return storage
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect storage: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_storage()
|
||||
|
||||
async def collect_networks(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -237,14 +340,27 @@ class ProxmoxCollector(BaseCollector):
|
||||
|
||||
try:
|
||||
networks = []
|
||||
# In production: iterate nodes and get network configs
|
||||
# for node in self.proxmox_client.nodes.get():
|
||||
# node_networks = self.proxmox_client.nodes(node['node']).network.get()
|
||||
# networks.extend(node_networks)
|
||||
# Iterate through all nodes and collect network configs
|
||||
nodes = self.proxmox_client.nodes.get()
|
||||
for node in nodes:
|
||||
node_name = node["node"]
|
||||
try:
|
||||
node_networks = self.proxmox_client.nodes(node_name).network.get()
|
||||
# Add node name to each network interface
|
||||
for net in node_networks:
|
||||
net["node"] = node_name
|
||||
networks.extend(node_networks)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to get networks from node {node_name}: {e}")
|
||||
continue
|
||||
|
||||
self.logger.info(f"Collected {len(networks)} network interfaces from {len(nodes)} nodes")
|
||||
return networks
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to collect networks: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.info("Falling back to mock data")
|
||||
return self._get_mock_networks()
|
||||
|
||||
def _calculate_statistics(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -23,6 +23,16 @@ class Settings(BaseSettings):
|
||||
MCP_SERVER_URL: str = "http://localhost:8080"
|
||||
MCP_API_KEY: str = "default-key"
|
||||
|
||||
# Proxmox VE Configuration
|
||||
PROXMOX_HOST: str = "proxmox.example.com"
|
||||
PROXMOX_PORT: int = 8006
|
||||
PROXMOX_USER: str = "root@pam"
|
||||
PROXMOX_PASSWORD: str = ""
|
||||
PROXMOX_TOKEN_NAME: str = "" # Alternative to password: token name
|
||||
PROXMOX_TOKEN_VALUE: str = "" # Alternative to password: token value
|
||||
PROXMOX_VERIFY_SSL: bool = False # Set to True in production with valid certs
|
||||
PROXMOX_TIMEOUT: int = 30
|
||||
|
||||
# OpenAI-Compatible LLM Configuration
|
||||
# Works with: OpenAI, Anthropic, LLMStudio, Open-WebUI, Ollama, LocalAI
|
||||
LLM_BASE_URL: str = "https://api.openai.com/v1"
|
||||
|
||||
Reference in New Issue
Block a user