mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 22:56:31 +00:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
commit
cb15602814
5
API.md
5
API.md
@ -111,7 +111,8 @@ Output is the raw docker log.
|
||||
{
|
||||
"slug": "SLUG",
|
||||
"date": "ISO",
|
||||
"name": "Custom name"
|
||||
"name": "Custom name",
|
||||
"type": "full|partial"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -417,7 +418,7 @@ Get all available addons.
|
||||
}
|
||||
```
|
||||
|
||||
For reset custom network/audio settings, set it `null`.
|
||||
Reset custom network/audio/options, set it `null`.
|
||||
|
||||
- POST `/addons/{addon}/start`
|
||||
|
||||
|
@ -87,25 +87,25 @@ class Addon(CoreSysAttributes):
|
||||
ATTR_OPTIONS: {},
|
||||
ATTR_VERSION: version,
|
||||
}
|
||||
self._data.save()
|
||||
self._data.save_data()
|
||||
|
||||
def _set_uninstall(self):
|
||||
"""Set addon as uninstalled."""
|
||||
self._data.system.pop(self._id, None)
|
||||
self._data.user.pop(self._id, None)
|
||||
self._data.save()
|
||||
self._data.save_data()
|
||||
|
||||
def _set_update(self, version):
|
||||
"""Update version of addon."""
|
||||
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
||||
self._data.user[self._id][ATTR_VERSION] = version
|
||||
self._data.save()
|
||||
self._data.save_data()
|
||||
|
||||
def _restore_data(self, user, system):
|
||||
"""Restore data to addon."""
|
||||
self._data.user[self._id] = deepcopy(user)
|
||||
self._data.system[self._id] = deepcopy(system)
|
||||
self._data.save()
|
||||
self._data.save_data()
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
@ -120,8 +120,10 @@ class Addon(CoreSysAttributes):
|
||||
@options.setter
|
||||
def options(self, value):
|
||||
"""Store user addon options."""
|
||||
if value is None:
|
||||
self._data.user[self._id][ATTR_OPTIONS] = {}
|
||||
else:
|
||||
self._data.user[self._id][ATTR_OPTIONS] = deepcopy(value)
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def boot(self):
|
||||
@ -134,7 +136,6 @@ class Addon(CoreSysAttributes):
|
||||
def boot(self, value):
|
||||
"""Store user boot options."""
|
||||
self._data.user[self._id][ATTR_BOOT] = value
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def auto_update(self):
|
||||
@ -147,7 +148,6 @@ class Addon(CoreSysAttributes):
|
||||
def auto_update(self, value):
|
||||
"""Set auto update."""
|
||||
self._data.user[self._id][ATTR_AUTO_UPDATE] = value
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -225,8 +225,6 @@ class Addon(CoreSysAttributes):
|
||||
|
||||
self._data.user[self._id][ATTR_NETWORK] = new_ports
|
||||
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def webui(self):
|
||||
"""Return URL to webui or None."""
|
||||
@ -347,7 +345,6 @@ class Addon(CoreSysAttributes):
|
||||
self._data.user[self._id].pop(ATTR_AUDIO_OUTPUT, None)
|
||||
else:
|
||||
self._data.user[self._id][ATTR_AUDIO_OUTPUT] = value
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def audio_input(self):
|
||||
@ -367,7 +364,6 @@ class Addon(CoreSysAttributes):
|
||||
self._data.user[self._id].pop(ATTR_AUDIO_INPUT, None)
|
||||
else:
|
||||
self._data.user[self._id][ATTR_AUDIO_INPUT] = value
|
||||
self._data.save()
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
@ -448,6 +444,10 @@ class Addon(CoreSysAttributes):
|
||||
"""Return path to addon changelog."""
|
||||
return Path(self.path_location, 'CHANGELOG.md')
|
||||
|
||||
def save_data(self):
|
||||
"""Save data of addon."""
|
||||
self._addons.data.save_data()
|
||||
|
||||
def write_options(self):
|
||||
"""Return True if addon options is written to data."""
|
||||
schema = self.schema
|
||||
@ -455,10 +455,14 @@ class Addon(CoreSysAttributes):
|
||||
|
||||
try:
|
||||
schema(options)
|
||||
return write_json_file(self.path_options, options)
|
||||
write_json_file(self.path_options, options)
|
||||
except vol.Invalid as ex:
|
||||
_LOGGER.error("Addon %s have wrong options: %s", self._id,
|
||||
humanize_error(options, ex))
|
||||
except (OSError, json.JSONDecodeError) as err:
|
||||
_LOGGER.error("Addon %s can't write options: %s", self._id, err)
|
||||
else:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@ -654,8 +658,10 @@ class Addon(CoreSysAttributes):
|
||||
}
|
||||
|
||||
# store local configs/state
|
||||
if not write_json_file(Path(temp, "addon.json"), data):
|
||||
_LOGGER.error("Can't write addon.json for %s", self._id)
|
||||
try:
|
||||
write_json_file(Path(temp, "addon.json"), data)
|
||||
except (OSError, json.JSONDecodeError) as err:
|
||||
_LOGGER.error("Can't save meta for %s: %s", self._id, err)
|
||||
return False
|
||||
|
||||
# write into tarfile
|
||||
|
@ -10,18 +10,23 @@ from ..utils.json import JsonConfig
|
||||
class AddonBuild(JsonConfig, CoreSysAttributes):
|
||||
"""Handle build options for addons."""
|
||||
|
||||
def __init__(self, coresys, addon):
|
||||
def __init__(self, coresys, slug):
|
||||
"""Initialize addon builder."""
|
||||
self.coresys = coresys
|
||||
self.addon = addon
|
||||
self._id = slug
|
||||
|
||||
super().__init__(
|
||||
Path(addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
||||
Path(self.addon.path_location, 'build.json'), SCHEMA_BUILD_CONFIG)
|
||||
|
||||
def save(self):
|
||||
def save_data(self):
|
||||
"""Ignore save function."""
|
||||
pass
|
||||
|
||||
@property
|
||||
def addon(self):
|
||||
"""Return addon of build data."""
|
||||
return self._addons.get(self._id)
|
||||
|
||||
@property
|
||||
def base_image(self):
|
||||
"""Base images for this addon."""
|
||||
|
@ -159,4 +159,4 @@ class Data(JsonConfig, CoreSysAttributes):
|
||||
have_change = True
|
||||
|
||||
if have_change:
|
||||
self.save()
|
||||
self.save_data()
|
||||
|
@ -101,7 +101,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
||||
vol.Optional(ATTR_AUTO_UART, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_TMPFS):
|
||||
vol.Match(r"^size=(\d)*[kmg](,uid=\d{1,4})?(,rw)?$"),
|
||||
vol.Optional(ATTR_MAP, default=[]): [vol.Match(RE_VOLUME)],
|
||||
vol.Optional(ATTR_MAP, default=list): [vol.Match(RE_VOLUME)],
|
||||
vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)},
|
||||
vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)],
|
||||
vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(),
|
||||
@ -141,7 +141,7 @@ SCHEMA_BUILD_CONFIG = vol.Schema({
|
||||
vol.In(ARCH_ALL): vol.Match(r"(?:^[\w{}]+/)?[\-\w{}]+:[\.\-\w{}]+$"),
|
||||
}),
|
||||
vol.Optional(ATTR_SQUASH, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_ARGS, default={}): vol.Schema({
|
||||
vol.Optional(ATTR_ARGS, default=dict): vol.Schema({
|
||||
vol.Coerce(str): vol.Coerce(str)
|
||||
}),
|
||||
}, extra=vol.REMOVE_EXTRA)
|
||||
@ -152,7 +152,7 @@ SCHEMA_ADDON_USER = vol.Schema({
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||
vol.Match(r"^[0-9a-f]{32}$"),
|
||||
vol.Optional(ATTR_OPTIONS, default={}): dict,
|
||||
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_BOOT):
|
||||
vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
@ -169,10 +169,10 @@ SCHEMA_ADDON_SYSTEM = SCHEMA_ADDON_CONFIG.extend({
|
||||
|
||||
|
||||
SCHEMA_ADDON_FILE = vol.Schema({
|
||||
vol.Optional(ATTR_USER, default={}): {
|
||||
vol.Optional(ATTR_USER, default=dict): {
|
||||
vol.Coerce(str): SCHEMA_ADDON_USER,
|
||||
},
|
||||
vol.Optional(ATTR_SYSTEM, default={}): {
|
||||
vol.Optional(ATTR_SYSTEM, default=dict): {
|
||||
vol.Coerce(str): SCHEMA_ADDON_SYSTEM,
|
||||
}
|
||||
})
|
||||
|
@ -140,7 +140,7 @@ class APIAddons(CoreSysAttributes):
|
||||
addon = self._extract_addon(request)
|
||||
|
||||
addon_schema = SCHEMA_OPTIONS.extend({
|
||||
vol.Optional(ATTR_OPTIONS): addon.schema,
|
||||
vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema),
|
||||
})
|
||||
|
||||
body = await api_validate(addon_schema, request)
|
||||
@ -158,6 +158,7 @@ class APIAddons(CoreSysAttributes):
|
||||
if ATTR_AUDIO_OUTPUT in body:
|
||||
addon.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
addon.save_data()
|
||||
return True
|
||||
|
||||
@api_process
|
||||
|
@ -11,7 +11,7 @@ from ..const import (
|
||||
ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX,
|
||||
ATTR_BLK_READ, ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..validate import NETWORK_PORT
|
||||
from ..validate import NETWORK_PORT, DOCKER_IMAGE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -22,7 +22,7 @@ SCHEMA_OPTIONS = vol.Schema({
|
||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'):
|
||||
vol.Any(None, vol.Coerce(str)),
|
||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
||||
vol.Any(None, vol.Coerce(str)),
|
||||
vol.Any(None, DOCKER_IMAGE),
|
||||
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||
vol.Optional(ATTR_SSL): vol.Boolean(),
|
||||
@ -75,7 +75,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
if ATTR_WATCHDOG in body:
|
||||
self._homeassistant.watchdog = body[ATTR_WATCHDOG]
|
||||
|
||||
self._homeassistant.save()
|
||||
self._homeassistant.save_data()
|
||||
return True
|
||||
|
||||
@api_process
|
||||
|
@ -49,6 +49,7 @@ class APIHost(CoreSysAttributes):
|
||||
if ATTR_AUDIO_INPUT in body:
|
||||
self._config.audio_input = body[ATTR_AUDIO_INPUT]
|
||||
|
||||
self._config.save_data()
|
||||
return True
|
||||
|
||||
@api_process_hostcontrol
|
||||
|
@ -18,8 +18,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_RESTORE_PARTIAL = vol.Schema({
|
||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||
vol.Optional(ATTR_ADDONS): [vol.Coerce(str)],
|
||||
vol.Optional(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
|
||||
vol.Optional(ATTR_ADDONS):
|
||||
vol.All([vol.Coerce(str)], vol.Unique()),
|
||||
vol.Optional(ATTR_FOLDERS):
|
||||
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||
})
|
||||
|
||||
SCHEMA_SNAPSHOT_FULL = vol.Schema({
|
||||
@ -27,8 +29,10 @@ SCHEMA_SNAPSHOT_FULL = vol.Schema({
|
||||
})
|
||||
|
||||
SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({
|
||||
vol.Optional(ATTR_ADDONS): [vol.Coerce(str)],
|
||||
vol.Optional(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
|
||||
vol.Optional(ATTR_ADDONS):
|
||||
vol.All([vol.Coerce(str)], vol.Unique()),
|
||||
vol.Optional(ATTR_FOLDERS):
|
||||
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||
})
|
||||
|
||||
|
||||
@ -51,6 +55,7 @@ class APISnapshots(CoreSysAttributes):
|
||||
ATTR_SLUG: snapshot.slug,
|
||||
ATTR_NAME: snapshot.name,
|
||||
ATTR_DATE: snapshot.date,
|
||||
ATTR_TYPE: snapshot.sys_type,
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -13,14 +13,14 @@ from ..const import (
|
||||
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
||||
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..validate import validate_timezone, WAIT_BOOT
|
||||
from ..validate import validate_timezone, WAIT_BOOT, REPOSITORIES
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCHEMA_OPTIONS = vol.Schema({
|
||||
# pylint: disable=no-value-for-parameter
|
||||
vol.Optional(ATTR_BETA_CHANNEL): vol.Boolean(),
|
||||
vol.Optional(ATTR_ADDONS_REPOSITORIES): [vol.Url()],
|
||||
vol.Optional(ATTR_ADDONS_REPOSITORIES): REPOSITORIES,
|
||||
vol.Optional(ATTR_TIMEZONE): validate_timezone,
|
||||
vol.Optional(ATTR_WAIT_BOOT): WAIT_BOOT,
|
||||
})
|
||||
@ -84,8 +84,8 @@ class APISupervisor(CoreSysAttributes):
|
||||
new = set(body[ATTR_ADDONS_REPOSITORIES])
|
||||
await asyncio.shield(self._addons.load_repositories(new))
|
||||
|
||||
self._updater.save()
|
||||
self._config.save()
|
||||
self._updater.save_data()
|
||||
self._config.save_data()
|
||||
return True
|
||||
|
||||
@api_process
|
||||
|
@ -45,7 +45,6 @@ class CoreConfig(JsonConfig):
|
||||
def timezone(self, value):
|
||||
"""Set system timezone."""
|
||||
self._data[ATTR_TIMEZONE] = value
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def wait_boot(self):
|
||||
@ -56,7 +55,6 @@ class CoreConfig(JsonConfig):
|
||||
def wait_boot(self, value):
|
||||
"""Set wait boot time."""
|
||||
self._data[ATTR_WAIT_BOOT] = value
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def last_boot(self):
|
||||
@ -72,7 +70,6 @@ class CoreConfig(JsonConfig):
|
||||
def last_boot(self, value):
|
||||
"""Set last boot datetime."""
|
||||
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def path_hassio(self):
|
||||
@ -170,7 +167,6 @@ class CoreConfig(JsonConfig):
|
||||
return
|
||||
|
||||
self._data[ATTR_ADDONS_CUSTOM_LIST].append(repo)
|
||||
self.save()
|
||||
|
||||
def drop_addon_repository(self, repo):
|
||||
"""Remove a custom repository from list."""
|
||||
@ -178,7 +174,6 @@ class CoreConfig(JsonConfig):
|
||||
return
|
||||
|
||||
self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo)
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def audio_output(self):
|
||||
@ -189,7 +184,6 @@ class CoreConfig(JsonConfig):
|
||||
def audio_output(self, value):
|
||||
"""Set ALSA audio output card,dev."""
|
||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def audio_input(self):
|
||||
@ -200,4 +194,3 @@ class CoreConfig(JsonConfig):
|
||||
def audio_input(self, value):
|
||||
"""Set ALSA audio input card,dev."""
|
||||
self._data[ATTR_AUDIO_INPUT] = value
|
||||
self.save()
|
||||
|
@ -2,7 +2,7 @@
|
||||
from pathlib import Path
|
||||
from ipaddress import ip_network
|
||||
|
||||
HASSIO_VERSION = '0.82'
|
||||
HASSIO_VERSION = '0.83'
|
||||
|
||||
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
|
||||
'hassio/{}/version.json')
|
||||
|
@ -26,7 +26,7 @@ class DockerAddon(DockerInterface):
|
||||
|
||||
@property
|
||||
def addon(self):
|
||||
"""Return name of docker image."""
|
||||
"""Return addon of docker image."""
|
||||
return self._addons.get(self._id)
|
||||
|
||||
@property
|
||||
@ -269,7 +269,7 @@ class DockerAddon(DockerInterface):
|
||||
|
||||
Need run inside executor.
|
||||
"""
|
||||
build_env = AddonBuild(self.coresys, self.addon)
|
||||
build_env = AddonBuild(self.coresys, self._id)
|
||||
|
||||
_LOGGER.info("Start build %s:%s", self.image, tag)
|
||||
try:
|
||||
|
@ -53,7 +53,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
def api_port(self, value):
|
||||
"""Set network port for home-assistant instance."""
|
||||
self._data[ATTR_PORT] = value
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def api_password(self):
|
||||
|
@ -112,14 +112,15 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
_LOGGER.info("Full-Snapshot %s store folders", snapshot.slug)
|
||||
await snapshot.store_folders()
|
||||
|
||||
_LOGGER.info("Full-Snapshot %s done", snapshot.slug)
|
||||
self.snapshots_obj[snapshot.slug] = snapshot
|
||||
return True
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Full-Snapshot %s error: %s", snapshot.slug, err)
|
||||
return False
|
||||
|
||||
else:
|
||||
_LOGGER.info("Full-Snapshot %s done", snapshot.slug)
|
||||
self.snapshots_obj[snapshot.slug] = snapshot
|
||||
return True
|
||||
|
||||
finally:
|
||||
self._scheduler.suspend = False
|
||||
self.lock.release()
|
||||
@ -157,14 +158,15 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
snapshot.slug, folders)
|
||||
await snapshot.store_folders(folders)
|
||||
|
||||
_LOGGER.info("Partial-Snapshot %s done", snapshot.slug)
|
||||
self.snapshots_obj[snapshot.slug] = snapshot
|
||||
return True
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Partial-Snapshot %s error: %s", snapshot.slug, err)
|
||||
return False
|
||||
|
||||
else:
|
||||
_LOGGER.info("Partial-Snapshot %s done", snapshot.slug)
|
||||
self.snapshots_obj[snapshot.slug] = snapshot
|
||||
return True
|
||||
|
||||
finally:
|
||||
self._scheduler.suspend = False
|
||||
self.lock.release()
|
||||
@ -229,7 +231,7 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
if addon:
|
||||
tasks.append(addon.uninstall())
|
||||
else:
|
||||
_LOGGER.warning("Can't remove addon %s", slug)
|
||||
_LOGGER.warning("Can't remove addon %s", snapshot.slug)
|
||||
|
||||
for slug in restore_addons:
|
||||
addon = self._addons.get(slug)
|
||||
@ -249,13 +251,14 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
await task_hass
|
||||
await self._homeassistant.run()
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Full-Restore %s error: %s", snapshot.slug, err)
|
||||
return False
|
||||
|
||||
else:
|
||||
_LOGGER.info("Full-Restore %s done", snapshot.slug)
|
||||
return True
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Full-Restore %s error: %s", slug, err)
|
||||
return False
|
||||
|
||||
finally:
|
||||
self._scheduler.suspend = False
|
||||
self.lock.release()
|
||||
@ -298,7 +301,8 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
if addon:
|
||||
tasks.append(snapshot.export_addon(addon))
|
||||
else:
|
||||
_LOGGER.warning("Can't restore addon %s", slug)
|
||||
_LOGGER.warning("Can't restore addon %s",
|
||||
snapshot.slug)
|
||||
|
||||
if tasks:
|
||||
_LOGGER.info("Partial-Restore %s run %d tasks",
|
||||
@ -308,13 +312,14 @@ class SnapshotsManager(CoreSysAttributes):
|
||||
# make sure homeassistant run agen
|
||||
await self._homeassistant.run()
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Partial-Restore %s error: %s", snapshot.slug, err)
|
||||
return False
|
||||
|
||||
else:
|
||||
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
|
||||
return True
|
||||
|
||||
except (OSError, ValueError, tarfile.TarError) as err:
|
||||
_LOGGER.info("Partial-Restore %s error: %s", slug, err)
|
||||
return False
|
||||
|
||||
finally:
|
||||
self._scheduler.suspend = False
|
||||
self.lock.release()
|
||||
|
@ -244,11 +244,12 @@ class Snapshot(CoreSysAttributes):
|
||||
with tarfile.open(self.tar_file, "w:") as tar:
|
||||
tar.add(self._tmp.name, arcname=".")
|
||||
|
||||
if write_json_file(Path(self._tmp.name, "snapshot.json"), self._data):
|
||||
try:
|
||||
write_json_file(Path(self._tmp.name, "snapshot.json"), self._data)
|
||||
await self._loop.run_in_executor(None, _create_snapshot)
|
||||
else:
|
||||
_LOGGER.error("Can't write snapshot.json")
|
||||
|
||||
except (OSError, json.JSONDecodeError) as err:
|
||||
_LOGGER.error("Can't write snapshot: %s", err)
|
||||
finally:
|
||||
self._tmp.cleanup()
|
||||
|
||||
async def import_addon(self, addon):
|
||||
@ -280,7 +281,7 @@ class Snapshot(CoreSysAttributes):
|
||||
|
||||
async def store_folders(self, folder_list=None):
|
||||
"""Backup hassio data into snapshot."""
|
||||
folder_list = folder_list or ALL_FOLDERS
|
||||
folder_list = set(folder_list or ALL_FOLDERS)
|
||||
|
||||
def _folder_save(name):
|
||||
"""Intenal function to snapshot a folder."""
|
||||
@ -293,8 +294,8 @@ class Snapshot(CoreSysAttributes):
|
||||
with tarfile.open(snapshot_tar, "w:gz",
|
||||
compresslevel=1) as tar_file:
|
||||
tar_file.add(origin_dir, arcname=".")
|
||||
_LOGGER.info("Snapshot folder %s done", name)
|
||||
|
||||
_LOGGER.info("Snapshot folder %s done", name)
|
||||
self._data[ATTR_FOLDERS].append(name)
|
||||
except (tarfile.TarError, OSError) as err:
|
||||
_LOGGER.warning("Can't snapshot folder %s: %s", name, err)
|
||||
@ -307,7 +308,7 @@ class Snapshot(CoreSysAttributes):
|
||||
|
||||
async def restore_folders(self, folder_list=None):
|
||||
"""Backup hassio data into snapshot."""
|
||||
folder_list = folder_list or ALL_FOLDERS
|
||||
folder_list = set(folder_list or self.folders)
|
||||
|
||||
def _folder_restore(name):
|
||||
"""Intenal function to restore a folder."""
|
||||
@ -365,7 +366,7 @@ class Snapshot(CoreSysAttributes):
|
||||
self._homeassistant.api_password = self.homeassistant_password
|
||||
|
||||
# save
|
||||
self._homeassistant.save()
|
||||
self._homeassistant.save_data()
|
||||
|
||||
def store_repositories(self):
|
||||
"""Store repository list into snapshot."""
|
||||
|
@ -9,31 +9,42 @@ from ..const import (
|
||||
ATTR_LAST_VERSION,
|
||||
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
|
||||
SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
|
||||
from ..validate import NETWORK_PORT
|
||||
from ..validate import NETWORK_PORT, REPOSITORIES, DOCKER_IMAGE
|
||||
|
||||
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
|
||||
|
||||
|
||||
def unique_addons(addons_list):
|
||||
"""Validate that a add-on is unique."""
|
||||
single = set([addon[ATTR_SLUG] for addon in addons_list])
|
||||
|
||||
if len(single) != len(addons_list):
|
||||
raise vol.Invalid("Invalid addon list on snapshot!")
|
||||
return addons_list
|
||||
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_SNAPSHOT = vol.Schema({
|
||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
|
||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Required(ATTR_DATE): vol.Coerce(str),
|
||||
vol.Optional(ATTR_HOMEASSISTANT, default={}): vol.Schema({
|
||||
vol.Optional(ATTR_HOMEASSISTANT, default=dict): vol.Schema({
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
vol.Optional(ATTR_IMAGE): vol.Coerce(str),
|
||||
vol.Optional(ATTR_LAST_VERSION): vol.Coerce(str),
|
||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
||||
}, extra=vol.REMOVE_EXTRA),
|
||||
vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)],
|
||||
vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({
|
||||
vol.Optional(ATTR_FOLDERS, default=list):
|
||||
vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||
vol.Optional(ATTR_ADDONS, default=list): vol.All([vol.Schema({
|
||||
vol.Required(ATTR_SLUG): vol.Coerce(str),
|
||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
}, extra=vol.REMOVE_EXTRA)],
|
||||
vol.Optional(ATTR_REPOSITORIES, default=[]): [vol.Url()],
|
||||
}, extra=vol.REMOVE_EXTRA)], unique_addons),
|
||||
vol.Optional(ATTR_REPOSITORIES, default=list): REPOSITORIES,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
@ -89,4 +89,4 @@ class Updater(JsonConfig, CoreSysAttributes):
|
||||
# update versions
|
||||
self._data[ATTR_HOMEASSISTANT] = data.get('homeassistant')
|
||||
self._data[ATTR_HASSIO] = data.get('hassio')
|
||||
self.save()
|
||||
self.save_data()
|
||||
|
@ -10,14 +10,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def write_json_file(jsonfile, data):
|
||||
"""Write a json file."""
|
||||
try:
|
||||
json_str = json.dumps(data, indent=2)
|
||||
with jsonfile.open('w') as conf_file:
|
||||
conf_file.write(json_str)
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def read_json_file(jsonfile):
|
||||
@ -35,7 +30,10 @@ class JsonConfig(object):
|
||||
self._schema = schema
|
||||
self._data = {}
|
||||
|
||||
# init or load data
|
||||
self.read_data()
|
||||
|
||||
def read_data(self):
|
||||
"""Read json file & validate."""
|
||||
if self._file.is_file():
|
||||
try:
|
||||
self._data = read_json_file(self._file)
|
||||
@ -43,27 +41,33 @@ class JsonConfig(object):
|
||||
_LOGGER.warning("Can't read %s", self._file)
|
||||
self._data = {}
|
||||
|
||||
# validate
|
||||
# Validate
|
||||
try:
|
||||
self._data = self._schema(self._data)
|
||||
except vol.Invalid as ex:
|
||||
_LOGGER.error("Can't parse %s: %s",
|
||||
self._file, humanize_error(self._data, ex))
|
||||
# reset data to default
|
||||
|
||||
# Reset data to default
|
||||
_LOGGER.warning("Reset %s to default", self._file)
|
||||
self._data = self._schema({})
|
||||
|
||||
def save(self):
|
||||
def save_data(self):
|
||||
"""Store data to config file."""
|
||||
# validate
|
||||
# Validate
|
||||
try:
|
||||
self._data = self._schema(self._data)
|
||||
except vol.Invalid as ex:
|
||||
_LOGGER.error("Can't parse data: %s",
|
||||
humanize_error(self._data, ex))
|
||||
return False
|
||||
|
||||
# Load last valid data
|
||||
_LOGGER.warning("Reset %s to last version", self._file)
|
||||
self.save_data()
|
||||
return
|
||||
|
||||
# write
|
||||
if not write_json_file(self._file, self._data):
|
||||
_LOGGER.error("Can't store config in %s", self._file)
|
||||
return False
|
||||
return True
|
||||
try:
|
||||
write_json_file(self._file, self._data)
|
||||
except (OSError, json.JSONDecodeError) as err:
|
||||
_LOGGER.error("Can't store config in %s: %s", self._file, err)
|
||||
|
@ -14,6 +14,10 @@ from .const import (
|
||||
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||
ALSA_CHANNEL = vol.Match(r"\d+,\d+")
|
||||
WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
||||
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
REPOSITORIES = vol.All([vol.Url()], vol.Unique())
|
||||
|
||||
|
||||
def validate_timezone(timezone):
|
||||
@ -62,7 +66,7 @@ SCHEMA_HASS_CONFIG = vol.Schema({
|
||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
||||
vol.Match(r"^[0-9a-f]{32}$"),
|
||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str),
|
||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||
@ -85,7 +89,7 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({
|
||||
vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str),
|
||||
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[
|
||||
"https://github.com/hassio-addons/repository",
|
||||
]): [vol.Url()],
|
||||
]): REPOSITORIES,
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL,
|
||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL,
|
||||
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"hassio": "0.82",
|
||||
"hassio": "0.83",
|
||||
"homeassistant": "0.61.1",
|
||||
"resinos": "1.1",
|
||||
"resinhup": "0.3",
|
||||
|
Loading…
x
Reference in New Issue
Block a user