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
This commit is contained in:
Pascal Vizeli 2017-05-02 00:02:55 +02:00 committed by GitHub
parent c76408e4e8
commit ff640c598d
14 changed files with 269 additions and 153 deletions

37
API.md
View File

@ -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 <fla@dld.ch>"
}
]
}
```
- POST `/supervisor/update`

View File

@ -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):

View File

@ -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."""

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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'

View File

@ -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'
}})

View File

@ -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'},
})

View File

@ -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())