mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
UniFi Protect removing early access checks and issue creation (#147432)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
4b02f22724
commit
8a18dea8c7
@ -8,7 +8,6 @@ import logging
|
||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
||||
from uiprotect.api import DEVICE_UPDATE_INTERVAL
|
||||
from uiprotect.data import Bootstrap
|
||||
from uiprotect.data.types import FirmwareReleaseChannel
|
||||
from uiprotect.exceptions import ClientError, NotAuthorized
|
||||
|
||||
# Import the test_util.anonymize module from the uiprotect package
|
||||
@ -16,6 +15,7 @@ from uiprotect.exceptions import ClientError, NotAuthorized
|
||||
# diagnostics module will not be imported in the executor.
|
||||
from uiprotect.test_util.anonymize import anonymize_data # noqa: F401
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
@ -58,10 +58,6 @@ SCAN_INTERVAL = timedelta(seconds=DEVICE_UPDATE_INTERVAL)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
EARLY_ACCESS_URL = (
|
||||
"https://www.home-assistant.io/integrations/unifiprotect#software-support"
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the UniFi Protect."""
|
||||
@ -123,47 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool:
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, data_service.async_stop)
|
||||
)
|
||||
|
||||
if not entry.options.get(CONF_ALLOW_EA, False) and (
|
||||
await nvr_info.get_is_prerelease()
|
||||
or nvr_info.release_channel != FirmwareReleaseChannel.RELEASE
|
||||
):
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"ea_channel_warning",
|
||||
is_fixable=True,
|
||||
is_persistent=False,
|
||||
learn_more_url=EARLY_ACCESS_URL,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="ea_channel_warning",
|
||||
translation_placeholders={"version": str(nvr_info.version)},
|
||||
data={"entry_id": entry.entry_id},
|
||||
)
|
||||
|
||||
try:
|
||||
await _async_setup_entry(hass, entry, data_service, bootstrap)
|
||||
except Exception as err:
|
||||
if await nvr_info.get_is_prerelease():
|
||||
# If they are running a pre-release, its quite common for setup
|
||||
# to fail so we want to create a repair issue for them so its
|
||||
# obvious what the problem is.
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"ea_setup_failed_{nvr_info.version}",
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
learn_more_url="https://www.home-assistant.io/integrations/unifiprotect#about-unifi-early-access",
|
||||
severity=IssueSeverity.ERROR,
|
||||
translation_key="ea_setup_failed",
|
||||
translation_placeholders={
|
||||
"error": str(err),
|
||||
"version": str(nvr_info.version),
|
||||
},
|
||||
)
|
||||
ir.async_delete_issue(hass, DOMAIN, "ea_channel_warning")
|
||||
_LOGGER.exception("Error setting up UniFi Protect integration")
|
||||
raise
|
||||
await _async_setup_entry(hass, entry, data_service, bootstrap)
|
||||
|
||||
return True
|
||||
|
||||
@ -211,3 +167,23 @@ async def async_remove_config_entry_device(
|
||||
if device.is_adopted_by_us and device.mac in unifi_macs:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate entry."""
|
||||
_LOGGER.debug("Migrating configuration from version %s", entry.version)
|
||||
|
||||
if entry.version > 1:
|
||||
return False
|
||||
|
||||
if entry.version == 1:
|
||||
options = dict(entry.options)
|
||||
if CONF_ALLOW_EA in options:
|
||||
options.pop(CONF_ALLOW_EA)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, unique_id=str(entry.unique_id), version=2, options=options
|
||||
)
|
||||
|
||||
_LOGGER.debug("Migration to configuration version %s successful", entry.version)
|
||||
|
||||
return True
|
||||
|
@ -44,7 +44,6 @@ from homeassistant.util.network import is_ip_address
|
||||
|
||||
from .const import (
|
||||
CONF_ALL_UPDATES,
|
||||
CONF_ALLOW_EA,
|
||||
CONF_DISABLE_RTSP,
|
||||
CONF_MAX_MEDIA,
|
||||
CONF_OVERRIDE_CHOST,
|
||||
@ -238,7 +237,6 @@ class ProtectFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_ALL_UPDATES: False,
|
||||
CONF_OVERRIDE_CHOST: False,
|
||||
CONF_MAX_MEDIA: DEFAULT_MAX_MEDIA,
|
||||
CONF_ALLOW_EA: False,
|
||||
},
|
||||
)
|
||||
|
||||
@ -408,10 +406,6 @@ class OptionsFlowHandler(OptionsFlow):
|
||||
CONF_MAX_MEDIA, DEFAULT_MAX_MEDIA
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Range(min=100, max=10000)),
|
||||
vol.Optional(
|
||||
CONF_ALLOW_EA,
|
||||
default=self.config_entry.options.get(CONF_ALLOW_EA, False),
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
@ -6,7 +6,6 @@ from typing import cast
|
||||
|
||||
from uiprotect import ProtectApiClient
|
||||
from uiprotect.data import Bootstrap, Camera, ModelType
|
||||
from uiprotect.data.types import FirmwareReleaseChannel
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
@ -15,7 +14,6 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import CONF_ALLOW_EA
|
||||
from .data import UFPConfigEntry, async_get_data_for_entry_id
|
||||
from .utils import async_create_api_client
|
||||
|
||||
@ -45,52 +43,6 @@ class ProtectRepair(RepairsFlow):
|
||||
return description_placeholders
|
||||
|
||||
|
||||
class EAConfirmRepair(ProtectRepair):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
|
||||
return await self.async_step_start()
|
||||
|
||||
async def async_step_start(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is None:
|
||||
placeholders = self._async_get_placeholders()
|
||||
return self.async_show_form(
|
||||
step_id="start",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders=placeholders,
|
||||
)
|
||||
|
||||
nvr = await self._api.get_nvr()
|
||||
if nvr.release_channel != FirmwareReleaseChannel.RELEASE:
|
||||
return await self.async_step_confirm()
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
options = dict(self._entry.options)
|
||||
options[CONF_ALLOW_EA] = True
|
||||
self.hass.config_entries.async_update_entry(self._entry, options=options)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
placeholders = self._async_get_placeholders()
|
||||
return self.async_show_form(
|
||||
step_id="confirm",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders=placeholders,
|
||||
)
|
||||
|
||||
|
||||
class CloudAccountRepair(ProtectRepair):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
@ -242,8 +194,6 @@ async def async_create_fix_flow(
|
||||
and (entry := hass.config_entries.async_get_entry(cast(str, data["entry_id"])))
|
||||
):
|
||||
api = _async_get_or_create_api_client(hass, entry)
|
||||
if issue_id == "ea_channel_warning":
|
||||
return EAConfirmRepair(api=api, entry=entry)
|
||||
if issue_id == "cloud_user":
|
||||
return CloudAccountRepair(api=api, entry=entry)
|
||||
if issue_id.startswith("rtsp_disabled_"):
|
||||
|
@ -55,32 +55,12 @@
|
||||
"disable_rtsp": "Disable the RTSP stream",
|
||||
"all_updates": "Realtime metrics (WARNING: Greatly increases CPU usage)",
|
||||
"override_connection_host": "Override connection host",
|
||||
"max_media": "Max number of event to load for Media Browser (increases RAM usage)",
|
||||
"allow_ea_channel": "Allow Early Access versions of Protect (WARNING: Will mark your integration as unsupported)"
|
||||
"max_media": "Max number of event to load for Media Browser (increases RAM usage)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"ea_channel_warning": {
|
||||
"title": "UniFi Protect Early Access enabled",
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"start": {
|
||||
"title": "UniFi Protect Early Access enabled",
|
||||
"description": "You are either running an Early Access version of UniFi Protect (v{version}) or opt-ed into a release channel that is not the official release channel.\n\nAs these Early Access releases may not be tested yet, using it may cause the UniFi Protect integration to behave unexpectedly. [Read more about Early Access and Home Assistant]({learn_more}).\n\nSubmit to dismiss this message."
|
||||
},
|
||||
"confirm": {
|
||||
"title": "[%key:component::unifiprotect::issues::ea_channel_warning::fix_flow::step::start::title%]",
|
||||
"description": "Are you sure you want to run unsupported versions of UniFi Protect? This may cause your Home Assistant integration to break."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ea_setup_failed": {
|
||||
"title": "Setup error using Early Access version",
|
||||
"description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please restore a backup of a stable release of UniFi Protect to continue using the integration.\n\nError: {error}"
|
||||
},
|
||||
"cloud_user": {
|
||||
"title": "Ubiquiti Cloud Users are not Supported",
|
||||
"fix_flow": {
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
import socket
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
from uiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||
@ -325,7 +325,6 @@ async def test_form_options(hass: HomeAssistant, ufp_client: ProtectApiClient) -
|
||||
"disable_rtsp": True,
|
||||
"override_connection_host": True,
|
||||
"max_media": 1000,
|
||||
"allow_ea_channel": False,
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
await hass.config_entries.async_unload(mock_config.entry_id)
|
||||
@ -794,6 +793,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa
|
||||
},
|
||||
unique_id="FFFFFFAAAAAA",
|
||||
)
|
||||
mock_config.runtime_data = Mock(async_stop=AsyncMock())
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
other_ip_dict = UNIFI_DISCOVERY_DICT.copy()
|
||||
@ -855,7 +855,7 @@ async def test_discovered_by_unifi_discovery_direct_connect_on_different_interfa
|
||||
"port": 443,
|
||||
"verify_ssl": True,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 2
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
from uiprotect.data import NVR, Light
|
||||
|
||||
from homeassistant.components.unifiprotect.const import CONF_ALLOW_EA
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .utils import MockUFPFixture, init_entry
|
||||
@ -22,7 +21,6 @@ async def test_diagnostics(
|
||||
await init_entry(hass, ufp, [light])
|
||||
|
||||
options = dict(ufp.entry.options)
|
||||
options[CONF_ALLOW_EA] = True
|
||||
hass.config_entries.async_update_entry(ufp.entry, options=options)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -30,7 +28,6 @@ async def test_diagnostics(
|
||||
|
||||
assert "options" in diag and isinstance(diag["options"], dict)
|
||||
options = diag["options"]
|
||||
assert options[CONF_ALLOW_EA] is True
|
||||
|
||||
assert "bootstrap" in diag and isinstance(diag["bootstrap"], dict)
|
||||
bootstrap = diag["bootstrap"]
|
||||
|
@ -11,6 +11,7 @@ from uiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
||||
|
||||
from homeassistant.components.unifiprotect.const import (
|
||||
AUTH_RETRIES,
|
||||
CONF_ALLOW_EA,
|
||||
CONF_DISABLE_RTSP,
|
||||
DOMAIN,
|
||||
)
|
||||
@ -345,3 +346,24 @@ async def test_async_ufp_instance_for_config_entry_ids(
|
||||
result = async_ufp_instance_for_config_entry_ids(hass, entry_ids)
|
||||
|
||||
assert result == expected_result
|
||||
|
||||
|
||||
async def test_migrate_entry_version_2(hass: HomeAssistant) -> None:
|
||||
"""Test remove CONF_ALLOW_EA from options while migrating a 1 config entry to 2."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.unifiprotect.async_setup_entry", return_value=True
|
||||
),
|
||||
patch("homeassistant.components.unifiprotect.async_start_discovery"),
|
||||
):
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={"test": "1", "test2": "2", CONF_ALLOW_EA: "True"},
|
||||
version=1,
|
||||
unique_id="123456",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.version == 2
|
||||
assert entry.options.get(CONF_ALLOW_EA) is None
|
||||
assert entry.unique_id == "123456"
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import copy, deepcopy
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
from copy import deepcopy
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from uiprotect.data import Camera, CloudAccount, ModelType, Version
|
||||
|
||||
@ -21,110 +21,6 @@ from tests.components.repairs import (
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
|
||||
|
||||
async def test_ea_warning_ignore(
|
||||
hass: HomeAssistant,
|
||||
ufp: MockUFPFixture,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test EA warning is created if using prerelease version of Protect."""
|
||||
|
||||
ufp.api.bootstrap.nvr.release_channel = "beta"
|
||||
ufp.api.bootstrap.nvr.version = Version("1.21.0-beta.2")
|
||||
version = ufp.api.bootstrap.nvr.version
|
||||
assert version.is_prerelease
|
||||
await init_entry(hass, ufp, [])
|
||||
await async_process_repairs_platforms(hass)
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert len(msg["result"]["issues"]) > 0
|
||||
issue = None
|
||||
for i in msg["result"]["issues"]:
|
||||
if i["issue_id"] == "ea_channel_warning":
|
||||
issue = i
|
||||
assert issue is not None
|
||||
|
||||
data = await start_repair_fix_flow(client, DOMAIN, "ea_channel_warning")
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "start"
|
||||
|
||||
data = await process_repair_fix_flow(client, flow_id)
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "confirm"
|
||||
|
||||
data = await process_repair_fix_flow(client, flow_id)
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
|
||||
|
||||
async def test_ea_warning_fix(
|
||||
hass: HomeAssistant,
|
||||
ufp: MockUFPFixture,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test EA warning is created if using prerelease version of Protect."""
|
||||
|
||||
ufp.api.bootstrap.nvr.release_channel = "beta"
|
||||
ufp.api.bootstrap.nvr.version = Version("1.21.0-beta.2")
|
||||
version = ufp.api.bootstrap.nvr.version
|
||||
assert version.is_prerelease
|
||||
await init_entry(hass, ufp, [])
|
||||
await async_process_repairs_platforms(hass)
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert len(msg["result"]["issues"]) > 0
|
||||
issue = None
|
||||
for i in msg["result"]["issues"]:
|
||||
if i["issue_id"] == "ea_channel_warning":
|
||||
issue = i
|
||||
assert issue is not None
|
||||
|
||||
data = await start_repair_fix_flow(client, DOMAIN, "ea_channel_warning")
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {
|
||||
"learn_more": "https://www.home-assistant.io/integrations/unifiprotect#software-support",
|
||||
"version": str(version),
|
||||
}
|
||||
assert data["step_id"] == "start"
|
||||
|
||||
new_nvr = copy(ufp.api.bootstrap.nvr)
|
||||
new_nvr.release_channel = "release"
|
||||
new_nvr.version = Version("2.2.6")
|
||||
mock_msg = Mock()
|
||||
mock_msg.changed_data = {"version": "2.2.6", "releaseChannel": "release"}
|
||||
mock_msg.new_obj = new_nvr
|
||||
|
||||
ufp.api.bootstrap.nvr = new_nvr
|
||||
ufp.ws_msg(mock_msg)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
data = await process_repair_fix_flow(client, flow_id)
|
||||
|
||||
assert data["type"] == "create_entry"
|
||||
|
||||
|
||||
async def test_cloud_user_fix(
|
||||
hass: HomeAssistant,
|
||||
ufp: MockUFPFixture,
|
||||
|
Loading…
x
Reference in New Issue
Block a user