mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 06:36:30 +00:00
Convert homeassistant into a dict (snapshot) (#90)
* Convert homeassistant into a dict * fix lint * fix bugs * cleanup code * fix cleanup
This commit is contained in:
parent
e2a29b7290
commit
0ac96c207e
7
API.md
7
API.md
@ -180,10 +180,13 @@ Return QR-Code
|
|||||||
{
|
{
|
||||||
"slug": "SNAPSHOT ID",
|
"slug": "SNAPSHOT ID",
|
||||||
"type": "full|partial",
|
"type": "full|partial",
|
||||||
"name": "custom snapshot name",
|
"name": "custom snapshot name / description",
|
||||||
"date": "ISO",
|
"date": "ISO",
|
||||||
"size": "SIZE_IN_MB",
|
"size": "SIZE_IN_MB",
|
||||||
"homeassistant": "INSTALLED_HASS_VERSION",
|
"homeassistant": {
|
||||||
|
"version": "INSTALLED_HASS_VERSION",
|
||||||
|
"devices": []
|
||||||
|
},
|
||||||
"addons": [
|
"addons": [
|
||||||
{
|
{
|
||||||
"slug": "ADDON_SLUG",
|
"slug": "ADDON_SLUG",
|
||||||
|
@ -434,14 +434,13 @@ class Addon(object):
|
|||||||
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
|
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
|
||||||
|
|
||||||
# check version / restore image
|
# check version / restore image
|
||||||
if data[ATTR_VERSION] != self.addon_docker.version:
|
version = data[ATTR_VERSION]
|
||||||
|
if version != self.addon_docker.version:
|
||||||
image_file = Path(temp, "image.tar")
|
image_file = Path(temp, "image.tar")
|
||||||
if image_file.is_file():
|
if image_file.is_file():
|
||||||
if not await self.addon_docker.import_image(image_file):
|
await self.addon_docker.import_image(image_file, version)
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
if not await self.addon_docker.install(data[ATTR_VERSION]):
|
if await self.addon_docker.install(version):
|
||||||
return False
|
|
||||||
await self.addon_docker.cleanup()
|
await self.addon_docker.cleanup()
|
||||||
else:
|
else:
|
||||||
await self.addon_docker.stop()
|
await self.addon_docker.stop()
|
||||||
|
@ -8,7 +8,8 @@ from .util import api_process, api_validate
|
|||||||
from ..snapshots.validate import ALL_FOLDERS
|
from ..snapshots.validate import ALL_FOLDERS
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_NAME, ATTR_SLUG, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
|
ATTR_NAME, ATTR_SLUG, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
|
||||||
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE)
|
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE,
|
||||||
|
ATTR_DEVICES)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -69,7 +70,10 @@ class APISnapshots(object):
|
|||||||
ATTR_NAME: snapshot.name,
|
ATTR_NAME: snapshot.name,
|
||||||
ATTR_DATE: snapshot.date,
|
ATTR_DATE: snapshot.date,
|
||||||
ATTR_SIZE: snapshot.size,
|
ATTR_SIZE: snapshot.size,
|
||||||
ATTR_HOMEASSISTANT: snapshot.homeassistant,
|
ATTR_HOMEASSISTANT: {
|
||||||
|
ATTR_VERSION: snapshot.homeassistant_version,
|
||||||
|
ATTR_DEVICES: snapshot.homeassistant_devices,
|
||||||
|
},
|
||||||
ATTR_ADDONS: self._addons_list(snapshot),
|
ATTR_ADDONS: self._addons_list(snapshot),
|
||||||
ATTR_REPOSITORIES: snapshot.repositories,
|
ATTR_REPOSITORIES: snapshot.repositories,
|
||||||
ATTR_FOLDERS: snapshot.folders,
|
ATTR_FOLDERS: snapshot.folders,
|
||||||
|
@ -231,6 +231,9 @@ class DockerBase(object):
|
|||||||
_LOGGER.warning("Can't remove image %s -> %s", self.image, err)
|
_LOGGER.warning("Can't remove image %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# clean metadata
|
||||||
|
self.version = None
|
||||||
|
self.arch = None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def update(self, tag):
|
async def update(self, tag):
|
||||||
|
@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
import requests
|
||||||
|
|
||||||
from . import DockerBase
|
from . import DockerBase
|
||||||
from .util import dockerfile_template
|
from .util import dockerfile_template
|
||||||
@ -196,7 +197,7 @@ class DockerAddon(DockerBase):
|
|||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
image = self.dock.api.get_image("{}:latest".format(self.image))
|
image = self.dock.api.get_image(self.image)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't fetch image %s -> %s", self.image, err)
|
_LOGGER.error("Can't fetch image %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
@ -205,13 +206,14 @@ class DockerAddon(DockerBase):
|
|||||||
with tar_file.open("wb") as write_tar:
|
with tar_file.open("wb") as write_tar:
|
||||||
for chunk in image.stream():
|
for chunk in image.stream():
|
||||||
write_tar.write(chunk)
|
write_tar.write(chunk)
|
||||||
except OSError() as err:
|
except (OSError, requests.exceptions.ReadTimeout) as err:
|
||||||
_LOGGER.error("Can't write tar file %s -> %s", tar_file, err)
|
_LOGGER.error("Can't write tar file %s -> %s", tar_file, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def import_image(self, path):
|
async def import_image(self, path, tag):
|
||||||
"""Import a tar file as image."""
|
"""Import a tar file as image."""
|
||||||
if self._lock.locked():
|
if self._lock.locked():
|
||||||
_LOGGER.error("Can't excute import while a task is in progress")
|
_LOGGER.error("Can't excute import while a task is in progress")
|
||||||
@ -219,9 +221,9 @@ class DockerAddon(DockerBase):
|
|||||||
|
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
return await self.loop.run_in_executor(
|
return await self.loop.run_in_executor(
|
||||||
None, self._import_image, path)
|
None, self._import_image, path, tag)
|
||||||
|
|
||||||
def _import_image(self, tar_file):
|
def _import_image(self, tar_file, tag):
|
||||||
"""Import a tar file as image.
|
"""Import a tar file as image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
@ -231,10 +233,12 @@ class DockerAddon(DockerBase):
|
|||||||
self.dock.api.load_image(read_tar)
|
self.dock.api.load_image(read_tar)
|
||||||
|
|
||||||
image = self.dock.images.get(self.image)
|
image = self.dock.images.get(self.image)
|
||||||
|
image.tag(self.image, tag=tag)
|
||||||
except (docker.errors.DockerException, OSError) as err:
|
except (docker.errors.DockerException, OSError) as err:
|
||||||
_LOGGER.error("Can't import image %s -> %s", self.image, err)
|
_LOGGER.error("Can't import image %s -> %s", self.image, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info("Import image %s and tag %s", tar_file, tag)
|
||||||
self.process_metadata(image.attrs, force=True)
|
self.process_metadata(image.attrs, force=True)
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
return True
|
return True
|
||||||
|
@ -41,9 +41,15 @@ class SnapshotsManager(object):
|
|||||||
slug = create_slug(name, date_str)
|
slug = create_slug(name, date_str)
|
||||||
tar_file = Path(self.config.path_backup, "{}.tar".format(slug))
|
tar_file = Path(self.config.path_backup, "{}.tar".format(slug))
|
||||||
|
|
||||||
|
# init object
|
||||||
snapshot = Snapshot(self.config, self.loop, tar_file)
|
snapshot = Snapshot(self.config, self.loop, tar_file)
|
||||||
snapshot.create(slug, name, date_str, sys_type)
|
snapshot.create(slug, name, date_str, sys_type)
|
||||||
|
|
||||||
|
# set general data
|
||||||
|
snapshot.homeassistant_version = self.homeassistant.version
|
||||||
|
snapshot.homeassistant_devices = self.config.homeassistant_devices
|
||||||
|
snapshot.repositories = self.config.addons_repositories
|
||||||
|
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
async def reload(self):
|
async def reload(self):
|
||||||
@ -87,9 +93,6 @@ class SnapshotsManager(object):
|
|||||||
await self._lock.acquire()
|
await self._lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
snapshot.homeassistant = self.homeassistant.version
|
|
||||||
snapshot.repositories = self.config.addons_repositories
|
|
||||||
|
|
||||||
# snapshot addons
|
# snapshot addons
|
||||||
tasks = []
|
tasks = []
|
||||||
for addon in self.addons.list_addons:
|
for addon in self.addons.list_addons:
|
||||||
@ -110,7 +113,7 @@ class SnapshotsManager(object):
|
|||||||
self.snapshots[snapshot.slug] = snapshot
|
self.snapshots[snapshot.slug] = snapshot
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Full-Snapshot %s error -> %s", snapshot.slug, err)
|
_LOGGER.info("Full-Snapshot %s error -> %s", snapshot.slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -134,9 +137,6 @@ class SnapshotsManager(object):
|
|||||||
await self._lock.acquire()
|
await self._lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
snapshot.homeassistant = self.homeassistant.version
|
|
||||||
snapshot.repositories = self.config.addons_repositories
|
|
||||||
|
|
||||||
# snapshot addons
|
# snapshot addons
|
||||||
tasks = []
|
tasks = []
|
||||||
for slug in addons:
|
for slug in addons:
|
||||||
@ -158,7 +158,7 @@ class SnapshotsManager(object):
|
|||||||
self.snapshots[snapshot.slug] = snapshot
|
self.snapshots[snapshot.slug] = snapshot
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Partial-Snapshot %s error -> %s", snapshot.slug, err)
|
_LOGGER.info("Partial-Snapshot %s error -> %s", snapshot.slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -198,8 +198,10 @@ class SnapshotsManager(object):
|
|||||||
await snapshot.restore_folders()
|
await snapshot.restore_folders()
|
||||||
|
|
||||||
# start homeassistant restore
|
# start homeassistant restore
|
||||||
|
self.config.homeassistant_devices = \
|
||||||
|
snapshot.homeassistant_devices
|
||||||
task_hass = self.loop.create_task(
|
task_hass = self.loop.create_task(
|
||||||
self.homeassistant.update(snapshot.homeassistant))
|
self.homeassistant.update(snapshot.homeassistant_version))
|
||||||
|
|
||||||
# restore repositories
|
# restore repositories
|
||||||
await self.addons.load_repositories(snapshot.repositories)
|
await self.addons.load_repositories(snapshot.repositories)
|
||||||
@ -244,7 +246,7 @@ class SnapshotsManager(object):
|
|||||||
_LOGGER.info("Full-Restore %s done", snapshot.slug)
|
_LOGGER.info("Full-Restore %s done", snapshot.slug)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Full-Restore %s error -> %s", slug, err)
|
_LOGGER.info("Full-Restore %s error -> %s", slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -279,8 +281,10 @@ class SnapshotsManager(object):
|
|||||||
await snapshot.restore_folders(folders)
|
await snapshot.restore_folders(folders)
|
||||||
|
|
||||||
if homeassistant:
|
if homeassistant:
|
||||||
|
self.config.homeassistant_devices = \
|
||||||
|
snapshot.homeassistant_devices
|
||||||
tasks.append(self.homeassistant.update(
|
tasks.append(self.homeassistant.update(
|
||||||
snapshot.homeassistant))
|
snapshot.homeassistant_version))
|
||||||
|
|
||||||
for slug in addons:
|
for slug in addons:
|
||||||
addon = self.addons.get(slug)
|
addon = self.addons.get(slug)
|
||||||
@ -300,7 +304,7 @@ class SnapshotsManager(object):
|
|||||||
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
|
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (OSError, tarfile.TarError) as err:
|
except (OSError, ValueError, tarfile.TarError) as err:
|
||||||
_LOGGER.info("Partial-Restore %s error -> %s", slug, err)
|
_LOGGER.info("Partial-Restore %s error -> %s", slug, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from .validate import SCHEMA_SNAPSHOT, ALL_FOLDERS
|
|||||||
from .util import remove_folder
|
from .util import remove_folder
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_SLUG, ATTR_NAME, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
|
ATTR_SLUG, ATTR_NAME, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
|
||||||
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE)
|
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_DEVICES)
|
||||||
from ..tools import write_json_file
|
from ..tools import write_json_file
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -43,27 +43,27 @@ class Snapshot(object):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return snapshot name."""
|
"""Return snapshot name."""
|
||||||
return self._data.get(ATTR_NAME)
|
return self._data[ATTR_NAME]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date(self):
|
def date(self):
|
||||||
"""Return snapshot date."""
|
"""Return snapshot date."""
|
||||||
return self._data.get(ATTR_DATE)
|
return self._data[ATTR_DATE]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addons(self):
|
def addons(self):
|
||||||
"""Return snapshot date."""
|
"""Return snapshot date."""
|
||||||
return self._data.get(ATTR_ADDONS, [])
|
return self._data[ATTR_ADDONS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def folders(self):
|
def folders(self):
|
||||||
"""Return list of saved folders."""
|
"""Return list of saved folders."""
|
||||||
return self._data.get(ATTR_FOLDERS, [])
|
return self._data[ATTR_FOLDERS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repositories(self):
|
def repositories(self):
|
||||||
"""Return snapshot date."""
|
"""Return snapshot date."""
|
||||||
return self._data.get(ATTR_REPOSITORIES, [])
|
return self._data[ATTR_REPOSITORIES]
|
||||||
|
|
||||||
@repositories.setter
|
@repositories.setter
|
||||||
def repositories(self, value):
|
def repositories(self, value):
|
||||||
@ -71,14 +71,24 @@ class Snapshot(object):
|
|||||||
self._data[ATTR_REPOSITORIES] = value
|
self._data[ATTR_REPOSITORIES] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant(self):
|
def homeassistant_version(self):
|
||||||
"""Return snapshot homeassistant version."""
|
"""Return snapshot homeassistant version."""
|
||||||
return self._data.get(ATTR_HOMEASSISTANT)
|
return self._data[ATTR_HOMEASSISTANT].get(ATTR_VERSION)
|
||||||
|
|
||||||
@homeassistant.setter
|
@homeassistant_version.setter
|
||||||
def homeassistant(self, value):
|
def homeassistant_version(self, value):
|
||||||
"""Set snapshot homeassistant version."""
|
"""Set snapshot homeassistant version."""
|
||||||
self._data[ATTR_HOMEASSISTANT] = value
|
self._data[ATTR_HOMEASSISTANT][ATTR_VERSION] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def homeassistant_devices(self):
|
||||||
|
"""Return snapshot homeassistant devices."""
|
||||||
|
return self._data[ATTR_HOMEASSISTANT].get(ATTR_DEVICES)
|
||||||
|
|
||||||
|
@homeassistant_devices.setter
|
||||||
|
def homeassistant_devices(self, value):
|
||||||
|
"""Set snapshot homeassistant devices."""
|
||||||
|
self._data[ATTR_HOMEASSISTANT][ATTR_DEVICES] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
@ -96,6 +106,7 @@ class Snapshot(object):
|
|||||||
self._data[ATTR_TYPE] = sys_type
|
self._data[ATTR_TYPE] = sys_type
|
||||||
|
|
||||||
# init other constructs
|
# init other constructs
|
||||||
|
self._data[ATTR_HOMEASSISTANT] = {}
|
||||||
self._data[ATTR_ADDONS] = []
|
self._data[ATTR_ADDONS] = []
|
||||||
self._data[ATTR_REPOSITORIES] = []
|
self._data[ATTR_REPOSITORIES] = []
|
||||||
self._data[ATTR_FOLDERS] = []
|
self._data[ATTR_FOLDERS] = []
|
||||||
@ -159,6 +170,14 @@ class Snapshot(object):
|
|||||||
if self.tar_file.is_file() or exception_type is not None:
|
if self.tar_file.is_file() or exception_type is not None:
|
||||||
return self._tmp.cleanup()
|
return self._tmp.cleanup()
|
||||||
|
|
||||||
|
# validate data
|
||||||
|
try:
|
||||||
|
self._data = SCHEMA_SNAPSHOT(self._data)
|
||||||
|
except vol.Invalid as err:
|
||||||
|
_LOGGER.error("Invalid data for %s -> %s", self.tar_file,
|
||||||
|
humanize_error(self._data, err))
|
||||||
|
raise ValueError("Invalid config") from None
|
||||||
|
|
||||||
# new snapshot, build it
|
# new snapshot, build it
|
||||||
def _create_snapshot():
|
def _create_snapshot():
|
||||||
"""Create a new snapshot."""
|
"""Create a new snapshot."""
|
||||||
@ -166,10 +185,7 @@ class Snapshot(object):
|
|||||||
tar.add(self._tmp.name, arcname=".")
|
tar.add(self._tmp.name, arcname=".")
|
||||||
|
|
||||||
if write_json_file(Path(self._tmp.name, "snapshot.json"), self._data):
|
if write_json_file(Path(self._tmp.name, "snapshot.json"), self._data):
|
||||||
try:
|
|
||||||
await self.loop.run_in_executor(None, _create_snapshot)
|
await self.loop.run_in_executor(None, _create_snapshot)
|
||||||
except tarfile.TarError as err:
|
|
||||||
_LOGGER.error("Can't create tar %s", err)
|
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Can't write snapshot.json")
|
_LOGGER.error("Can't write snapshot.json")
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_NAME, ATTR_SLUG, ATTR_DATE,
|
ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_NAME, ATTR_SLUG, ATTR_DATE,
|
||||||
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, FOLDER_SHARE,
|
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_DEVICES,
|
||||||
FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, SNAPSHOT_FULL,
|
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
||||||
SNAPSHOT_PARTIAL)
|
SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
|
||||||
|
|
||||||
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
||||||
|
|
||||||
@ -16,12 +16,15 @@ SCHEMA_SNAPSHOT = vol.Schema({
|
|||||||
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
|
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
|
||||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||||
vol.Required(ATTR_DATE): vol.Coerce(str),
|
vol.Required(ATTR_DATE): vol.Coerce(str),
|
||||||
vol.Required(ATTR_HOMEASSISTANT): vol.Coerce(str),
|
vol.Required(ATTR_HOMEASSISTANT): vol.Schema({
|
||||||
vol.Required(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Required(ATTR_ADDONS): [vol.Schema({
|
vol.Optional(ATTR_DEVICES, default=[]): [vol.Match(r"^[^/]*$")],
|
||||||
|
}),
|
||||||
|
vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)],
|
||||||
|
vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({
|
||||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
})],
|
})],
|
||||||
vol.Required(ATTR_REPOSITORIES): [vol.Url()],
|
vol.Optional(ATTR_REPOSITORIES, default=[]): [vol.Url()],
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user