diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 06696865d03a1c..c896c8ac32dd86 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -17,9 +17,8 @@ SynologyDSMLoginFailedException, SynologyDSMRequestException, ) -import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_DISKS, @@ -37,6 +36,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, @@ -46,10 +46,10 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import ( + CONF_FILTER_STORAGE, CONF_SERIAL, CONF_VOLUMES, DEFAULT_SCAN_INTERVAL, - DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, DOMAIN, ENTITY_CLASS, @@ -70,23 +70,8 @@ UTILISATION_SENSORS, ) -CONFIG_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_SSL, default=DEFAULT_USE_SSL): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_DISKS): cv.ensure_list, - vol.Optional(CONF_VOLUMES): cv.ensure_list, - } -) +CONFIG_SCHEMA = cv.deprecated(DOMAIN, invalidation_version="0.120") -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Schema(vol.All(cv.ensure_list, [CONFIG_SCHEMA]))}, - extra=vol.ALLOW_EXTRA, -) ATTRIBUTION = "Data provided by Synology" @@ -96,20 +81,6 @@ async def async_setup(hass, config): """Set up Synology DSM sensors from legacy config file.""" - - conf = config.get(DOMAIN) - if conf is None: - return True - - for dsm_conf in conf: - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=dsm_conf, - ) - ) - return True @@ -180,6 +151,25 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry): entry, data={**entry.data, CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL} ) + # Consider if filter storage devices is true + if entry.options.get(CONF_FILTER_STORAGE): + hass.config_entries.async_update_entry( + entry, + data={ + **entry.data, + CONF_DISKS: entry.options[CONF_DISKS], + CONF_VOLUMES: entry.options[CONF_VOLUMES], + }, + ) + else: + data = dict(entry.data) + data.pop(CONF_DISKS, None) + data.pop(CONF_VOLUMES, None) + hass.config_entries.async_update_entry( + entry, + data=data, + ) + # Continue setup api = SynoApi(hass, entry) try: @@ -230,6 +220,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): entry_data[UNDO_UPDATE_LISTENER]() await entry_data[SYNO_API].async_unload() hass.data[DOMAIN].pop(entry.unique_id) + dev_reg = await async_get_registry(hass) + dev_reg.async_clear_config_entry(entry.entry_id) return unload_ok diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 5a1ab53b3f765f..b3d828f0b03b1d 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -31,13 +31,16 @@ import homeassistant.helpers.config_validation as cv from .const import ( + CONF_FILTER_STORAGE, CONF_VOLUMES, + DEFAULT_FILTER_STORAGE, DEFAULT_PORT, DEFAULT_PORT_SSL, DEFAULT_SCAN_INTERVAL, DEFAULT_TIMEOUT, DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, + SYNO_API, ) from .const import DOMAIN # pylint: disable=unused-import @@ -90,6 +93,7 @@ def __init__(self): """Initialize the synology_dsm config flow.""" self.saved_user_input = {} self.discovered_conf = {} + self.syno_info = {} async def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" @@ -140,7 +144,7 @@ async def async_step_user(self, user_input=None): ) try: - serial = await self.hass.async_add_executor_job( + self.syno_info = await self.hass.async_add_executor_job( _login_and_fetch_syno_info, api, otp_code ) except SynologyDSMLogin2SARequiredException: @@ -164,10 +168,10 @@ async def async_step_user(self, user_input=None): if errors: return await self._show_setup_form(user_input, errors) - # unique_id should be serial for services purpose - await self.async_set_unique_id(serial, raise_on_progress=False) - # Check if already configured + await self.async_set_unique_id( + self.syno_info["serial"], raise_on_progress=False + ) self._abort_if_unique_id_configured() config_data = { @@ -212,10 +216,6 @@ async def async_step_ssdp(self, discovery_info): self.context["title_placeholders"] = self.discovered_conf return await self.async_step_user() - async def async_step_import(self, user_input=None): - """Import a config entry.""" - return await self.async_step_user(user_input) - async def async_step_link(self, user_input): """Link a config entry from discovery.""" return await self.async_step_user(user_input) @@ -259,6 +259,12 @@ async def async_step_init(self, user_input=None): if user_input is not None: return self.async_create_entry(title="", data=user_input) + disks_ids = self.hass.data[DOMAIN][self.config_entry.unique_id][ + SYNO_API + ].storage.disks_ids + volumes_ids = self.hass.data[DOMAIN][self.config_entry.unique_id][ + SYNO_API + ].storage.volumes_ids data_schema = vol.Schema( { vol.Optional( @@ -273,6 +279,20 @@ async def async_step_init(self, user_input=None): CONF_TIMEOUT, DEFAULT_TIMEOUT ), ): cv.positive_int, + vol.Optional( + CONF_FILTER_STORAGE, + default=self.config_entry.options.get( + CONF_FILTER_STORAGE, DEFAULT_FILTER_STORAGE + ), + ): bool, + vol.Required( + CONF_DISKS, + default=self.config_entry.options.get(CONF_DISKS, disks_ids), + ): cv.multi_select(disks_ids), + vol.Required( + CONF_VOLUMES, + default=self.config_entry.options.get(CONF_VOLUMES, volumes_ids), + ): cv.multi_select(volumes_ids), } ) return self.async_show_form(step_id="init", data_schema=data_schema) @@ -294,7 +314,11 @@ def _login_and_fetch_syno_info(api, otp_code): ): raise InvalidData - return api.information.serial + return { + "serial": api.information.serial, + CONF_DISKS: api.storage.disks_ids, + CONF_VOLUMES: api.storage.volumes_ids, + } class InvalidData(exceptions.HomeAssistantError): diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index ba1a8034223a8c..766694a6c35ba6 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -27,11 +27,13 @@ # Configuration CONF_SERIAL = "serial" CONF_VOLUMES = "volumes" +CONF_FILTER_STORAGE = "filter_storage" DEFAULT_USE_SSL = True DEFAULT_VERIFY_SSL = False DEFAULT_PORT = 5000 DEFAULT_PORT_SSL = 5001 +DEFAULT_FILTER_STORAGE = False # Options DEFAULT_SCAN_INTERVAL = 15 # min DEFAULT_TIMEOUT = 10 # sec diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index 9193357102868f..b3d34f96d58f2f 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -29,6 +29,14 @@ "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]" } + }, + "filter_storage": { + "title": "Synology DSM: filter disks and volumes", + "description": "Select the disks and volumes you want to add", + "data": { + "disks": "Disks", + "volumes": "Volumes" + } } }, "error": { @@ -47,7 +55,8 @@ "init": { "data": { "scan_interval": "Minutes between scans", - "timeout": "Timeout (seconds)" + "timeout": "Timeout (seconds)", + "filter_storage": "Filter disks and volumes to add" } } } diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index 1501aa89485d44..4f0afb722674a3 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -47,7 +47,10 @@ "init": { "data": { "scan_interval": "Minutes between scans", - "timeout": "Timeout (seconds)" + "timeout": "Timeout (seconds)", + "filter_storage": "Filter disks and volumes to add", + "disks": "Disks", + "volumes": "Volumes" } } } diff --git a/tests/components/synology_dsm/consts.py b/tests/components/synology_dsm/consts.py index 3c305745aa7650..d31e9657542538 100644 --- a/tests/components/synology_dsm/consts.py +++ b/tests/components/synology_dsm/consts.py @@ -11,4 +11,7 @@ PASSWORD = "password" DEVICE_TOKEN = "Dév!cè_T0k€ñ" +DISKS = ["sda", "sdb", "sdc"] +VOLUMES = ["volume_1"] + MACS = ["00-11-32-XX-XX-59", "00-11-32-XX-XX-5A"] diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 59ed8eea6573c3..b1b32c678a2a54 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -12,7 +12,9 @@ from homeassistant.components import ssdp from homeassistant.components.synology_dsm.config_flow import CONF_OTP_CODE from homeassistant.components.synology_dsm.const import ( + CONF_FILTER_STORAGE, CONF_VOLUMES, + DEFAULT_FILTER_STORAGE, DEFAULT_PORT, DEFAULT_PORT_SSL, DEFAULT_SCAN_INTERVAL, @@ -20,8 +22,9 @@ DEFAULT_USE_SSL, DEFAULT_VERIFY_SSL, DOMAIN, + SYNO_API, ) -from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP, SOURCE_USER +from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER from homeassistant.const import ( CONF_DISKS, CONF_HOST, @@ -38,8 +41,8 @@ from .consts import ( DEVICE_TOKEN, + DISKS, HOST, - HOST_2, MACS, PASSWORD, PORT, @@ -48,6 +51,7 @@ USE_SSL, USERNAME, VERIFY_SSL, + VOLUMES, ) from tests.async_mock import MagicMock, Mock, patch @@ -62,8 +66,8 @@ def mock_controller_service(): ) as service_mock: service_mock.return_value.information.serial = SERIAL service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] - service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.storage.disks_ids = DISKS + service_mock.return_value.storage.volumes_ids = VOLUMES service_mock.return_value.network.macs = MACS yield service_mock @@ -79,8 +83,8 @@ def mock_controller_service_2sa(): ) service_mock.return_value.information.serial = SERIAL service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] - service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.storage.disks_ids = DISKS + service_mock.return_value.storage.volumes_ids = VOLUMES service_mock.return_value.network.macs = MACS yield service_mock @@ -94,7 +98,7 @@ def mock_controller_service_vdsm(): service_mock.return_value.information.serial = SERIAL service_mock.return_value.utilisation.cpu_user_load = 1 service_mock.return_value.storage.disks_ids = [] - service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.storage.volumes_ids = VOLUMES service_mock.return_value.network.macs = MACS yield service_mock @@ -255,59 +259,6 @@ async def test_user_vdsm(hass: HomeAssistantType, service_vdsm: MagicMock): assert result["data"].get(CONF_VOLUMES) is None -async def test_import(hass: HomeAssistantType, service: MagicMock): - """Test import step.""" - # import with minimum setup - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == SERIAL - assert result["title"] == HOST - assert result["data"][CONF_HOST] == HOST - assert result["data"][CONF_PORT] == DEFAULT_PORT_SSL - assert result["data"][CONF_SSL] == DEFAULT_USE_SSL - assert result["data"][CONF_VERIFY_SSL] == DEFAULT_VERIFY_SSL - assert result["data"][CONF_USERNAME] == USERNAME - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_MAC] == MACS - assert result["data"].get("device_token") is None - assert result["data"].get(CONF_DISKS) is None - assert result["data"].get(CONF_VOLUMES) is None - - service.return_value.information.serial = SERIAL_2 - # import with all - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={ - CONF_HOST: HOST_2, - CONF_PORT: PORT, - CONF_SSL: USE_SSL, - CONF_VERIFY_SSL: VERIFY_SSL, - CONF_USERNAME: USERNAME, - CONF_PASSWORD: PASSWORD, - CONF_DISKS: ["sda", "sdb", "sdc"], - CONF_VOLUMES: ["volume_1"], - }, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["result"].unique_id == SERIAL_2 - assert result["title"] == HOST_2 - assert result["data"][CONF_HOST] == HOST_2 - assert result["data"][CONF_PORT] == PORT - assert result["data"][CONF_SSL] == USE_SSL - assert result["data"][CONF_VERIFY_SSL] == VERIFY_SSL - assert result["data"][CONF_USERNAME] == USERNAME - assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_MAC] == MACS - assert result["data"].get("device_token") is None - assert result["data"][CONF_DISKS] == ["sda", "sdb", "sdc"] - assert result["data"][CONF_VOLUMES] == ["volume_1"] - - async def test_abort_if_already_setup(hass: HomeAssistantType, service: MagicMock): """Test we abort if the account is already setup.""" MockConfigEntry( @@ -316,16 +267,7 @@ async def test_abort_if_already_setup(hass: HomeAssistantType, service: MagicMoc unique_id=SERIAL, ).add_to_hass(hass) - # Should fail, same HOST:PORT (import) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - # Should fail, same HOST:PORT (flow) + # Should fail, same HOST:PORT result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, @@ -466,11 +408,12 @@ async def test_options_flow(hass: HomeAssistantType, service: MagicMock): CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, - CONF_MAC: MACS, + CONF_MAC: MACS[0], }, unique_id=SERIAL, ) config_entry.add_to_hass(hass) + hass.data[DOMAIN] = {config_entry.unique_id: {SYNO_API: service()}} assert config_entry.options == {} @@ -478,7 +421,6 @@ async def test_options_flow(hass: HomeAssistantType, service: MagicMock): assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "init" - # Scan interval # Default result = await hass.config_entries.options.async_configure( result["flow_id"], @@ -487,7 +429,9 @@ async def test_options_flow(hass: HomeAssistantType, service: MagicMock): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert config_entry.options[CONF_SCAN_INTERVAL] == DEFAULT_SCAN_INTERVAL assert config_entry.options[CONF_TIMEOUT] == DEFAULT_TIMEOUT + assert config_entry.options[CONF_FILTER_STORAGE] == DEFAULT_FILTER_STORAGE + # Scan interval # Manual result = await hass.config_entries.options.async_init(config_entry.entry_id) result = await hass.config_entries.options.async_configure( @@ -497,3 +441,16 @@ async def test_options_flow(hass: HomeAssistantType, service: MagicMock): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert config_entry.options[CONF_SCAN_INTERVAL] == 2 assert config_entry.options[CONF_TIMEOUT] == 30 + assert config_entry.options[CONF_FILTER_STORAGE] == DEFAULT_FILTER_STORAGE + + # Storage filter + # Manual + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_FILTER_STORAGE: True}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options[CONF_FILTER_STORAGE] + assert config_entry.options[CONF_DISKS] == DISKS + assert config_entry.options[CONF_VOLUMES] == VOLUMES