Add diagnostics to portainer (#153126)

Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
This commit is contained in:
Erwin Douna
2025-10-27 16:14:16 +01:00
committed by GitHub
parent a7a673d437
commit 6394fdbc97
4 changed files with 176 additions and 1 deletions

View File

@@ -0,0 +1,55 @@
"""Diagnostics for the Portainer integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_API_TOKEN
from homeassistant.core import HomeAssistant
from . import PortainerConfigEntry
from .coordinator import PortainerCoordinator
TO_REDACT = [CONF_API_TOKEN]
def _serialize_coordinator(coordinator: PortainerCoordinator) -> dict[str, Any]:
"""Serialize coordinator data into a JSON-safe structure."""
serialized_endpoints: list[dict[str, Any]] = []
for endpoint_id, endpoint_data in coordinator.data.items():
serialized_endpoints.append(
{
"id": endpoint_id,
"name": endpoint_data.name,
"endpoint": {
"status": endpoint_data.endpoint.status,
"url": endpoint_data.endpoint.url,
"public_url": endpoint_data.endpoint.public_url,
},
"containers": [
{
"id": container.id,
"names": list(container.names or []),
"image": container.image,
"state": container.state,
"status": container.status,
}
for container in endpoint_data.containers.values()
],
}
)
return {"endpoints": serialized_endpoints}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: PortainerConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a Portainer config entry."""
return {
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
"coordinator": _serialize_coordinator(config_entry.runtime_data),
}

View File

@@ -44,7 +44,7 @@ rules:
test-coverage: done
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: |

View File

@@ -0,0 +1,88 @@
# serializer version: 1
# name: test_get_config_entry_diagnostics
dict({
'config_entry': dict({
'data': dict({
'api_token': '**REDACTED**',
'url': 'https://127.0.0.1:9000/',
'verify_ssl': True,
}),
'disabled_by': None,
'discovery_keys': dict({
}),
'domain': 'portainer',
'entry_id': 'portainer_test_entry_123',
'minor_version': 1,
'options': dict({
}),
'pref_disable_new_entities': False,
'pref_disable_polling': False,
'source': 'user',
'subentries': list([
]),
'title': 'Portainer test',
'unique_id': None,
'version': 2,
}),
'coordinator': dict({
'endpoints': list([
dict({
'containers': list([
dict({
'id': 'aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'image': 'docker.io/library/ubuntu:latest',
'names': list([
'/funny_chatelet',
]),
'state': 'running',
'status': 'Up 4 days',
}),
dict({
'id': 'bb97facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'image': 'docker.io/library/nginx:latest',
'names': list([
'/serene_banach',
]),
'state': 'running',
'status': 'Up 2 days',
}),
dict({
'id': 'cc08facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'image': 'docker.io/library/postgres:15',
'names': list([
'/stoic_turing',
]),
'state': 'running',
'status': 'Up 1 day',
}),
dict({
'id': 'dd19facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'image': 'docker.io/library/redis:7',
'names': list([
'/focused_einstein',
]),
'state': 'running',
'status': 'Up 12 hours',
}),
dict({
'id': 'ee20facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
'image': 'docker.io/library/python:3.13-slim',
'names': list([
'/practical_morse',
]),
'state': 'running',
'status': 'Up 6 hours',
}),
]),
'endpoint': dict({
'public_url': 'docker.mydomain.tld:2375',
'status': 1,
'url': 'docker.mydomain.tld:2375',
}),
'id': 1,
'name': 'my-environment',
}),
]),
}),
})
# ---

View File

@@ -0,0 +1,32 @@
"""Test the Portainer component diagnostics."""
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from syrupy.filters import props
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_get_config_entry_diagnostics(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_portainer_client: AsyncMock,
mock_config_entry: MockConfigEntry,
hass_client: ClientSessionGenerator,
) -> None:
"""Test if get_config_entry_diagnostics returns the correct data."""
await setup_integration(hass, mock_config_entry)
diagnostics_entry = await get_diagnostics_for_config_entry(
hass, hass_client, mock_config_entry
)
assert diagnostics_entry == snapshot(
exclude=props("created_at", "modified_at"),
)