From 02c8baef68252abbb5a57cbfffc6ef2dc88ba13b Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 11 Jul 2017 12:26:58 +0200 Subject: [PATCH 01/14] Add landingpage / cleanup homeassistant core handling --- hassio/addons/data.py | 38 ++-------------------- hassio/config.py | 61 ++---------------------------------- hassio/const.py | 1 + hassio/dock/homeassistant.py | 9 +++--- hassio/homeassistant.py | 58 ++++++++++++++++++++++++++++++++++ hassio/tools.py | 42 +++++++++++++++++++++++++ hassio/validate.py | 9 ++++++ 7 files changed, 121 insertions(+), 97 deletions(-) create mode 100644 hassio/homeassistant.py diff --git a/hassio/addons/data.py b/hassio/addons/data.py index 35c1cddce..51c652cb1 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -15,54 +15,22 @@ from .validate import ( from ..const import ( FILE_HASSIO_ADDONS, ATTR_VERSION, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON, REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM) -from ..tools import read_json_file, write_json_file +from ..tools import JsonConfig _LOGGER = logging.getLogger(__name__) RE_VOLUME = re.compile(MAP_VOLUME) -class Data(object): +class Data(JsonConfig): """Hold data for addons inside HassIO.""" def __init__(self, config): """Initialize data holder.""" - self._file = FILE_HASSIO_ADDONS - self._data = {} + super().__init__(FILE_HASSIO_ADDONS, SCHEMA_ADDON_FILE) self.config = config - self._cache = {} self._repositories = {} - # init or load data - if self._file.is_file(): - try: - self._data = read_json_file(self._file) - except (OSError, json.JSONDecodeError): - _LOGGER.warning("Can't read %s", self._file) - self._data = {} - - # validate - try: - self._data = SCHEMA_ADDON_FILE(self._data) - except vol.Invalid as ex: - _LOGGER.error("Can't parse addons.json -> %s", - humanize_error(self._data, ex)) - - def save(self): - """Store data to config file.""" - # validate - try: - self._data = SCHEMA_ADDON_FILE(self._data) - except vol.Invalid as ex: - _LOGGER.error("Can't parse addons data -> %s", - humanize_error(self._data, ex)) - return False - - if not write_json_file(self._file, self._data): - _LOGGER.error("Can't store config in %s", self._file) - return False - return True - @property def user(self): """Return local addon user data.""" diff --git a/hassio/config.py b/hassio/config.py index 02fe0e3a4..bc57e9865 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -6,11 +6,9 @@ import os from pathlib import Path, PurePath import voluptuous as vol -from voluptuous.humanize import humanize_error from .const import FILE_HASSIO_CONFIG, HASSIO_DATA -from .tools import ( - fetch_last_versions, write_json_file, read_json_file, validate_timezone) +from .tools import fetch_last_versions, JsonConfig, validate_timezone from .validate import HASS_DEVICES _LOGGER = logging.getLogger(__name__) @@ -19,7 +17,6 @@ DATETIME_FORMAT = "%Y%m%d %H:%M:%S" HOMEASSISTANT_CONFIG = PurePath("homeassistant") HOMEASSISTANT_LAST = 'homeassistant_last' -HOMEASSISTANT_DEVICES = 'homeassistant_devices' HASSIO_SSL = PurePath("ssl") HASSIO_LAST = 'hassio_last' @@ -50,7 +47,6 @@ SCHEMA_CONFIG = vol.Schema({ vol.Optional(API_ENDPOINT): vol.Coerce(str), vol.Optional(TIMEZONE, default='UTC'): validate_timezone, vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str), - vol.Optional(HOMEASSISTANT_DEVICES, default=[]): HASS_DEVICES, vol.Optional(HASSIO_LAST): vol.Coerce(str), vol.Optional(ADDONS_CUSTOM_LIST, default=[]): [vol.Url()], vol.Optional(SECURITY_INITIALIZE, default=False): vol.Boolean(), @@ -61,48 +57,13 @@ SCHEMA_CONFIG = vol.Schema({ }, extra=vol.REMOVE_EXTRA) -class CoreConfig(object): +class CoreConfig(JsonConfig): """Hold all core config data.""" def __init__(self): """Initialize config object.""" + super().__init__(FILE_HASSIO_CONFIG, SCHEMA_CONFIG) self.arch = None - self._file = FILE_HASSIO_CONFIG - self._data = {} - - # init or load data - if self._file.is_file(): - try: - self._data = read_json_file(self._file) - except (OSError, json.JSONDecodeError): - _LOGGER.warning("Can't read %s", self._file) - self._data = {} - - # validate data - if not self._validate_config(): - self._data = SCHEMA_CONFIG({}) - - def _validate_config(self): - """Validate config and return True or False.""" - # validate data - try: - self._data = SCHEMA_CONFIG(self._data) - except vol.Invalid as ex: - _LOGGER.warning( - "Invalid config %s", humanize_error(self._data, ex)) - return False - - return True - - def save(self): - """Store data to config file.""" - if not self._validate_config(): - return False - - if not write_json_file(self._file, self._data): - _LOGGER.error("Can't store config in %s", self._file) - return False - return True async def fetch_update_infos(self, websession): """Read current versions from web.""" @@ -150,22 +111,6 @@ class CoreConfig(object): self._data[TIMEZONE] = value self.save() - @property - def homeassistant_devices(self): - """Return list of special device to map into homeassistant.""" - return self._data[HOMEASSISTANT_DEVICES] - - @homeassistant_devices.setter - def homeassistant_devices(self, value): - """Set list of special device.""" - self._data[HOMEASSISTANT_DEVICES] = value - self.save() - - @property - def homeassistant_image(self): - """Return docker homeassistant repository.""" - return os.environ['HOMEASSISTANT_REPOSITORY'] - @property def last_homeassistant(self): """Actual version of homeassistant.""" diff --git a/hassio/const.py b/hassio/const.py index f63515f14..c8ee16ad9 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -24,6 +24,7 @@ RESTART_EXIT_CODE = 100 FILE_HASSIO_ADDONS = Path(HASSIO_DATA, "addons.json") FILE_HASSIO_CONFIG = Path(HASSIO_DATA, "config.json") +FILE_HASSIO_HOMEASSISTANT = Path(HASSIO_DATA, "homeassistant.json") SOCKET_DOCKER = Path("/var/run/docker.sock") SOCKET_HC = Path("/var/run/hassio-hc.sock") diff --git a/hassio/dock/homeassistant.py b/hassio/dock/homeassistant.py index 7b361bf5b..d86202b66 100644 --- a/hassio/dock/homeassistant.py +++ b/hassio/dock/homeassistant.py @@ -13,9 +13,10 @@ HASS_DOCKER_NAME = 'homeassistant' class DockerHomeAssistant(DockerBase): """Docker hassio wrapper for HomeAssistant.""" - def __init__(self, config, loop, dock): + def __init__(self, config, loop, dock, data): """Initialize docker homeassistant wrapper.""" - super().__init__(config, loop, dock, image=config.homeassistant_image) + super().__init__(config, loop, dock, image=data.image) + self.data = data @property def name(self): @@ -25,11 +26,11 @@ class DockerHomeAssistant(DockerBase): @property def devices(self): """Create list of special device to map into docker.""" - if not self.config.homeassistant_devices: + if not self.data.devices: return devices = [] - for device in self.config.homeassistant_devices: + for device in self.data.devices: devices.append("/dev/{0}:/dev/{0}:rwm".format(device)) return devices diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py new file mode 100644 index 000000000..82525e5f5 --- /dev/null +++ b/hassio/homeassistant.py @@ -0,0 +1,58 @@ +"""HomeAssistant control object.""" +import logging +import os + +from .const import FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE +from .dock.homeassistant import DockerHomeAssistant +from .tools import JsonConfig +from .validate import SCHEMA_HASS_CONFIG + + +class HomeAssistant(JsonConfig): + """Hass core object for handle it.""" + + def __init__(self, config, loop, dock): + """Initialize hass object.""" + super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) + self.config = config + self.loop = loop + + async def prepare(self): + """Prepare HomeAssistant object.""" + + @property + def version(self): + """Return version of running homeassistant.""" + return self.docker.version + + @property + def last_version(self): + """Return last available version of homeassistant.""" + return self.config.last_homeassistant + + @property + def image(self): + """Return image name of hass containter.""" + if ATTR_IMAGE in self._data: + return self._data[ATTR_IMAGE] + return os.environ['HOMEASSISTANT_REPOSITORY'] + + @image.setter + def image(self, value): + """Set image name of hass containter.""" + if value is None: + self._data.pop(ATTR_IMAGE, None) + else: + self._data[ATTR_IMAGE] = value + self.save() + + @property + def devices(self): + """Return extend device mapping.""" + return self._data[ATTR_DEVICES] + + @devices.setter + def devices(self, value): + """Set extend device mapping.""" + self._data[ATTR_DEVICES] = value + self.save() diff --git a/hassio/tools.py b/hassio/tools.py index 7147fe62d..975cfc303 100644 --- a/hassio/tools.py +++ b/hassio/tools.py @@ -9,6 +9,7 @@ import aiohttp import async_timeout import pytz import voluptuous as vol +from voluptuous.humanize import humanize_error from .const import URL_HASSIO_VERSION, URL_HASSIO_VERSION_BETA @@ -98,3 +99,44 @@ async def fetch_timezone(websession): data = await request.json() return data.get('time_zone', 'UTC') + + +class JsonConfig(object): + """Hass core object for handle it.""" + + def __init__(self, json_file, schema): + """Initialize hass object.""" + self._file = json_file + self._schema = schema + self._data = {} + + # init or load data + if self._file.is_file(): + try: + self._data = read_json_file(self._file) + except (OSError, json.JSONDecodeError): + _LOGGER.warning("Can't read %s", self._file) + self._data = {} + + # validate + try: + self._data = self._schema(self._data) + except vol.Invalid as ex: + _LOGGER.error("Can't parse %s -> %s", + self._file, humanize_error(self._data, ex)) + + def save(self): + """Store data to config file.""" + # validate + try: + self._data = self._schema(self._data) + except vol.Invalid as ex: + _LOGGER.error("Can't parse data -> %s", + humanize_error(self._data, ex)) + return False + + # write + if not write_json_file(self._file, self._data): + _LOGGER.error("Can't store config in %s", self._file) + return False + return True diff --git a/hassio/validate.py b/hassio/validate.py index 4a97a54b7..f339a9348 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -1,6 +1,9 @@ """Validate functions.""" import voluptuous as vol +from .const import ATTR_DEVICES, ATTR_IMAGE + + NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) HASS_DEVICES = [vol.Match(r"^[^/]*$")] @@ -30,3 +33,9 @@ DOCKER_PORTS = vol.Schema({ vol.All(vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")): convert_to_docker_ports, }) + + +SCHEMA_HASS_CONFIG = vol.Schema({ + vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES, + vol.Optional(ATTR_IMAGE): vol.Coerce(str) +}) From 906c4e03fb9fecba8d229c1605cfbac4f024fc71 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 11 Jul 2017 16:19:24 +0200 Subject: [PATCH 02/14] Finish new homeassistant handling --- API.md | 8 +++- hassio/api/homeassistant.py | 18 ++++++-- hassio/const.py | 1 + hassio/core.py | 22 +++++----- hassio/homeassistant.py | 84 +++++++++++++++++++++++++++++++----- hassio/snapshots/__init__.py | 8 ++-- hassio/tasks.py | 17 -------- hassio/validate.py | 5 ++- 8 files changed, 113 insertions(+), 50 deletions(-) diff --git a/API.md b/API.md index bb38ffe0f..1951dc0f6 100644 --- a/API.md +++ b/API.md @@ -269,7 +269,9 @@ Optional: { "version": "INSTALL_VERSION", "last_version": "LAST_VERSION", - "devices": [] + "devices": [""], + "image": "str", + "custom": "bool -> if custom image" } ``` @@ -291,9 +293,13 @@ Output the raw docker log ```json { "devices": [], + "image": "Optional|null", + "last_version": "Optional for custom image|null" } ``` +Image with `null` and last_version with `null` reset this options. + ### REST API addons - POST `/addons/reload` diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index 6ba15511a..cc791b4ce 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -5,7 +5,8 @@ import logging import voluptuous as vol from .util import api_process, api_process_raw, api_validate -from ..const import ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES +from ..const import ( + ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES, ATTR_IMAGE, ATTR_CUSTOM) from ..validate import HASS_DEVICES _LOGGER = logging.getLogger(__name__) @@ -13,6 +14,9 @@ _LOGGER = logging.getLogger(__name__) SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_DEVICES): HASS_DEVICES, + vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Any(None, vol.Coerce(str)), + vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): + vol.Any(None, vol.Coerce(str)), }) SCHEMA_VERSION = vol.Schema({ @@ -34,8 +38,10 @@ class APIHomeAssistant(object): """Return host information.""" return { ATTR_VERSION: self.homeassistant.version, - ATTR_LAST_VERSION: self.config.last_homeassistant, - ATTR_DEVICES: self.config.homeassistant_devices, + ATTR_LAST_VERSION: self.homeassistant.last_version, + ATTR_IMAGE: self.homeassistant.image, + ATTR_DEVICES: self.homeassistant.devices, + ATTR_CUSTOM: self.homeassistant.is_custom_image, } @api_process @@ -44,7 +50,11 @@ class APIHomeAssistant(object): body = await api_validate(SCHEMA_OPTIONS, request) if ATTR_DEVICES in body: - self.config.homeassistant_devices = body[ATTR_DEVICES] + self.homeassistant.devices = body[ATTR_DEVICES] + + if ATTR_IMAGE in body: + self.homeassistant.set_custom( + body[ATTR_IMAGE], body[ATTR_LAST_VERSION]) return True diff --git a/hassio/const.py b/hassio/const.py index c8ee16ad9..88ca1be6b 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -95,6 +95,7 @@ ATTR_SIZE = 'size' ATTR_TYPE = 'type' ATTR_TIMEOUT = 'timeout' ATTR_AUTO_UPDATE = 'auto_update' +ATTR_CUSTOM = 'custom' STARTUP_INITIALIZE = 'initialize' STARTUP_BEFORE = 'before' diff --git a/hassio/core.py b/hassio/core.py index eaba036e9..6a74386cc 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -13,13 +13,12 @@ from .const import ( RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT, RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE, STARTUP_INITIALIZE, RUN_RELOAD_SNAPSHOTS_TASKS, RUN_UPDATE_ADDONS_TASKS) +from .homeassistant import HomeAssistant from .scheduler import Scheduler -from .dock.homeassistant import DockerHomeAssistant from .dock.supervisor import DockerSupervisor from .snapshots import SnapshotsManager from .tasks import ( - hassio_update, homeassistant_watchdog, homeassistant_setup, - api_sessions_cleanup, addons_update) + hassio_update, homeassistant_watchdog, api_sessions_cleanup, addons_update) from .tools import get_local_ip, fetch_timezone _LOGGER = logging.getLogger(__name__) @@ -41,7 +40,9 @@ class HassIO(object): # init basic docker container self.supervisor = DockerSupervisor(config, loop, self.dock, self.stop) - self.homeassistant = DockerHomeAssistant(config, loop, self.dock) + + # init homeassistant + self.homeassistant = HomeAssistant(config, loop, self.dock) # init HostControl self.host_control = HostControl(loop) @@ -94,13 +95,8 @@ class HassIO(object): api_sessions_cleanup(self.config), RUN_CLEANUP_API_SESSIONS, now=True) - # first start of supervisor? - if not await self.homeassistant.exists(): - _LOGGER.info("No HomeAssistant docker found.") - await homeassistant_setup( - self.config, self.loop, self.homeassistant, self.websession) - else: - await self.homeassistant.attach() + # Load homeassistant + await self.homeassistant.prepare(): # Load addons await self.addons.prepare() @@ -132,6 +128,10 @@ class HassIO(object): loop=self.loop ) + # If laningpage / run upgrade in background + if self.homeassistant.version == 'landingpage': + self.loop.create_task(self.homeassistant.install()) + # start api await self.api.start() _LOGGER.info("Start hassio api on %s", self.config.api_endpoint) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 82525e5f5..e7c6721e3 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -1,8 +1,10 @@ """HomeAssistant control object.""" +import asyncio import logging import os -from .const import FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE +from .const import ( + FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION) from .dock.homeassistant import DockerHomeAssistant from .tools import JsonConfig from .validate import SCHEMA_HASS_CONFIG @@ -11,14 +13,24 @@ from .validate import SCHEMA_HASS_CONFIG class HomeAssistant(JsonConfig): """Hass core object for handle it.""" - def __init__(self, config, loop, dock): + def __init__(self, config, loop, dock, websession): """Initialize hass object.""" super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) self.config = config self.loop = loop + self.websession = websession + self.docker = DockerHomeAssistant(config, loop, dock, self) async def prepare(self): """Prepare HomeAssistant object.""" + if not await self.docker.exists(): + _LOGGER.info("No HomeAssistant docker %s found.", self.image) + if self.is_custom_image: + await self.install() + else: + await self.install_landingpage() + else: + await self.docker.attach() @property def version(self): @@ -28,6 +40,8 @@ class HomeAssistant(JsonConfig): @property def last_version(self): """Return last available version of homeassistant.""" + if self.is_custom_image: + return self._data.get(ATTR_LAST_VERSION) return self.config.last_homeassistant @property @@ -37,14 +51,10 @@ class HomeAssistant(JsonConfig): return self._data[ATTR_IMAGE] return os.environ['HOMEASSISTANT_REPOSITORY'] - @image.setter - def image(self, value): - """Set image name of hass containter.""" - if value is None: - self._data.pop(ATTR_IMAGE, None) - else: - self._data[ATTR_IMAGE] = value - self.save() + @property + def is_custom_image(self): + """Return True if a custom image is used.""" + return ATTR_IMAGE in self._data @property def devices(self): @@ -56,3 +66,57 @@ class HomeAssistant(JsonConfig): """Set extend device mapping.""" self._data[ATTR_DEVICES] = value self.save() + + def set_custom(self, image, version): + """Set a custom image for homeassistant.""" + # reset + if image is None and version is None: + self._data.pop(ATTR_IMAGE, None) + self._data.pop(ATTR_VERSION, None) + else: + if image: + self._data[ATTR_IMAGE] = image + if version: + self._data[ATTR_VERSION] = version + self.save() + + async def install_landingpage(self): + """Install a landingpage.""" + _LOGGER.info("Setup HomeAssistant landingpage") + while True: + if self.docker.install('landingpage'): + break + _LOGGER.warning("Fails install landingpage, retry after 60sec") + await asyncio.sleep(60, loop=self.loop) + + async def install(self): + """Install a landingpage.""" + _LOGGER.info("Setup HomeAssistant") + while True: + # read homeassistant tag and install it + if not self.last_version: + await self.config.fetch_update_infos(websession) + + tag = self.last_version + if tag and await self.docker.install(tag): + break + _LOGGER.warning("Error on install HomeAssistant. Retry in 60sec") + await asyncio.sleep(60, loop=loop) + + # store version + _LOGGER.info("HomeAssistant docker now installed") + + async def update(self, version=None): + """Update HomeAssistant version.""" + version = version or self.last_version + if version == self.version: + return True + + return self.docker.update(version) + + def run(self): + """Run HomeAssistant docker. + + Return a coroutine. + """ + return self.docker.run() diff --git a/hassio/snapshots/__init__.py b/hassio/snapshots/__init__.py index 29f3b277a..8d2bd3f91 100644 --- a/hassio/snapshots/__init__.py +++ b/hassio/snapshots/__init__.py @@ -47,7 +47,7 @@ class SnapshotsManager(object): # set general data snapshot.homeassistant_version = self.homeassistant.version - snapshot.homeassistant_devices = self.config.homeassistant_devices + snapshot.homeassistant_devices = self.homeassistant.devices snapshot.repositories = self.config.addons_repositories return snapshot @@ -198,8 +198,7 @@ class SnapshotsManager(object): await snapshot.restore_folders() # start homeassistant restore - self.config.homeassistant_devices = \ - snapshot.homeassistant_devices + self.homeassistant.devices = snapshot.homeassistant_devices task_hass = self.loop.create_task( self.homeassistant.update(snapshot.homeassistant_version)) @@ -281,8 +280,7 @@ class SnapshotsManager(object): await snapshot.restore_folders(folders) if homeassistant: - self.config.homeassistant_devices = \ - snapshot.homeassistant_devices + self.homeassistant.devices = snapshot.homeassistant_devices tasks.append(self.homeassistant.update( snapshot.homeassistant_version)) diff --git a/hassio/tasks.py b/hassio/tasks.py index 25409e16e..a3421b560 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -66,20 +66,3 @@ def homeassistant_watchdog(loop, homeassistant): loop.create_task(homeassistant.run()) return _homeassistant_watchdog - - -async def homeassistant_setup(config, loop, homeassistant, websession): - """Install a homeassistant docker container.""" - while True: - # read homeassistant tag and install it - if not config.last_homeassistant: - await config.fetch_update_infos(websession) - - tag = config.last_homeassistant - if tag and await homeassistant.install(tag): - break - _LOGGER.warning("Error on setup HomeAssistant. Retry in 60.") - await asyncio.sleep(60, loop=loop) - - # store version - _LOGGER.info("HomeAssistant docker now installed.") diff --git a/hassio/validate.py b/hassio/validate.py index f339a9348..88c2cf2ca 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -1,7 +1,7 @@ """Validate functions.""" import voluptuous as vol -from .const import ATTR_DEVICES, ATTR_IMAGE +from .const import ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) @@ -37,5 +37,6 @@ DOCKER_PORTS = vol.Schema({ SCHEMA_HASS_CONFIG = vol.Schema({ vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES, - vol.Optional(ATTR_IMAGE): vol.Coerce(str) + vol.inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str), + vol.inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str), }) From e5fc6846e0ab8e8a152d47d053f7cdd847631e20 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 11 Jul 2017 16:52:16 +0200 Subject: [PATCH 03/14] fix snapshot with custom image --- hassio/homeassistant.py | 3 +++ hassio/snapshots/__init__.py | 7 +++---- hassio/snapshots/snapshot.py | 31 ++++++++++++++++++++++++++++++- hassio/snapshots/validate.py | 3 ++- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index e7c6721e3..c062a6be8 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -73,9 +73,12 @@ class HomeAssistant(JsonConfig): if image is None and version is None: self._data.pop(ATTR_IMAGE, None) self._data.pop(ATTR_VERSION, None) + + self.docker.image = self.image else: if image: self._data[ATTR_IMAGE] = image + self.docker.image = image if version: self._data[ATTR_VERSION] = version self.save() diff --git a/hassio/snapshots/__init__.py b/hassio/snapshots/__init__.py index 8d2bd3f91..bc8603a9b 100644 --- a/hassio/snapshots/__init__.py +++ b/hassio/snapshots/__init__.py @@ -46,8 +46,7 @@ class SnapshotsManager(object): snapshot.create(slug, name, date_str, sys_type) # set general data - snapshot.homeassistant_version = self.homeassistant.version - snapshot.homeassistant_devices = self.homeassistant.devices + snapshot.snapshot_homeassistant(self.homeassistant) snapshot.repositories = self.config.addons_repositories return snapshot @@ -198,7 +197,7 @@ class SnapshotsManager(object): await snapshot.restore_folders() # start homeassistant restore - self.homeassistant.devices = snapshot.homeassistant_devices + snapshot.restore_homeassistant(self.homeassistant) task_hass = self.loop.create_task( self.homeassistant.update(snapshot.homeassistant_version)) @@ -280,7 +279,7 @@ class SnapshotsManager(object): await snapshot.restore_folders(folders) if homeassistant: - self.homeassistant.devices = snapshot.homeassistant_devices + snapshot.restore_homeassistant(self.homeassistant) tasks.append(self.homeassistant.update( snapshot.homeassistant_version)) diff --git a/hassio/snapshots/snapshot.py b/hassio/snapshots/snapshot.py index 1882a5f07..a8730e20a 100644 --- a/hassio/snapshots/snapshot.py +++ b/hassio/snapshots/snapshot.py @@ -13,7 +13,8 @@ from .validate import SCHEMA_SNAPSHOT, ALL_FOLDERS from .util import remove_folder from ..const import ( ATTR_SLUG, ATTR_NAME, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES, - ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_DEVICES) + ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_DEVICES, + ATTR_IMAGE) from ..tools import write_json_file _LOGGER = logging.getLogger(__name__) @@ -90,6 +91,16 @@ class Snapshot(object): """Set snapshot homeassistant devices.""" self._data[ATTR_HOMEASSISTANT][ATTR_DEVICES] = value + @property + def homeassistant_image(self): + """Return snapshot homeassistant custom image.""" + return self._data[ATTR_HOMEASSISTANT].get(ATTR_IMAGE) + + @homeassistant_image.setter + def homeassistant_image(self, value): + """Set snapshot homeassistant custom image.""" + self._data[ATTR_HOMEASSISTANT][ATTR_IMAGE] = value + @property def size(self): """Return snapshot size.""" @@ -111,6 +122,24 @@ class Snapshot(object): self._data[ATTR_REPOSITORIES] = [] self._data[ATTR_FOLDERS] = [] + def snapshot_homeassistant(self, homeassistant): + """Read all data from homeassistant object.""" + self.homeassistant_version = homeassistant.version + self.homeassistant_devices = homeassistant.devices + + # custom image + if self.homeassistant.is_custom_image: + self.homeassistant_image = homeassistant.image + + def restore_homeassistant(self, homeassistant): + """Write all data to homeassistant object.""" + homeassistant.devices = self.homeassistant_devices + + # custom image + if self.homeassistant_image: + homeassistant.set_custom( + self.homeassistant_image, self.homeassistant_version) + async def load(self): """Read snapshot.json from tar file.""" if not self.tar_file.is_file(): diff --git a/hassio/snapshots/validate.py b/hassio/snapshots/validate.py index 2259009d5..e0a497d76 100644 --- a/hassio/snapshots/validate.py +++ b/hassio/snapshots/validate.py @@ -5,7 +5,7 @@ import voluptuous as vol from ..const import ( ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_NAME, ATTR_SLUG, ATTR_DATE, ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_DEVICES, - FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, + ATTR_IMAGE, FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, SNAPSHOT_FULL, SNAPSHOT_PARTIAL) from ..validate import HASS_DEVICES @@ -20,6 +20,7 @@ SCHEMA_SNAPSHOT = vol.Schema({ vol.Required(ATTR_HOMEASSISTANT): vol.Schema({ vol.Required(ATTR_VERSION): vol.Coerce(str), vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES, + vol.Optional(ATTR_IMAGE): vol.Coerce(str), }), vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)], vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({ From 5ebf2068b278760622cb3e02212af5647f11b05f Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 11 Jul 2017 16:57:03 +0200 Subject: [PATCH 04/14] Update interface, allow update every time --- hassio/api/homeassistant.py | 3 --- hassio/homeassistant.py | 31 ++++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index cc791b4ce..9b10497ca 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -67,9 +67,6 @@ class APIHomeAssistant(object): if self.homeassistant.in_progress: raise RuntimeError("Other task is in progress") - if version == self.homeassistant.version: - raise RuntimeError("Version is already in use") - return await asyncio.shield( self.homeassistant.update(version), loop=self.loop) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index c062a6be8..63e27795a 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -109,12 +109,12 @@ class HomeAssistant(JsonConfig): # store version _LOGGER.info("HomeAssistant docker now installed") - async def update(self, version=None): - """Update HomeAssistant version.""" - version = version or self.last_version - if version == self.version: - return True + def update(self, version=None): + """Update HomeAssistant version. + Return a coroutine. + """ + version = version or self.last_version return self.docker.update(version) def run(self): @@ -123,3 +123,24 @@ class HomeAssistant(JsonConfig): Return a coroutine. """ return self.docker.run() + + def stop(self): + """Stop HomeAssistant docker. + + Return a coroutine. + """ + return self.docker.stop() + + def restart(self): + """Restart HomeAssistant docker. + + Return a coroutine. + """ + return self.docker.restart() + + def logs(self): + """Get HomeAssistant docker logs. + + Return a coroutine. + """ + return self.docker.logs() From 421b380043c4b7f069dc800a65124d53870cc66a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 22:11:25 +0200 Subject: [PATCH 05/14] fix lint --- hassio/core.py | 2 +- hassio/dock/__init__.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hassio/core.py b/hassio/core.py index 6a74386cc..f21e9daf0 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -96,7 +96,7 @@ class HassIO(object): now=True) # Load homeassistant - await self.homeassistant.prepare(): + await self.homeassistant.prepare() # Load addons await self.addons.prepare() diff --git a/hassio/dock/__init__.py b/hassio/dock/__init__.py index 733bbce67..7fb7adb4d 100644 --- a/hassio/dock/__init__.py +++ b/hassio/dock/__init__.py @@ -260,9 +260,13 @@ class DockerBase(object): if not self._install(tag): return False - # cleanup old stuff + # run or cleanup container if was_running: self._run() + else: + self._stop() + + # cleanup images self._cleanup() return True From 40e8f411ff9d5bc5ab36a0d8abc09f55327ff2d0 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 22:19:22 +0200 Subject: [PATCH 06/14] fix lint p2 --- hassio/api/addons.py | 1 + hassio/config.py | 2 -- hassio/core.py | 3 ++- hassio/homeassistant.py | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 1f9e96663..b03e582a4 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -11,6 +11,7 @@ from ..const import ( ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY, ATTR_BUILD, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_HOST_NETWORK, BOOT_AUTO, BOOT_MANUAL) +from ..tools import read_json_file from ..validate import DOCKER_PORTS _LOGGER = logging.getLogger(__name__) diff --git a/hassio/config.py b/hassio/config.py index bc57e9865..b8a2630fb 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -1,7 +1,6 @@ """Bootstrap HassIO.""" from datetime import datetime import logging -import json import os from pathlib import Path, PurePath @@ -9,7 +8,6 @@ import voluptuous as vol from .const import FILE_HASSIO_CONFIG, HASSIO_DATA from .tools import fetch_last_versions, JsonConfig, validate_timezone -from .validate import HASS_DEVICES _LOGGER = logging.getLogger(__name__) diff --git a/hassio/core.py b/hassio/core.py index f21e9daf0..cb39d9f04 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -42,7 +42,8 @@ class HassIO(object): self.supervisor = DockerSupervisor(config, loop, self.dock, self.stop) # init homeassistant - self.homeassistant = HomeAssistant(config, loop, self.dock) + self.homeassistant = HomeAssistant( + config, loop, self.dock, self.websession) # init HostControl self.host_control = HostControl(loop) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 63e27795a..6f4464459 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -4,11 +4,14 @@ import logging import os from .const import ( - FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION) + FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION, + ATTR_VERSION) from .dock.homeassistant import DockerHomeAssistant from .tools import JsonConfig from .validate import SCHEMA_HASS_CONFIG +_LOGGER = logging.getLogger(__name__) + class HomeAssistant(JsonConfig): """Hass core object for handle it.""" From ebf4daf4cce96e69d7629b9d251f3ce232fd8ace Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 22:38:07 +0200 Subject: [PATCH 07/14] fix lint --- hassio/addons/data.py | 2 +- hassio/api/addons.py | 1 - hassio/homeassistant.py | 11 +++++++++-- hassio/snapshots/snapshot.py | 2 +- hassio/validate.py | 4 ++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/hassio/addons/data.py b/hassio/addons/data.py index 51c652cb1..58d7452d1 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -15,7 +15,7 @@ from .validate import ( from ..const import ( FILE_HASSIO_ADDONS, ATTR_VERSION, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON, REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM) -from ..tools import JsonConfig +from ..tools import JsonConfig, read_json_file _LOGGER = logging.getLogger(__name__) diff --git a/hassio/api/addons.py b/hassio/api/addons.py index b03e582a4..1f9e96663 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -11,7 +11,6 @@ from ..const import ( ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY, ATTR_BUILD, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_HOST_NETWORK, BOOT_AUTO, BOOT_MANUAL) -from ..tools import read_json_file from ..validate import DOCKER_PORTS _LOGGER = logging.getLogger(__name__) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 6f4464459..627856f19 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -101,13 +101,13 @@ class HomeAssistant(JsonConfig): while True: # read homeassistant tag and install it if not self.last_version: - await self.config.fetch_update_infos(websession) + await self.config.fetch_update_infos(self.websession) tag = self.last_version if tag and await self.docker.install(tag): break _LOGGER.warning("Error on install HomeAssistant. Retry in 60sec") - await asyncio.sleep(60, loop=loop) + await asyncio.sleep(60, loop=self.loop) # store version _LOGGER.info("HomeAssistant docker now installed") @@ -147,3 +147,10 @@ class HomeAssistant(JsonConfig): Return a coroutine. """ return self.docker.logs() + + def is_running(self): + """Return True if docker container is running. + + Return a coroutine. + """ + return self.docker.is_running() diff --git a/hassio/snapshots/snapshot.py b/hassio/snapshots/snapshot.py index a8730e20a..41a03a8f3 100644 --- a/hassio/snapshots/snapshot.py +++ b/hassio/snapshots/snapshot.py @@ -128,7 +128,7 @@ class Snapshot(object): self.homeassistant_devices = homeassistant.devices # custom image - if self.homeassistant.is_custom_image: + if homeassistant.is_custom_image: self.homeassistant_image = homeassistant.image def restore_homeassistant(self, homeassistant): diff --git a/hassio/validate.py b/hassio/validate.py index 88c2cf2ca..ae0cb5a60 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -37,6 +37,6 @@ DOCKER_PORTS = vol.Schema({ SCHEMA_HASS_CONFIG = vol.Schema({ vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES, - vol.inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str), - vol.inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str), + vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str), + vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str), }) From cf154b57f34faa60738f7ba9f64e3703da5fafa7 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 22:45:09 +0200 Subject: [PATCH 08/14] fix lint p4 --- hassio/addons/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hassio/addons/data.py b/hassio/addons/data.py index 58d7452d1..7d8e00e79 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -30,6 +30,7 @@ class Data(JsonConfig): super().__init__(FILE_HASSIO_ADDONS, SCHEMA_ADDON_FILE) self.config = config self._repositories = {} + self._cache = {} @property def user(self): From 55ec1a84faad200dba9881659b26d2400a2371fc Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 22:58:05 +0200 Subject: [PATCH 09/14] expose running state of docker --- hassio/homeassistant.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 627856f19..3ee5090ae 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -148,6 +148,7 @@ class HomeAssistant(JsonConfig): """ return self.docker.logs() + @property def is_running(self): """Return True if docker container is running. From e40963a6869baf072b22821606a64f2404e5a551 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 23:02:20 +0200 Subject: [PATCH 10/14] fix calling --- hassio/homeassistant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 3ee5090ae..5ed908454 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -154,4 +154,4 @@ class HomeAssistant(JsonConfig): Return a coroutine. """ - return self.docker.is_running() + return self.docker.is_running From d978ec00aa72653e78ecc3d13bb7bb6a5aa69cb2 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 23:08:47 +0200 Subject: [PATCH 11/14] fix progress v2 --- hassio/homeassistant.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 5ed908454..0e00542bb 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -148,10 +148,14 @@ class HomeAssistant(JsonConfig): """ return self.docker.logs() - @property def is_running(self): """Return True if docker container is running. Return a coroutine. """ - return self.docker.is_running + return self.docker.is_running() + + @property + def in_progress(self): + """Return True if a task is in progress.""" + return self.docker.in_progress From a0a1fd4875077c67bb5d36c199b744dbf363e580 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 23:29:38 +0200 Subject: [PATCH 12/14] fix coro --- hassio/homeassistant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 0e00542bb..9c94c4184 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -90,7 +90,7 @@ class HomeAssistant(JsonConfig): """Install a landingpage.""" _LOGGER.info("Setup HomeAssistant landingpage") while True: - if self.docker.install('landingpage'): + if await self.docker.install('landingpage'): break _LOGGER.warning("Fails install landingpage, retry after 60sec") await asyncio.sleep(60, loop=self.loop) From dbcd09024460146736774824520fb4e72899d861 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 11 Jul 2017 23:45:29 +0200 Subject: [PATCH 13/14] fix flow --- hassio/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hassio/core.py b/hassio/core.py index cb39d9f04..0544b7d76 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -129,10 +129,6 @@ class HassIO(object): loop=self.loop ) - # If laningpage / run upgrade in background - if self.homeassistant.version == 'landingpage': - self.loop.create_task(self.homeassistant.install()) - # start api await self.api.start() _LOGGER.info("Start hassio api on %s", self.config.api_endpoint) @@ -158,6 +154,10 @@ class HassIO(object): homeassistant_watchdog(self.loop, self.homeassistant), RUN_WATCHDOG_HOMEASSISTANT) + # If landingpage / run upgrade in background + if self.homeassistant.version == 'landingpage': + self.loop.create_task(self.homeassistant.install()) + async def stop(self, exit_code=0): """Stop a running orchestration.""" # don't process scheduler anymore From 2bbe7e7dc153cc07aff61369e3401a84cbe0caab Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 12 Jul 2017 00:09:03 +0200 Subject: [PATCH 14/14] cleanup after install --- hassio/homeassistant.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 9c94c4184..70bcf05c6 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -111,6 +111,7 @@ class HomeAssistant(JsonConfig): # store version _LOGGER.info("HomeAssistant docker now installed") + await self.docker.cleanup() def update(self, version=None): """Update HomeAssistant version.