More schema options (#187)

* Extend the addon schema options

* convert range to float

* convert match to string

* fix lint

* cleanup

* fix lint

* fix options name
This commit is contained in:
Pascal Vizeli 2017-09-12 19:38:26 +02:00 committed by GitHub
parent 8249f042c0
commit 3b26136636
3 changed files with 41 additions and 22 deletions

View File

@ -13,7 +13,7 @@ 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_SNAPSHOT, MAP_VOLUME) validate_options, SCHEMA_ADDON_SNAPSHOT, RE_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,
@ -28,7 +28,6 @@ from ..tools import write_json_file, read_json_file
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
RE_VOLUME = re.compile(MAP_VOLUME)
RE_WEBUI = re.compile(r"^(.*\[HOST\]:)\[PORT:(\d+)\](.*)$") RE_WEBUI = re.compile(r"^(.*\[HOST\]:)\[PORT:(\d+)\](.*)$")
MERGE_OPT = Merger([(dict, ['merge'])], ['override'], ['override']) MERGE_OPT = Merger([(dict, ['merge'])], ['override'], ['override'])

View File

@ -3,15 +3,13 @@ import copy
import logging import logging
import json import json
from pathlib import Path from pathlib import Path
import re
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error 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_CONFIG, SCHEMA_ADDON_FILE, SCHEMA_REPOSITORY_CONFIG, 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)
@ -19,8 +17,6 @@ from ..tools import JsonConfig, read_json_file
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
RE_VOLUME = re.compile(MAP_VOLUME)
class Data(JsonConfig): class Data(JsonConfig):
"""Hold data for addons inside HassIO.""" """Hold data for addons inside HassIO."""

View File

@ -1,4 +1,6 @@
"""Validate addons options schema.""" """Validate addons options schema."""
import re
import voluptuous as vol import voluptuous as vol
from ..const import ( from ..const import (
@ -15,7 +17,7 @@ from ..const import (
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL
MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$" RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$")
V_STR = 'str' V_STR = 'str'
V_INT = 'int' V_INT = 'int'
@ -24,8 +26,18 @@ V_BOOL = 'bool'
V_EMAIL = 'email' V_EMAIL = 'email'
V_URL = 'url' V_URL = 'url'
V_PORT = 'port' V_PORT = 'port'
V_MATCH = 'match'
ADDON_ELEMENT = vol.In([V_STR, V_INT, V_FLOAT, V_BOOL, V_EMAIL, V_URL, V_PORT]) RE_SCHEMA_ELEMENT = re.compile(
r"^(?:"
r"|str|bool|email|url|port"
r"|int(?:\((?P<i_min>\d+)?,(?P<i_max>\d+)?\))?"
r"|float(?:\((?P<f_min>[\d\.]+)?,(?P<f_max>[\d\.]+)?\))?"
r"|match\((?P<match>.*)\)"
r")$"
)
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
ARCH_ALL = [ ARCH_ALL = [
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386 ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
@ -71,16 +83,16 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
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):
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"), vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
vol.Optional(ATTR_MAP, default=[]): [vol.Match(MAP_VOLUME)], vol.Optional(ATTR_MAP, default=[]): [vol.Match(RE_VOLUME)],
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)}, vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)], vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(), vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(), vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(),
vol.Required(ATTR_OPTIONS): dict, vol.Required(ATTR_OPTIONS): dict,
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({ vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
vol.Coerce(str): vol.Any(ADDON_ELEMENT, [ vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
vol.Any(ADDON_ELEMENT, {vol.Coerce(str): ADDON_ELEMENT}) vol.Any(SCHEMA_ELEMENT, {vol.Coerce(str): SCHEMA_ELEMENT})
], vol.Schema({vol.Coerce(str): ADDON_ELEMENT})) ], vol.Schema({vol.Coerce(str): SCHEMA_ELEMENT}))
}), 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.Optional(ATTR_TIMEOUT, default=10):
@ -172,20 +184,32 @@ def _single_validate(typ, value, key):
if value is None: if value is None:
raise vol.Invalid("Missing required option '{}'.".format(key)) raise vol.Invalid("Missing required option '{}'.".format(key))
if typ == V_STR: # parse extend data from type
match = RE_SCHEMA_ELEMENT.match(typ)
# prepare range
range_args = {}
for group_name in ('i_min', 'i_max', 'f_min', 'f_max'):
group_value = match.group(group_name)
if group_value:
range_args[group_name[2:]] = float(group_value)
if typ.startswith(V_STR):
return str(value) return str(value)
elif typ == V_INT: elif typ.startswith(V_INT):
return int(value) return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
elif typ == V_FLOAT: elif typ.startswith(V_FLOAT):
return float(value) return vol.All(vol.Coerce(float), vol.Range(**range_args))(value)
elif typ == V_BOOL: elif typ.startswith(V_BOOL):
return vol.Boolean()(value) return vol.Boolean()(value)
elif typ == V_EMAIL: elif typ.startswith(V_EMAIL):
return vol.Email()(value) return vol.Email()(value)
elif typ == V_URL: elif typ.startswith(V_URL):
return vol.Url()(value) return vol.Url()(value)
elif typ == V_PORT: elif typ.startswith(V_PORT):
return NETWORK_PORT(value) return NETWORK_PORT(value)
elif typ.startswith(V_MATCH):
return vol.Match(match.group('match'))(str(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: