"""Support for Proxmox VE.""" from __future__ import annotations from datetime import timedelta from typing import Any from proxmoxer import AuthenticationError, ProxmoxAPI import requests.exceptions from requests.exceptions import ConnectTimeout, SSLError import voluptuous as vol from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_VERIFY_SSL, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .common import ProxmoxClient, call_api_container_vm, parse_api_container_vm from .const import ( _LOGGER, CONF_CONTAINERS, CONF_NODE, CONF_NODES, CONF_REALM, CONF_VMS, COORDINATORS, DEFAULT_PORT, DEFAULT_REALM, DEFAULT_VERIFY_SSL, DOMAIN, PROXMOX_CLIENTS, TYPE_CONTAINER, TYPE_VM, UPDATE_INTERVAL, ) PLATFORMS = [Platform.BINARY_SENSOR] CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( cv.ensure_list, [ vol.Schema( { vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_REALM, default=DEFAULT_REALM): cv.string, vol.Optional( CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL ): cv.boolean, vol.Required(CONF_NODES): vol.All( cv.ensure_list, [ vol.Schema( { vol.Required(CONF_NODE): cv.string, vol.Optional(CONF_VMS, default=[]): [ cv.positive_int ], vol.Optional(CONF_CONTAINERS, default=[]): [ cv.positive_int ], } ) ], ), } ) ], ) }, extra=vol.ALLOW_EXTRA, ) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the platform.""" hass.data.setdefault(DOMAIN, {}) def build_client() -> ProxmoxAPI: """Build the Proxmox client connection.""" hass.data[PROXMOX_CLIENTS] = {} for entry in config[DOMAIN]: host = entry[CONF_HOST] port = entry[CONF_PORT] user = entry[CONF_USERNAME] realm = entry[CONF_REALM] password = entry[CONF_PASSWORD] verify_ssl = entry[CONF_VERIFY_SSL] hass.data[PROXMOX_CLIENTS][host] = None try: # Construct an API client with the given data for the given host proxmox_client = ProxmoxClient( host, port, user, realm, password, verify_ssl ) proxmox_client.build_client() except AuthenticationError: _LOGGER.warning( "Invalid credentials for proxmox instance %s:%d", host, port ) continue except SSLError: _LOGGER.error( ( "Unable to verify proxmox server SSL. " 'Try using "verify_ssl: false" for proxmox instance %s:%d' ), host, port, ) continue except ConnectTimeout: _LOGGER.warning("Connection to host %s timed out during setup", host) continue except requests.exceptions.ConnectionError: _LOGGER.warning("Host %s is not reachable", host) continue hass.data[PROXMOX_CLIENTS][host] = proxmox_client await hass.async_add_executor_job(build_client) coordinators: dict[ str, dict[str, dict[int, DataUpdateCoordinator[dict[str, Any] | None]]] ] = {} hass.data[DOMAIN][COORDINATORS] = coordinators # Create a coordinator for each vm/container for host_config in config[DOMAIN]: host_name = host_config["host"] coordinators[host_name] = {} proxmox_client = hass.data[PROXMOX_CLIENTS][host_name] # Skip invalid hosts if proxmox_client is None: continue proxmox = proxmox_client.get_api_client() for node_config in host_config["nodes"]: node_name = node_config["node"] node_coordinators = coordinators[host_name][node_name] = {} for vm_id in node_config["vms"]: coordinator = create_coordinator_container_vm( hass, proxmox, host_name, node_name, vm_id, TYPE_VM ) # Fetch initial data await coordinator.async_refresh() node_coordinators[vm_id] = coordinator for container_id in node_config["containers"]: coordinator = create_coordinator_container_vm( hass, proxmox, host_name, node_name, container_id, TYPE_CONTAINER ) # Fetch initial data await coordinator.async_refresh() node_coordinators[container_id] = coordinator for component in PLATFORMS: await hass.async_create_task( async_load_platform(hass, component, DOMAIN, {"config": config}, config) ) return True def create_coordinator_container_vm( hass: HomeAssistant, proxmox: ProxmoxAPI, host_name: str, node_name: str, vm_id: int, vm_type: int, ) -> DataUpdateCoordinator[dict[str, Any] | None]: """Create and return a DataUpdateCoordinator for a vm/container.""" async def async_update_data() -> dict[str, Any] | None: """Call the api and handle the response.""" def poll_api() -> dict[str, Any] | None: """Call the api.""" return call_api_container_vm(proxmox, node_name, vm_id, vm_type) vm_status = await hass.async_add_executor_job(poll_api) if vm_status is None: _LOGGER.warning( "Vm/Container %s unable to be found in node %s", vm_id, node_name ) return None return parse_api_container_vm(vm_status) return DataUpdateCoordinator( hass, _LOGGER, name=f"proxmox_coordinator_{host_name}_{node_name}_{vm_id}", update_method=async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), )