diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 7e557e0808e..199ffc66c4c 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -15,6 +15,7 @@ from homeassistant.const import ( RESTART_EXIT_CODE, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, + SERVICE_RELOAD, SERVICE_SAVE_PERSISTENT_STATES, SERVICE_TOGGLE, SERVICE_TURN_OFF, @@ -40,6 +41,7 @@ SERVICE_RELOAD_CONFIG_ENTRY = "reload_config_entry" SERVICE_CHECK_CONFIG = "check_config" SERVICE_UPDATE_ENTITY = "update_entity" SERVICE_SET_LOCATION = "set_location" +SERVICE_RELOAD_ALL = "reload_all" SCHEMA_UPDATE_ENTITY = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) SCHEMA_RELOAD_CONFIG_ENTRY = vol.All( vol.Schema( @@ -275,4 +277,50 @@ async def async_setup(hass: ha.HomeAssistant, config: ConfigType) -> bool: # no schema=SCHEMA_RELOAD_CONFIG_ENTRY, ) + async def async_handle_reload_all(call: ha.ServiceCall) -> None: + """Service handler for calling all integration reload services. + + Calls all reload services on all active domains, which triggers the + reload of YAML configurations for the domain that support it. + + Additionally, it also calls the `homeasssitant.reload_core_config` + service, as that reloads the core YAML configuration, and the + `frontend.reload_themes` service, as that reloads the themes. + + We only do so, if there are no configuration errors. + """ + + if errors := await conf_util.async_check_ha_config_file(hass): + _LOGGER.error( + "The system cannot reload because the configuration is not valid: %s", + errors, + ) + raise HomeAssistantError( + "Cannot quick reload all YAML configurations because the " + f"configuration is not valid: {errors}" + ) + + services = hass.services.async_services() + tasks = [ + hass.services.async_call( + domain, SERVICE_RELOAD, context=call.context, blocking=True + ) + for domain, domain_services in services.items() + if domain != "notify" and SERVICE_RELOAD in domain_services + ] + [ + hass.services.async_call( + domain, service, context=call.context, blocking=True + ) + for domain, service in { + ha.DOMAIN: SERVICE_RELOAD_CORE_CONFIG, + "frontend": "reload_themes", + }.items() + ] + + await asyncio.gather(*tasks) + + async_register_admin_service( + hass, ha.DOMAIN, SERVICE_RELOAD_ALL, async_handle_reload_all + ) + return True diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index d41fa4291ec..8b982dc1c31 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -12,6 +12,7 @@ import homeassistant.components as comps from homeassistant.components.homeassistant import ( ATTR_ENTRY_ID, SERVICE_CHECK_CONFIG, + SERVICE_RELOAD_ALL, SERVICE_RELOAD_CORE_CONFIG, SERVICE_SET_LOCATION, ) @@ -572,3 +573,62 @@ async def test_save_persistent_states(hass: HomeAssistant) -> None: blocking=True, ) assert mock_save.called + + +async def test_reload_all( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """Test reload_all service.""" + await async_setup_component(hass, "homeassistant", {}) + test1 = async_mock_service(hass, "test1", "reload") + test2 = async_mock_service(hass, "test2", "reload") + no_reload = async_mock_service(hass, "test3", "not_reload") + notify = async_mock_service(hass, "notify", "reload") + core_config = async_mock_service(hass, "homeassistant", "reload_core_config") + themes = async_mock_service(hass, "frontend", "reload_themes") + + with patch( + "homeassistant.config.async_check_ha_config_file", + return_value=None, + ) as mock_async_check_ha_config_file: + await hass.services.async_call( + "homeassistant", + SERVICE_RELOAD_ALL, + blocking=True, + ) + + assert mock_async_check_ha_config_file.called + assert len(test1) == 1 + assert len(test2) == 1 + assert len(no_reload) == 0 + assert len(notify) == 0 + assert len(core_config) == 1 + assert len(themes) == 1 + + with pytest.raises( + HomeAssistantError, + match=( + "Cannot quick reload all YAML configurations because the configuration is " + "not valid: Oh no, drama!" + ), + ), patch( + "homeassistant.config.async_check_ha_config_file", + return_value="Oh no, drama!", + ) as mock_async_check_ha_config_file: + await hass.services.async_call( + "homeassistant", + SERVICE_RELOAD_ALL, + blocking=True, + ) + + assert mock_async_check_ha_config_file.called + assert ( + "The system cannot reload because the configuration is not valid: Oh no, drama!" + in caplog.text + ) + + # None have been called again + assert len(test1) == 1 + assert len(test2) == 1 + assert len(core_config) == 1 + assert len(themes) == 1