Skip to content

Commit 9ee9bb3

Browse files
authored
Move Supervisor created persistent notifications into repairs (#152066)
1 parent 6e4258c commit 9ee9bb3

File tree

6 files changed

+217
-18
lines changed

6 files changed

+217
-18
lines changed

homeassistant/components/hassio/const.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,14 @@
112112
PLACEHOLDER_KEY_ADDON_URL = "addon_url"
113113
PLACEHOLDER_KEY_REFERENCE = "reference"
114114
PLACEHOLDER_KEY_COMPONENTS = "components"
115+
PLACEHOLDER_KEY_FREE_SPACE = "free_space"
115116

116117
ISSUE_KEY_ADDON_BOOT_FAIL = "issue_addon_boot_fail"
117118
ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config"
118119
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING = "issue_addon_detached_addon_missing"
119120
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED = "issue_addon_detached_addon_removed"
121+
ISSUE_KEY_ADDON_PWNED = "issue_addon_pwned"
122+
ISSUE_KEY_SYSTEM_FREE_SPACE = "issue_system_free_space"
120123

121124
CORE_CONTAINER = "homeassistant"
122125
SUPERVISOR_CONTAINER = "hassio_supervisor"
@@ -137,6 +140,24 @@
137140

138141
REQUEST_REFRESH_DELAY = 10
139142

143+
HELP_URLS = {
144+
"help_url": "https://www.home-assistant.io/help/",
145+
"community_url": "https://community.home-assistant.io/",
146+
}
147+
148+
EXTRA_PLACEHOLDERS = {
149+
"issue_mount_mount_failed": {
150+
"storage_url": "/config/storage",
151+
},
152+
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED: HELP_URLS,
153+
ISSUE_KEY_SYSTEM_FREE_SPACE: {
154+
"more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
155+
},
156+
ISSUE_KEY_ADDON_PWNED: {
157+
"more_info_pwned": "https://www.home-assistant.io/more-info/pwned-passwords",
158+
},
159+
}
160+
140161

141162
class SupervisorEntityModel(StrEnum):
142163
"""Supervisor entity model."""

homeassistant/components/hassio/issues.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,21 @@
4141
EVENT_SUPERVISOR_EVENT,
4242
EVENT_SUPERVISOR_UPDATE,
4343
EVENT_SUPPORTED_CHANGED,
44+
EXTRA_PLACEHOLDERS,
4445
ISSUE_KEY_ADDON_BOOT_FAIL,
4546
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING,
4647
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
48+
ISSUE_KEY_ADDON_PWNED,
4749
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
50+
ISSUE_KEY_SYSTEM_FREE_SPACE,
4851
PLACEHOLDER_KEY_ADDON,
4952
PLACEHOLDER_KEY_ADDON_URL,
53+
PLACEHOLDER_KEY_FREE_SPACE,
5054
PLACEHOLDER_KEY_REFERENCE,
5155
REQUEST_REFRESH_DELAY,
5256
UPDATE_KEY_SUPERVISOR,
5357
)
54-
from .coordinator import get_addons_info
58+
from .coordinator import get_addons_info, get_host_info
5559
from .handler import HassIO, get_supervisor_client
5660

5761
ISSUE_KEY_UNHEALTHY = "unhealthy"
@@ -78,6 +82,8 @@
7882
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING,
7983
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
8084
"issue_system_disk_lifetime",
85+
ISSUE_KEY_SYSTEM_FREE_SPACE,
86+
ISSUE_KEY_ADDON_PWNED,
8187
}
8288

8389
_LOGGER = logging.getLogger(__name__)
@@ -241,11 +247,17 @@ def issues(self) -> set[Issue]:
241247
def add_issue(self, issue: Issue) -> None:
242248
"""Add or update an issue in the list. Create or update a repair if necessary."""
243249
if issue.key in ISSUE_KEYS_FOR_REPAIRS:
244-
placeholders: dict[str, str] | None = None
250+
placeholders: dict[str, str] = {}
251+
if not issue.suggestions and issue.key in EXTRA_PLACEHOLDERS:
252+
placeholders |= EXTRA_PLACEHOLDERS[issue.key]
253+
245254
if issue.reference:
246-
placeholders = {PLACEHOLDER_KEY_REFERENCE: issue.reference}
255+
placeholders[PLACEHOLDER_KEY_REFERENCE] = issue.reference
247256

248-
if issue.key == ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING:
257+
if issue.key in {
258+
ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING,
259+
ISSUE_KEY_ADDON_PWNED,
260+
}:
249261
placeholders[PLACEHOLDER_KEY_ADDON_URL] = (
250262
f"/hassio/addon/{issue.reference}"
251263
)
@@ -257,14 +269,27 @@ def add_issue(self, issue: Issue) -> None:
257269
else:
258270
placeholders[PLACEHOLDER_KEY_ADDON] = issue.reference
259271

