diff --git a/homeassistant/components/portainer/binary_sensor.py b/homeassistant/components/portainer/binary_sensor.py index 96d9727a3bc..0b7c563eb65 100644 --- a/homeassistant/components/portainer/binary_sensor.py +++ b/homeassistant/components/portainer/binary_sensor.py @@ -61,30 +61,53 @@ async def async_setup_entry( ) -> None: """Set up Portainer binary sensors.""" coordinator = entry.runtime_data - entities: list[BinarySensorEntity] = [] - for endpoint in coordinator.data.values(): - entities.extend( + def _async_add_new_endpoints(endpoints: list[PortainerCoordinatorData]) -> None: + """Add new endpoint binary sensors.""" + async_add_entities( PortainerEndpointSensor( coordinator, entity_description, endpoint, ) for entity_description in ENDPOINT_SENSORS + for endpoint in endpoints + if entity_description.state_fn(endpoint) ) - entities.extend( + def _async_add_new_containers( + containers: list[tuple[PortainerCoordinatorData, DockerContainer]], + ) -> None: + """Add new container binary sensors.""" + async_add_entities( PortainerContainerSensor( coordinator, entity_description, container, endpoint, ) - for container in endpoint.containers.values() + for (endpoint, container) in containers for entity_description in CONTAINER_SENSORS + if entity_description.state_fn(container) ) - async_add_entities(entities) + coordinator.new_endpoints_callbacks.append(_async_add_new_endpoints) + coordinator.new_containers_callbacks.append(_async_add_new_containers) + + _async_add_new_endpoints( + [ + endpoint + for endpoint in coordinator.data.values() + if endpoint.id in coordinator.known_endpoints + ] + ) + _async_add_new_containers( + [ + (endpoint, container) + for endpoint in coordinator.data.values() + for container in endpoint.containers.values() + ] + ) class PortainerEndpointSensor(PortainerEndpointEntity, BinarySensorEntity): diff --git a/homeassistant/components/portainer/button.py b/homeassistant/components/portainer/button.py index 5bd9ba0f7f7..5668d72185d 100644 --- a/homeassistant/components/portainer/button.py +++ b/homeassistant/components/portainer/button.py @@ -4,7 +4,6 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass -import logging from typing import Any from pyportainer import Portainer @@ -30,8 +29,6 @@ from .const import DOMAIN from .coordinator import PortainerCoordinator, PortainerCoordinatorData from .entity import PortainerContainerEntity -_LOGGER = logging.getLogger(__name__) - @dataclass(frozen=True, kw_only=True) class PortainerButtonDescription(ButtonEntityDescription): @@ -64,18 +61,30 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Portainer buttons.""" - coordinator: PortainerCoordinator = entry.runtime_data + coordinator = entry.runtime_data - async_add_entities( - PortainerButton( - coordinator=coordinator, - entity_description=entity_description, - device_info=container, - via_device=endpoint, + def _async_add_new_containers( + containers: list[tuple[PortainerCoordinatorData, DockerContainer]], + ) -> None: + """Add new container button sensors.""" + async_add_entities( + PortainerButton( + coordinator, + entity_description, + container, + endpoint, + ) + for (endpoint, container) in containers + for entity_description in BUTTONS ) - for endpoint in coordinator.data.values() - for container in endpoint.containers.values() - for entity_description in BUTTONS + + coordinator.new_containers_callbacks.append(_async_add_new_containers) + _async_add_new_containers( + [ + (endpoint, container) + for endpoint in coordinator.data.values() + for container in endpoint.containers.values() + ] ) diff --git a/homeassistant/components/portainer/coordinator.py b/homeassistant/components/portainer/coordinator.py index f77e5ff3a76..2fe79b23f03 100644 --- a/homeassistant/components/portainer/coordinator.py +++ b/homeassistant/components/portainer/coordinator.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass from datetime import timedelta import logging @@ -64,6 +65,16 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD ) self.portainer = portainer + self.known_endpoints: set[int] = set() + self.known_containers: set[tuple[int, str]] = set() + + self.new_endpoints_callbacks: list[ + Callable[[list[PortainerCoordinatorData]], None] + ] = [] + self.new_containers_callbacks: list[ + Callable[[list[tuple[PortainerCoordinatorData, DockerContainer]]], None] + ] = [] + async def _async_setup(self) -> None: """Set up the Portainer Data Update Coordinator.""" try: @@ -152,4 +163,27 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD docker_info=docker_info, ) + self._async_add_remove_endpoints(mapped_endpoints) + return mapped_endpoints + + def _async_add_remove_endpoints( + self, mapped_endpoints: dict[int, PortainerCoordinatorData] + ) -> None: + """Add new endpoints, remove non-existing endpoints.""" + current_endpoints = {endpoint.id for endpoint in mapped_endpoints.values()} + new_endpoints = current_endpoints - self.known_endpoints + if new_endpoints: + _LOGGER.debug("New endpoints found: %s", new_endpoints) + self.known_endpoints.update(new_endpoints) + + # Surprise, we also handle containers here :) + current_containers = { + (endpoint.id, container.id) + for endpoint in mapped_endpoints.values() + for container in endpoint.containers.values() + } + new_containers = current_containers - self.known_containers + if new_containers: + _LOGGER.debug("New containers found: %s", new_containers) + self.known_containers.update(new_containers) diff --git a/homeassistant/components/portainer/sensor.py b/homeassistant/components/portainer/sensor.py index 9e4d66927f1..014ba7e17fa 100644 --- a/homeassistant/components/portainer/sensor.py +++ b/homeassistant/components/portainer/sensor.py @@ -159,30 +159,53 @@ async def async_setup_entry( ) -> None: """Set up Portainer sensors based on a config entry.""" coordinator = entry.runtime_data - entities: list[SensorEntity] = [] - for endpoint in coordinator.data.values(): - entities.extend( + def _async_add_new_endpoints(endpoints: list[PortainerCoordinatorData]) -> None: + """Add new endpoint sensor.""" + async_add_entities( PortainerEndpointSensor( coordinator, entity_description, endpoint, ) for entity_description in ENDPOINT_SENSORS + for endpoint in endpoints + if entity_description.value_fn(endpoint) ) - entities.extend( + def _async_add_new_containers( + containers: list[tuple[PortainerCoordinatorData, DockerContainer]], + ) -> None: + """Add new container sensors.""" + async_add_entities( PortainerContainerSensor( coordinator, entity_description, container, endpoint, ) - for container in endpoint.containers.values() + for (endpoint, container) in containers for entity_description in CONTAINER_SENSORS + if entity_description.value_fn(container) ) - async_add_entities(entities) + coordinator.new_endpoints_callbacks.append(_async_add_new_endpoints) + coordinator.new_containers_callbacks.append(_async_add_new_containers) + + _async_add_new_endpoints( + [ + endpoint + for endpoint in coordinator.data.values() + if endpoint.id in coordinator.known_endpoints + ] + ) + _async_add_new_containers( + [ + (endpoint, container) + for endpoint in coordinator.data.values() + for container in endpoint.containers.values() + ] + ) class PortainerContainerSensor(PortainerContainerEntity, SensorEntity): diff --git a/homeassistant/components/portainer/switch.py b/homeassistant/components/portainer/switch.py index 5c795bed112..f1d250004fb 100644 --- a/homeassistant/components/portainer/switch.py +++ b/homeassistant/components/portainer/switch.py @@ -85,19 +85,30 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up Portainer switch sensors.""" - coordinator = entry.runtime_data - async_add_entities( - PortainerContainerSwitch( - coordinator=coordinator, - entity_description=entity_description, - device_info=container, - via_device=endpoint, + def _async_add_new_containers( + containers: list[tuple[PortainerCoordinatorData, DockerContainer]], + ) -> None: + """Add new container switch sensors.""" + async_add_entities( + PortainerContainerSwitch( + coordinator, + entity_description, + container, + endpoint, + ) + for (endpoint, container) in containers + for entity_description in SWITCHES ) - for endpoint in coordinator.data.values() - for container in endpoint.containers.values() - for entity_description in SWITCHES + + coordinator.new_containers_callbacks.append(_async_add_new_containers) + _async_add_new_containers( + [ + (endpoint, container) + for endpoint in coordinator.data.values() + for container in endpoint.containers.values() + ] )