Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Adds config flow for dnsip integration."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import contextlib
7 from typing import Any
8 
9 import aiodns
10 from aiodns.error import DNSError
11 import voluptuous as vol
12 
13 from homeassistant.config_entries import (
14  ConfigEntry,
15  ConfigFlow,
16  ConfigFlowResult,
17  OptionsFlow,
18 )
19 from homeassistant.const import CONF_NAME, CONF_PORT
20 from homeassistant.core import callback
22 
23 from .const import (
24  CONF_HOSTNAME,
25  CONF_IPV4,
26  CONF_IPV6,
27  CONF_IPV6_V4,
28  CONF_PORT_IPV6,
29  CONF_RESOLVER,
30  CONF_RESOLVER_IPV6,
31  DEFAULT_HOSTNAME,
32  DEFAULT_NAME,
33  DEFAULT_PORT,
34  DEFAULT_RESOLVER,
35  DEFAULT_RESOLVER_IPV6,
36  DOMAIN,
37 )
38 
39 DATA_SCHEMA = vol.Schema(
40  {
41  vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
42  }
43 )
44 DATA_SCHEMA_ADV = vol.Schema(
45  {
46  vol.Required(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
47  vol.Optional(CONF_RESOLVER): cv.string,
48  vol.Optional(CONF_PORT): cv.port,
49  vol.Optional(CONF_RESOLVER_IPV6): cv.string,
50  vol.Optional(CONF_PORT_IPV6): cv.port,
51  }
52 )
53 
54 
56  hostname: str,
57  resolver_ipv4: str,
58  resolver_ipv6: str,
59  port: int,
60  port_ipv6: int,
61 ) -> dict[str, bool]:
62  """Validate hostname."""
63 
64  async def async_check(
65  hostname: str, resolver: str, qtype: str, port: int = 53
66  ) -> bool:
67  """Return if able to resolve hostname."""
68  result = False
69  with contextlib.suppress(DNSError):
70  result = bool(
71  await aiodns.DNSResolver(
72  nameservers=[resolver], udp_port=port, tcp_port=port
73  ).query(hostname, qtype)
74  )
75  return result
76 
77  result: dict[str, bool] = {}
78 
79  tasks = await asyncio.gather(
80  async_check(hostname, resolver_ipv4, "A", port=port),
81  async_check(hostname, resolver_ipv6, "AAAA", port=port_ipv6),
82  async_check(hostname, resolver_ipv4, "AAAA", port=port),
83  )
84 
85  result[CONF_IPV4] = tasks[0]
86  result[CONF_IPV6] = tasks[1]
87  result[CONF_IPV6_V4] = tasks[2]
88 
89  return result
90 
91 
92 class DnsIPConfigFlow(ConfigFlow, domain=DOMAIN):
93  """Handle a config flow for dnsip integration."""
94 
95  VERSION = 1
96  MINOR_VERSION = 2
97 
98  @staticmethod
99  @callback
101  config_entry: ConfigEntry,
102  ) -> DnsIPOptionsFlowHandler:
103  """Return Option handler."""
104  return DnsIPOptionsFlowHandler()
105 
106  async def async_step_user(
107  self, user_input: dict[str, Any] | None = None
108  ) -> ConfigFlowResult:
109  """Handle the initial step."""
110 
111  errors = {}
112 
113  if user_input:
114  hostname = user_input[CONF_HOSTNAME]
115  name = DEFAULT_NAME if hostname == DEFAULT_HOSTNAME else hostname
116  resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER)
117  resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6)
118  port = user_input.get(CONF_PORT, DEFAULT_PORT)
119  port_ipv6 = user_input.get(CONF_PORT_IPV6, DEFAULT_PORT)
120 
121  validate = await async_validate_hostname(
122  hostname, resolver, resolver_ipv6, port, port_ipv6
123  )
124 
125  set_resolver = resolver
126  if validate[CONF_IPV6]:
127  set_resolver = resolver_ipv6
128 
129  if (
130  not validate[CONF_IPV4]
131  and not validate[CONF_IPV6]
132  and not validate[CONF_IPV6_V4]
133  ):
134  errors["base"] = "invalid_hostname"
135  else:
136  await self.async_set_unique_idasync_set_unique_id(hostname)
137  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
138 
139  return self.async_create_entryasync_create_entryasync_create_entry(
140  title=name,
141  data={
142  CONF_HOSTNAME: hostname,
143  CONF_NAME: name,
144  CONF_IPV4: validate[CONF_IPV4],
145  CONF_IPV6: validate[CONF_IPV6] or validate[CONF_IPV6_V4],
146  },
147  options={
148  CONF_RESOLVER: resolver,
149  CONF_PORT: port,
150  CONF_RESOLVER_IPV6: set_resolver,
151  CONF_PORT_IPV6: port_ipv6,
152  },
153  )
154 
155  if self.show_advanced_optionsshow_advanced_options is True:
156  return self.async_show_formasync_show_formasync_show_form(
157  step_id="user",
158  data_schema=DATA_SCHEMA_ADV,
159  errors=errors,
160  )
161  return self.async_show_formasync_show_formasync_show_form(
162  step_id="user",
163  data_schema=DATA_SCHEMA,
164  errors=errors,
165  )
166 
167 
169  """Handle a option config flow for dnsip integration."""
170 
171  async def async_step_init(
172  self, user_input: dict[str, Any] | None = None
173  ) -> ConfigFlowResult:
174  """Manage the options."""
175  errors = {}
176  if user_input is not None:
177  resolver = user_input.get(CONF_RESOLVER, DEFAULT_RESOLVER)
178  port = user_input.get(CONF_PORT, DEFAULT_PORT)
179  resolver_ipv6 = user_input.get(CONF_RESOLVER_IPV6, DEFAULT_RESOLVER_IPV6)
180  port_ipv6 = user_input.get(CONF_PORT_IPV6, DEFAULT_PORT)
181  validate = await async_validate_hostname(
182  self.config_entryconfig_entryconfig_entry.data[CONF_HOSTNAME],
183  resolver,
184  resolver_ipv6,
185  port,
186  port_ipv6,
187  )
188 
189  if (
190  validate[CONF_IPV4] is False
191  and self.config_entryconfig_entryconfig_entry.data[CONF_IPV4] is True
192  ):
193  errors[CONF_RESOLVER] = "invalid_resolver"
194  elif (
195  validate[CONF_IPV6] is False
196  and self.config_entryconfig_entryconfig_entry.data[CONF_IPV6] is True
197  ):
198  errors[CONF_RESOLVER_IPV6] = "invalid_resolver"
199  else:
200  return self.async_create_entryasync_create_entry(
201  title=self.config_entryconfig_entryconfig_entry.title,
202  data={
203  CONF_RESOLVER: resolver,
204  CONF_PORT: port,
205  CONF_RESOLVER_IPV6: resolver_ipv6,
206  CONF_PORT_IPV6: port_ipv6,
207  },
208  )
209 
210  schema = self.add_suggested_values_to_schemaadd_suggested_values_to_schema(
211  vol.Schema(
212  {
213  vol.Optional(CONF_RESOLVER): cv.string,
214  vol.Optional(CONF_PORT): cv.port,
215  vol.Optional(CONF_RESOLVER_IPV6): cv.string,
216  vol.Optional(CONF_PORT_IPV6): cv.port,
217  }
218  ),
219  self.config_entryconfig_entryconfig_entry.options,
220  )
221 
222  return self.async_show_formasync_show_form(step_id="init", data_schema=schema, errors=errors)
DnsIPOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:102
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:108
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:173
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
None config_entry(self, ConfigEntry value)
bool show_advanced_options(self)
vol.Schema add_suggested_values_to_schema(self, vol.Schema data_schema, Mapping[str, Any]|None suggested_values)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
dict[str, bool] async_validate_hostname(str hostname, str resolver_ipv4, str resolver_ipv6, int port, int port_ipv6)
Definition: config_flow.py:61