Compare commits

...

52 Commits
0.93 ... 0.98

Author SHA1 Message Date
Pascal Vizeli
65b0e17b5b Merge pull request #414 from home-assistant/dev
Release 0.98
2018-03-14 22:25:14 +01:00
Pascal Vizeli
6947131b47 Update Hass.io to version 0.98 2018-03-14 22:10:13 +01:00
Pascal Vizeli
914dd53da0 Merge pull request #411 from home-assistant/fix_watchdog
Use lock on homeassistant level
2018-03-14 21:52:20 +01:00
Pascal Vizeli
58616ef686 bugfix aiohttp 2018-03-14 21:12:08 +01:00
Pascal Vizeli
563e0c1e0e fix wrong startup blocking 2018-03-14 19:08:03 +01:00
Pascal Vizeli
437070fd7a Merge pull request #412 from home-assistant/fix-geoip
Fix URL for freegeoip
2018-03-13 23:34:06 +01:00
Pascal Vizeli
baa9cf451c Fix URL for freegeoip 2018-03-13 23:28:38 +01:00
Pascal Vizeli
c2918d4519 Use lock on homeassistant level 2018-03-13 23:09:53 +01:00
Pascal Vizeli
1efdcd4691 Merge remote-tracking branch 'origin/master' into dev 2018-03-13 21:31:56 +01:00
Pascal Vizeli
2a43087ed7 Pump version to 0.98 2018-03-13 16:06:44 +01:00
Pascal Vizeli
5716324934 Merge pull request #410 from home-assistant/dev
Release 0.97
2018-03-13 16:05:31 +01:00
Pascal Vizeli
ae267e0380 Merge branch 'master' into dev 2018-03-13 14:09:13 +01:00
Pascal Vizeli
3918a2a228 Update Home-Assistant version 0.65.4 2018-03-13 14:07:21 +01:00
Pascal Vizeli
e375fc36d3 Update Hass.io to version 0.97 2018-03-13 00:09:57 +01:00
Pascal Vizeli
f5e29b4651 Update panel to last (#408) 2018-03-12 23:51:09 +01:00
Pascal Vizeli
524d875516 Update aioHttp3 (#403)
* Update aioHttp3

* fix line ending

* fix close session
2018-03-12 23:40:06 +01:00
Pascal Vizeli
60bdc00ce9 Update Home-Assistant to version 0.65.3 2018-03-12 07:13:47 +01:00
Pascal Vizeli
073166190f Update Home-Assistant to version 0.65.3 2018-03-12 07:13:27 +01:00
Pascal Vizeli
b80e4d7d70 Update Home-Assistant to version 0.65.2 2018-03-11 23:58:16 +01:00
Pascal Vizeli
cc434e27cf Update Home-Assistant to version 0.65.2 2018-03-11 23:57:57 +01:00
Pascal Vizeli
8377e04b62 Update Home-Assistant to version 0.65.1 2018-03-11 20:32:43 +01:00
Pascal Vizeli
0a47fb9c83 Update Home-Assistant to version 0.65.1 2018-03-11 20:32:25 +01:00
Pascal Vizeli
a5d3c850e9 Update Home-Assistant to version 0.65.0 2018-03-09 23:32:47 +01:00
Pascal Vizeli
d6391f62be Update Home-Assistant to version 0.65.0 2018-03-09 23:10:27 +01:00
Pascal Vizeli
c6f302e448 Update ResinOS to version 1.3 2018-03-05 22:51:44 +01:00
Pascal Vizeli
9706022c21 Update ResinOS to version 1.3 2018-03-05 22:51:08 +01:00
Pascal Vizeli
1d858f4920 Update ResinOS to version 1.2 2018-03-04 00:43:24 +01:00
Pascal Vizeli
e09ba30d46 Update ResinOS to version 1.2 2018-03-04 00:43:00 +01:00
mark9white
38ec3d14ed Allow addons that require IPC_LOCK capability (#397) 2018-03-03 23:06:42 +01:00
Pascal Vizeli
8ee9380cc7 Pump version to 0.97 2018-03-03 11:15:39 +01:00
Pascal Vizeli
6e74e4c008 Fix version conflicts 2018-03-03 11:12:59 +01:00
Pascal Vizeli
5ebc58851b Update Hass.io to version 0.96 2018-03-03 11:08:00 +01:00
Pascal Vizeli
16b09bbfc5 Allow to use branch on repositories (#395)
* Allow to use branch on repositories

* Fix argument extraction

* fix lint
2018-03-03 11:00:58 +01:00
Pascal Vizeli
d4b5fc79f4 Update Home-Assistant to version 0.64.3 2018-03-03 00:07:04 +01:00
Pascal Vizeli
e51c044ccd Update Home-Assistant to version 0.64.3 2018-03-02 23:56:48 +01:00
Pascal Vizeli
d3b1ba81f7 Update panel for encrypted backups (#394)
* Update panel for encrypted backups

* fix lint
2018-03-02 23:23:40 +01:00
Pascal Vizeli
26f55f02c0 Update Home-Assistant to version 0.64.2 2018-03-02 07:01:42 +01:00
Pascal Vizeli
8050707ff9 Update Home-Assistant to version 0.64.2 2018-03-02 06:54:32 +01:00
c727
46252030cf Improve names for built-in repos (#391) 2018-03-01 19:00:21 +01:00
Pascal Vizeli
681fa835ef Update Home-Assistant to version 0.64.1 2018-02-28 08:16:18 +01:00
Pascal Vizeli
d6560eb976 Update Home-Assistant to version 0.64.1 2018-02-28 07:48:54 +01:00
Pascal Vizeli
3770b307af Pump version to 0.96 2018-02-26 22:55:53 +01:00
Pascal Vizeli
0dacbb31be Fix version conflicts 2018-02-26 22:53:31 +01:00
Pascal Vizeli
bbdbd756a7 Update Hass.io to version 0.95 2018-02-26 22:42:29 +01:00
Pascal Vizeli
508e38e622 Fix snapshot partial API (#389) 2018-02-26 22:26:39 +01:00
Pascal Vizeli
ffe45d0d02 Bugfix if no data is given for encryption (#387)
* Bugfix if no data is given for encryption

* Update snapshot.py
2018-02-26 22:17:25 +01:00
Pascal Vizeli
9206d1acf8 Update Home-Assistant to version 0.64 2018-02-26 06:10:40 +01:00
Pascal Vizeli
da867ef8ef Update Home-Assistant to version 0.64 2018-02-26 06:03:24 +01:00
Pascal Vizeli
4826201e51 Pump version to 0.95 2018-02-25 12:57:53 +01:00
Pascal Vizeli
463c97f9e7 Update Hass.io to version 0.94 2018-02-25 12:49:39 +01:00
Pascal Vizeli
3983928c6c Bugfix snapshot dialog (#380) 2018-02-25 12:18:05 +01:00
Pascal Vizeli
15e626027f Pump version to 0.94 2018-02-24 08:50:21 +01:00
23 changed files with 178 additions and 121 deletions

View File

@@ -1,12 +1,12 @@
{
"local": {
"name": "Local Add-Ons",
"name": "Local add-ons",
"url": "https://home-assistant.io/hassio",
"maintainer": "you"
},
"core": {
"name": "Built-in Add-Ons",
"name": "Official add-ons",
"url": "https://home-assistant.io/addons",
"maintainer": "Home Assistant authors"
"maintainer": "Home Assistant"
}
}

View File

@@ -8,8 +8,9 @@ import shutil
import git
from .utils import get_hash_from_repository
from ..const import URL_HASSIO_ADDONS
from ..const import URL_HASSIO_ADDONS, ATTR_URL, ATTR_BRANCH
from ..coresys import CoreSysAttributes
from ..validate import RE_REPOSITORY
_LOGGER = logging.getLogger(__name__)
@@ -22,9 +23,20 @@ class GitRepo(CoreSysAttributes):
self.coresys = coresys
self.repo = None
self.path = path
self.url = url
self.lock = asyncio.Lock(loop=coresys.loop)
self._data = RE_REPOSITORY.match(url).groupdict()
@property
def url(self):
"""Return repository URL."""
return self._data[ATTR_URL]
@property
def branch(self):
"""Return repository branch."""
return self._data[ATTR_BRANCH]
async def load(self):
"""Init git addon repo."""
if not self.path.is_dir():
@@ -46,12 +58,20 @@ class GitRepo(CoreSysAttributes):
async def clone(self):
"""Clone git addon repo."""
async with self.lock:
git_args = {
attribute: value
for attribute, value in (
('recursive', True),
('branch', self.branch)
) if value is not None
}
try:
_LOGGER.info("Clone addon %s repository", self.url)
self.repo = await self._loop.run_in_executor(
None, ft.partial(
git.Repo.clone_from, self.url, str(self.path),
recursive=True))
self.repo = await self._loop.run_in_executor(None, ft.partial(
git.Repo.clone_from, self.url, str(self.path),
**git_args
))
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
git.GitCommandError) as err:

View File

@@ -60,6 +60,7 @@ PRIVILEGED_ALL = [
"NET_ADMIN",
"SYS_ADMIN",
"SYS_RAWIO",
"IPC_LOCK",
"SYS_TIME",
"SYS_NICE"
]

View File

@@ -13,7 +13,7 @@ from .proxy import APIProxy
from .supervisor import APISupervisor
from .snapshots import APISnapshots
from .services import APIServices
from .security import security_layer
from .security import SecurityMiddleware
from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__)
@@ -25,16 +25,14 @@ class RestAPI(CoreSysAttributes):
def __init__(self, coresys):
"""Initialize docker base wrapper."""
self.coresys = coresys
self.security = SecurityMiddleware(coresys)
self.webapp = web.Application(
middlewares=[security_layer], loop=self._loop)
middlewares=[self.security.token_validation], loop=self._loop)
# service stuff
self._handler = None
self.server = None
# middleware
self.webapp['coresys'] = coresys
async def load(self):
"""Register REST API Calls."""
self._register_supervisor()

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -83,7 +83,7 @@ class APIProxy(CoreSysAttributes):
if not data:
await response.write_eof()
break
response.write(data)
await response.write(data)
except aiohttp.ClientError:
await response.write_eof()

View File

@@ -6,6 +6,7 @@ from aiohttp.web import middleware
from aiohttp.web_exceptions import HTTPUnauthorized
from ..const import HEADER_TOKEN, REQUEST_FROM
from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__)
@@ -16,35 +17,41 @@ NO_SECURITY_CHECK = set((
))
@middleware
async def security_layer(request, handler):
"""Check security access of this layer."""
coresys = request.app['coresys']
hassio_token = request.headers.get(HEADER_TOKEN)
class SecurityMiddleware(CoreSysAttributes):
"""Security middleware functions."""
# Ignore security check
for rule in NO_SECURITY_CHECK:
if rule.match(request.path):
_LOGGER.debug("Passthrough %s", request.path)
def __init__(self, coresys):
"""Initialize security middleware."""
self.coresys = coresys
@middleware
async def token_validation(self, request, handler):
"""Check security access of this layer."""
hassio_token = request.headers.get(HEADER_TOKEN)
# Ignore security check
for rule in NO_SECURITY_CHECK:
if rule.match(request.path):
_LOGGER.debug("Passthrough %s", request.path)
return await handler(request)
# Need to be removed later
if not hassio_token:
_LOGGER.warning("Invalid token for access %s", request.path)
request[REQUEST_FROM] = 'UNKNOWN'
return await handler(request)
# Need to be removed later
if not hassio_token:
_LOGGER.warning("Invalid token for access %s", request.path)
request[REQUEST_FROM] = 'UNKNOWN'
return await handler(request)
# Home-Assistant
if hassio_token == self._homeassistant.uuid:
_LOGGER.debug("%s access from Home-Assistant", request.path)
request[REQUEST_FROM] = 'homeassistant'
return await handler(request)
# Home-Assistant
if hassio_token == coresys.homeassistant.uuid:
_LOGGER.debug("%s access from Home-Assistant", request.path)
request[REQUEST_FROM] = 'homeassistant'
return await handler(request)
# Add-on
addon = self._addons.from_uuid(hassio_token)
if addon:
_LOGGER.info("%s access from %s", request.path, addon.slug)
request[REQUEST_FROM] = addon.slug
return await handler(request)
# Add-on
addon = coresys.addons.from_uuid(hassio_token)
if addon:
_LOGGER.info("%s access from %s", request.path, addon.slug)
request[REQUEST_FROM] = addon.slug
return await handler(request)
raise HTTPUnauthorized()
raise HTTPUnauthorized()

View File

@@ -2,7 +2,7 @@
from pathlib import Path
from ipaddress import ip_network
HASSIO_VERSION = '0.93'
HASSIO_VERSION = '0.98'
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
'hassio/{}/version.json')
@@ -158,6 +158,7 @@ ATTR_SERVICES = 'services'
ATTR_DISCOVERY = 'discovery'
ATTR_PROTECTED = 'protected'
ATTR_CRYPTO = 'crypto'
ATTR_BRANCH = 'branch'
SERVICE_MQTT = 'mqtt'

View File

@@ -108,10 +108,10 @@ class HassIO(CoreSysAttributes):
# don't process scheduler anymore
self._scheduler.suspend = True
# process stop tasks
self._websession.close()
self._websession_ssl.close()
# process async stop tasks
await asyncio.wait(
[self._api.stop(), self._dns.stop()], loop=self._loop)
await asyncio.wait([
self._api.stop(),
self._dns.stop(),
self._websession.close(),
self._websession_ssl.close()
], loop=self._loop)

View File

@@ -6,11 +6,11 @@ import docker
import requests
from .interface import DockerInterface
from .utils import docker_process
from ..addons.build import AddonBuild
from ..const import (
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
ENV_TIME)
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__)
@@ -285,7 +285,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Build %s:%s done", self.image, tag)
return True
@docker_process
@process_lock
def export_image(self, path):
"""Export current images into a tar file."""
return self._loop.run_in_executor(None, self._export_image, path)
@@ -313,7 +313,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Export image %s done", self.image)
return True
@docker_process
@process_lock
def import_image(self, path, tag):
"""Import a tar file as image."""
return self._loop.run_in_executor(None, self._import_image, path, tag)
@@ -338,7 +338,7 @@ class DockerAddon(DockerInterface):
self._cleanup()
return True
@docker_process
@process_lock
def write_stdin(self, data):
"""Write to add-on stdin."""
return self._loop.run_in_executor(None, self._write_stdin, data)

View File

@@ -5,10 +5,10 @@ import logging
import docker
from .utils import docker_process
from .stats import DockerStats
from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes):
"""Initialize docker base wrapper."""
self.coresys = coresys
self._meta = None
self.lock = asyncio.Lock(loop=self._loop)
self.lock = asyncio.Lock(loop=coresys.loop)
@property
def timeout(self):
@@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes):
"""Return True if a task is in progress."""
return self.lock.locked()
@docker_process
@process_lock
def install(self, tag):
"""Pull docker image."""
return self._loop.run_in_executor(None, self._install, tag)
@@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes):
return True
@docker_process
@process_lock
def attach(self):
"""Attach to running docker container."""
return self._loop.run_in_executor(None, self._attach)
@@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes):
return True
@docker_process
@process_lock
def run(self):
"""Run docker image."""
return self._loop.run_in_executor(None, self._run)
@@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes):
"""
raise NotImplementedError()
@docker_process
@process_lock
def stop(self):
"""Stop/remove docker container."""
return self._loop.run_in_executor(None, self._stop)
@@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes):
return True
@docker_process
@process_lock
def remove(self):
"""Remove docker images."""
return self._loop.run_in_executor(None, self._remove)
@@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes):
self._meta = None
return True
@docker_process
@process_lock
def update(self, tag):
"""Update a docker image."""
return self._loop.run_in_executor(None, self._update, tag)
@@ -264,7 +264,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
@docker_process
@process_lock
def restart(self):
"""Restart docker container."""
return self._loop.run_in_executor(None, self._restart)
@@ -289,7 +289,7 @@ class DockerInterface(CoreSysAttributes):
return True
@docker_process
@process_lock
def cleanup(self):
"""Check if old version exists and cleanup."""
return self._loop.run_in_executor(None, self._cleanup)
@@ -315,7 +315,7 @@ class DockerInterface(CoreSysAttributes):
return True
@docker_process
@process_lock
def execute_command(self, command):
"""Create a temporary container and run command."""
return self._loop.run_in_executor(None, self._execute_command, command)

View File

@@ -1,20 +0,0 @@
"""HassIO docker utilitys."""
import logging
_LOGGER = logging.getLogger(__name__)
# pylint: disable=protected-access
def docker_process(method):
"""Wrap function with only run once."""
async def wrap_api(api, *args, **kwargs):
"""Return api wrapper."""
if api.lock.locked():
_LOGGER.error(
"Can't excute %s while a task is in progress", method.__name__)
return False
async with api.lock:
return await method(api, *args, **kwargs)
return wrap_api

View File

@@ -16,7 +16,7 @@ from .const import (
ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
from .coresys import CoreSysAttributes
from .docker.homeassistant import DockerHomeAssistant
from .utils import convert_to_ascii
from .utils import convert_to_ascii, process_lock
from .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG
@@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
self.coresys = coresys
self.instance = DockerHomeAssistant(coresys)
self.lock = asyncio.Lock(loop=coresys.loop)
async def load(self):
"""Prepare HomeAssistant object."""
@@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Return a UUID of this HomeAssistant."""
return self._data[ATTR_UUID]
@process_lock
async def install_landingpage(self):
"""Install a landingpage."""
_LOGGER.info("Setup HomeAssistant landingpage")
@@ -172,8 +174,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await asyncio.sleep(60, loop=self._loop)
# Run landingpage after installation
await self.start()
await self._start()
@process_lock
async def install(self):
"""Install a landingpage."""
_LOGGER.info("Setup HomeAssistant")
@@ -191,9 +194,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# finishing
_LOGGER.info("HomeAssistant docker now installed")
if self.boot:
await self.start()
await self._start()
await self.instance.cleanup()
@process_lock
async def update(self, version=None):
"""Update HomeAssistant version."""
version = version or self.last_version
@@ -208,8 +212,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return await self.instance.update(version)
finally:
if running:
await self.start()
await self._start()
async def _start(self):
"""Start HomeAssistant docker & wait."""
if await self.instance.run():
await self._block_till_run()
@process_lock
async def start(self):
"""Run HomeAssistant docker."""
if not await self.instance.run():
@@ -224,6 +234,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""
return self.instance.stop()
@process_lock
async def restart(self):
"""Restart HomeAssistant docker."""
if not await self.instance.restart():
@@ -262,7 +273,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property
def in_progress(self):
"""Return True if a task is in progress."""
return self.instance.in_progress
return self.instance.in_progress or self.lock.locked()
async def check_config(self):
"""Run homeassistant config check."""

