1
0
mirror of https://github.com/home-assistant/supervisor.git synced 2025-12-24 20:35:55 +00:00

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

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