Home Assistant Unofficial Reference 2024.12.1
repairs.py
Go to the documentation of this file.
1 """Repairs implementation for supervisor integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Coroutine
6 from types import MethodType
7 from typing import Any
8 
9 from aiohasupervisor import SupervisorError
10 from aiohasupervisor.models import ContextType
11 import voluptuous as vol
12 
13 from homeassistant.components.repairs import RepairsFlow
14 from homeassistant.core import HomeAssistant
15 from homeassistant.data_entry_flow import FlowResult
16 
17 from . import get_addons_info, get_issues_info
18 from .const import (
19  ISSUE_KEY_ADDON_BOOT_FAIL,
20  ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
21  ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
22  PLACEHOLDER_KEY_ADDON,
23  PLACEHOLDER_KEY_COMPONENTS,
24  PLACEHOLDER_KEY_REFERENCE,
25 )
26 from .handler import get_supervisor_client
27 from .issues import Issue, Suggestion
28 
29 HELP_URLS = {
30  "help_url": "https://www.home-assistant.io/help/",
31  "community_url": "https://community.home-assistant.io/",
32 }
33 
34 SUGGESTION_CONFIRMATION_REQUIRED = {
35  "addon_execute_remove",
36  "system_adopt_data_disk",
37  "system_execute_reboot",
38 }
39 
40 
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 
49 class SupervisorIssueRepairFlow(RepairsFlow):
50  """Handler for an issue fixing flow."""
51 
52  _data: dict[str, Any] | None = None
53  _issue: Issue | None = None
54 
55  def __init__(self, hass: HomeAssistant, issue_id: str) -> None:
56  """Initialize repair flow."""
57  self._issue_id_issue_id = issue_id
58  self._supervisor_client_supervisor_client = get_supervisor_client(hass)
59  super().__init__()
60 
61  @property
62  def issue(self) -> Issue | None:
63  """Get associated issue."""
64  supervisor_issues = get_issues_info(self.hass)
65  if not self._issue_issue and supervisor_issues:
66  self._issue_issue = supervisor_issues.get_issue(self._issue_id_issue_id)
67 
68  return self._issue_issue
69 
70  @property
71  def description_placeholders(self) -> dict[str, str] | None:
72  """Get description placeholders for steps."""
73  placeholders = {}
74  if self.issueissue:
75  placeholders = EXTRA_PLACEHOLDERS.get(self.issueissue.key, {})
76  if self.issueissue.reference:
77  placeholders |= {PLACEHOLDER_KEY_REFERENCE: self.issueissue.reference}
78 
79  return placeholders or None
80 
81  def _async_form_for_suggestion(self, suggestion: Suggestion) -> FlowResult:
82  """Return form for suggestion."""
83  return self.async_show_form(
84  step_id=suggestion.key,
85  data_schema=vol.Schema({}),
86  description_placeholders=self.description_placeholdersdescription_placeholders,
87  last_step=True,
88  )
89 
90  async def async_step_init(self, _: None = None) -> FlowResult:
91  """Handle the first step of a fix flow."""
92  # Out of sync with supervisor, issue is resolved or not fixable. Remove it
93  if not self.issueissue or not self.issueissue.suggestions:
94  return self.async_create_entry(data={})
95 
96  # All suggestions have the same logic: Apply them in supervisor,
97  # optionally with a confirmation step. Generating the required handler for each
98  # allows for shared logic but screens can still be translated per step id.
99  for suggestion in self.issueissue.suggestions:
100  setattr(
101  self,
102  f"async_step_{suggestion.key}",
103  MethodType(self._async_step_async_step(suggestion), self),
104  )
105 
106  if len(self.issueissue.suggestions) > 1:
107  return await self.async_step_fix_menuasync_step_fix_menu()
108 
109  # Always show a form for one suggestion to explain to user what's happening
110  return self._async_form_for_suggestion_async_form_for_suggestion(self.issueissue.suggestions[0])
111 
112  async def async_step_fix_menu(self, _: None = None) -> FlowResult:
113  """Show the fix menu."""
114  assert self.issueissue
115 
116  return self.async_show_menu(
117  step_id="fix_menu",
118  menu_options=[suggestion.key for suggestion in self.issueissue.suggestions],
119  description_placeholders=self.description_placeholdersdescription_placeholders,
120  )
121 
123  self, suggestion: Suggestion, confirmed: bool = False
124  ) -> FlowResult:
125  """Handle applying a suggestion as a flow step. Optionally request confirmation."""
126  if not confirmed and suggestion.key in SUGGESTION_CONFIRMATION_REQUIRED:
127  return self._async_form_for_suggestion_async_form_for_suggestion(suggestion)
128 
129  try:
130  await self._supervisor_client_supervisor_client.resolution.apply_suggestion(suggestion.uuid)
131  except SupervisorError:
132  return self.async_abort(reason="apply_suggestion_fail")
133 
134  return self.async_create_entry(data={})
135 
136  @staticmethod
138  suggestion: Suggestion,
139  ) -> Callable[
140  [SupervisorIssueRepairFlow, dict[str, str] | None],
141  Coroutine[Any, Any, FlowResult],
142  ]:
143  """Generate a step handler for a suggestion."""
144 
145  async def _async_step(
146  self: SupervisorIssueRepairFlow, user_input: dict[str, str] | None = None
147  ) -> FlowResult:
148  """Handle a flow step for a suggestion."""
149  return await self._async_step_apply_suggestion_async_step_apply_suggestion(
150  suggestion, confirmed=user_input is not None
151  )
152 
153  return _async_step
154 
155 
157  """Handler for docker config issue fixing flow."""
158 
159  @property
160  def description_placeholders(self) -> dict[str, str] | None:
161  """Get description placeholders for steps."""
162  placeholders = {PLACEHOLDER_KEY_COMPONENTS: ""}
163  supervisor_issues = get_issues_info(self.hass)
164  if supervisor_issues and self.issueissue:
165  addons = get_addons_info(self.hass) or {}
166  components: list[str] = []
167  for issue in supervisor_issues.issues:
168  if issue.key == self.issueissue.key or issue.type != self.issueissue.type:
169  continue
170 
171  if issue.context == ContextType.CORE:
172  components.insert(0, "Home Assistant")
173  elif issue.context == ContextType.ADDON:
174  components.append(
175  next(
176  (
177  info["name"]
178  for slug, info in addons.items()
179  if slug == issue.reference
180  ),
181  issue.reference or "",
182  )
183  )
184 
185  placeholders[PLACEHOLDER_KEY_COMPONENTS] = "\n- ".join(components)
186 
187  return placeholders
188 
189 
191  """Handler for addon issue fixing flows."""
192 
193  @property
194  def description_placeholders(self) -> dict[str, str] | None:
195  """Get description placeholders for steps."""
196  placeholders: dict[str, str] = super().description_placeholders or {}
197  if self.issueissue and self.issueissue.reference:
198  addons = get_addons_info(self.hass)
199  if addons and self.issueissue.reference in addons:
200  placeholders[PLACEHOLDER_KEY_ADDON] = addons[self.issueissue.reference][
201  "name"
202  ]
203  else:
204  placeholders[PLACEHOLDER_KEY_ADDON] = self.issueissue.reference
205 
206  return placeholders or None
207 
208 
210  hass: HomeAssistant,
211  issue_id: str,
212  data: dict[str, str | int | float | None] | None,
213 ) -> RepairsFlow:
214  """Create flow."""
215  supervisor_issues = get_issues_info(hass)
216  issue = supervisor_issues and supervisor_issues.get_issue(issue_id)
217  if issue and issue.key == ISSUE_KEY_SYSTEM_DOCKER_CONFIG:
218  return DockerConfigIssueRepairFlow(hass, issue_id)
219  if issue and issue.key in {
220  ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
221  ISSUE_KEY_ADDON_BOOT_FAIL,
222  }:
223  return AddonIssueRepairFlow(hass, issue_id)
224 
225  return SupervisorIssueRepairFlow(hass, issue_id)
Callable[[SupervisorIssueRepairFlow, dict[str, str]|None], Coroutine[Any, Any, FlowResult],] _async_step(Suggestion suggestion)
Definition: repairs.py:142
FlowResult _async_form_for_suggestion(self, Suggestion suggestion)
Definition: repairs.py:81
FlowResult _async_step_apply_suggestion(self, Suggestion suggestion, bool confirmed=False)
Definition: repairs.py:124
None __init__(self, HomeAssistant hass, str issue_id)
Definition: repairs.py:55
dict[str, dict[str, Any]]|None get_addons_info(HomeAssistant hass)
Definition: coordinator.py:119
SupervisorIssues|None get_issues_info(HomeAssistant hass)
Definition: coordinator.py:189
SupervisorClient get_supervisor_client(HomeAssistant hass)
Definition: handler.py:344
RepairsFlow async_create_fix_flow(HomeAssistant hass, str issue_id, dict[str, str|int|float|None]|None data)
Definition: repairs.py:213