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": { "local": {
"name": "Local Add-Ons", "name": "Local add-ons",
"url": "https://home-assistant.io/hassio", "url": "https://home-assistant.io/hassio",
"maintainer": "you" "maintainer": "you"
}, },
"core": { "core": {
"name": "Built-in Add-Ons", "name": "Official add-ons",
"url": "https://home-assistant.io/addons", "url": "https://home-assistant.io/addons",
"maintainer": "Home Assistant authors" "maintainer": "Home Assistant"
} }
} }

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ from .proxy import APIProxy
from .supervisor import APISupervisor from .supervisor import APISupervisor
from .snapshots import APISnapshots from .snapshots import APISnapshots
from .services import APIServices from .services import APIServices
from .security import security_layer from .security import SecurityMiddleware
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -25,16 +25,14 @@ class RestAPI(CoreSysAttributes):
def __init__(self, coresys): def __init__(self, coresys):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.coresys = coresys self.coresys = coresys
self.security = SecurityMiddleware(coresys)
self.webapp = web.Application( self.webapp = web.Application(
middlewares=[security_layer], loop=self._loop) middlewares=[self.security.token_validation], loop=self._loop)
# service stuff # service stuff
self._handler = None self._handler = None
self.server = None self.server = None
# middleware
self.webapp['coresys'] = coresys
async def load(self): async def load(self):
"""Register REST API Calls.""" """Register REST API Calls."""
self._register_supervisor() 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: if not data:
await response.write_eof() await response.write_eof()
break break
response.write(data) await response.write(data)
except aiohttp.ClientError: except aiohttp.ClientError:
await response.write_eof() await response.write_eof()

View File

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

View File

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

View File

@@ -108,10 +108,10 @@ class HassIO(CoreSysAttributes):
# don't process scheduler anymore # don't process scheduler anymore
self._scheduler.suspend = True self._scheduler.suspend = True
# process stop tasks
self._websession.close()
self._websession_ssl.close()
# process async stop tasks # process async stop tasks
await asyncio.wait( await asyncio.wait([
[self._api.stop(), self._dns.stop()], loop=self._loop) 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 import requests
from .interface import DockerInterface from .interface import DockerInterface
from .utils import docker_process
from ..addons.build import AddonBuild from ..addons.build import AddonBuild
from ..const import ( from ..const import (
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
ENV_TIME) ENV_TIME)
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -285,7 +285,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Build %s:%s done", self.image, tag) _LOGGER.info("Build %s:%s done", self.image, tag)
return True return True
@docker_process @process_lock
def export_image(self, path): def export_image(self, path):
"""Export current images into a tar file.""" """Export current images into a tar file."""
return self._loop.run_in_executor(None, self._export_image, path) 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) _LOGGER.info("Export image %s done", self.image)
return True return True
@docker_process @process_lock
def import_image(self, path, tag): def import_image(self, path, tag):
"""Import a tar file as image.""" """Import a tar file as image."""
return self._loop.run_in_executor(None, self._import_image, path, tag) return self._loop.run_in_executor(None, self._import_image, path, tag)
@@ -338,7 +338,7 @@ class DockerAddon(DockerInterface):
self._cleanup() self._cleanup()
return True return True
@docker_process @process_lock
def write_stdin(self, data): def write_stdin(self, data):
"""Write to add-on stdin.""" """Write to add-on stdin."""
return self._loop.run_in_executor(None, self._write_stdin, data) return self._loop.run_in_executor(None, self._write_stdin, data)

View File

@@ -5,10 +5,10 @@ import logging
import docker import docker
from .utils import docker_process
from .stats import DockerStats from .stats import DockerStats
from ..const import LABEL_VERSION, LABEL_ARCH from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.coresys = coresys self.coresys = coresys
self._meta = None self._meta = None
self.lock = asyncio.Lock(loop=self._loop) self.lock = asyncio.Lock(loop=coresys.loop)
@property @property
def timeout(self): def timeout(self):
@@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes):
"""Return True if a task is in progress.""" """Return True if a task is in progress."""
return self.lock.locked() return self.lock.locked()
@docker_process @process_lock
def install(self, tag): def install(self, tag):
"""Pull docker image.""" """Pull docker image."""
return self._loop.run_in_executor(None, self._install, tag) return self._loop.run_in_executor(None, self._install, tag)
@@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def attach(self): def attach(self):
"""Attach to running docker container.""" """Attach to running docker container."""
return self._loop.run_in_executor(None, self._attach) return self._loop.run_in_executor(None, self._attach)
@@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def run(self): def run(self):
"""Run docker image.""" """Run docker image."""
return self._loop.run_in_executor(None, self._run) return self._loop.run_in_executor(None, self._run)
@@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes):
""" """
raise NotImplementedError() raise NotImplementedError()
@docker_process @process_lock
def stop(self): def stop(self):
"""Stop/remove docker container.""" """Stop/remove docker container."""
return self._loop.run_in_executor(None, self._stop) return self._loop.run_in_executor(None, self._stop)
@@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def remove(self): def remove(self):
"""Remove docker images.""" """Remove docker images."""
return self._loop.run_in_executor(None, self._remove) return self._loop.run_in_executor(None, self._remove)
@@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes):
self._meta = None self._meta = None
return True return True
@docker_process @process_lock
def update(self, tag): def update(self, tag):
"""Update a docker image.""" """Update a docker image."""
return self._loop.run_in_executor(None, self._update, tag) return self._loop.run_in_executor(None, self._update, tag)
@@ -264,7 +264,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err) _LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
@docker_process @process_lock
def restart(self): def restart(self):
"""Restart docker container.""" """Restart docker container."""
return self._loop.run_in_executor(None, self._restart) return self._loop.run_in_executor(None, self._restart)
@@ -289,7 +289,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def cleanup(self): def cleanup(self):
"""Check if old version exists and cleanup.""" """Check if old version exists and cleanup."""
return self._loop.run_in_executor(None, self._cleanup) return self._loop.run_in_executor(None, self._cleanup)
@@ -315,7 +315,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def execute_command(self, command): def execute_command(self, command):
"""Create a temporary container and run command.""" """Create a temporary container and run command."""
return self._loop.run_in_executor(None, self._execute_command, 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) ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .docker.homeassistant import DockerHomeAssistant 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 .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG from .validate import SCHEMA_HASS_CONFIG
@@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
self.coresys = coresys self.coresys = coresys
self.instance = DockerHomeAssistant(coresys) self.instance = DockerHomeAssistant(coresys)
self.lock = asyncio.Lock(loop=coresys.loop)
async def load(self): async def load(self):
"""Prepare HomeAssistant object.""" """Prepare HomeAssistant object."""
@@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Return a UUID of this HomeAssistant.""" """Return a UUID of this HomeAssistant."""
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@process_lock
async def install_landingpage(self): async def install_landingpage(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant landingpage") _LOGGER.info("Setup HomeAssistant landingpage")
@@ -172,8 +174,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await asyncio.sleep(60, loop=self._loop) await asyncio.sleep(60, loop=self._loop)
# Run landingpage after installation # Run landingpage after installation
await self.start() await self._start()
@process_lock
async def install(self): async def install(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant") _LOGGER.info("Setup HomeAssistant")
@@ -191,9 +194,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# finishing # finishing
_LOGGER.info("HomeAssistant docker now installed") _LOGGER.info("HomeAssistant docker now installed")
if self.boot: if self.boot:
await self.start() await self._start()
await self.instance.cleanup() await self.instance.cleanup()
@process_lock
async def update(self, version=None): async def update(self, version=None):
"""Update HomeAssistant version.""" """Update HomeAssistant version."""
version = version or self.last_version version = version or self.last_version
@@ -208,8 +212,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return await self.instance.update(version) return await self.instance.update(version)
finally: finally:
if running: 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): async def start(self):
"""Run HomeAssistant docker.""" """Run HomeAssistant docker."""
if not await self.instance.run(): if not await self.instance.run():
@@ -224,6 +234,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
""" """
return self.instance.stop() return self.instance.stop()
@process_lock
async def restart(self): async def restart(self):
"""Restart HomeAssistant docker.""" """Restart HomeAssistant docker."""
if not await self.instance.restart(): if not await self.instance.restart():
@@ -262,7 +273,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def in_progress(self): def in_progress(self):
"""Return True if a task is in progress.""" """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): async def check_config(self):
"""Run homeassistant config check.""" """Run homeassistant config check."""

View File

@@ -173,14 +173,17 @@ class SnapshotManager(CoreSysAttributes):
if addon and addon.is_installed: if addon and addon.is_installed:
addon_list.append(addon) addon_list.append(addon)
continue 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) if addon_list:
await snapshot.store_addons(addon_list) _LOGGER.info("Snapshot %s store Add-ons", snapshot.slug)
await snapshot.store_addons(addon_list)
# snapshot folders # Snapshot folders
_LOGGER.info("Snapshot %s store folders", snapshot.slug) if folders:
await snapshot.store_folders(folders) _LOGGER.info("Snapshot %s store folders", snapshot.slug)
await snapshot.store_folders(folders)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Snapshot %s error", snapshot.slug) _LOGGER.exception("Snapshot %s error", snapshot.slug)

View File

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

View File

@@ -1,7 +1,9 @@
"""Tools file for HassIO.""" """Tools file for HassIO."""
from datetime import datetime from datetime import datetime
import logging
import re import re
_LOGGER = logging.getLogger(__name__)
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))") RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
@@ -10,6 +12,21 @@ def convert_to_ascii(raw):
return RE_STRING.sub("", raw.decode()) 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): class AsyncThrottle(object):
""" """
Decorator that prevents a function from being called more than once every 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__) _LOGGER = logging.getLogger(__name__)
FREEGEOIP_URL = "https://freegeoip.io/json/" FREEGEOIP_URL = "https://freegeoip.net/json/"
# Copyright (c) Django Software Foundation and individual contributors. # Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved. # All rights reserved.

View File

@@ -1,5 +1,6 @@
"""Validate functions.""" """Validate functions."""
import uuid import uuid
import re
import voluptuous as vol import voluptuous as vol
import pytz import pytz
@@ -11,13 +12,29 @@ from .const import (
ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID) 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)) NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
ALSA_CHANNEL = vol.Match(r"\d+,\d+") ALSA_CHANNEL = vol.Match(r"\d+,\d+")
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60)) WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$") 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 # pylint: disable=no-value-for-parameter
REPOSITORIES = vol.All([vol.Url()], vol.Unique()) REPOSITORIES = vol.All([validate_repository], vol.Unique())
def validate_timezone(timezone): def validate_timezone(timezone):

View File

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

View File

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