View File

@@ -173,14 +173,17 @@ class SnapshotManager(CoreSysAttributes):
if addon and addon.is_installed:
addon_list.append(addon)
continue
_LOGGER.warning("Add-on %s not found", addon_slug)
_LOGGER.warning(
"Add-on %s not found/installed", addon_slug)
_LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
await snapshot.store_addons(addon_list)
if addon_list:
_LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
await snapshot.store_addons(addon_list)
# snapshot folders
_LOGGER.info("Snapshot %s store folders", snapshot.slug)
await snapshot.store_folders(folders)
# Snapshot folders
if folders:
_LOGGER.info("Snapshot %s store folders", snapshot.slug)
await snapshot.store_folders(folders)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Snapshot %s error", snapshot.slug)

View File

@@ -151,7 +151,7 @@ class Snapshot(CoreSysAttributes):
def _encrypt_data(self, data):
"""Make data secure."""
if not self._key:
if not self._key or data is None:
return data
return b64encode(
@@ -159,7 +159,7 @@ class Snapshot(CoreSysAttributes):
def _decrypt_data(self, data):
"""Make data readable."""
if not self._key:
if not self._key or data is None:
return data
return Padding.unpad(

View File

@@ -1,7 +1,9 @@
"""Tools file for HassIO."""
from datetime import datetime
import logging
import re
_LOGGER = logging.getLogger(__name__)
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
@@ -10,6 +12,21 @@ def convert_to_ascii(raw):
return RE_STRING.sub("", raw.decode())
def process_lock(method):
"""Wrap function with only run once."""
async def wrap_api(api, *args, **kwargs):
"""Return api wrapper."""
if api.lock.locked():
_LOGGER.error(
"Can't excute %s while a task is in progress", method.__name__)
return False
async with api.lock:
return await method(api, *args, **kwargs)
return wrap_api
class AsyncThrottle(object):
"""
Decorator that prevents a function from being called more than once every

View File

@@ -12,7 +12,7 @@ UTC = pytz.utc
_LOGGER = logging.getLogger(__name__)
FREEGEOIP_URL = "https://freegeoip.io/json/"
FREEGEOIP_URL = "https://freegeoip.net/json/"
# Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.

View File

@@ -1,5 +1,6 @@
"""Validate functions."""
import uuid
import re
import voluptuous as vol
import pytz
@@ -11,13 +12,29 @@ from .const import (
ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID)
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
def validate_repository(repository):
"""Validate a valide repository."""
data = RE_REPOSITORY.match(repository)
if not data:
raise vol.Invalid("No valid repository format!")
# Validate URL
# pylint: disable=no-value-for-parameter
vol.Url()(data.group('url'))
return repository
# pylint: disable=no-value-for-parameter
REPOSITORIES = vol.All([vol.Url()], vol.Unique())
REPOSITORIES = vol.All([validate_repository], vol.Unique())
def validate_timezone(timezone):

View File

@@ -41,8 +41,8 @@ setup(
include_package_data=True,
install_requires=[
'async_timeout==2.0.0',
'aiohttp==2.3.10',
'docker==3.1.0',
'aiohttp==3.0.9',
'docker==3.1.1',
'colorlog==3.1.2',
'voluptuous==0.11.1',
'gitpython==2.1.8',

View File

@@ -1,7 +1,7 @@
{
"hassio": "0.93",
"homeassistant": "0.63.3",
"resinos": "1.1",
"hassio": "0.98",
"homeassistant": "0.65.4",
"resinos": "1.3",
"resinhup": "0.3",
"generic": "0.3",
"cluster": "0.1"