Fix stack devices merging with container devices in Portainer (#164135)

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Erwin Douna
2026-02-26 16:14:50 +01:00
committed by GitHub
co-authored by epenet
parent 06a25de0d5
commit 75798bfb5e
3 changed files with 227 additions and 2 deletions
+2 -2
View File
@@ -91,7 +91,7 @@ class PortainerContainerEntity(PortainerCoordinatorEntity):
# else it's the endpoint
via_device=(
DOMAIN,
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_{device_info.stack.name}"
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_stack_{device_info.stack.id}"
if device_info.stack
else f"{coordinator.config_entry.entry_id}_{self.endpoint_id}",
),
@@ -135,7 +135,7 @@ class PortainerStackEntity(PortainerCoordinatorEntity):
identifiers={
(
DOMAIN,
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_{self.device_name}",
f"{coordinator.config_entry.entry_id}_{self.endpoint_id}_stack_{self.stack_id}",
)
},
manufacturer=DEFAULT_NAME,
@@ -0,0 +1,208 @@
# serializer version: 1
# name: test_device_registry
list([
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/dashboard',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Endpoint',
'model_id': None,
'name': 'my-environment',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': None,
}),
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/containers/aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_funny_chatelet',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Container',
'model_id': None,
'name': 'funny_chatelet',
'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/containers/dd19facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_focused_einstein',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Container',
'model_id': None,
'name': 'focused_einstein',
'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/containers/ee20facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_practical_morse',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Container',
'model_id': None,
'name': 'practical_morse',
'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/stacks/webstack',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_stack_1',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Stack',
'model_id': None,
'name': 'webstack',
'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/containers/bb97facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_serene_banach',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Container',
'model_id': None,
'name': 'serene_banach',
'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/containers/cc08facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'connections': set({
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'portainer',
'portainer_test_entry_123_1_stoic_turing',
),
}),
'labels': set({
}),
'manufacturer': 'Portainer',
'model': 'Container',
'model_id': None,
'name': 'stoic_turing',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'sw_version': None,
'via_device_id': <ANY>,
}),
])
# ---
+17
View File
@@ -8,6 +8,7 @@ from pyportainer.exceptions import (
PortainerTimeoutError,
)
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.portainer.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
@@ -166,3 +167,19 @@ async def test_migration_v3_to_v4(
(DOMAIN, f"{entry.entry_id}_1_adguard"),
}
assert entity_after.unique_id == f"{entry.entry_id}_1_adguard_container"
async def test_device_registry(
hass: HomeAssistant,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test devices are correctly registered."""
await setup_integration(hass, mock_config_entry)
device_entries = dr.async_entries_for_config_entry(
device_registry, mock_config_entry.entry_id
)
assert device_entries == snapshot