mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 07:51:46 -05:00
Add initial update platform to Proxmox (#167620)
This commit is contained in:
@@ -20,6 +20,7 @@ PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.SENSOR,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -42,5 +42,6 @@ class ProxmoxPermission(StrEnum):
|
||||
POWER = "VM.PowerMgmt"
|
||||
SNAPSHOT = "VM.Snapshot"
|
||||
SYSAUDIT = "Sys.Audit"
|
||||
SYSMOD = "Sys.Modify"
|
||||
SYSPOWER = "Sys.PowerMgmt"
|
||||
VMAUDIT = "VM.Audit"
|
||||
|
||||
@@ -35,7 +35,9 @@ from .const import (
|
||||
DEFAULT_VERIFY_SSL,
|
||||
DOMAIN,
|
||||
NODE_ONLINE,
|
||||
ProxmoxPermission,
|
||||
)
|
||||
from .helpers import is_granted
|
||||
|
||||
type ProxmoxConfigEntry = ConfigEntry[ProxmoxCoordinator]
|
||||
|
||||
@@ -52,6 +54,8 @@ class NodeResources:
|
||||
containers: list[dict[str, Any]]
|
||||
storages: list[dict[str, Any]]
|
||||
backups: list[dict[str, Any]]
|
||||
version: dict[str, Any]
|
||||
update: list[dict[str, Any]] | bool
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@@ -63,6 +67,8 @@ class ProxmoxNodeData:
|
||||
containers: dict[int, dict[str, Any]] = field(default_factory=dict)
|
||||
storages: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
backups: list[dict[str, Any]] = field(default_factory=list)
|
||||
version: dict[str, Any] = field(default_factory=dict)
|
||||
update: list[dict[str, Any]] | bool = False
|
||||
|
||||
|
||||
def proxmox_base_url(coordinator: ProxmoxCoordinator) -> URL:
|
||||
@@ -212,6 +218,8 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
|
||||
},
|
||||
storages={s["storage"]: s for s in resources.storages},
|
||||
backups=resources.backups,
|
||||
version=resources.version,
|
||||
update=resources.update,
|
||||
)
|
||||
|
||||
self._async_add_remove_nodes(data)
|
||||
@@ -258,7 +266,7 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
|
||||
raise ProxmoxServerError from err
|
||||
|
||||
def _fetch_all_nodes(self) -> list[tuple[dict[str, Any], NodeResources]]:
|
||||
"""Fetch all nodes with their VMs, containers, storages, and backups."""
|
||||
"""Fetch all nodes with their VMs, containers, storages, etc."""
|
||||
nodes = self.proxmox.nodes.get() or []
|
||||
return [(node, self._get_node_data(node)) for node in nodes]
|
||||
|
||||
@@ -266,13 +274,20 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
|
||||
self,
|
||||
node: dict[str, Any],
|
||||
) -> NodeResources:
|
||||
"""Get vms, containers, storages, and backups for a node."""
|
||||
"""Get vms, containers, storages, etc. for a node."""
|
||||
if node.get("status") != NODE_ONLINE:
|
||||
_LOGGER.debug(
|
||||
"Node %s is offline, skipping VM/container/storage fetch",
|
||||
node[CONF_NODE],
|
||||
)
|
||||
return NodeResources(vms=[], containers=[], storages=[], backups=[])
|
||||
return NodeResources(
|
||||
vms=[],
|
||||
containers=[],
|
||||
storages=[],
|
||||
backups=[],
|
||||
version={},
|
||||
update=False,
|
||||
)
|
||||
|
||||
vms = self.proxmox.nodes(node[CONF_NODE]).qemu.get() or []
|
||||
containers = self.proxmox.nodes(node[CONF_NODE]).lxc.get() or []
|
||||
@@ -281,9 +296,23 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
|
||||
self.proxmox.nodes(node[CONF_NODE]).tasks.get(typefilter="vzdump", limit=1)
|
||||
or []
|
||||
)
|
||||
version = self.proxmox.nodes(node[CONF_NODE]).version.get() or {}
|
||||
update: list | bool = False
|
||||
if is_granted(
|
||||
self.permissions,
|
||||
p_type="nodes",
|
||||
p_id=node[CONF_NODE],
|
||||
permission=ProxmoxPermission.SYSMOD,
|
||||
):
|
||||
update = self.proxmox.nodes(node[CONF_NODE]).apt.update.get() or []
|
||||
|
||||
return NodeResources(
|
||||
vms=vms, containers=containers, storages=storages, backups=backups
|
||||
vms=vms,
|
||||
containers=containers,
|
||||
storages=storages,
|
||||
backups=backups,
|
||||
version=version,
|
||||
update=update,
|
||||
)
|
||||
|
||||
def _async_add_remove_nodes(self, data: dict[str, ProxmoxNodeData]) -> None:
|
||||
|
||||
@@ -18,10 +18,14 @@ async def async_get_config_entry_diagnostics(
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a Proxmox VE config entry."""
|
||||
|
||||
devices = {}
|
||||
# De-noise updates list
|
||||
for node, node_data in config_entry.runtime_data.data.items():
|
||||
d = asdict(node_data)
|
||||
d.pop("update", None)
|
||||
devices[node] = d
|
||||
|
||||
return {
|
||||
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
|
||||
"devices": {
|
||||
node: asdict(node_data)
|
||||
for node, node_data in config_entry.runtime_data.data.items()
|
||||
},
|
||||
"devices": devices,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
"""Helpers for Proxmox VE."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from .const import ProxmoxPermission
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProxmoxUpdateInfo:
|
||||
"""Describes Proxmox VE update information."""
|
||||
|
||||
latest_version: str | None = None
|
||||
latest_version_id: str | None = None
|
||||
total_updates: int = 0
|
||||
proxmox_updates: int = 0
|
||||
other_updates: int = 0
|
||||
|
||||
|
||||
def is_granted(
|
||||
permissions: dict[str, dict[str, int]],
|
||||
p_type: str = "vms",
|
||||
@@ -16,3 +32,47 @@ def is_granted(
|
||||
if value is not None:
|
||||
return value == 1
|
||||
return False
|
||||
|
||||
|
||||
def is_proxmox_package(update: dict[str, Any]) -> bool:
|
||||
"""Indicate if the given update is related to Proxmox VE."""
|
||||
package = update.get("Package", "")
|
||||
origin = update.get("Origin", "")
|
||||
title = update.get("Title", "")
|
||||
return (
|
||||
package.startswith(("pve-", "libpve-"))
|
||||
or "proxmox" in origin.lower()
|
||||
or "proxmox" in title.lower()
|
||||
)
|
||||
|
||||
|
||||
def latest_version(versions: list[str]) -> str:
|
||||
"""Return the latest version from a list of version strings."""
|
||||
# Fix proxmox -pve1 style suffixes
|
||||
safe_versions = [v.split("-")[0] for v in versions]
|
||||
return max(safe_versions, key=parse_version)
|
||||
|
||||
|
||||
def update_version(
|
||||
current_version: str,
|
||||
updates: list[dict[str, Any]],
|
||||
) -> ProxmoxUpdateInfo:
|
||||
"""Return the updated version based on the current version and updates."""
|
||||
|
||||
count = len(updates)
|
||||
pve_count = sum(is_proxmox_package(u) for u in updates)
|
||||
other_count = len(updates) - pve_count
|
||||
versions = [current_version] + [
|
||||
u["Version"] for u in updates if is_proxmox_package(u)
|
||||
]
|
||||
|
||||
latest = latest_version(versions) if pve_count else current_version
|
||||
return ProxmoxUpdateInfo(
|
||||
latest_version=latest if count else current_version,
|
||||
latest_version_id=f"{latest}-p{pve_count}-d{other_count}"
|
||||
if count
|
||||
else current_version,
|
||||
total_updates=count,
|
||||
proxmox_updates=pve_count,
|
||||
other_updates=other_count,
|
||||
)
|
||||
|
||||
@@ -296,6 +296,11 @@
|
||||
"vm_uptime": {
|
||||
"name": "Uptime"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"node_update": {
|
||||
"name": "Software update"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Update platform for Proxmox VE."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import override
|
||||
|
||||
from homeassistant.components.update import (
|
||||
UpdateEntity,
|
||||
UpdateEntityDescription,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ProxmoxConfigEntry, ProxmoxNodeData
|
||||
from .entity import ProxmoxNodeEntity
|
||||
from .helpers import ProxmoxUpdateInfo, update_version
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ProxmoxNodeUpdateEntityDescription(UpdateEntityDescription):
|
||||
"""Describes Proxmox node update entity."""
|
||||
|
||||
installed_version: Callable[[ProxmoxNodeData], str]
|
||||
update_info: Callable[[ProxmoxNodeData], ProxmoxUpdateInfo | bool]
|
||||
|
||||
|
||||
NODE_UPDATES: tuple[ProxmoxNodeUpdateEntityDescription, ...] = (
|
||||
ProxmoxNodeUpdateEntityDescription(
|
||||
key="node_update",
|
||||
translation_key="node_update",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
installed_version=lambda node_data: node_data.version.get("version", "unknown"),
|
||||
update_info=lambda node_data: update_version(
|
||||
node_data.version.get("version", "unknown"),
|
||||
node_data.update if isinstance(node_data.update, list) else [],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ProxmoxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Proxmox VE update entities."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
def _async_add_new_nodes(nodes: list[ProxmoxNodeData]) -> None:
|
||||
"""Add update entities for newly discovered nodes."""
|
||||
async_add_entities(
|
||||
ProxmoxNodeUpdateEntity(coordinator, entity_description, node)
|
||||
for node in nodes
|
||||
for entity_description in NODE_UPDATES
|
||||
if node.update
|
||||
is not False # Only create entities for nodes where update information is available
|
||||
)
|
||||
|
||||
coordinator.new_nodes_callbacks.append(_async_add_new_nodes)
|
||||
_async_add_new_nodes(
|
||||
[
|
||||
node_data
|
||||
for node_data in coordinator.data.values()
|
||||
if node_data.node["node"] in coordinator.known_nodes
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class ProxmoxNodeUpdateEntity(ProxmoxNodeEntity, UpdateEntity):
|
||||
"""Represents a Proxmox VE node software update."""
|
||||
|
||||
_attr_supported_features = UpdateEntityFeature.RELEASE_NOTES
|
||||
entity_description: ProxmoxNodeUpdateEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def installed_version(self) -> str | None:
|
||||
"""Return installed version."""
|
||||
return self.entity_description.installed_version(
|
||||
self.coordinator.data[self.device_name]
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def latest_version(self) -> str | None:
|
||||
"""Return latest version."""
|
||||
update_info = self._update_info()
|
||||
return update_info.latest_version_id if update_info else None
|
||||
|
||||
@property
|
||||
@override
|
||||
def release_summary(self) -> str | None:
|
||||
"""Return the release summary for the update."""
|
||||
url = self.device_info.get("configuration_url") if self.device_info else None
|
||||
update_info = self._update_info()
|
||||
if update_info and update_info.total_updates > 0:
|
||||
return f"A total of {update_info.total_updates} package update(s) are pending installation: of these {update_info.proxmox_updates} relate to Proxmox and {update_info.other_updates} to other updates. Please visit the [Proxmox VE node]({url}) for details on the pending updates and to upgrade to {update_info.latest_version}."
|
||||
return None
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if the update platform is available."""
|
||||
return self._update_info() is not None
|
||||
|
||||
@override
|
||||
def release_notes(self) -> str | None:
|
||||
"""Return the release notes for the update."""
|
||||
return self.release_summary
|
||||
|
||||
def _update_info(self) -> ProxmoxUpdateInfo | None:
|
||||
"""Return update info or None if unavailable."""
|
||||
info = self.entity_description.update_info(
|
||||
self.coordinator.data[self.device_name]
|
||||
)
|
||||
return info if isinstance(info, ProxmoxUpdateInfo) else None
|
||||
@@ -43,16 +43,21 @@ SNAPSHOT_PERMISSIONS = {
|
||||
"/vms": {"VM.Snapshot": 1},
|
||||
"/vms/101": {"VM.Snapshot": 0},
|
||||
}
|
||||
SYSMOD_PERMISSIONS = {
|
||||
"/": {"Sys.Modify": 1},
|
||||
}
|
||||
|
||||
MERGED_PERMISSIONS = {
|
||||
key: {
|
||||
**AUDIT_PERMISSIONS.get(key, {}),
|
||||
**POWER_PERMISSIONS.get(key, {}),
|
||||
**SNAPSHOT_PERMISSIONS.get(key, {}),
|
||||
**SYSMOD_PERMISSIONS.get(key, {}),
|
||||
}
|
||||
for key in set(AUDIT_PERMISSIONS)
|
||||
| set(POWER_PERMISSIONS)
|
||||
| set(SNAPSHOT_PERMISSIONS)
|
||||
| set(SYSMOD_PERMISSIONS)
|
||||
}
|
||||
|
||||
PVEVMUSER_PERMISSIONS = deepcopy(MERGED_PERMISSIONS)
|
||||
|
||||
@@ -132,6 +132,12 @@ def mock_proxmox_client():
|
||||
not in mock_instance.access.permissions.get.return_value.get("/nodes", [])
|
||||
else load_json_array_fixture("nodes/tasks.json", DOMAIN)
|
||||
)
|
||||
node_mock.version.get.return_value = load_json_object_fixture(
|
||||
"nodes/version.json", DOMAIN
|
||||
)
|
||||
node_mock.apt.update.get.return_value = load_json_array_fixture(
|
||||
"nodes/update.json", DOMAIN
|
||||
)
|
||||
|
||||
qemu_by_vmid = {int(vm["vmid"]): vm for vm in qemu_list}
|
||||
lxc_by_vmid = {int(vm["vmid"]): vm for vm in lxc_list}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
[
|
||||
{
|
||||
"Arch": "amd64",
|
||||
"Description": "This package provides the 'host' DNS lookup utility in the form that is bundled with the BIND 9 sources.",
|
||||
"OldVersion": "1:9.20.18-1~deb13u1",
|
||||
"Origin": "Debian",
|
||||
"Package": "bind9-host",
|
||||
"Priority": "standard",
|
||||
"Section": "net",
|
||||
"Title": "DNS Lookup Utility",
|
||||
"Version": "1:9.20.21-1~deb13u1"
|
||||
},
|
||||
{
|
||||
"Arch": "all",
|
||||
"Description": "This package contains the Proxmox Virtual Environment management tools.",
|
||||
"OldVersion": "9.1.6",
|
||||
"Origin": "Proxmox",
|
||||
"Package": "pve-manager",
|
||||
"Priority": "optional",
|
||||
"Section": "admin",
|
||||
"Title": "Proxmox Virtual Environment Management Tools",
|
||||
"Version": "9.1.7"
|
||||
},
|
||||
{
|
||||
"Arch": "all",
|
||||
"Description": "This package contains the base library used by other Proxmox VE components.",
|
||||
"OldVersion": "9.1.7",
|
||||
"Origin": "Proxmox",
|
||||
"Package": "libpve-common-perl",
|
||||
"Priority": "optional",
|
||||
"Section": "perl",
|
||||
"Title": "Proxmox VE base library",
|
||||
"Version": "9.1.9"
|
||||
},
|
||||
{
|
||||
"Arch": "amd64",
|
||||
"Description": "The Corosync Cluster Engine is a Group Communication System with additional features for implementing high availability within applications. The project provides four C Application Programming Interface features: . * A closed process group communication model with virtual synchrony guarantees for creating replicated state machines. * A simple availability manager that restarts the application process when it has failed. * A configuration and statistics in-memory database that provide the ability to set, retrieve, and receive change notifications of information. * A quorum system that notifies applications when quorum is achieved or lost. . This package contains the Corosync daemon and some administration tools.",
|
||||
"OldVersion": "3.1.10-pve1",
|
||||
"Origin": "Proxmox",
|
||||
"Package": "corosync",
|
||||
"Priority": "optional",
|
||||
"Section": "admin",
|
||||
"Title": "cluster engine daemon and utilities",
|
||||
"Version": "3.1.10-pve2"
|
||||
},
|
||||
{
|
||||
"Arch": "all",
|
||||
"Description": "This package contains the binary firmware for various modules used in the pve-kernel.",
|
||||
"OldVersion": "3.18-1",
|
||||
"Origin": "Proxmox",
|
||||
"Package": "pve-firmware",
|
||||
"Priority": "optional",
|
||||
"Section": "misc",
|
||||
"Title": "Binary firmware code for the pve-kernel",
|
||||
"Version": "3.18-2"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"release": "9.1",
|
||||
"repoid": "abcdefghijklmnop",
|
||||
"version": "9.1.6"
|
||||
}
|
||||
@@ -129,6 +129,11 @@
|
||||
'used_fraction': 0.73,
|
||||
}),
|
||||
}),
|
||||
'version': dict({
|
||||
'release': '9.1',
|
||||
'repoid': 'abcdefghijklmnop',
|
||||
'version': '9.1.6',
|
||||
}),
|
||||
'vms': dict({
|
||||
'100': dict({
|
||||
'cpu': 0.15,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[update.pve1_software_update-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'update',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'update.pve1_software_update',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Software update',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Software update',
|
||||
'platform': 'proxmoxve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <UpdateEntityFeature: 16>,
|
||||
'translation_key': 'node_update',
|
||||
'unique_id': '1234_node/pve1_node_update',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[update.pve1_software_update-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<UpdateEntityStateAttribute.AUTO_UPDATE: 'auto_update'>: False,
|
||||
<UpdateEntityStateAttribute.DISPLAY_PRECISION: 'display_precision'>: 0,
|
||||
<EntityStateAttribute.ENTITY_PICTURE: 'entity_picture'>: '/api/brands/integration/proxmoxve/icon.png',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'pve1 Software update',
|
||||
<UpdateEntityStateAttribute.IN_PROGRESS: 'in_progress'>: False,
|
||||
<UpdateEntityStateAttribute.INSTALLED_VERSION: 'installed_version'>: '9.1.6',
|
||||
<UpdateEntityStateAttribute.LATEST_VERSION: 'latest_version'>: '9.1.9-p4-d1',
|
||||
<UpdateEntityStateAttribute.RELEASE_SUMMARY: 'release_summary'>: 'A total of 5 package update(s) are pending installation: of these 4 relate to Proxmox and 1 to other updates. Please visit the [Proxmox VE node](https://127.0.0.1:8006/#v1:0:=node/pve1) for details on the pending updates and to upgrade to 9.1.9.',
|
||||
<UpdateEntityStateAttribute.RELEASE_URL: 'release_url'>: None,
|
||||
<UpdateEntityStateAttribute.SKIPPED_VERSION: 'skipped_version'>: None,
|
||||
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <UpdateEntityFeature: 16>,
|
||||
<UpdateEntityStateAttribute.TITLE: 'title'>: None,
|
||||
<UpdateEntityStateAttribute.UPDATE_PERCENTAGE: 'update_percentage'>: None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'update.pve1_software_update',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Tests for the Proxmox VE update platform."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import AUDIT_PERMISSIONS, MERGED_PERMISSIONS, setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
ENTITY_ID = "update.pve1_software_update"
|
||||
|
||||
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_proxmox_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
# Ensure Sys.Modify permissions to ensure update status can be determined
|
||||
mock_proxmox_client.access.permissions.get.return_value = MERGED_PERMISSIONS
|
||||
with patch(
|
||||
"homeassistant.components.proxmoxve.PLATFORMS",
|
||||
[Platform.UPDATE],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
await snapshot_platform(
|
||||
hass, entity_registry, snapshot, mock_config_entry.entry_id
|
||||
)
|
||||
|
||||
|
||||
async def test_update_entities_ignored(
|
||||
hass: HomeAssistant,
|
||||
mock_proxmox_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that updates entities are not created with only auditor permissions."""
|
||||
mock_proxmox_client.access.permissions.get.return_value = AUDIT_PERMISSIONS
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.proxmoxve.PLATFORMS",
|
||||
[Platform.UPDATE],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get(ENTITY_ID) is None
|
||||
|
||||
|
||||
async def test_update_unavailable_on_permission_change(
|
||||
hass: HomeAssistant,
|
||||
mock_proxmox_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that updates entities are unavailable with only auditor permissions."""
|
||||
mock_proxmox_client.access.permissions.get.return_value = MERGED_PERMISSIONS
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.proxmoxve.PLATFORMS",
|
||||
[Platform.UPDATE],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert hass.states.get(ENTITY_ID).state is not None
|
||||
|
||||
mock_proxmox_client.access.permissions.get.return_value = AUDIT_PERMISSIONS
|
||||
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_update_up_to_date(
|
||||
hass: HomeAssistant,
|
||||
mock_proxmox_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that updates are up to date when no updates are pending."""
|
||||
mock_proxmox_client.access.permissions.get.return_value = MERGED_PERMISSIONS
|
||||
mock_proxmox_client.nodes.return_value.apt.update.get.return_value = []
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.proxmoxve.PLATFORMS",
|
||||
[Platform.UPDATE],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes.get("latest_version") == "9.1.6"
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
async def test_update_release_notes(
|
||||
hass: HomeAssistant,
|
||||
mock_proxmox_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test that updates release notes are correctly set."""
|
||||
mock_proxmox_client.access.permissions.get.return_value = MERGED_PERMISSIONS
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.proxmoxve.PLATFORMS",
|
||||
[Platform.UPDATE],
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
await ws_client.send_json(
|
||||
{"id": 1, "type": "update/release_notes", "entity_id": ENTITY_ID}
|
||||
)
|
||||
result = await ws_client.receive_json()
|
||||
assert "5 package" in result["result"]
|
||||
Reference in New Issue
Block a user