Skip to content

Commit 079d65a

Browse files
authored
Portainer add reconfigure flow (#155289)
1 parent 162737a commit 079d65a

File tree

4 files changed

+155
-2
lines changed

4 files changed

+155
-2
lines changed

homeassistant/components/portainer/config_flow.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,55 @@ async def async_step_reauth_confirm(
130130
errors=errors,
131131
)
132132

133+
async def async_step_reconfigure(
134+
self, user_input: dict[str, Any] | None = None
135+
) -> ConfigFlowResult:
136+
"""Handle reconfiguration of the integration."""
137+
errors: dict[str, str] = {}
138+
reconf_entry = self._get_reconfigure_entry()
139+
suggested_values = {
140+
CONF_URL: reconf_entry.data[CONF_URL],
141+
CONF_API_TOKEN: reconf_entry.data[CONF_API_TOKEN],
142+
CONF_VERIFY_SSL: reconf_entry.data[CONF_VERIFY_SSL],
143+
}
144+
145+
if user_input:
146+
try:
147+
await _validate_input(
148+
self.hass,
149+
data={
150+
**reconf_entry.data,
151+
**user_input,
152+
},
153+
)
154+
except CannotConnect:
155+
errors["base"] = "cannot_connect"
156+
except InvalidAuth:
157+
errors["base"] = "invalid_auth"
158+
except PortainerTimeout:
159+
errors["base"] = "timeout_connect"
160+
except Exception:
161+
_LOGGER.exception("Unexpected exception")
162+
errors["base"] = "unknown"
163+
else:
164+
return self.async_update_reload_and_abort(
165+
reconf_entry,
166+
data_updates={
167+
CONF_URL: user_input[CONF_URL],
168+
CONF_API_TOKEN: user_input[CONF_API_TOKEN],
169+
CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
170+
},
171+
)
172+
173+
return self.async_show_form(
174+
step_id="reconfigure",
175+
data_schema=self.add_suggested_values_to_schema(
176+
data_schema=STEP_USER_DATA_SCHEMA,
177+
suggested_values=user_input or suggested_values,
178+
),
179+
errors=errors,
180+
)
181+
133182

134183
class CannotConnect(HomeAssistantError):
135184
"""Error to indicate we cannot connect."""

homeassistant/components/portainer/quality_scale.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ rules:
6767
entity-translations: todo
6868
exception-translations: todo
6969
icon-translations: todo
70-
reconfiguration-flow: todo
70+
reconfiguration-flow: done
7171
repair-issues: todo
7272
stale-devices: todo
7373

homeassistant/components/portainer/strings.json

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"config": {
33
"abort": {
44
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
5-
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
5+
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
6+
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
67
},
78
"error": {
89
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -20,6 +21,20 @@
2021
},
2122
"description": "The access token for your Portainer instance needs to be re-authenticated. You can create a new access token in the Portainer UI. Go to **My account > Access tokens** and select **Add access token**"
2223
},
24+
"reconfigure": {
25+
"data": {
26+
"api_token": "[%key:common::config_flow::data::api_token%]",
27+
"url": "[%key:common::config_flow::data::url%]",
28+
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
29+
},
30+
"data_description": {
31+
"api_token": "[%key:component::portainer::config::step::user::data_description::api_token%]",
32+
"url": "[%key:component::portainer::config::step::user::data_description::url%]",
33+
"verify_ssl": "[%key:component::portainer::config::step::user::data_description::verify_ssl%]"
34+
},
35+
"description": "Use the following form to reconfigure your Portainer instance.",
36+
"title": "Reconfigure Portainer Integration"
37+
},
2338
"user": {
2439
"data": {
2540
"api_token": "[%key:common::config_flow::data::api_token%]",

tests/components/portainer/test_config_flow.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
CONF_VERIFY_SSL: True,
2626
}
2727

28+
USER_INPUT_RECONFIGURE = {
29+
CONF_URL: "https://new_domain:9000/",
30+
CONF_API_TOKEN: "new_api_key",
31+
CONF_VERIFY_SSL: True,
32+
}
33+
2834

2935
async def test_form(
3036
hass: HomeAssistant,
@@ -222,3 +228,86 @@ async def test_reauth_flow_exceptions(
222228
assert result["reason"] == "reauth_successful"
223229
assert mock_config_entry.data[CONF_API_TOKEN] == "new_api_key"
224230
assert len(mock_setup_entry.mock_calls) == 1
231+
232+
233+
async def test_full_flow_reconfigure(
234+
hass: HomeAssistant,
235+
mock_portainer_client: AsyncMock,
236+
mock_setup_entry: MagicMock,
237+
mock_config_entry: MockConfigEntry,
238+
) -> None:
239+
"""Test the full flow of the config flow."""
240+
mock_config_entry.add_to_hass(hass)
241+
result = await mock_config_entry.start_reconfigure_flow(hass)
242+
assert result["type"] is FlowResultType.FORM
243+
assert result["step_id"] == "reconfigure"
244+
245+
result = await hass.config_entries.flow.async_configure(
246+
result["flow_id"],
247+
user_input=USER_INPUT_RECONFIGURE,
248+
)
249+
250+
assert result["type"] is FlowResultType.ABORT
251+
assert result["reason"] == "reconfigure_successful"
252+
assert mock_config_entry.data[CONF_API_TOKEN] == "new_api_key"
253+
assert mock_config_entry.data[CONF_URL] == "https://new_domain:9000/"
254+
assert mock_config_entry.data[CONF_VERIFY_SSL] is True
255+
assert len(mock_setup_entry.mock_calls) == 1
256+
257+
258+
@pytest.mark.parametrize(
259+
("exception", "reason"),
260+
[
261+
(
262+
PortainerAuthenticationError,
263+
"invalid_auth",
264+
),
265+
(
266+
PortainerConnectionError,
267+
"cannot_connect",
268+
),
269+
(
270+
PortainerTimeoutError,
271+
"timeout_connect",
272+
),
273+
(
274+
Exception("Some other error"),
275+
"unknown",
276+
),
277+
],
278+
)
279+
async def test_full_flow_reconfigure_exceptions(
280+
hass: HomeAssistant,
281+
mock_portainer_client: AsyncMock,
282+
mock_setup_entry: MagicMock,
283+
mock_config_entry: MockConfigEntry,
284+
exception: Exception,
285+
reason: str,
286+
) -> None:
287+
"""Test the full flow of the config flow, this time with exceptions."""
288+
mock_config_entry.add_to_hass(hass)
289+
result = await mock_config_entry.start_reconfigure_flow(hass)
290+
assert result["type"] is FlowResultType.FORM
291+
assert result["step_id"] == "reconfigure"
292+
293+
mock_portainer_client.get_endpoints.side_effect = exception
294+
result = await hass.config_entries.flow.async_configure(
295+
result["flow_id"],
296+
user_input=USER_INPUT_RECONFIGURE,
297+
)
298+
299+
assert result["type"] is FlowResultType.FORM
300+
assert result["errors"] == {"base": reason}
301+
302+
mock_portainer_client.get_endpoints.side_effect = None
303+
result = await hass.config_entries.flow.async_configure(
304+
result["flow_id"],
305+
user_input=USER_INPUT_RECONFIGURE,
306+
)
307+
308+
assert result["type"] is FlowResultType.ABORT
309+
assert result["reason"] == "reconfigure_successful"
310+
assert mock_config_entry.data[CONF_API_TOKEN] == "new_api_key"
311+
assert mock_config_entry.data[CONF_URL] == "https://new_domain:9000/"
312+
assert mock_config_entry.data[CONF_VERIFY_SSL] is True
313+
assert len(mock_setup_entry.mock_calls) == 1

0 commit comments

Comments
 (0)