Add volumes to Portainer (#167326)

This commit is contained in:
Erwin Douna
2026-04-21 23:23:27 +02:00
committed by GitHub
parent de6586684a
commit d23dbfb214
10 changed files with 991 additions and 7 deletions
@@ -20,6 +20,8 @@ from pyportainer.models.docker import (
DockerContainer,
DockerContainerStats,
DockerSystemDF,
DockerVolume,
DockerVolumeUsageData,
)
from pyportainer.models.docker_inspect import DockerInfo, DockerVersion
from pyportainer.models.portainer import Endpoint
@@ -52,6 +54,7 @@ class PortainerCoordinatorData:
docker_info: DockerInfo
docker_system_df: DockerSystemDF
stacks: dict[str, PortainerStackData]
volumes: dict[str, PortainerVolumeData]
@dataclass(slots=True)
@@ -72,6 +75,13 @@ class PortainerStackData:
container_count: int = 0
@dataclass(slots=True)
class PortainerVolumeData:
"""Volume data held by the Portainer coordinator."""
volume: DockerVolume
class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorData]]):
"""Data Update Coordinator for Portainer."""
@@ -96,6 +106,7 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
self.known_endpoints: set[int] = set()
self.known_containers: set[tuple[int, str]] = set()
self.known_stacks: set[tuple[int, str]] = set()
self.known_volumes: set[tuple[int, str]] = set()
self.new_endpoints_callbacks: list[
Callable[[list[PortainerCoordinatorData]], None]
@@ -108,6 +119,9 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
self.new_stacks_callbacks: list[
Callable[[list[tuple[PortainerCoordinatorData, PortainerStackData]]], None]
] = []
self.new_volumes_callbacks: list[
Callable[[list[tuple[PortainerCoordinatorData, PortainerVolumeData]]], None]
] = []
async def _async_setup(self) -> None:
"""Set up the Portainer Data Update Coordinator."""
@@ -170,11 +184,13 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
docker_version,
docker_info,
docker_system_df,
volumes,
) = await asyncio.gather(
self.portainer.get_containers(endpoint.id),
self.portainer.docker_version(endpoint.id),
self.portainer.docker_info(endpoint.id),
self.portainer.docker_system_df(endpoint.id),
self.portainer.docker_system_df(endpoint.id, verbose=True),
self.portainer.get_volumes(endpoint.id),
)
stack_requests = [self.portainer.get_stacks(endpoint_id=endpoint.id)]
@@ -205,6 +221,19 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
for stack in stacks
}
volume_usage_map = {
item["Name"]: item
for item in (docker_system_df.volume_disk_usage.items or [])
}
volume_map: dict[str, PortainerVolumeData] = {}
for volume in volumes:
if item := volume_usage_map.get(volume.name):
volume.usage_data = DockerVolumeUsageData(
size=item["UsageData"]["Size"],
ref_count=item["UsageData"]["RefCount"],
)
volume_map[volume.name] = PortainerVolumeData(volume=volume)
# Map containers, started and stopped
for container in containers:
container_name = self._get_container_name(container.names[0])
@@ -286,6 +315,7 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
docker_version=docker_version,
docker_info=docker_info,
docker_system_df=docker_system_df,
volumes=volume_map,
stacks=stack_map,
)
@@ -332,6 +362,28 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD
for container_callback in self.new_containers_callbacks:
container_callback(new_container_data)
# Volume management
current_volumes = {
(endpoint.id, volume_name)
for endpoint in mapped_endpoints.values()
for volume_name in endpoint.volumes
}
self.known_volumes &= current_volumes
new_volumes = current_volumes - self.known_volumes
if new_volumes:
_LOGGER.debug("New volumes found: %s", new_volumes)
self.known_volumes.update(new_volumes)
new_volume_data = [
(
mapped_endpoints[endpoint_id],
mapped_endpoints[endpoint_id].volumes[name],
)
for endpoint_id, name in new_volumes
]
for volume_callback in self.new_volumes_callbacks:
volume_callback(new_volume_data)
# Stack management
current_stacks = {
(endpoint.id, stack_name)
@@ -9,10 +9,12 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_NAME, DOMAIN
from .coordinator import (
DockerVolume,
PortainerContainerData,
PortainerCoordinator,
PortainerCoordinatorData,
PortainerStackData,
PortainerVolumeData,
)
@@ -173,3 +175,56 @@ class PortainerStackEntity(PortainerCoordinatorEntity):
def stack_data(self) -> PortainerStackData:
"""Return the coordinator data for this stack."""
return self.coordinator.data[self.endpoint_id].stacks[self.device_name]
class PortainerVolumeEntity(PortainerCoordinatorEntity):
"""Base implementation for Portainer volume."""
def __init__(
self,
coordinator: PortainerCoordinator,
entity_description: EntityDescription,
device_info: DockerVolume,
via_device: PortainerCoordinatorData,
) -> None:
"""Initialize a Portainer volume."""
super().__init__(coordinator)
self.entity_description = entity_description
self._device_info = device_info
self.volume_name = device_info.name
self.endpoint_id = via_device.endpoint.id
self.endpoint_name = via_device.endpoint.name
self._attr_device_info = DeviceInfo(
identifiers={
(
DOMAIN,
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_volume_{self.volume_name}",
)
},
manufacturer=DEFAULT_NAME,
configuration_url=URL(
f"{coordinator.config_entry.data[CONF_URL]}#!/{self.endpoint_id}/docker/volumes/{self.volume_name}"
),
model="Volume",
name=self.volume_name,
via_device=(
DOMAIN,
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}",
),
)
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_volume_{self.volume_name}_{entity_description.key}"
@property
def available(self) -> bool:
"""Return if the volume is available."""
return (
super().available
and self.endpoint_id in self.coordinator.data
and self.volume_name in self.coordinator.data[self.endpoint_id].volumes
)
@property
def volume_data(self) -> PortainerVolumeData:
"""Return the coordinator data for this volume."""
return self.coordinator.data[self.endpoint_id].volumes[self.volume_name]
@@ -95,6 +95,9 @@
},
"volume_disk_usage_total_size": {
"default": "mdi:harddisk"
},
"volume_driver": {
"default": "mdi:docker"
}
},
"switch": {
+62 -1
View File
@@ -23,12 +23,14 @@ from .coordinator import (
PortainerConfigEntry,
PortainerContainerData,
PortainerStackData,
PortainerVolumeData,
)
from .entity import (
PortainerContainerEntity,
PortainerCoordinatorData,
PortainerEndpointEntity,
PortainerStackEntity,
PortainerVolumeEntity,
)
PARALLEL_UPDATES = 0
@@ -55,6 +57,13 @@ class PortainerStackSensorEntityDescription(SensorEntityDescription):
value_fn: Callable[[PortainerStackData], StateType]
@dataclass(frozen=True, kw_only=True)
class PortainerVolumeSensorEntityDescription(SensorEntityDescription):
"""Class to hold Portainer volume sensor description."""
value_fn: Callable[[PortainerVolumeData], StateType]
CONTAINER_SENSORS: tuple[PortainerContainerSensorEntityDescription, ...] = (
PortainerContainerSensorEntityDescription(
key="image",
@@ -287,7 +296,6 @@ ENDPOINT_SENSORS: tuple[PortainerEndpointSensorEntityDescription, ...] = (
entity_category=EntityCategory.DIAGNOSTIC,
),
)
STACK_SENSORS: tuple[PortainerStackSensorEntityDescription, ...] = (
PortainerStackSensorEntityDescription(
key="stack_type",
@@ -313,6 +321,25 @@ STACK_SENSORS: tuple[PortainerStackSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
),
)
VOLUME_SENSORS: tuple[PortainerVolumeSensorEntityDescription, ...] = (
PortainerVolumeSensorEntityDescription(
key="volume_driver",
translation_key="volume_driver",
value_fn=lambda data: data.volume.driver,
),
PortainerVolumeSensorEntityDescription(
key="volume_size",
translation_key="volume_size",
value_fn=lambda data: (
data.volume.usage_data.size if data.volume.usage_data else None
),
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
async def async_setup_entry(
@@ -366,9 +393,25 @@ async def async_setup_entry(
for entity_description in STACK_SENSORS
)
def _async_add_new_volumes(
volumes: list[tuple[PortainerCoordinatorData, PortainerVolumeData]],
) -> None:
"""Add new volume sensors."""
async_add_entities(
PortainerVolumeSensor(
coordinator,
entity_description,
volume.volume,
endpoint,
)
for (endpoint, volume) in volumes
for entity_description in VOLUME_SENSORS
)
coordinator.new_endpoints_callbacks.append(_async_add_new_endpoints)
coordinator.new_containers_callbacks.append(_async_add_new_containers)
coordinator.new_stacks_callbacks.append(_async_add_new_stacks)
coordinator.new_volumes_callbacks.append(_async_add_new_volumes)
_async_add_new_endpoints(
[
@@ -391,6 +434,13 @@ async def async_setup_entry(
for stack in endpoint.stacks.values()
]
)
_async_add_new_volumes(
[
(endpoint, volume)
for endpoint in coordinator.data.values()
for volume in endpoint.volumes.values()
]
)
class PortainerContainerSensor(PortainerContainerEntity, SensorEntity):
@@ -425,3 +475,14 @@ class PortainerStackSensor(PortainerStackEntity, SensorEntity):
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.stack_data)
class PortainerVolumeSensor(PortainerVolumeEntity, SensorEntity):
"""Representation of a Portainer volume sensor."""
entity_description: PortainerVolumeSensorEntityDescription
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.volume_data)
@@ -177,6 +177,12 @@
},
"volume_disk_usage_total_size": {
"name": "Volume disk usage total size"
},
"volume_driver": {
"name": "Volume driver"
},
"volume_size": {
"name": "Volume size"
}
},
"switch": {
+5
View File
@@ -7,6 +7,7 @@ from pyportainer.models.docker import (
DockerContainer,
DockerContainerStats,
DockerSystemDF,
DockerVolume,
)
from pyportainer.models.docker_inspect import DockerInfo, DockerVersion
from pyportainer.models.portainer import Endpoint, PortainerSystemStatus
@@ -81,6 +82,10 @@ def mock_portainer_client() -> Generator[AsyncMock]:
client.portainer_system_status.return_value = PortainerSystemStatus.from_dict(
load_json_value_fixture("portainer_system_status.json", DOMAIN)
)
client.get_volumes.return_value = [
DockerVolume.from_dict(volume)
for volume in load_json_array_fixture("volumes.json", DOMAIN)
]
client.restart_container = AsyncMock(return_value=None)
client.images_prune = AsyncMock(return_value=None)
@@ -1,26 +1,41 @@
{
"ImagesDiskUsage": {
"ImageUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
},
"ContainersDiskUsage": {
"ContainerUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
},
"VolumesDiskUsage": {
"VolumeUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
"TotalSize": 98765432,
"Items": []
"Items": [
{
"Name": "myvolume",
"UsageData": {
"Size": 104857600,
"RefCount": 2
}
},
{
"Name": "db_data",
"UsageData": {
"Size": 67108864,
"RefCount": 1
}
}
]
},
"BuildCacheDiskUsage": {
"BuildCacheUsage": {
"ActiveCount": 1,
"TotalCount": 4,
"Reclaimable": 12345678,
@@ -0,0 +1,62 @@
[
{
"Name": "myvolume",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
"CreatedAt": "2025-02-15T12:34:56.000000000Z",
"Status": {
"ref": "volume-local"
},
"Labels": {
"com.example.some-label": "some-value",
"com.example.some-other-label": "some-other-value"
},
"Scope": "local",
"Options": {
"type": "none",
"o": "bind",
"device": "/data/volumes/myvolume"
},
"UsageData": {
"Size": 104857600,
"RefCount": 2
}
},
{
"Name": "db_data",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/db_data/_data",
"CreatedAt": "2025-02-10T08:11:22.000000000Z",
"Labels": {
"com.docker.compose.project": "webstack",
"com.docker.compose.volume": "db_data"
},
"Scope": "local",
"Options": {
"type": "tmpfs",
"o": "size=64m,uid=1000",
"device": "tmpfs"
},
"UsageData": {
"Size": 67108864,
"RefCount": 1
}
},
{
"Name": "dashy_config",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/dashy_config/_data",
"CreatedAt": "2025-02-18T19:45:10.000000000Z",
"Status": {
"ref": "volume-swarm"
},
"Labels": {
"com.docker.stack.namespace": "dashy"
},
"Scope": "local",
"UsageData": {
"Size": 3145728,
"RefCount": 1
}
}
]
@@ -262,5 +262,92 @@
'sw_version': None,
'via_device_id': <ANY>,
}),
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/volumes/myvolume',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_volume_myvolume',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Volume',
'model_id': None,
'name': 'myvolume',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': <ANY>,
}),
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/volumes/db_data',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_volume_db_data',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Volume',
'model_id': None,
'name': 'db_data',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': <ANY>,
}),
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/volumes/dashy_config',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_volume_dashy_config',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Volume',
'model_id': None,
'name': 'dashy_config',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': <ANY>,
}),
])
# ---
@@ -1,4 +1,115 @@
# serializer version: 1
# name: test_all_entities[sensor.dashy_config_volume_driver-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': 'sensor',
'entity_category': None,
'entity_id': 'sensor.dashy_config_volume_driver',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume driver',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Volume driver',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_driver',
'unique_id': 'portainer_test_entry_123_1_volume_dashy_config_volume_driver',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.dashy_config_volume_driver-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'dashy_config Volume driver',
}),
'context': <ANY>,
'entity_id': 'sensor.dashy_config_volume_driver',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'local',
})
# ---
# name: test_all_entities[sensor.dashy_config_volume_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.dashy_config_volume_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Volume size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_size',
'unique_id': 'portainer_test_entry_123_1_volume_dashy_config_volume_size',
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
})
# ---
# name: test_all_entities[sensor.dashy_config_volume_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'dashy_config Volume size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.dashy_config_volume_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0029296875',
})
# ---
# name: test_all_entities[sensor.dashy_containers-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -469,6 +580,117 @@
'state': 'swarm',
})
# ---
# name: test_all_entities[sensor.db_data_volume_driver-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': 'sensor',
'entity_category': None,
'entity_id': 'sensor.db_data_volume_driver',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume driver',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Volume driver',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_driver',
'unique_id': 'portainer_test_entry_123_1_volume_db_data_volume_driver',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.db_data_volume_driver-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'db_data Volume driver',
}),
'context': <ANY>,
'entity_id': 'sensor.db_data_volume_driver',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'local',
})
# ---
# name: test_all_entities[sensor.db_data_volume_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.db_data_volume_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Volume size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_size',
'unique_id': 'portainer_test_entry_123_1_volume_db_data_volume_size',
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
})
# ---
# name: test_all_entities[sensor.db_data_volume_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'db_data Volume size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.db_data_volume_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.0625',
})
# ---
# name: test_all_entities[sensor.focused_einstein_cpu_usage_total-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -1330,6 +1552,128 @@
'state': '14',
})
# ---
# name: test_all_entities[sensor.my_environment_container_disk_usage_reclaimable-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_environment_container_disk_usage_reclaimable',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Container disk usage reclaimable',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Container disk usage reclaimable',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'container_disk_usage_reclaimable',
'unique_id': 'portainer_test_entry_123_1_container_disk_usage_reclaimable',
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
})
# ---
# name: test_all_entities[sensor.my_environment_container_disk_usage_reclaimable-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'my-environment Container disk usage reclaimable',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_environment_container_disk_usage_reclaimable',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '11.7737560272217',
})
# ---
# name: test_all_entities[sensor.my_environment_container_disk_usage_total_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_environment_container_disk_usage_total_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Container disk usage total size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Container disk usage total size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'container_disk_usage_total_size',
'unique_id': 'portainer_test_entry_123_1_container_disk_usage_total_size',
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
})
# ---
# name: test_all_entities[sensor.my_environment_container_disk_usage_total_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'my-environment Container disk usage total size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_environment_container_disk_usage_total_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '94.190055847168',
})
# ---
# name: test_all_entities[sensor.my_environment_containers_paused-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -1592,6 +1936,128 @@
'state': '508',
})
# ---
# name: test_all_entities[sensor.my_environment_image_disk_usage_reclaimable-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_environment_image_disk_usage_reclaimable',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Image disk usage reclaimable',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Image disk usage reclaimable',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'image_disk_usage_reclaimable',
'unique_id': 'portainer_test_entry_123_1_image_disk_usage_reclaimable',
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
})
# ---
# name: test_all_entities[sensor.my_environment_image_disk_usage_reclaimable-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'my-environment Image disk usage reclaimable',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_environment_image_disk_usage_reclaimable',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '11.7737560272217',
})
# ---
# name: test_all_entities[sensor.my_environment_image_disk_usage_total_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_environment_image_disk_usage_total_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Image disk usage total size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Image disk usage total size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'image_disk_usage_total_size',
'unique_id': 'portainer_test_entry_123_1_image_disk_usage_total_size',
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
})
# ---
# name: test_all_entities[sensor.my_environment_image_disk_usage_total_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'my-environment Image disk usage total size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_environment_image_disk_usage_total_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '94.190055847168',
})
# ---
# name: test_all_entities[sensor.my_environment_kernel_version-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
@@ -1856,6 +2322,178 @@
'state': '1998.7890625',
})
# ---
# name: test_all_entities[sensor.my_environment_volume_disk_usage_total_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_environment_volume_disk_usage_total_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume disk usage total size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Volume disk usage total size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_disk_usage_total_size',
'unique_id': 'portainer_test_entry_123_1_volume_disk_usage_total',
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
})
# ---
# name: test_all_entities[sensor.my_environment_volume_disk_usage_total_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'my-environment Volume disk usage total size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.MEBIBYTES: 'MiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_environment_volume_disk_usage_total_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '94.190055847168',
})
# ---
# name: test_all_entities[sensor.myvolume_volume_driver-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': 'sensor',
'entity_category': None,
'entity_id': 'sensor.myvolume_volume_driver',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume driver',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Volume driver',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_driver',
'unique_id': 'portainer_test_entry_123_1_volume_myvolume_volume_driver',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[sensor.myvolume_volume_driver-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'myvolume Volume driver',
}),
'context': <ANY>,
'entity_id': 'sensor.myvolume_volume_driver',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'local',
})
# ---
# name: test_all_entities[sensor.myvolume_volume_size-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.myvolume_volume_size',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Volume size',
'options': dict({
'sensor': dict({
'suggested_display_precision': 2,
}),
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
}),
'original_device_class': <SensorDeviceClass.DATA_SIZE: 'data_size'>,
'original_icon': None,
'original_name': 'Volume size',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'volume_size',
'unique_id': 'portainer_test_entry_123_1_volume_myvolume_volume_size',
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
})
# ---
# name: test_all_entities[sensor.myvolume_volume_size-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'data_size',
'friendly_name': 'myvolume Volume size',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfInformation.GIBIBYTES: 'GiB'>,
}),
'context': <ANY>,
'entity_id': 'sensor.myvolume_volume_size',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.09765625',
})
# ---
# name: test_all_entities[sensor.practical_morse_cpu_usage_total-entry]
EntityRegistryEntrySnapshot({
'aliases': list([