272+
elif issue.key == ISSUE_KEY_SYSTEM_FREE_SPACE:
273+
host_info = get_host_info(self._hass)
274+
if (
275+
host_info
276+
and "data" in host_info
277+
and "disk_free" in host_info["data"]
278+
):
279+
placeholders[PLACEHOLDER_KEY_FREE_SPACE] = str(
280+
host_info["data"]["disk_free"]
281+
)
282+
else:
283+
placeholders[PLACEHOLDER_KEY_FREE_SPACE] = "<2"
284+
260285
async_create_issue(
261286
self._hass,
262287
DOMAIN,
263288
issue.uuid.hex,
264289
is_fixable=bool(issue.suggestions),
265290
severity=IssueSeverity.WARNING,
266291
translation_key=issue.key,
267-
translation_placeholders=placeholders,
292+
translation_placeholders=placeholders or None,
268293
)
269294

270295
self._issues[issue.uuid] = issue

homeassistant/components/hassio/repairs.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
from . import get_addons_info, get_issues_info
1818
from .const import (
19+
EXTRA_PLACEHOLDERS,
1920
ISSUE_KEY_ADDON_BOOT_FAIL,
2021
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
22+
ISSUE_KEY_ADDON_PWNED,
2123
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
2224
PLACEHOLDER_KEY_ADDON,
2325
PLACEHOLDER_KEY_COMPONENTS,
@@ -26,26 +28,13 @@
2628
from .handler import get_supervisor_client
2729
from .issues import Issue, Suggestion
2830

29-
HELP_URLS = {
30-
"help_url": "https://www.home-assistant.io/help/",
31-
"community_url": "https://community.home-assistant.io/",
32-
}
33-
3431
SUGGESTION_CONFIRMATION_REQUIRED = {
3532
"addon_execute_remove",
3633
"system_adopt_data_disk",
3734
"system_execute_reboot",
3835
}
3936

4037

41-
EXTRA_PLACEHOLDERS = {
42-
"issue_mount_mount_failed": {
43-
"storage_url": "/config/storage",
44-
},
45-
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED: HELP_URLS,
46-
}
47-
48-
4938
class SupervisorIssueRepairFlow(RepairsFlow):
5039
"""Handler for an issue fixing flow."""
5140

@@ -219,6 +208,7 @@ async def async_create_fix_flow(
219208
if issue and issue.key in {
220209
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
221210
ISSUE_KEY_ADDON_BOOT_FAIL,
211+
ISSUE_KEY_ADDON_PWNED,
222212
}:
223213
return AddonIssueRepairFlow(hass, issue_id)
224214

homeassistant/components/hassio/strings.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
}
5353
}
5454
},
55+
"issue_addon_pwned": {
56+
"title": "Insecure secrets detected in add-on configuration",
57+
"description": "Add-on {addon} uses secrets/passwords in its configuration which are detected as not secure. See [pwned passwords and secrets]({more_info_pwned}) for more information on this issue."
58+
},
5559
"issue_mount_mount_failed": {
5660
"title": "Network storage device failed",
5761
"fix_flow": {
@@ -119,6 +123,10 @@
119123
"title": "Disk lifetime exceeding 90%",
120124
"description": "The data disk has exceeded 90% of its expected lifespan. The disk may soon malfunction which can lead to data loss. You should replace it soon and migrate your data."
121125
},
126+
"issue_system_free_space": {
127+
"title": "Data disk is running low on free space",
128+
"description": "The data disk has only {free_space}GB free space left. This may cause issues with system stability and interfere with functionality such as backups and updates. See [clear up storage]({more_info_free_space}) for tips on how to free up space."
129+
},
122130
"unhealthy": {
123131
"title": "Unhealthy system - {reason}",
124132
"description": "System is currently unhealthy due to {reason}. For troubleshooting information, select Learn more."

tests/components/hassio/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def all_setup_requests(
108108
"chassis": "vm",
109109
"operating_system": "Debian GNU/Linux 10 (buster)",
110110
"kernel": "4.19.0-6-amd64",
111+
"disk_free": 1.6,
111112
},
112113
},
113114
},

