diff --git a/homeassistant/components/namecheapdns.py b/homeassistant/components/namecheapdns.py new file mode 100644 index 00000000000..8397fcd0033 --- /dev/null +++ b/homeassistant/components/namecheapdns.py @@ -0,0 +1,76 @@ +"""Integrate with NamecheapDNS.""" +import asyncio +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_HOST, CONF_ACCESS_TOKEN, CONF_DOMAIN +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +DOMAIN = 'namecheapdns' +UPDATE_URL = 'https://dynamicdns.park-your-domain.com/update' +INTERVAL = timedelta(minutes=5) +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_DOMAIN): cv.string, + vol.Required(CONF_ACCESS_TOKEN): cv.string, + }) +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Initialize the NamecheapDNS component.""" + host = config[DOMAIN][CONF_HOST] + domain = config[DOMAIN][CONF_DOMAIN] + token = config[DOMAIN][CONF_ACCESS_TOKEN] + session = async_get_clientsession(hass) + + result = yield from _update_namecheapdns(session, host, domain, token) + + if not result: + return False + + @asyncio.coroutine + def update_domain_interval(now): + """Update the NamecheapDNS entry.""" + yield from _update_namecheapdns(session, host, domain, token) + + @asyncio.coroutine + def update_domain_service(call): + """Update the NamecheapDNS entry.""" + yield from _update_namecheapdns(session, host, domain, token) + + async_track_time_interval(hass, update_domain_interval, INTERVAL) + hass.services.async_register(DOMAIN, 'update dns', update_domain_service) + + return result + + +@asyncio.coroutine +def _update_namecheapdns(session, host, domain, token): + """Update NamecheapDNS.""" + import xml.etree.ElementTree as ET + + params = { + 'host': host, + 'domain': domain, + 'password': token, + } + + resp = yield from session.get(UPDATE_URL, params=params) + xml_string = yield from resp.text() + root = ET.fromstring(xml_string) + err_count = root.find('ErrCount').text + + if int(err_count) != 0: + _LOGGER.warning('Updating Namecheap domain %s failed', domain) + return False + + return True diff --git a/tests/components/test_namecheapdns.py b/tests/components/test_namecheapdns.py new file mode 100644 index 00000000000..b225c0af7c8 --- /dev/null +++ b/tests/components/test_namecheapdns.py @@ -0,0 +1,78 @@ +"""Test the NamecheapDNS component.""" +import asyncio +from datetime import timedelta + +import pytest + +from homeassistant.setup import async_setup_component +from homeassistant.components import namecheapdns +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed + +HOST = 'test' +DOMAIN = 'bla' +TOKEN = 'abcdefgh' + + +@pytest.fixture +def setup_namecheapdns(hass, aioclient_mock): + """Fixture that sets up NamecheapDNS.""" + aioclient_mock.get(namecheapdns.UPDATE_URL, params={ + 'host': HOST, + 'domain': DOMAIN, + 'password': TOKEN + }, text='0') + + hass.loop.run_until_complete(async_setup_component( + hass, namecheapdns.DOMAIN, { + 'namecheapdns': { + 'host': HOST, + 'domain': DOMAIN, + 'access_token': TOKEN + } + })) + + +@asyncio.coroutine +def test_setup(hass, aioclient_mock): + """Test setup works if update passes.""" + aioclient_mock.get(namecheapdns.UPDATE_URL, params={ + 'host': HOST, + 'domain': DOMAIN, + 'password': TOKEN + }, text='0') + + result = yield from async_setup_component(hass, namecheapdns.DOMAIN, { + 'namecheapdns': { + 'host': HOST, + 'domain': DOMAIN, + 'access_token': TOKEN + } + }) + 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(namecheapdns.UPDATE_URL, params={ + 'host': HOST, + 'domain': DOMAIN, + 'password': TOKEN + }, text='1') + + result = yield from async_setup_component(hass, namecheapdns.DOMAIN, { + 'namecheapdns': { + 'host': HOST, + 'domain': DOMAIN, + 'access_token': TOKEN + } + }) + assert not result + assert aioclient_mock.call_count == 1