From ff640c598dbeeae71834e95f398862e8d5e83fab Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 2 May 2017 00:02:55 +0200 Subject: [PATCH] Support for repository store. (#26) * Support for repository store. * Fix api * part 1 of restruct and migrate pathlib * Migrate p2 * fix lint / cleanups * fix lint p2 * fix lint p3 --- API.md | 37 +++++--- hassio/addons/__init__.py | 6 +- hassio/addons/data.py | 169 +++++++++++++++++++++++++---------- hassio/addons/git.py | 15 ++-- hassio/addons/util.py | 21 ++--- hassio/addons/validate.py | 10 ++- hassio/api/supervisor.py | 18 ++-- hassio/api/util.py | 2 +- hassio/bootstrap.py | 35 +++++--- hassio/config.py | 73 ++++++++------- hassio/const.py | 18 ++-- hassio/dock/addon.py | 10 +-- hassio/dock/homeassistant.py | 4 +- hassio/tools.py | 4 +- 14 files changed, 269 insertions(+), 153 deletions(-) diff --git a/API.md b/API.md index 96eaf1ab8..90b3244c9 100644 --- a/API.md +++ b/API.md @@ -37,9 +37,9 @@ The addons from `addons` are only installed one. { "name": "xy bla", "slug": "xy", - "version": "LAST_VERSION", - "installed": "INSTALL_VERSION", - "dedicated": "bool", + "version": "INSTALL_VERSION", + "last_version": "VERSION_FOR_UPDATE", + "detached": "bool", "description": "description" } ], @@ -54,16 +54,27 @@ The addons from `addons` are only installed one. Get all available addons ```json -[ - { - "name": "xy bla", - "slug": "xy", - "version": "LAST_VERSION", - "installed": "none|INSTALL_VERSION", - "dedicated": "bool", - "description": "description" - } -] +{ + "addons": [ + { + "name": "xy bla", + "slug": "xy", + "repository": "12345678|null", + "version": "LAST_VERSION", + "installed": "none|INSTALL_VERSION", + "detached": "bool", + "description": "description" + } + ], + "repositories": [ + { + "slug": "12345678", + "name": "Repitory Name", + "url": "WEBSITE", + "maintainer": "BLA BLU " + } + ] +} ``` - POST `/supervisor/update` diff --git a/hassio/addons/__init__.py b/hassio/addons/__init__.py index 5536df866..dd315ec42 100644 --- a/hassio/addons/__init__.py +++ b/hassio/addons/__init__.py @@ -51,7 +51,7 @@ class AddonManager(AddonsData): self.config, self.loop, self.dock, self, addon) await self.dockers[addon].attach() - async def add_custom_repository(self, url): + async def add_git_repository(self, url): """Add a new custom repository.""" if url in self.config.addons_repositories: _LOGGER.warning("Repository already exists %s", url) @@ -67,7 +67,7 @@ class AddonManager(AddonsData): self.repositories.append(repo) return True - def drop_custom_repository(self, url): + def drop_git_repository(self, url): """Remove a custom repository.""" for repo in self.repositories: if repo.url == url: @@ -91,7 +91,7 @@ class AddonManager(AddonsData): self.merge_update_config() # remove stalled addons - for addon in self.list_removed: + for addon in self.list_detached: _LOGGER.warning("Dedicated addon '%s' found!", addon) async def auto_boot(self, start_type): diff --git a/hassio/addons/data.py b/hassio/addons/data.py index ae4286aeb..a3354eaa8 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -1,26 +1,30 @@ """Init file for HassIO addons.""" import copy import logging -import glob +from pathlib import Path, PurePath import voluptuous as vol from voluptuous.humanize import humanize_error from .util import extract_hash_from_path -from .validate import validate_options, SCHEMA_ADDON_CONFIG +from .validate import ( + validate_options, SCHEMA_ADDON_CONFIG, SCHEMA_REPOSITORY_CONFIG) from ..const import ( FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO, - DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA, ATTR_IMAGE, ATTR_DEDICATED, - MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP) + DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA, ATTR_IMAGE, ATTR_DETACHED, + MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, ATTR_REPOSITORY, ATTR_URL, + ATTR_MAINTAINER, ATTR_LAST_VERSION) from ..config import Config from ..tools import read_json_file, write_json_file _LOGGER = logging.getLogger(__name__) -ADDONS_REPO_PATTERN = "{}/**/config.json" -SYSTEM = "system" -USER = "user" +SYSTEM = 'system' +USER = 'user' + +REPOSITORY_CORE = 'core' +REPOSITORY_LOCAL = 'local' class AddonsData(Config): @@ -32,7 +36,8 @@ class AddonsData(Config): self.config = config self._system_data = self._data.get(SYSTEM, {}) self._user_data = self._data.get(USER, {}) - self._current_data = {} + self._addons_cache = {} + self._repositories_data = {} self.arch = None def save(self): @@ -45,29 +50,62 @@ class AddonsData(Config): def read_data_from_repositories(self): """Read data from addons repository.""" - self._current_data = {} + self._addons_cache = {} + self._repositories_data = {} - self._read_addons_folder(self.config.path_addons_repo) - self._read_addons_folder(self.config.path_addons_custom, custom=True) + # read core repository + self._read_addons_folder( + self.config.path_addons_core, REPOSITORY_CORE) - def _read_addons_folder(self, folder, custom=False): + # read local repository + self._read_addons_folder( + self.config.path_addons_local, REPOSITORY_LOCAL) + + # read custom git repositories + for repository_dir in self.config.path_addons_git.glob("/*/"): + self._read_git_repository(repository_dir) + + def _read_git_repository(self, path): + """Process a custom repository folder.""" + slug = extract_hash_from_path(path) + repository_info = {ATTR_SLUG: slug} + + # exists repository json + repository_file = Path(path, "repository.json") + try: + repository_info.update(SCHEMA_REPOSITORY_CONFIG( + read_json_file(repository_file) + )) + + except OSError: + _LOGGER.warning("Can't read repository information from %s", + repository_file) + return + + except vol.Invalid: + _LOGGER.warning("Repository parse error %s", repository_file) + return + + # process data + self._repositories_data[slug] = repository_info + self._read_addons_folder(path, slug) + + def _read_addons_folder(self, path, repository): """Read data from addons folder.""" - pattern = ADDONS_REPO_PATTERN.format(folder) - - for addon in glob.iglob(pattern, recursive=True): + for addon in path.glob("**/*.config.json"): try: addon_config = read_json_file(addon) + # validate addon_config = SCHEMA_ADDON_CONFIG(addon_config) - if custom: - addon_slug = "{}_{}".format( - extract_hash_from_path(folder, addon), - addon_config[ATTR_SLUG], - ) - else: - addon_slug = addon_config[ATTR_SLUG] - self._current_data[addon_slug] = addon_config + # Generate slug + addon_slug = "{}_{}".format( + repository, addon_config[ATTR_SLUG]) + + # store + addon_config[ATTR_REPOSITORY] = repository + self._addons_cache[addon_slug] = addon_config except OSError: _LOGGER.warning("Can't read %s", addon) @@ -84,14 +122,14 @@ class AddonsData(Config): have_change = False for addon, data in self._system_data.items(): - # dedicated - if addon not in self._current_data: + # detached + if addon not in self._addons_cache: continue - current = self._current_data[addon] - if data[ATTR_VERSION] == current[ATTR_VERSION]: - if data != current: - self._system_data[addon] = copy.deepcopy(current) + cache = self._addons_cache[addon] + if data[ATTR_VERSION] == cache[ATTR_VERSION]: + if data != cache: + self._system_data[addon] = copy.deepcopy(cache) have_change = True if have_change: @@ -103,11 +141,11 @@ class AddonsData(Config): return set(self._system_data.keys()) @property - def list_api(self): + def list_all_api(self): """Return a list of available addons for api.""" data = [] - all_addons = {**self._system_data, **self._current_data} - dedicated = self.list_removed + all_addons = {**self._system_data, **self._addons_cache} + detached = self.list_detached for addon, values in all_addons.items(): i_version = self._user_data.get(addon, {}).get(ATTR_VERSION) @@ -118,7 +156,30 @@ class AddonsData(Config): ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], ATTR_VERSION: values[ATTR_VERSION], ATTR_INSTALLED: i_version, - ATTR_DEDICATED: addon in dedicated, + ATTR_DETACHED: addon in detached, + ATTR_REPOSITORY: values[ATTR_REPOSITORY], + }) + + return data + + @property + def list_installed_api(self): + """Return a list of available addons for api.""" + data = [] + all_addons = {**self._system_data, **self._addons_cache} + detached = self.list_detached + + for addon, values in all_addons.items(): + i_version = self._user_data.get(addon, {}).get(ATTR_VERSION) + + data.append({ + ATTR_NAME: values[ATTR_NAME], + ATTR_SLUG: addon, + ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], + ATTR_VERSION: values[ATTR_VERSION], + ATTR_LAST_VERSION: values[ATTR_VERSION], + ATTR_INSTALLED: i_version, + ATTR_DETACHED: addon in detached }) return data @@ -140,18 +201,33 @@ class AddonsData(Config): return addon_list @property - def list_removed(self): + def list_detached(self): """Return local addons they not support from repo.""" addon_list = set() for addon in self._system_data.keys(): - if addon not in self._current_data: + if addon not in self._addons_cache: addon_list.add(addon) return addon_list + @property + def list_repositories_api(self): + """Return list of addon repositories.""" + repositories = [] + + for slug, data in self._repositories_data.items(): + repositories.append({ + ATTR_SLUG: slug, + ATTR_NAME: data[ATTR_NAME], + ATTR_URL: data.get(ATTR_URL), + ATTR_MAINTAINER: data.get(ATTR_MAINTAINER), + }) + + return repositories + def exists_addon(self, addon): """Return True if a addon exists.""" - return addon in self._current_data or addon in self._system_data + return addon in self._addons_cache or addon in self._system_data def is_installed(self, addon): """Return True if a addon is installed.""" @@ -163,7 +239,7 @@ class AddonsData(Config): def set_addon_install(self, addon, version): """Set addon as installed.""" - self._system_data[addon] = copy.deepcopy(self._current_data[addon]) + self._system_data[addon] = copy.deepcopy(self._addons_cache[addon]) self._user_data[addon] = { ATTR_OPTIONS: {}, ATTR_VERSION: version, @@ -178,7 +254,7 @@ class AddonsData(Config): def set_addon_update(self, addon, version): """Update version of addon.""" - self._system_data[addon] = copy.deepcopy(self._current_data[addon]) + self._system_data[addon] = copy.deepcopy(self._addons_cache[addon]) self._user_data[addon][ATTR_VERSION] = version self.save() @@ -216,9 +292,9 @@ class AddonsData(Config): def get_last_version(self, addon): """Return version of addon.""" - if addon not in self._current_data: + if addon not in self._addons_cache: return self.version_installed(addon) - return self._current_data[addon][ATTR_VERSION] + return self._addons_cache[addon][ATTR_VERSION] def get_ports(self, addon): """Return ports of addon.""" @@ -226,10 +302,11 @@ class AddonsData(Config): def get_image(self, addon): """Return image name of addon.""" - addon_data = self._system_data.get(addon, self._current_data[addon]) + addon_data = self._system_data.get(addon, self._addons_cache[addon]) if ATTR_IMAGE not in addon_data: - return "{}/{}-addon-{}".format(DOCKER_REPO, self.arch, addon) + return "{}/{}-addon-{}".format( + DOCKER_REPO, self.arch, addon_data[ATTR_SLUG]) return addon_data[ATTR_IMAGE].format(arch=self.arch) @@ -251,15 +328,15 @@ class AddonsData(Config): def path_data(self, addon): """Return addon data path inside supervisor.""" - return "{}/{}".format(self.config.path_addons_data, addon) + return Path(self.config.path_addons_data, addon) - def path_data_docker(self, addon): + def path_extern_data(self, addon): """Return addon data path external for docker.""" - return "{}/{}".format(self.config.path_addons_data_docker, addon) + return PurePath(self.config.path_extern_addons_data, addon) def path_addon_options(self, addon): """Return path to addons options.""" - return "{}/options.json".format(self.path_data(addon)) + return Path(self.path_data, addon, "options.json") def write_addon_options(self, addon): """Return True if addon options is written to data.""" diff --git a/hassio/addons/git.py b/hassio/addons/git.py index 69d4df698..37b519fd5 100644 --- a/hassio/addons/git.py +++ b/hassio/addons/git.py @@ -1,7 +1,7 @@ """Init file for HassIO addons git.""" import asyncio import logging -import os +from pathlib import Path import shutil import git @@ -26,14 +26,14 @@ class AddonsRepo(object): async def load(self): """Init git addon repo.""" - if not os.path.isdir(self.path): + if not self.path.is_dir(): return await self.clone() async with self._lock: try: _LOGGER.info("Load addon %s repository", self.path) self.repo = await self.loop.run_in_executor( - None, git.Repo, self.path) + None, git.Repo, str(self.path)) except (git.InvalidGitRepositoryError, git.NoSuchPathError) as err: _LOGGER.error("Can't load %s repo: %s.", self.path, err) @@ -47,7 +47,7 @@ class AddonsRepo(object): try: _LOGGER.info("Clone addon %s repository", self.url) self.repo = await self.loop.run_in_executor( - None, git.Repo.clone_from, self.url, self.path) + None, git.Repo.clone_from, self.url, str(self.path)) except (git.InvalidGitRepositoryError, git.NoSuchPathError) as err: _LOGGER.error("Can't clone %s repo: %s.", self.url, err) @@ -88,18 +88,17 @@ class AddonsRepoCustom(AddonsRepo): def __init__(self, config, loop, url): """Initialize git hassio addon repository.""" - path = os.path.join( - config.path_addons_custom, get_hash_from_repository(url)) + path = Path(config.path_addons_git, get_hash_from_repository(url)) super().__init__(config, loop, path, url) def remove(self): """Remove a custom addon.""" - if os.path.isdir(self.path): + if self.path.is_dir(): _LOGGER.info("Remove custom addon repository %s", self.url) def log_err(funct, path, _): """Log error.""" _LOGGER.warning("Can't remove %s", path) - shutil.rmtree(self.path, onerror=log_err) + shutil.rmtree(str(self.path), onerror=log_err) diff --git a/hassio/addons/util.py b/hassio/addons/util.py index e33afdecf..152c28866 100644 --- a/hassio/addons/util.py +++ b/hassio/addons/util.py @@ -1,28 +1,21 @@ """Util addons functions.""" import hashlib -import pathlib import re RE_SLUGIFY = re.compile(r'[^a-z0-9_]+') RE_SHA1 = re.compile(r"[a-f0-9]{8}") -def get_hash_from_repository(repo): +def get_hash_from_repository(name): """Generate a hash from repository.""" - key = repo.lower().encode() + key = name.lower().encode() return hashlib.sha1(key).hexdigest()[:8] -def extract_hash_from_path(base_path, options_path): +def extract_hash_from_path(path): """Extract repo id from path.""" - base_dir = pathlib.PurePosixPath(base_path).parts[-1] + repo_dir = path.parts[-1] - dirlist = iter(pathlib.PurePosixPath(options_path).parts) - for obj in dirlist: - if obj != base_dir: - continue - - repo_dir = next(dirlist) - if not RE_SHA1.match(repo_dir): - return get_hash_from_repository(repo_dir) - return repo_dir + if not RE_SHA1.match(repo_dir): + return get_hash_from_repository(repo_dir) + return repo_dir diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 760aa6f18..b01820e7d 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -5,7 +5,7 @@ from ..const import ( ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, STARTUP_ONCE, STARTUP_AFTER, STARTUP_BEFORE, BOOT_AUTO, BOOT_MANUAL, ATTR_SCHEMA, ATTR_IMAGE, MAP_SSL, - MAP_CONFIG, MAP_ADDONS, MAP_BACKUP) + MAP_CONFIG, MAP_ADDONS, MAP_BACKUP, ATTR_URL, ATTR_MAINTAINER) V_STR = 'str' V_INT = 'int' @@ -40,6 +40,14 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ }, extra=vol.ALLOW_EXTRA) +# pylint: disable=no-value-for-parameter +SCHEMA_REPOSITORY_CONFIG = vol.Schema({ + vol.Required(ATTR_NAME): vol.Coerce(str), + vol.Optional(ATTR_URL): vol.Url(), + vol.Optional(ATTR_MAINTAINER): vol.Coerce(str), +}, extra=vol.ALLOW_EXTRA) + + def validate_options(raw_schema): """Validate schema.""" def validate(struct): diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index c80a92653..8fc841af3 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -7,7 +7,7 @@ import voluptuous as vol from .util import api_process, api_process_raw, api_validate from ..const import ( ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, - HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES) + HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES) _LOGGER = logging.getLogger(__name__) @@ -45,14 +45,17 @@ class APISupervisor(object): ATTR_VERSION: HASSIO_VERSION, ATTR_LAST_VERSION: self.config.last_hassio, ATTR_BETA_CHANNEL: self.config.upstream_beta, - ATTR_ADDONS: self.addons.list_api, + ATTR_ADDONS: self.addons.list_installed_api, ATTR_ADDONS_REPOSITORIES: self.config.addons_repositories, } @api_process async def available_addons(self, request): """Return information for all available addons.""" - return self.addons.list_api + return { + ATTR_ADDONS: self.addons.list_all_api, + ATTR_REPOSITORIES: self.addons.list_repositories_api, + } @api_process async def options(self, request): @@ -67,12 +70,15 @@ class APISupervisor(object): old = set(self.config.addons_repositories) # add new repositories - for url in set(new - old): - await self.addons.add_custom_repository(url) + tasks = [self.addons.add_git_repository(url) for url in + set(new - old)] + if tasks: + await asyncio.shield( + asyncio.wait(tasks, loop=self.loop), loop=self.loop) # remove old repositories for url in set(old - new): - self.addons.drop_custom_repository(url) + self.addons.drop_git_repository(url) return True diff --git a/hassio/api/util.py b/hassio/api/util.py index cb5161673..5204e9079 100644 --- a/hassio/api/util.py +++ b/hassio/api/util.py @@ -30,7 +30,7 @@ def api_process(method): except RuntimeError as err: return api_return_error(message=str(err)) - if isinstance(answer, (dict, list)): + if isinstance(answer, dict): return api_return_ok(data=answer) elif answer: return api_return_ok() diff --git a/hassio/bootstrap.py b/hassio/bootstrap.py index 261ec41c9..b55b63f59 100644 --- a/hassio/bootstrap.py +++ b/hassio/bootstrap.py @@ -1,7 +1,6 @@ """Bootstrap HassIO.""" import logging import os -import stat import signal from colorlog import ColoredFormatter @@ -17,26 +16,37 @@ def initialize_system_data(websession): config = CoreConfig(websession) # homeassistant config folder - if not os.path.isdir(config.path_config): + if not config.path_config.is_dir(): _LOGGER.info( "Create Home-Assistant config folder %s", config.path_config) - os.mkdir(config.path_config) + config.path_config.mkdir() # homeassistant ssl folder - if not os.path.isdir(config.path_ssl): + if not config.path_ssl.is_dir(): _LOGGER.info("Create Home-Assistant ssl folder %s", config.path_ssl) - os.mkdir(config.path_ssl) + config.path_ssl.mkdir() # homeassistant addon data folder - if not os.path.isdir(config.path_addons_data): + if not config.path_addons_data.is_dir(): _LOGGER.info("Create Home-Assistant addon data folder %s", config.path_addons_data) - os.mkdir(config.path_addons_data) + config.path_addons_data.mkdir(parents=True) - if not os.path.isdir(config.path_addons_custom): - _LOGGER.info("Create Home-Assistant addon custom folder %s", - config.path_addons_custom) - os.mkdir(config.path_addons_custom) + if not config.path_addons_local.is_dir(): + _LOGGER.info("Create Home-Assistant addon local repository folder %s", + config.path_addons_local) + config.path_addons_local.mkdir(parents=True) + + if not config.path_addons_git.is_dir(): + _LOGGER.info("Create Home-Assistant addon git repositories folder %s", + config.path_addons_git) + config.path_addons_git.mkdir(parents=True) + + # homeassistant backup folder + if not config.path_backup.is_dir(): + _LOGGER.info("Create Home-Assistant backup folder %s", + config.path_backup) + config.path_backup.mkdir() return config @@ -76,8 +86,7 @@ def check_environment(): _LOGGER.fatal("Can't find %s in env!", key) return False - mode = os.stat(SOCKET_DOCKER)[stat.ST_MODE] - if not stat.S_ISSOCK(mode): + if not SOCKET_DOCKER.is_socket(): _LOGGER.fatal("Can't find docker socket!") return False diff --git a/hassio/config.py b/hassio/config.py index b176ea6a5..2e7b8f8a3 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -2,6 +2,7 @@ import logging import json import os +from pathlib import Path, PurePath import voluptuous as vol from voluptuous.humanize import humanize_error @@ -12,19 +13,20 @@ from .tools import ( _LOGGER = logging.getLogger(__name__) -HOMEASSISTANT_CONFIG = "{}/homeassistant" +HOMEASSISTANT_CONFIG = PurePath("homeassistant") HOMEASSISTANT_LAST = 'homeassistant_last' -HASSIO_SSL = "{}/ssl" +HASSIO_SSL = PurePath("ssl") HASSIO_LAST = 'hassio_last' HASSIO_CLEANUP = 'hassio_cleanup' -ADDONS_REPO = "{}/addons" -ADDONS_DATA = "{}/addons_data" -ADDONS_CUSTOM = "{}/addons_custom" +ADDONS_CORE = PurePath("addons/core") +ADDONS_LOCAL = PurePath("addons/local") +ADDONS_GIT = PurePath("addons/git") +ADDONS_DATA = PurePath("addons/data") ADDONS_CUSTOM_LIST = 'addons_custom_list' -BACKUP_DATA = "{}/backup" +BACKUP_DATA = PurePath("backup") UPSTREAM_BETA = 'upstream_beta' @@ -47,21 +49,21 @@ class Config(object): def __init__(self, config_file): """Initialize config object.""" - self._filename = config_file + self._file = config_file self._data = {} # init or load data - if os.path.isfile(self._filename): + if self._file.is_file(): try: - self._data = read_json_file(self._filename) + self._data = read_json_file(self._file) except (OSError, json.JSONDecodeError): - _LOGGER.warning("Can't read %s", self._filename) + _LOGGER.warning("Can't read %s", self._file) self._data = {} def save(self): """Store data to config file.""" - if not write_json_file(self._filename, self._data): - _LOGGER.error("Can't store config in %s", self._filename) + if not write_json_file(self._file, self._data): + _LOGGER.error("Can't store config in %s", self._file) return False return True @@ -148,64 +150,69 @@ class CoreConfig(Config): return self._data.get(HASSIO_LAST) @property - def path_hassio_docker(self): + def path_extern_hassio(self): """Return hassio data path extern for docker.""" - return os.environ['SUPERVISOR_SHARE'] + return PurePath(os.environ['SUPERVISOR_SHARE']) @property - def path_config_docker(self): + def path_extern_config(self): """Return config path extern for docker.""" - return HOMEASSISTANT_CONFIG.format(self.path_hassio_docker) + return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG)) @property def path_config(self): """Return config path inside supervisor.""" - return HOMEASSISTANT_CONFIG.format(HASSIO_SHARE) + return Path(HASSIO_SHARE, HOMEASSISTANT_CONFIG) @property - def path_ssl_docker(self): + def path_extern_ssl(self): """Return SSL path extern for docker.""" - return HASSIO_SSL.format(self.path_hassio_docker) + return str(PurePath(self.path_extern_hassio, HASSIO_SSL)) @property def path_ssl(self): """Return SSL path inside supervisor.""" - return HASSIO_SSL.format(HASSIO_SHARE) + return Path(HASSIO_SHARE, HASSIO_SSL) @property - def path_addons_repo(self): - """Return git repo path for addons.""" - return ADDONS_REPO.format(HASSIO_SHARE) + def path_addons_core(self): + """Return git path for core addons.""" + return Path(HASSIO_SHARE, ADDONS_CORE) @property - def path_addons_custom(self): + def path_addons_git(self): + """Return path for git addons.""" + return Path(HASSIO_SHARE, ADDONS_GIT) + + @property + def path_addons_local(self): """Return path for customs addons.""" - return ADDONS_CUSTOM.format(HASSIO_SHARE) + return Path(HASSIO_SHARE, ADDONS_LOCAL) @property - def path_addons_custom_docker(self): + def path_extern_addons_local(self): """Return path for customs addons.""" - return ADDONS_CUSTOM.format(self.path_hassio_docker) + return str(PurePath(self.path_extern_hassio, ADDONS_LOCAL)) @property def path_addons_data(self): """Return root addon data folder.""" - return ADDONS_DATA.format(HASSIO_SHARE) + return Path(HASSIO_SHARE, ADDONS_DATA) @property - def path_addons_data_docker(self): + def path_extern_addons_data(self): """Return root addon data folder extern for docker.""" - return ADDONS_DATA.format(self.path_hassio_docker) + return str(PurePath(self.path_extern_hassio, ADDONS_DATA)) @property def path_backup(self): """Return root backup data folder.""" - return BACKUP_DATA.format(HASSIO_SHARE) + return Path(HASSIO_SHARE, BACKUP_DATA) @property - def path_backup_docker(self): + def path_extern_backup(self): """Return root backup data folder extern for docker.""" - return BACKUP_DATA.format(self.path_hassio_docker) + return str(PurePath(self.path_extern_hassio, BACKUP_DATA)) @property def addons_repositories(self): diff --git a/hassio/const.py b/hassio/const.py index 02c917ff3..f48178328 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,4 +1,6 @@ """Const file for HassIO.""" +from pathlib import Path + HASSIO_VERSION = '0.19' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' @@ -10,7 +12,7 @@ URL_HASSIO_ADDONS = 'https://github.com/home-assistant/hassio-addons' DOCKER_REPO = "homeassistant" -HASSIO_SHARE = "/data" +HASSIO_SHARE = Path("/data") RUN_UPDATE_INFO_TASKS = 28800 RUN_UPDATE_SUPERVISOR_TASKS = 29100 @@ -18,11 +20,11 @@ RUN_RELOAD_ADDONS_TASKS = 28800 RESTART_EXIT_CODE = 100 -FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE) -FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE) +FILE_HASSIO_ADDONS = Path(HASSIO_SHARE, "addons.json") +FILE_HASSIO_CONFIG = Path(HASSIO_SHARE, "config.json") -SOCKET_DOCKER = "/var/run/docker.sock" -SOCKET_HC = "/var/run/hassio-hc.sock" +SOCKET_DOCKER = Path("/var/run/docker.sock") +SOCKET_HC = Path("/var/run/hassio-hc.sock") JSON_RESULT = 'result' JSON_DATA = 'data' @@ -48,11 +50,15 @@ ATTR_PORTS = 'ports' ATTR_MAP = 'map' ATTR_OPTIONS = 'options' ATTR_INSTALLED = 'installed' -ATTR_DEDICATED = 'dedicated' +ATTR_DETACHED = 'detached' ATTR_STATE = 'state' ATTR_SCHEMA = 'schema' ATTR_IMAGE = 'image' ATTR_ADDONS_REPOSITORIES = 'addons_repositories' +ATTR_REPOSITORY = 'repository' +ATTR_REPOSITORIES = 'repositories' +ATTR_URL = 'url' +ATTR_MAINTAINER = 'maintainer' STARTUP_BEFORE = 'before' STARTUP_AFTER = 'after' diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index afdb6092a..1c3399bc6 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -30,31 +30,31 @@ class DockerAddon(DockerBase): def volumes(self): """Generate volumes for mappings.""" volumes = { - self.addons_data.path_data_docker(self.addon): { + self.addons_data.path_extern_data(self.addon): { 'bind': '/data', 'mode': 'rw' }} if self.addons_data.map_config(self.addon): volumes.update({ - self.config.path_config_docker: { + self.config.path_extern_config: { 'bind': '/config', 'mode': 'rw' }}) if self.addons_data.map_ssl(self.addon): volumes.update({ - self.config.path_ssl_docker: { + self.config.path_extern_ssl: { 'bind': '/ssl', 'mode': 'rw' }}) if self.addons_data.map_addons(self.addon): volumes.update({ - self.config.path_addons_custom_docker: { + self.config.path_extern_addons_local: { 'bind': '/addons', 'mode': 'rw' }}) if self.addons_data.map_backup(self.addon): volumes.update({ - self.config.path_backup_docker: { + self.config.path_extern_backup: { 'bind': '/backup', 'mode': 'rw' }}) diff --git a/hassio/dock/homeassistant.py b/hassio/dock/homeassistant.py index 635e43af4..0f314bb2d 100644 --- a/hassio/dock/homeassistant.py +++ b/hassio/dock/homeassistant.py @@ -45,9 +45,9 @@ class DockerHomeAssistant(DockerBase): 'HASSIO': self.config.api_endpoint, }, volumes={ - self.config.path_config_docker: + self.config.path_extern_config: {'bind': '/config', 'mode': 'rw'}, - self.config.path_ssl_docker: + self.config.path_extern_ssl: {'bind': '/ssl', 'mode': 'rw'}, }) diff --git a/hassio/tools.py b/hassio/tools.py index a012d3361..f44e2a9ad 100644 --- a/hassio/tools.py +++ b/hassio/tools.py @@ -78,7 +78,7 @@ def write_json_file(jsonfile, data): """Write a json file.""" try: json_str = json.dumps(data, indent=2) - with open(jsonfile, 'w') as conf_file: + with jsonfile.open('w') as conf_file: conf_file.write(json_str) except (OSError, json.JSONDecodeError): return False @@ -88,5 +88,5 @@ def write_json_file(jsonfile, data): def read_json_file(jsonfile): """Read a json file and return a dict.""" - with open(jsonfile, 'r') as cfile: + with jsonfile.open('r') as cfile: return json.loads(cfile.read())