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:
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user