Skip to content

Commit 6394fdb

Browse files
erwindounathecodeepenet
authored
Add diagnostics to portainer (home-assistant#153126)
Co-authored-by: Shay Levy <[email protected]> Co-authored-by: epenet <[email protected]>
1 parent a7a673d commit 6394fdb

File tree

4 files changed

+176
-1
lines changed

4 files changed

+176
-1
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Diagnostics for the Portainer integration."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from homeassistant.components.diagnostics import async_redact_data
8+
from homeassistant.const import CONF_API_TOKEN
9+
from homeassistant.core import HomeAssistant
10+
11+
from . import PortainerConfigEntry
12+
from .coordinator import PortainerCoordinator
13+
14+
TO_REDACT = [CONF_API_TOKEN]
15+
16+
17+
def _serialize_coordinator(coordinator: PortainerCoordinator) -> dict[str, Any]:
18+
"""Serialize coordinator data into a JSON-safe structure."""
19+
20+
serialized_endpoints: list[dict[str, Any]] = []
21+
for endpoint_id, endpoint_data in coordinator.data.items():
22+
serialized_endpoints.append(
23+
{
24+
"id": endpoint_id,
25+
"name": endpoint_data.name,
26+
"endpoint": {
27+
"status": endpoint_data.endpoint.status,
28+
"url": endpoint_data.endpoint.url,
29+
"public_url": endpoint_data.endpoint.public_url,
30+
},
31+
"containers": [
32+
{
33+
"id": container.id,
34+
"names": list(container.names or []),
35+
"image": container.image,
36+
"state": container.state,
37+
"status": container.status,
38+
}
39+
for container in endpoint_data.containers.values()
40+
],
41+
}
42+
)
43+
44+
return {"endpoints": serialized_endpoints}
45+
46+
47+
async def async_get_config_entry_diagnostics(
48+
hass: HomeAssistant, config_entry: PortainerConfigEntry
49+
) -> dict[str, Any]:
50+
"""Return diagnostics for a Portainer config entry."""
51+
52+
return {
53+
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
54+
"coordinator": _serialize_coordinator(config_entry.runtime_data),
55+
}

homeassistant/components/portainer/quality_scale.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ rules:
4444
test-coverage: done
4545
# Gold
4646
devices: done
47-
diagnostics: todo
47+
diagnostics: done
4848
discovery-update-info:
4949
status: exempt
5050
comment: |
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# serializer version: 1
2+
# name: test_get_config_entry_diagnostics
3+
dict({
4+
'config_entry': dict({
5+
'data': dict({
6+
'api_token': '**REDACTED**',
7+
'url': 'https://127.0.0.1:9000/',
8+
'verify_ssl': True,
9+
}),
10+
'disabled_by': None,
11+
'discovery_keys': dict({
12+
}),
13+
'domain': 'portainer',
14+
'entry_id': 'portainer_test_entry_123',
15+
'minor_version': 1,
16+
'options': dict({
17+
}),
18+
'pref_disable_new_entities': False,
19+
'pref_disable_polling': False,
20+
'source': 'user',
21+
'subentries': list([
22+
]),
23+
'title': 'Portainer test',
24+
'unique_id': None,
25+
'version': 2,
26+
}),
27+
'coordinator': dict({
28+
'endpoints': list([
29+
dict({
30+
'containers': list([
31+
dict({
32+
'id': 'aa86eacfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
33+
'image': 'docker.io/library/ubuntu:latest',
34+
'names': list([
35+
'/funny_chatelet',
36+
]),
37+
'state': 'running',
38+
'status': 'Up 4 days',
39+
}),
40+
dict({
41+
'id': 'bb97facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
42+
'image': 'docker.io/library/nginx:latest',
43+
'names': list([
44+
'/serene_banach',
45+
]),
46+
'state': 'running',
47+
'status': 'Up 2 days',
48+
}),
49+
dict({
50+
'id': 'cc08facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
51+
'image': 'docker.io/library/postgres:15',
52+
'names': list([
53+
'/stoic_turing',
54+
]),
55+
'state': 'running',
56+
'status': 'Up 1 day',
57+
}),
58+
dict({
59+
'id': 'dd19facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
60+
'image': 'docker.io/library/redis:7',
61+
'names': list([
62+
'/focused_einstein',
63+
]),
64+
'state': 'running',
65+
'status': 'Up 12 hours',
66+
}),
67+
dict({
68+
'id': 'ee20facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf',
69+
'image': 'docker.io/library/python:3.13-slim',
70+
'names': list([
71+
'/practical_morse',
72+
]),
73+
'state': 'running',
74+
'status': 'Up 6 hours',
75+
}),
76+
]),
77+
'endpoint': dict({
78+
'public_url': 'docker.mydomain.tld:2375',
79+
'status': 1,
80+
'url': 'docker.mydomain.tld:2375',
81+
}),
82+
'id': 1,
83+
'name': 'my-environment',
84+
}),
85+
]),
86+
}),
87+
})
88+
# ---
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Test the Portainer component diagnostics."""
2+
3+
from unittest.mock import AsyncMock
4+
5+
from syrupy.assertion import SnapshotAssertion
6+
from syrupy.filters import props
7+
8+
from homeassistant.core import HomeAssistant
9+
10+
from . import setup_integration
11+
12+
from tests.common import MockConfigEntry
13+
from tests.components.diagnostics import get_diagnostics_for_config_entry
14+
from tests.typing import ClientSessionGenerator
15+
16+
17+
async def test_get_config_entry_diagnostics(
18+
hass: HomeAssistant,
19+
snapshot: SnapshotAssertion,
20+
mock_portainer_client: AsyncMock,
21+
mock_config_entry: MockConfigEntry,
22+
hass_client: ClientSessionGenerator,
23+
) -> None:
24+
"""Test if get_config_entry_diagnostics returns the correct data."""
25+
await setup_integration(hass, mock_config_entry)
26+
27+
diagnostics_entry = await get_diagnostics_for_config_entry(
28+
hass, hass_client, mock_config_entry
29+
)
30+
assert diagnostics_entry == snapshot(
31+
exclude=props("created_at", "modified_at"),
32+
)

0 commit comments

Comments
 (0)