diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 0e9d554a579..2bb7af23662 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -156,10 +156,18 @@ def async_setup(hass, config): return try: - yield from conf_util.async_check_ha_config_file(hass) + errors = yield from conf_util.async_check_ha_config_file(hass) except HomeAssistantError: return + if errors: + notif = get_component('persistent_notification') + _LOGGER.error(errors) + notif.async_create( + hass, "Config error. See dev-info panel for details.", + "Config validating", "{0}.check_config".format(ha.DOMAIN)) + return + if call.service == SERVICE_HOMEASSISTANT_RESTART: hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE)) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index dc8946776f5..bf05ba9d99f 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -6,6 +6,7 @@ from homeassistant.components.frontend import register_built_in_panel DOMAIN = 'config' DEPENDENCIES = ['http'] +SECTIONS = ('core', 'hassbian') @asyncio.coroutine @@ -13,7 +14,7 @@ def async_setup(hass, config): """Setup the hassbian component.""" register_built_in_panel(hass, 'config', 'Configuration', 'mdi:settings') - for panel_name in ('hassbian',): + for panel_name in SECTIONS: panel = yield from async_prepare_setup_platform(hass, config, DOMAIN, panel_name) diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py new file mode 100644 index 00000000000..15456ee74ad --- /dev/null +++ b/homeassistant/components/config/core.py @@ -0,0 +1,31 @@ +"""Component to interact with Hassbian tools.""" +import asyncio + +from homeassistant.components.http import HomeAssistantView +from homeassistant.config import async_check_ha_config_file + + +@asyncio.coroutine +def async_setup(hass): + """Setup the hassbian config.""" + hass.http.register_view(CheckConfigView) + return True + + +class CheckConfigView(HomeAssistantView): + """Hassbian packages endpoint.""" + + url = '/api/config/core/check_config' + name = 'api:config:core:check_config' + + @asyncio.coroutine + def post(self, request): + """Validate config and return results.""" + errors = yield from async_check_ha_config_file(request.app['hass']) + + state = 'invalid' if errors else 'valid' + + return self.json({ + "result": state, + "errors": errors, + }) diff --git a/homeassistant/config.py b/homeassistant/config.py index 101c1a1dc87..6c89c84b37b 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -53,6 +53,9 @@ introduction: # Enables the frontend frontend: +# Enables configuration UI +config: + http: # Uncomment this to add a password (recommended!) # api_password: PASSWORD @@ -463,22 +466,15 @@ def async_check_ha_config_file(hass): This method is a coroutine. """ - import homeassistant.components.persistent_notification as pn - proc = yield from asyncio.create_subprocess_exec( - sys.argv[0], - '--script', - 'check_config', - stdout=asyncio.subprocess.PIPE) + sys.executable, '-m', 'homeassistant', '--script', + 'check_config', '--config', hass.config.config_dir, + stdout=asyncio.subprocess.PIPE, loop=hass.loop) # Wait for the subprocess exit - (stdout_data, dummy) = yield from proc.communicate() + stdout_data, dummy = yield from proc.communicate() result = yield from proc.wait() - if result: - content = re.sub(r'\033\[[^m]*m', '', str(stdout_data, 'utf-8')) - # Put error cleaned from color codes in the error log so it - # will be visible at the UI. - _LOGGER.error(content) - pn.async_create( - hass, "Config error. See dev-info panel for details.", - "Config validating", "{0}.check_config".format(CONF_CORE)) - raise HomeAssistantError("Invalid config") + + if not result: + return None + + return re.sub(r'\033\[[^m]*m', '', str(stdout_data, 'utf-8')) diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py new file mode 100644 index 00000000000..6492f2fabe6 --- /dev/null +++ b/tests/components/config/test_core.py @@ -0,0 +1,39 @@ +"""Test hassbian config.""" +import asyncio +from unittest.mock import patch + +from homeassistant.bootstrap import async_setup_component +from homeassistant.components import config +from homeassistant.components.config.core import CheckConfigView +from tests.common import mock_http_component_app, mock_coro + + +@asyncio.coroutine +def test_validate_config_ok(hass, test_client): + """Test getting suites.""" + app = mock_http_component_app(hass) + with patch.object(config, 'SECTIONS', ['core']): + yield from async_setup_component(hass, 'config', {}) + + hass.http.views[CheckConfigView.name].register(app.router) + client = yield from test_client(app) + + with patch( + 'homeassistant.components.config.core.async_check_ha_config_file', + return_value=mock_coro(None)()): + resp = yield from client.post('/api/config/core/check_config') + + assert resp.status == 200 + result = yield from resp.json() + assert result['result'] == 'valid' + assert result['errors'] is None + + with patch( + 'homeassistant.components.config.core.async_check_ha_config_file', + return_value=mock_coro('beer')()): + resp = yield from client.post('/api/config/core/check_config') + + assert resp.status == 200 + result = yield from resp.json() + assert result['result'] == 'invalid' + assert result['errors'] == 'beer' diff --git a/tests/components/config/test_hassbian.py b/tests/components/config/test_hassbian.py index 5ed48fa7794..811a8add3e2 100644 --- a/tests/components/config/test_hassbian.py +++ b/tests/components/config/test_hassbian.py @@ -4,6 +4,7 @@ import os from unittest.mock import patch from homeassistant.bootstrap import async_setup_component +from homeassistant.components import config from homeassistant.components.config.hassbian import ( HassbianSuitesView, HassbianSuiteInstallView) from tests.common import ( @@ -13,7 +14,8 @@ from tests.common import ( def test_setup_check_env_prevents_load(hass, loop): """Test it does not set up hassbian if environment var not present.""" mock_http_component(hass) - with patch.dict(os.environ, clear=True): + with patch.dict(os.environ, clear=True), \ + patch.object(config, 'SECTIONS', ['hassbian']): loop.run_until_complete(async_setup_component(hass, 'config', {})) assert 'config' in hass.config.components assert HassbianSuitesView.name not in hass.http.views @@ -23,7 +25,8 @@ def test_setup_check_env_prevents_load(hass, loop): def test_setup_check_env_works(hass, loop): """Test it sets up hassbian if environment var present.""" mock_http_component(hass) - with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}), \ + patch.object(config, 'SECTIONS', ['hassbian']): loop.run_until_complete(async_setup_component(hass, 'config', {})) assert 'config' in hass.config.components assert HassbianSuitesView.name in hass.http.views @@ -35,7 +38,8 @@ def test_get_suites(hass, test_client): """Test getting suites.""" app = mock_http_component_app(hass) - with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}), \ + patch.object(config, 'SECTIONS', ['hassbian']): yield from async_setup_component(hass, 'config', {}) hass.http.views[HassbianSuitesView.name].register(app.router) @@ -56,7 +60,8 @@ def test_install_suite(hass, test_client): """Test getting suites.""" app = mock_http_component_app(hass) - with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}): + with patch.dict(os.environ, {'FORCE_HASSBIAN': '1'}), \ + patch.object(config, 'SECTIONS', ['hassbian']): yield from async_setup_component(hass, 'config', {}) hass.http.views[HassbianSuiteInstallView.name].register(app.router) diff --git a/tests/test_config.py b/tests/test_config.py index 2ad25dece11..a796cc615ef 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -396,16 +396,15 @@ class TestConfig(unittest.TestCase): process_mock = mock.MagicMock() attrs = { 'communicate.return_value': - mock_generator((r'\033[hellom'.encode('utf-8'), 'error')), + mock_generator(('\033[34mhello'.encode('utf-8'), 'error')), 'wait.return_value': mock_generator(1)} process_mock.configure_mock(**attrs) mock_create.return_value = mock_generator(process_mock) - with self.assertRaises(HomeAssistantError): - run_coroutine_threadsafe( - config_util.async_check_ha_config_file(self.hass), - self.hass.loop - ).result() + assert run_coroutine_threadsafe( + config_util.async_check_ha_config_file(self.hass), + self.hass.loop + ).result() == 'hello' # pylint: disable=redefined-outer-name