tests/components/hassio/test_issues.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,3 +950,157 @@ async def test_supervisor_issues_disk_lifetime(
950950
fixable=False,
951951
placeholders=None,
952952
)
953+
954+
955+
@pytest.mark.usefixtures("all_setup_requests")
956+
async def test_supervisor_issues_free_space(
957+
hass: HomeAssistant,
958+
supervisor_client: AsyncMock,
959+
hass_ws_client: WebSocketGenerator,
960+
) -> None:
961+
"""Test supervisor issue for too little free space remaining."""
962+
mock_resolution_info(supervisor_client)
963+
964+
result = await async_setup_component(hass, "hassio", {})
965+
assert result
966+
967+
client = await hass_ws_client(hass)
968+
969+
await client.send_json(
970+
{
971+
"id": 1,
972+
"type": "supervisor/event",
973+
"data": {
974+
"event": "issue_changed",
975+
"data": {
976+
"uuid": (issue_uuid := uuid4().hex),
977+
"type": "free_space",
978+
"context": "system",
979+
"reference": None,
980+
},
981+
},
982+
}
983+
)
984+
msg = await client.receive_json()
985+
assert msg["success"]
986+
await hass.async_block_till_done()
987+
988+
await client.send_json({"id": 2, "type": "repairs/list_issues"})
989+
msg = await client.receive_json()
990+
assert msg["success"]
991+
assert len(msg["result"]["issues"]) == 1
992+
assert_issue_repair_in_list(
993+
msg["result"]["issues"],
994+
uuid=issue_uuid,
995+
context="system",
996+
type_="free_space",
997+
fixable=False,
998+
placeholders={
999+
"more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
1000+
"free_space": "1.6",
1001+
},
1002+
)
1003+
1004+
1005+
async def test_supervisor_issues_free_space_host_info_fail(
1006+
hass: HomeAssistant,
1007+
supervisor_client: AsyncMock,
1008+
hass_ws_client: WebSocketGenerator,
1009+
) -> None:
1010+
"""Test supervisor issue for too little free space remaining without host info."""
1011+
mock_resolution_info(supervisor_client)
1012+
1013+
result = await async_setup_component(hass, "hassio", {})
1014+
assert result
1015+
1016+
client = await hass_ws_client(hass)
1017+
1018+
await client.send_json(
1019+
{
1020+
"id": 1,
1021+
"type": "supervisor/event",
1022+
"data": {
1023+
"event": "issue_changed",
1024+
"data": {
1025+
"uuid": (issue_uuid := uuid4().hex),
1026+
"type": "free_space",
1027+
"context": "system",
1028+
"reference": None,
1029+
},
1030+
},
1031+
}
1032+
)
1033+
msg = await client.receive_json()
1034+
assert msg["success"]
1035+
await hass.async_block_till_done()
1036+
1037+
await client.send_json({"id": 2, "type": "repairs/list_issues"})
1038+
msg = await client.receive_json()
1039+
assert msg["success"]
1040+
assert len(msg["result"]["issues"]) == 1
1041+
assert_issue_repair_in_list(
1042+
msg["result"]["issues"],
1043+
uuid=issue_uuid,
1044+
context="system",
1045+
type_="free_space",
1046+
fixable=False,
1047+
placeholders={
1048+
"more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
1049+
"free_space": "<2",
1050+
},
1051+
)
1052+
1053+
1054+
@pytest.mark.parametrize(
1055+
"all_setup_requests", [{"include_addons": True}], indirect=True
1056+
)
1057+
@pytest.mark.usefixtures("all_setup_requests")
1058+
async def test_supervisor_issues_addon_pwned(
1059+
hass: HomeAssistant,
1060+
supervisor_client: AsyncMock,
1061+
hass_ws_client: WebSocketGenerator,
1062+
) -> None:
1063+
"""Test supervisor issue for pwned secret in an addon."""
1064+
mock_resolution_info(supervisor_client)
1065+
1066+
result = await async_setup_component(hass, "hassio", {})
1067+
assert result
1068+
1069+
client = await hass_ws_client(hass)
1070+
1071+
await client.send_json(
1072+
{
1073+
"id": 1,
1074+
"type": "supervisor/event",
1075+
"data": {
1076+
"event": "issue_changed",
1077+
"data": {
1078+
"uuid": (issue_uuid := uuid4().hex),
1079+
"type": "pwned",
1080+
"context": "addon",
1081+
"reference": "test",
1082+
},
1083+
},
1084+
}
1085+
)
1086+
msg = await client.receive_json()
1087+
assert msg["success"]
1088+
await hass.async_block_till_done()
1089+
1090+
await client.send_json({"id": 2, "type": "repairs/list_issues"})
1091+
msg = await client.receive_json()
1092+
assert msg["success"]
1093+
assert len(msg["result"]["issues"]) == 1
1094+
assert_issue_repair_in_list(
1095+
msg["result"]["issues"],
1096+
uuid=issue_uuid,
1097+
context="addon",
1098+
type_="pwned",
1099+
fixable=False,
1100+
placeholders={
1101+
"reference": "test",
1102+
"addon": "test",
1103+
"addon_url": "/hassio/addon/test",
1104+
"more_info_pwned": "https://www.home-assistant.io/more-info/pwned-passwords",
1105+
},
1106+
)

0 commit comments

Comments
 (0)