diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 6b25b03f0..5b633946e 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -345,11 +345,14 @@ class Addon(AddonModel): """Save data of add-on.""" self.sys_addons.data.save_data() - def write_options(self): + async def write_options(self): """Return True if add-on options is written to data.""" schema = self.schema options = self.options + # Update secrets for validation + await self.sys_secrets.reload() + try: options = schema(options) write_json_file(self.path_options, options) @@ -467,7 +470,7 @@ class Addon(AddonModel): self.save_persist() # Options - self.write_options() + await self.write_options() # Sound if self.with_audio: diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 701dc3e39..50a026742 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -5,7 +5,6 @@ from typing import Any, Awaitable, Dict, List from aiohttp import web import voluptuous as vol -from voluptuous.humanize import humanize_error from ..addons import AnyAddon from ..docker.stats import DockerStats @@ -266,13 +265,16 @@ class APIAddons(CoreSysAttributes): """Store user options for add-on.""" addon: AnyAddon = self._extract_addon(request) + # Update secrets for validation + await self.sys_secrets.reload() + + # Extend schema with add-on specific validation addon_schema = SCHEMA_OPTIONS.extend( {vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema)} ) - body: Dict[str, Any] = await api_validate( - addon_schema, request, origin=[ATTR_OPTIONS] - ) + # Validate/Process Body + body = await api_validate(addon_schema, request, origin=[ATTR_OPTIONS]) if ATTR_OPTIONS in body: addon.options = body[ATTR_OPTIONS] if ATTR_BOOT in body: @@ -336,14 +338,6 @@ class APIAddons(CoreSysAttributes): def start(self, request: web.Request) -> Awaitable[None]: """Start add-on.""" addon: AnyAddon = self._extract_addon(request) - - # check options - options = addon.options - try: - addon.schema(options) - except vol.Invalid as ex: - raise APIError(humanize_error(options, ex)) from None - return asyncio.shield(addon.start()) @api_process diff --git a/hassio/api/utils.py b/hassio/api/utils.py index 126c2f40d..7bf05497f 100644 --- a/hassio/api/utils.py +++ b/hassio/api/utils.py @@ -1,26 +1,26 @@ """Init file for Hass.io util for RESTful API.""" import json import logging -from typing import Optional, List +from typing import Any, Dict, List, Optional from aiohttp import web import voluptuous as vol from voluptuous.humanize import humanize_error from ..const import ( - JSON_RESULT, + CONTENT_TYPE_BINARY, JSON_DATA, JSON_MESSAGE, - RESULT_OK, + JSON_RESULT, RESULT_ERROR, - CONTENT_TYPE_BINARY, + RESULT_OK, ) -from ..exceptions import HassioError, APIError, APIForbidden +from ..exceptions import APIError, APIForbidden, HassioError _LOGGER: logging.Logger = logging.getLogger(__name__) -def json_loads(data): +def json_loads(data: Any) -> Dict[str, Any]: """Extract json from string with support for '' and None.""" if not data: return {} @@ -78,23 +78,23 @@ def api_process_raw(content): return wrap_method -def api_return_error(message=None): +def api_return_error(message: Optional[str] = None) -> web.Response: """Return an API error message.""" return web.json_response( {JSON_RESULT: RESULT_ERROR, JSON_MESSAGE: message}, status=400 ) -def api_return_ok(data=None): +def api_return_ok(data: Optional[Dict[str, Any]] = None) -> web.Response: """Return an API ok answer.""" return web.json_response({JSON_RESULT: RESULT_OK, JSON_DATA: data or {}}) async def api_validate( schema: vol.Schema, request: web.Request, origin: Optional[List[str]] = None -): +) -> Dict[str, Any]: """Validate request data with schema.""" - data = await request.json(loads=json_loads) + data: Dict[str, Any] = await request.json(loads=json_loads) try: data_validated = schema(data) except vol.Invalid as ex: diff --git a/hassio/const.py b/hassio/const.py index ad608f0da..ece99c808 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -2,7 +2,7 @@ from pathlib import Path from ipaddress import ip_network -HASSIO_VERSION = "186" +HASSIO_VERSION = "187" URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" diff --git a/hassio/docker/homeassistant.py b/hassio/docker/homeassistant.py index 778c497bb..3c4ad1c32 100644 --- a/hassio/docker/homeassistant.py +++ b/hassio/docker/homeassistant.py @@ -127,7 +127,9 @@ class DockerHomeAssistant(DockerInterface): """ try: docker_container = self.sys_docker.containers.get(self.name) - docker_image = self.sys_docker.images.get(self.image) + docker_image = self.sys_docker.images.get( + f"{self.image}:{self.sys_homeassistant.version}" + ) except docker.errors.DockerException: return False diff --git a/hassio/secrets.py b/hassio/secrets.py index 33ff792df..6b9401c8e 100644 --- a/hassio/secrets.py +++ b/hassio/secrets.py @@ -1,11 +1,13 @@ """Handle Home Assistant secrets to add-ons.""" -from typing import Dict -from pathlib import Path +from datetime import timedelta import logging +from pathlib import Path +from typing import Dict from ruamel.yaml import YAML, YAMLError from .coresys import CoreSys, CoreSysAttributes +from .utils import AsyncThrottle _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -38,6 +40,7 @@ class SecretsManager(CoreSysAttributes): """Reload secrets.""" await self._read_secrets() + @AsyncThrottle(timedelta(seconds=60)) async def _read_secrets(self): """Read secrets.yaml into memory.""" if not self.path_secrets.exists(): diff --git a/hassio/tasks.py b/hassio/tasks.py index 95cb695a2..2b2bc322d 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -16,10 +16,9 @@ RUN_UPDATE_DNS = 30100 RUN_RELOAD_ADDONS = 10800 RUN_RELOAD_SNAPSHOTS = 72000 -RUN_RELOAD_HOST = 72000 +RUN_RELOAD_HOST = 7600 RUN_RELOAD_UPDATER = 7200 RUN_RELOAD_INGRESS = 930 -RUN_RELOAD_SECRETS = 940 RUN_WATCHDOG_HOMEASSISTANT_DOCKER = 15 RUN_WATCHDOG_HOMEASSISTANT_API = 300 @@ -78,11 +77,6 @@ class Tasks(CoreSysAttributes): self.sys_ingress.reload, RUN_RELOAD_INGRESS ) ) - self.jobs.add( - self.sys_scheduler.register_task( - self.sys_secrets.reload, RUN_RELOAD_SECRETS - ) - ) # Watchdog self.jobs.add(