Portainer add prune unused images (#160137)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Erwin Douna
2026-01-08 17:05:45 +01:00
committed by GitHub
co-authored by Copilot
parent 5a1fe17580
commit a2ef0c9a75
5 changed files with 253 additions and 49 deletions
+1
View File
@@ -65,6 +65,7 @@ def mock_portainer_client() -> Generator[AsyncMock]:
)
client.restart_container = AsyncMock(return_value=None)
client.images_prune = AsyncMock(return_value=None)
yield client
@@ -24,12 +24,12 @@
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Restart Container',
'original_name': 'Restart container',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'restart_container',
'unique_id': 'portainer_test_entry_123_focused_einstein_restart',
'unit_of_measurement': None,
})
@@ -38,7 +38,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'focused_einstein Restart Container',
'friendly_name': 'focused_einstein Restart container',
}),
'context': <ANY>,
'entity_id': 'button.focused_einstein_restart_container',
@@ -73,12 +73,12 @@
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Restart Container',
'original_name': 'Restart container',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'restart_container',
'unique_id': 'portainer_test_entry_123_funny_chatelet_restart',
'unit_of_measurement': None,
})
@@ -87,7 +87,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'funny_chatelet Restart Container',
'friendly_name': 'funny_chatelet Restart container',
}),
'context': <ANY>,
'entity_id': 'button.funny_chatelet_restart_container',
@@ -97,6 +97,55 @@
'state': 'unknown',
})
# ---
# name: test_all_button_entities_snapshot[button.my_environment_prune_unused_images-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'button.my_environment_prune_unused_images',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Prune unused images',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'images_prune',
'unique_id': 'portainer_test_entry_123_1_images_prune',
'unit_of_measurement': None,
})
# ---
# name: test_all_button_entities_snapshot[button.my_environment_prune_unused_images-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'my-environment Prune unused images',
}),
'context': <ANY>,
'entity_id': 'button.my_environment_prune_unused_images',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_all_button_entities_snapshot[button.practical_morse_restart_container-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -122,12 +171,12 @@
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Restart Container',
'original_name': 'Restart container',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'restart_container',
'unique_id': 'portainer_test_entry_123_practical_morse_restart',
'unit_of_measurement': None,
})
@@ -136,7 +185,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'practical_morse Restart Container',
'friendly_name': 'practical_morse Restart container',
}),
'context': <ANY>,
'entity_id': 'button.practical_morse_restart_container',
@@ -171,12 +220,12 @@
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Restart Container',
'original_name': 'Restart container',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'restart_container',
'unique_id': 'portainer_test_entry_123_serene_banach_restart',
'unit_of_measurement': None,
})
@@ -185,7 +234,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'serene_banach Restart Container',
'friendly_name': 'serene_banach Restart container',
}),
'context': <ANY>,
'entity_id': 'button.serene_banach_restart_container',
@@ -220,12 +269,12 @@
}),
'original_device_class': <ButtonDeviceClass.RESTART: 'restart'>,
'original_icon': None,
'original_name': 'Restart Container',
'original_name': 'Restart container',
'platform': 'portainer',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'translation_key': 'restart_container',
'unique_id': 'portainer_test_entry_123_stoic_turing_restart',
'unit_of_measurement': None,
})
@@ -234,7 +283,7 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'restart',
'friendly_name': 'stoic_turing Restart Container',
'friendly_name': 'stoic_turing Restart container',
}),
'context': <ANY>,
'entity_id': 'button.stoic_turing_restart_container',
+62 -2
View File
@@ -49,7 +49,7 @@ async def test_all_button_entities_snapshot(
("restart", "restart_container"),
],
)
async def test_buttons(
async def test_buttons_containers(
hass: HomeAssistant,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
@@ -81,7 +81,7 @@ async def test_buttons(
(PortainerTimeoutError("timeout"), "restart_container"),
],
)
async def test_buttons_exceptions(
async def test_buttons_containers_exceptions(
hass: HomeAssistant,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
@@ -104,3 +104,63 @@ async def test_buttons_exceptions(
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
@pytest.mark.parametrize(
("action", "client_method"),
[
("prune", "images_prune"),
],
)
async def test_buttons_endpoint(
hass: HomeAssistant,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
action: str,
client_method: str,
) -> None:
"""Test pressing a Portainer endpoint action button triggers client call. Click, click!"""
await setup_integration(hass, mock_config_entry)
entity_id = f"button.my_environment_{action}_unused_images"
method_mock = getattr(mock_portainer_client, client_method)
pre_calls = len(method_mock.mock_calls)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: entity_id},
blocking=True,
)
assert len(method_mock.mock_calls) == pre_calls + 1
@pytest.mark.parametrize(
("exception", "client_method"),
[
(PortainerAuthenticationError("auth"), "images_prune"),
(PortainerConnectionError("conn"), "images_prune"),
(PortainerTimeoutError("timeout"), "images_prune"),
],
)
async def test_buttons_endpoints_exceptions(
hass: HomeAssistant,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
client_method: str,
) -> None:
"""Test that Portainer buttons, but this time when they will do boom for sure."""
await setup_integration(hass, mock_config_entry)
method_mock = getattr(mock_portainer_client, client_method)
method_mock.side_effect = exception
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.my_environment_prune_unused_images"},
blocking=True,
)