diff --git a/homeassistant/components/google_domains.py b/homeassistant/components/google_domains.py new file mode 100644 index 00000000000..5d68b077417 --- /dev/null +++ b/homeassistant/components/google_domains.py @@ -0,0 +1,94 @@ +""" +Integrate with Google Domains. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/google_domains/ +""" +import aiohttp +import async_timeout +import asyncio +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME) + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'google_domains' + +INTERVAL = timedelta(minutes=5) + +DEFAULT_TIMEOUT = 10 + +UPDATE_URL = 'https://{}:{}@domains.google.com/nic/update' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + }) +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Initialize the Google Domains component.""" + domain = config[DOMAIN].get(CONF_DOMAIN) + user = config[DOMAIN].get(CONF_USERNAME) + password = config[DOMAIN].get(CONF_PASSWORD) + timeout = config[DOMAIN].get(CONF_TIMEOUT) + + session = hass.helpers.aiohttp_client.async_get_clientsession() + + result = yield from _update_google_domains( + hass, session, domain, user, password, timeout) + + if not result: + return False + + @asyncio.coroutine + def update_domain_interval(now): + """Update the Google Domains entry.""" + yield from _update_google_domains( + hass, session, domain, user, password, timeout) + + hass.helpers.event.async_track_time_interval( + update_domain_interval, INTERVAL) + + return True + + +@asyncio.coroutine +def _update_google_domains(hass, session, domain, user, password, timeout): + """Update Google Domains.""" + url = UPDATE_URL.format(user, password) + + params = { + 'hostname': domain + } + + try: + with async_timeout.timeout(timeout, loop=hass.loop): + resp = yield from session.get(url, params=params) + body = yield from resp.text() + + if body.startswith('good') or body.startswith('nochg'): + return True + + _LOGGER.warning('Updating Google Domains failed: %s => %s', + domain, body) + + except aiohttp.ClientError: + _LOGGER.warning("Can't connect to Google Domains API") + + except asyncio.TimeoutError: + _LOGGER.warning("Timeout from Google Domains API for domain: %s", + domain) + + return False diff --git a/tests/components/test_google_domains.py b/tests/components/test_google_domains.py new file mode 100644 index 00000000000..1884e18dc1a --- /dev/null +++ b/tests/components/test_google_domains.py @@ -0,0 +1,74 @@ +"""Test the Google Domains component.""" +import asyncio +from datetime import timedelta + +import pytest + +from homeassistant.setup import async_setup_component +from homeassistant.components import google_domains +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed + +DOMAIN = 'test.example.com' +USERNAME = 'abc123' +PASSWORD = 'xyz789' + +UPDATE_URL = google_domains.UPDATE_URL.format(USERNAME, PASSWORD) + + +@pytest.fixture +def setup_google_domains(hass, aioclient_mock): + """Fixture that sets up NamecheapDNS.""" + aioclient_mock.get(UPDATE_URL, params={ + 'hostname': DOMAIN + }, text='ok 0.0.0.0') + + hass.loop.run_until_complete(async_setup_component( + hass, google_domains.DOMAIN, { + 'google_domains': { + 'domain': DOMAIN, + 'username': USERNAME, + 'password': PASSWORD, + } + })) + + +@asyncio.coroutine +def test_setup(hass, aioclient_mock): + """Test setup works if update passes.""" + aioclient_mock.get(UPDATE_URL, params={ + 'hostname': DOMAIN + }, text='nochg 0.0.0.0') + + result = yield from async_setup_component(hass, google_domains.DOMAIN, { + 'google_domains': { + 'domain': DOMAIN, + 'username': USERNAME, + 'password': PASSWORD + } + }) + assert result + assert aioclient_mock.call_count == 1 + + async_fire_time_changed(hass, utcnow() + timedelta(minutes=5)) + yield from hass.async_block_till_done() + assert aioclient_mock.call_count == 2 + + +@asyncio.coroutine +def test_setup_fails_if_update_fails(hass, aioclient_mock): + """Test setup fails if first update fails.""" + aioclient_mock.get(UPDATE_URL, params={ + 'hostname': DOMAIN + }, text='nohost') + + result = yield from async_setup_component(hass, google_domains.DOMAIN, { + 'google_domains': { + 'domain': DOMAIN, + 'username': USERNAME, + 'password': PASSWORD + } + }) + assert not result + assert aioclient_mock.call_count == 1