Add more addons functions. (#91)

* Add more addons functions.

* fix lint

* fix lint p2

* Allow more customable network settings

* fix lint

* change point of validate

* fix lint

* fix handling

* fix lint & validate data before write
This commit is contained in:
Pascal Vizeli 2017-07-06 23:40:49 +02:00 committed by GitHub
parent f52d1c4509
commit 7186f5a8c0
14 changed files with 175 additions and 62 deletions

9
API.md
View File

@ -303,6 +303,7 @@ Output the raw docker log
{ {
"name": "xy bla", "name": "xy bla",
"description": "description", "description": "description",
"auto_update": "bool",
"url": "null|url of addon", "url": "null|url of addon",
"detached": "bool", "detached": "bool",
"repository": "12345678|null", "repository": "12345678|null",
@ -312,6 +313,8 @@ Output the raw docker log
"boot": "auto|manual", "boot": "auto|manual",
"build": "bool", "build": "bool",
"options": "{}", "options": "{}",
"network": "{}|null",
"host_network": "bool"
} }
``` ```
@ -319,10 +322,16 @@ Output the raw docker log
```json ```json
{ {
"boot": "auto|manual", "boot": "auto|manual",
"auto_update": "bool",
"network": {
"CONTAINER": "port|[ip, port]"
},
"options": {}, "options": {},
} }
``` ```
For reset custom network settings, set it `null`.
- POST `/addons/{addon}/start` - POST `/addons/{addon}/start`
- POST `/addons/{addon}/stop` - POST `/addons/{addon}/stop`

View File

@ -12,15 +12,14 @@ import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from .validate import ( from .validate import (
validate_options, SCHEMA_ADDON_USER, SCHEMA_ADDON_SYSTEM, validate_options, SCHEMA_ADDON_SNAPSHOT, MAP_VOLUME)
SCHEMA_ADDON_SNAPSHOT, MAP_VOLUME)
from ..const import ( from ..const import (
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_BOOT, ATTR_MAP, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_BOOT, ATTR_MAP,
ATTR_OPTIONS, ATTR_PORTS, ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY, ATTR_OPTIONS, ATTR_PORTS, ATTR_SCHEMA, ATTR_IMAGE, ATTR_REPOSITORY,
ATTR_URL, ATTR_ARCH, ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT, ATTR_URL, ATTR_ARCH, ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT,
ATTR_HOST_NETWORK, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_STARTUP, ATTR_HOST_NETWORK, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_STARTUP,
STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM,
ATTR_STATE) ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK)
from .util import check_installed from .util import check_installed
from ..dock.addon import DockerAddon from ..dock.addon import DockerAddon
from ..tools import write_json_file, read_json_file from ..tools import write_json_file, read_json_file
@ -45,21 +44,8 @@ class Addon(object):
async def load(self): async def load(self):
"""Async initialize of object.""" """Async initialize of object."""
if self.is_installed: if self.is_installed:
self._validate_system_user()
await self.addon_docker.attach() await self.addon_docker.attach()
def _validate_system_user(self):
"""Validate internal data they read from file."""
for data, schema in ((self.data.system, SCHEMA_ADDON_SYSTEM),
(self.data.user, SCHEMA_ADDON_USER)):
try:
data[self._id] = schema(data[self._id])
except vol.Invalid as err:
_LOGGER.warning("Can't validate addon load %s -> %s", self._id,
humanize_error(data[self._id], err))
except KeyError:
pass
@property @property
def slug(self): def slug(self):
"""Return slug/id of addon.""" """Return slug/id of addon."""
@ -141,11 +127,27 @@ class Addon(object):
self.data.user[self._id][ATTR_BOOT] = value self.data.user[self._id][ATTR_BOOT] = value
self.data.save() self.data.save()
@property
def auto_update(self):
"""Return if auto update is enable."""
return self.data.user[self._id][ATTR_AUTO_UPDATE]
@auto_update.setter
def auto_update(self, value):
"""Set auto update."""
self.data.user[self._id][ATTR_AUTO_UPDATE] = value
self.data.save()
@property @property
def name(self): def name(self):
"""Return name of addon.""" """Return name of addon."""
return self._mesh[ATTR_NAME] return self._mesh[ATTR_NAME]
@property
def timeout(self):
"""Return timeout of addon for docker stop."""
return self._mesh[ATTR_TIMEOUT]
@property @property
def description(self): def description(self):
"""Return description of addon.""" """Return description of addon."""
@ -171,7 +173,28 @@ class Addon(object):
@property @property
def ports(self): def ports(self):
"""Return ports of addon.""" """Return ports of addon."""
return self._mesh.get(ATTR_PORTS) if self.network_mode != 'bridge' or ATTR_PORTS not in self._mesh:
return
if not self.is_installed or \
ATTR_NETWORK not in self.data.user[self._id]:
return self._mesh[ATTR_PORTS]
return self.data.user[self._id][ATTR_NETWORK]
@ports.setter
def ports(self, value):
"""Set custom ports of addon."""
if value is None:
self.data.user[self._id].pop(ATTR_NETWORK, None)
else:
new_ports = {}
for container_port, host_port in value.items():
if container_port in self._mesh.get(ATTR_PORTS, {}):
new_ports[container_port] = host_port
self.data.user[self._id][ATTR_NETWORK] = new_ports
self.data.save()
@property @property
def network_mode(self): def network_mode(self):

View File

@ -10,7 +10,8 @@ from voluptuous.humanize import humanize_error
from .util import extract_hash_from_path from .util import extract_hash_from_path
from .validate import ( from .validate import (
SCHEMA_ADDON, SCHEMA_REPOSITORY_CONFIG, MAP_VOLUME) SCHEMA_ADDON_CONFIG, SCHEMA_ADDON_FILE, SCHEMA_REPOSITORY_CONFIG,
MAP_VOLUME)
from ..const import ( from ..const import (
FILE_HASSIO_ADDONS, ATTR_VERSION, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON, FILE_HASSIO_ADDONS, ATTR_VERSION, ATTR_SLUG, ATTR_REPOSITORY, ATTR_LOCATON,
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM) REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_USER, ATTR_SYSTEM)
@ -40,13 +41,23 @@ class Data(object):
_LOGGER.warning("Can't read %s", self._file) _LOGGER.warning("Can't read %s", self._file)
self._data = {} self._data = {}
# init data # validate
if not self._data: try:
self._data[ATTR_USER] = {} self._data = SCHEMA_ADDON_FILE(self._data)
self._data[ATTR_SYSTEM] = {} except vol.Invalid as ex:
_LOGGER.error("Can't parse addons.json -> %s",
humanize_error(self._data, ex))
def save(self): def save(self):
"""Store data to config file.""" """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): if not write_json_file(self._file, self._data):
_LOGGER.error("Can't store config in %s", self._file) _LOGGER.error("Can't store config in %s", self._file)
return False return False
@ -127,7 +138,7 @@ class Data(object):
addon_config = read_json_file(addon) addon_config = read_json_file(addon)
# validate # validate
addon_config = SCHEMA_ADDON(addon_config) addon_config = SCHEMA_ADDON_CONFIG(addon_config)
# Generate slug # Generate slug
addon_slug = "{}_{}".format( addon_slug = "{}_{}".format(

View File

@ -8,7 +8,9 @@ from ..const import (
ATTR_IMAGE, ATTR_URL, ATTR_MAINTAINER, ATTR_ARCH, ATTR_DEVICES, ATTR_IMAGE, ATTR_URL, ATTR_MAINTAINER, ATTR_ARCH, ATTR_DEVICES,
ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64,
ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_USER, ATTR_STATE, ATTR_SYSTEM, ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_USER, ATTR_STATE, ATTR_SYSTEM,
STATE_STARTED, STATE_STOPPED, ATTR_LOCATON, ATTR_REPOSITORY) STATE_STARTED, STATE_STOPPED, ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT,
ATTR_NETWORK, ATTR_AUTO_UPDATE)
from ..validate import NETWORK_PORT, DOCKER_PORTS
MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$" MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$"
@ -19,8 +21,9 @@ V_FLOAT = 'float'
V_BOOL = 'bool' V_BOOL = 'bool'
V_EMAIL = 'email' V_EMAIL = 'email'
V_URL = 'url' V_URL = 'url'
V_PORT = 'port'
ADDON_ELEMENT = vol.In([V_STR, V_INT, V_FLOAT, V_BOOL, V_EMAIL, V_URL]) ADDON_ELEMENT = vol.In([V_STR, V_INT, V_FLOAT, V_BOOL, V_EMAIL, V_URL, V_PORT])
ARCH_ALL = [ ARCH_ALL = [
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386 ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
@ -31,16 +34,6 @@ PRIVILEGE_ALL = [
] ]
def check_network(data):
"""Validate network settings."""
host_network = data[ATTR_HOST_NETWORK]
if ATTR_PORTS in data and host_network:
raise vol.Invalid("Hostnetwork & ports are not allow!")
return data
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_ADDON_CONFIG = vol.Schema({ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Required(ATTR_NAME): vol.Coerce(str), vol.Required(ATTR_NAME): vol.Coerce(str),
@ -54,7 +47,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
STARTUP_INITIALIZE]), STARTUP_INITIALIZE]),
vol.Required(ATTR_BOOT): vol.Required(ATTR_BOOT):
vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_PORTS): dict, vol.Optional(ATTR_PORTS): DOCKER_PORTS,
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")], vol.Optional(ATTR_DEVICES): [vol.Match(r"^(.*):(.*):([rwm]{1,3})$")],
vol.Optional(ATTR_TMPFS): vol.Optional(ATTR_TMPFS):
@ -69,8 +62,10 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
]) ])
}, False), }, False),
vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"), vol.Optional(ATTR_IMAGE): vol.Match(r"\w*/\w*"),
vol.Optional(ATTR_TIMEOUT, default=10):
vol.All(vol.Coerce(int), vol.Range(min=10, max=120))
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SCHEMA_ADDON = vol.Schema(vol.All(SCHEMA_ADDON_CONFIG, check_network))
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_REPOSITORY_CONFIG = vol.Schema({ SCHEMA_REPOSITORY_CONFIG = vol.Schema({
@ -80,11 +75,14 @@ SCHEMA_REPOSITORY_CONFIG = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
# pylint: disable=no-value-for-parameter
SCHEMA_ADDON_USER = vol.Schema({ SCHEMA_ADDON_USER = vol.Schema({
vol.Required(ATTR_VERSION): vol.Coerce(str), vol.Required(ATTR_VERSION): vol.Coerce(str),
vol.Required(ATTR_OPTIONS): dict, vol.Required(ATTR_OPTIONS): dict,
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
vol.Optional(ATTR_BOOT): vol.Optional(ATTR_BOOT):
vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_NETWORK): DOCKER_PORTS,
}) })
@ -94,6 +92,16 @@ SCHEMA_ADDON_SYSTEM = SCHEMA_ADDON_CONFIG.extend({
}) })
SCHEMA_ADDON_FILE = vol.Schema({
vol.Optional(ATTR_USER, default={}): {
vol.Coerce(str): SCHEMA_ADDON_USER,
},
vol.Optional(ATTR_SYSTEM, default={}): {
vol.Coerce(str): SCHEMA_ADDON_SYSTEM,
}
})
SCHEMA_ADDON_SNAPSHOT = vol.Schema({ SCHEMA_ADDON_SNAPSHOT = vol.Schema({
vol.Required(ATTR_USER): SCHEMA_ADDON_USER, vol.Required(ATTR_USER): SCHEMA_ADDON_USER,
vol.Required(ATTR_SYSTEM): SCHEMA_ADDON_SYSTEM, vol.Required(ATTR_SYSTEM): SCHEMA_ADDON_SYSTEM,
@ -150,6 +158,8 @@ def _single_validate(typ, value, key):
return vol.Email()(value) return vol.Email()(value)
elif typ == V_URL: elif typ == V_URL:
return vol.Url()(value) return vol.Url()(value)
elif typ == V_PORT:
return NETWORK_PORT(value)
raise vol.Invalid("Fatal error for {} type {}.".format(key, typ)) raise vol.Invalid("Fatal error for {} type {}.".format(key, typ))
except ValueError: except ValueError:

View File

@ -9,7 +9,9 @@ from .util import api_process, api_process_raw, api_validate
from ..const import ( from ..const import (
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS,
ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY, ATTR_URL, ATTR_DESCRIPTON, ATTR_DETACHED, ATTR_NAME, ATTR_REPOSITORY,
ATTR_BUILD, BOOT_AUTO, BOOT_MANUAL) ATTR_BUILD, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_HOST_NETWORK,
BOOT_AUTO, BOOT_MANUAL)
from ..validate import DOCKER_PORTS
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -17,8 +19,11 @@ SCHEMA_VERSION = vol.Schema({
vol.Optional(ATTR_VERSION): vol.Coerce(str), vol.Optional(ATTR_VERSION): vol.Coerce(str),
}) })
# pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema({
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]) vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_NETWORK): vol.Any(None, DOCKER_PORTS),
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
}) })
@ -51,6 +56,7 @@ class APIAddons(object):
ATTR_NAME: addon.name, ATTR_NAME: addon.name,
ATTR_DESCRIPTON: addon.description, ATTR_DESCRIPTON: addon.description,
ATTR_VERSION: addon.version_installed, ATTR_VERSION: addon.version_installed,
ATTR_AUTO_UPDATE: addon.auto_update,
ATTR_REPOSITORY: addon.repository, ATTR_REPOSITORY: addon.repository,
ATTR_LAST_VERSION: addon.last_version, ATTR_LAST_VERSION: addon.last_version,
ATTR_STATE: await addon.state(), ATTR_STATE: await addon.state(),
@ -59,6 +65,8 @@ class APIAddons(object):
ATTR_URL: addon.url, ATTR_URL: addon.url,
ATTR_DETACHED: addon.is_detached, ATTR_DETACHED: addon.is_detached,
ATTR_BUILD: addon.need_build, ATTR_BUILD: addon.need_build,
ATTR_NETWORK: addon.ports,
ATTR_HOST_NETWORK: addon.network_mode == 'host',
} }
@api_process @api_process
@ -76,6 +84,10 @@ class APIAddons(object):
addon.options = body[ATTR_OPTIONS] addon.options = body[ATTR_OPTIONS]
if ATTR_BOOT in body: if ATTR_BOOT in body:
addon.boot = body[ATTR_BOOT] addon.boot = body[ATTR_BOOT]
if ATTR_AUTO_UPDATE in body:
addon.auto_update = body[ATTR_AUTO_UPDATE]
if ATTR_NETWORK in body:
addon.ports = body[ATTR_NETWORK]
return True return True

View File

@ -6,12 +6,13 @@ import voluptuous as vol
from .util import api_process, api_process_raw, api_validate 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
from ..validate import HASS_DEVICES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema({
vol.Optional(ATTR_DEVICES): [vol.Match(r"^[^/]*$")], vol.Optional(ATTR_DEVICES): HASS_DEVICES,
}) })
SCHEMA_VERSION = vol.Schema({ SCHEMA_VERSION = vol.Schema({

View File

@ -11,6 +11,7 @@ from voluptuous.humanize import humanize_error
from .const import FILE_HASSIO_CONFIG, HASSIO_DATA from .const import FILE_HASSIO_CONFIG, HASSIO_DATA
from .tools import ( from .tools import (
fetch_last_versions, write_json_file, read_json_file, validate_timezone) fetch_last_versions, write_json_file, read_json_file, validate_timezone)
from .validate import HASS_DEVICES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -49,7 +50,7 @@ SCHEMA_CONFIG = vol.Schema({
vol.Optional(API_ENDPOINT): vol.Coerce(str), vol.Optional(API_ENDPOINT): vol.Coerce(str),
vol.Optional(TIMEZONE, default='UTC'): validate_timezone, vol.Optional(TIMEZONE, default='UTC'): validate_timezone,
vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str), vol.Optional(HOMEASSISTANT_LAST): vol.Coerce(str),
vol.Optional(HOMEASSISTANT_DEVICES, default=[]): [vol.Coerce(str)], vol.Optional(HOMEASSISTANT_DEVICES, default=[]): HASS_DEVICES,
vol.Optional(HASSIO_LAST): vol.Coerce(str), vol.Optional(HASSIO_LAST): vol.Coerce(str),
vol.Optional(ADDONS_CUSTOM_LIST, default=[]): [vol.Url()], vol.Optional(ADDONS_CUSTOM_LIST, default=[]): [vol.Url()],
vol.Optional(SECURITY_INITIALIZE, default=False): vol.Boolean(), vol.Optional(SECURITY_INITIALIZE, default=False): vol.Boolean(),

View File

@ -14,6 +14,7 @@ HASSIO_DATA = Path("/data")
RUN_UPDATE_INFO_TASKS = 28800 RUN_UPDATE_INFO_TASKS = 28800
RUN_UPDATE_SUPERVISOR_TASKS = 29100 RUN_UPDATE_SUPERVISOR_TASKS = 29100
RUN_UPDATE_ADDONS_TASKS = 57600
RUN_RELOAD_ADDONS_TASKS = 28800 RUN_RELOAD_ADDONS_TASKS = 28800
RUN_RELOAD_SNAPSHOTS_TASKS = 72000 RUN_RELOAD_SNAPSHOTS_TASKS = 72000
RUN_WATCHDOG_HOMEASSISTANT = 15 RUN_WATCHDOG_HOMEASSISTANT = 15
@ -81,6 +82,7 @@ ATTR_BUILD = 'build'
ATTR_DEVICES = 'devices' ATTR_DEVICES = 'devices'
ATTR_ENVIRONMENT = 'environment' ATTR_ENVIRONMENT = 'environment'
ATTR_HOST_NETWORK = 'host_network' ATTR_HOST_NETWORK = 'host_network'
ATTR_NETWORK = 'network'
ATTR_TMPFS = 'tmpfs' ATTR_TMPFS = 'tmpfs'
ATTR_PRIVILEGED = 'privileged' ATTR_PRIVILEGED = 'privileged'
ATTR_USER = 'user' ATTR_USER = 'user'
@ -90,6 +92,8 @@ ATTR_HOMEASSISTANT = 'homeassistant'
ATTR_FOLDERS = 'folders' ATTR_FOLDERS = 'folders'
ATTR_SIZE = 'size' ATTR_SIZE = 'size'
ATTR_TYPE = 'type' ATTR_TYPE = 'type'
ATTR_TIMEOUT = 'timeout'
ATTR_AUTO_UPDATE = 'auto_update'
STARTUP_INITIALIZE = 'initialize' STARTUP_INITIALIZE = 'initialize'
STARTUP_BEFORE = 'before' STARTUP_BEFORE = 'before'

View File

@ -12,14 +12,14 @@ from .const import (
SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS, SOCKET_DOCKER, RUN_UPDATE_INFO_TASKS, RUN_RELOAD_ADDONS_TASKS,
RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT, RUN_UPDATE_SUPERVISOR_TASKS, RUN_WATCHDOG_HOMEASSISTANT,
RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE, RUN_CLEANUP_API_SESSIONS, STARTUP_AFTER, STARTUP_BEFORE,
STARTUP_INITIALIZE, RUN_RELOAD_SNAPSHOTS_TASKS) STARTUP_INITIALIZE, RUN_RELOAD_SNAPSHOTS_TASKS, RUN_UPDATE_ADDONS_TASKS)
from .scheduler import Scheduler from .scheduler import Scheduler
from .dock.homeassistant import DockerHomeAssistant from .dock.homeassistant import DockerHomeAssistant
from .dock.supervisor import DockerSupervisor from .dock.supervisor import DockerSupervisor
from .snapshots import SnapshotsManager from .snapshots import SnapshotsManager
from .tasks import ( from .tasks import (
hassio_update, homeassistant_watchdog, homeassistant_setup, hassio_update, homeassistant_watchdog, homeassistant_setup,
api_sessions_cleanup) api_sessions_cleanup, addons_update)
from .tools import get_local_ip, fetch_timezone from .tools import get_local_ip, fetch_timezone
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -108,6 +108,8 @@ class HassIO(object):
# schedule addon update task # schedule addon update task
self.scheduler.register_task( self.scheduler.register_task(
self.addons.reload, RUN_RELOAD_ADDONS_TASKS, now=True) self.addons.reload, RUN_RELOAD_ADDONS_TASKS, now=True)
self.scheduler.register_task(
addons_update(self.loop, self.addons), RUN_UPDATE_ADDONS_TASKS)
# schedule self update task # schedule self update task
self.scheduler.register_task( self.scheduler.register_task(

View File

@ -13,12 +13,13 @@ _LOGGER = logging.getLogger(__name__)
class DockerBase(object): class DockerBase(object):
"""Docker hassio wrapper.""" """Docker hassio wrapper."""
def __init__(self, config, loop, dock, image=None): def __init__(self, config, loop, dock, image=None, timeout=30):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.dock = dock self.dock = dock
self.image = image self.image = image
self.timeout = timeout
self.version = None self.version = None
self.arch = None self.arch = None
self._lock = asyncio.Lock(loop=loop) self._lock = asyncio.Lock(loop=loop)
@ -192,7 +193,7 @@ class DockerBase(object):
if container.status == 'running': if container.status == 'running':
_LOGGER.info("Stop %s docker application", self.image) _LOGGER.info("Stop %s docker application", self.image)
with suppress(docker.errors.DockerException): with suppress(docker.errors.DockerException):
container.stop(timeout=15) container.stop(timeout=self.timeout)
with suppress(docker.errors.DockerException): with suppress(docker.errors.DockerException):
_LOGGER.info("Clean %s docker application", self.image) _LOGGER.info("Clean %s docker application", self.image)
@ -312,7 +313,7 @@ class DockerBase(object):
_LOGGER.info("Restart %s", self.image) _LOGGER.info("Restart %s", self.image)
try: try:
container.restart(timeout=30) container.restart(timeout=self.timeout)
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't restart %s -> %s", self.image, err) _LOGGER.warning("Can't restart %s -> %s", self.image, err)
return False return False

View File

@ -1,5 +1,4 @@
"""Init file for HassIO addon docker object.""" """Init file for HassIO addon docker object."""
from contextlib import suppress
import logging import logging
from pathlib import Path from pathlib import Path
import shutil import shutil
@ -21,7 +20,7 @@ class DockerAddon(DockerBase):
def __init__(self, config, loop, dock, addon): def __init__(self, config, loop, dock, addon):
"""Initialize docker homeassistant wrapper.""" """Initialize docker homeassistant wrapper."""
super().__init__( super().__init__(
config, loop, dock, image=addon.image) config, loop, dock, image=addon.image, timeout=addon.timeout)
self.addon = addon self.addon = addon
@property @property
@ -249,17 +248,5 @@ class DockerAddon(DockerBase):
Addons prepare some thing on start and that is normaly not repeatable. Addons prepare some thing on start and that is normaly not repeatable.
Need run inside executor. Need run inside executor.
""" """
try: self._stop()
container = self.dock.containers.get(self.name)
except docker.errors.DockerException:
return False
# for restart it need to run!
if container.status != 'running':
return False
_LOGGER.info("Restart %s", self.image)
with suppress(docker.errors.DockerException):
container.stop(timeout=15)
return self._run() return self._run()

View File

@ -7,6 +7,7 @@ from ..const import (
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_DEVICES, ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_DEVICES,
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
SNAPSHOT_FULL, SNAPSHOT_PARTIAL) SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
from ..validate import HASS_DEVICES
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL] ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
@ -18,7 +19,7 @@ SCHEMA_SNAPSHOT = vol.Schema({
vol.Required(ATTR_DATE): vol.Coerce(str), vol.Required(ATTR_DATE): vol.Coerce(str),
vol.Required(ATTR_HOMEASSISTANT): vol.Schema({ vol.Required(ATTR_HOMEASSISTANT): vol.Schema({
vol.Required(ATTR_VERSION): vol.Coerce(str), vol.Required(ATTR_VERSION): vol.Coerce(str),
vol.Optional(ATTR_DEVICES, default=[]): [vol.Match(r"^[^/]*$")], vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES,
}), }),
vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)], vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)],
vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({ vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({

View File

@ -18,6 +18,25 @@ def api_sessions_cleanup(config):
return _api_sessions_cleanup return _api_sessions_cleanup
def addons_update(loop, addons):
"""Create scheduler task for auto update addons."""
async def _addons_update():
"""Check if a update is available of a addon and update it."""
tasks = []
for addon in addons.list_addons:
if not addon.is_installed:
continue
if addon.version_installed != addon.version:
tasks.append(addon.update())
if tasks:
_LOGGER.info("Addon auto update process %d tasks", len(tasks))
await asyncio.wait(tasks, loop=loop)
return _addons_update
def hassio_update(config, supervisor, websession): def hassio_update(config, supervisor, websession):
"""Create scheduler task for update of supervisor hassio.""" """Create scheduler task for update of supervisor hassio."""
async def _hassio_update(): async def _hassio_update():

32
hassio/validate.py Normal file
View File

@ -0,0 +1,32 @@
"""Validate functions."""
import voluptuous as vol
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
HASS_DEVICES = [vol.Match(r"^[^/]*$")]
def convert_to_docker_ports(data):
"""Convert data into docker port list."""
# dynamic ports
if data is None:
return
# single port
if isinstance(data, int):
return NETWORK_PORT(data)
# port list
if isinstance(data, list) and len(data) > 2:
return vol.Schema([NETWORK_PORT])(data)
# ip port mapping
if isinstance(data, list) and len(data) == 2:
return (vol.Coerce(str)(data[0]), NETWORK_PORT(data[1]))
raise vol.Invalid("Can't validate docker host settings")
DOCKER_PORTS = vol.Schema({
vol.All(vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")):
convert_to_docker_ports,
})