Skip to content

Commit 0ac96c2

Browse files
authored
Convert homeassistant into a dict (snapshot) (#90)
* Convert homeassistant into a dict * fix lint * fix bugs * cleanup code * fix cleanup
1 parent e2a29b7 commit 0ac96c2

File tree

8 files changed

+85
-49
lines changed

8 files changed

+85
-49
lines changed

API.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,13 @@ Return QR-Code
180180
{
181181
"slug": "SNAPSHOT ID",
182182
"type": "full|partial",
183-
"name": "custom snapshot name",
183+
"name": "custom snapshot name / description",
184184
"date": "ISO",
185185
"size": "SIZE_IN_MB",
186-
"homeassistant": "INSTALLED_HASS_VERSION",
186+
"homeassistant": {
187+
"version": "INSTALLED_HASS_VERSION",
188+
"devices": []
189+
},
187190
"addons": [
188191
{
189192
"slug": "ADDON_SLUG",

hassio/addons/addon.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,15 +434,14 @@ def _extract_tar():
434434
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
435435

436436
# check version / restore image
437-
if data[ATTR_VERSION] != self.addon_docker.version:
437+
version = data[ATTR_VERSION]
438+
if version != self.addon_docker.version:
438439
image_file = Path(temp, "image.tar")
439440
if image_file.is_file():
440-
if not await self.addon_docker.import_image(image_file):
441-
return False
441+
await self.addon_docker.import_image(image_file, version)
442442
else:
443-
if not await self.addon_docker.install(data[ATTR_VERSION]):
444-
return False
445-
await self.addon_docker.cleanup()
443+
if await self.addon_docker.install(version):
444+
await self.addon_docker.cleanup()
446445
else:
447446
await self.addon_docker.stop()
448447

hassio/api/snapshots.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from ..snapshots.validate import ALL_FOLDERS
99
from ..const import (
1010
ATTR_NAME, ATTR_SLUG, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
11-
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE)
11+
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE,
12+
ATTR_DEVICES)
1213

1314
_LOGGER = logging.getLogger(__name__)
1415

@@ -69,7 +70,10 @@ async def info(self, request):
6970
ATTR_NAME: snapshot.name,
7071
ATTR_DATE: snapshot.date,
7172
ATTR_SIZE: snapshot.size,
72-
ATTR_HOMEASSISTANT: snapshot.homeassistant,
73+
ATTR_HOMEASSISTANT: {
74+
ATTR_VERSION: snapshot.homeassistant_version,
75+
ATTR_DEVICES: snapshot.homeassistant_devices,
76+
},
7377
ATTR_ADDONS: self._addons_list(snapshot),
7478
ATTR_REPOSITORIES: snapshot.repositories,
7579
ATTR_FOLDERS: snapshot.folders,

hassio/dock/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ def _remove(self):
231231
_LOGGER.warning("Can't remove image %s -> %s", self.image, err)
232232
return False
233233

234+
# clean metadata
235+
self.version = None
236+
self.arch = None
234237
return True
235238

236239
async def update(self, tag):

hassio/dock/addon.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import shutil
66

77
import docker
8+
import requests
89

910
from . import DockerBase
1011
from .util import dockerfile_template
@@ -196,7 +197,7 @@ def _export_image(self, tar_file):
196197
Need run inside executor.
197198
"""
198199
try:
199-
image = self.dock.api.get_image("{}:latest".format(self.image))
200+
image = self.dock.api.get_image(self.image)
200201
except docker.errors.DockerException as err:
201202
_LOGGER.error("Can't fetch image %s -> %s", self.image, err)
202203
return False
@@ -205,23 +206,24 @@ def _export_image(self, tar_file):
205206
with tar_file.open("wb") as write_tar:
206207
for chunk in image.stream():
207208
write_tar.write(chunk)
208-
except OSError() as err:
209+
except (OSError, requests.exceptions.ReadTimeout) as err:
209210
_LOGGER.error("Can't write tar file %s -> %s", tar_file, err)
210211
return False
211212

213+
_LOGGER.info("Export image %s to %s", self.image, tar_file)
212214
return True
213215

214-
async def import_image(self, path):
216+
async def import_image(self, path, tag):
215217
"""Import a tar file as image."""
216218
if self._lock.locked():
217219
_LOGGER.error("Can't excute import while a task is in progress")
218220
return False
219221

220222
async with self._lock:
221223
return await self.loop.run_in_executor(
222-
None, self._import_image, path)
224+
None, self._import_image, path, tag)
223225

224-
def _import_image(self, tar_file):
226+
def _import_image(self, tar_file, tag):
225227
"""Import a tar file as image.
226228
227229
Need run inside executor.
@@ -231,10 +233,12 @@ def _import_image(self, tar_file):
231233
self.dock.api.load_image(read_tar)
232234

233235
image = self.dock.images.get(self.image)
236+
image.tag(self.image, tag=tag)
234237
except (docker.errors.DockerException, OSError) as err:
235238
_LOGGER.error("Can't import image %s -> %s", self.image, err)
236239
return False
237240

241+
_LOGGER.info("Import image %s and tag %s", tar_file, tag)
238242
self.process_metadata(image.attrs, force=True)
239243
self._cleanup()
240244
return True

hassio/snapshots/__init__.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,15 @@ def _create_snapshot(self, name, sys_type):
4141
slug = create_slug(name, date_str)
4242
tar_file = Path(self.config.path_backup, "{}.tar".format(slug))
4343

44+
# init object
4445
snapshot = Snapshot(self.config, self.loop, tar_file)
4546
snapshot.create(slug, name, date_str, sys_type)
4647

48+
# set general data
49+
snapshot.homeassistant_version = self.homeassistant.version
50+
snapshot.homeassistant_devices = self.config.homeassistant_devices
51+
snapshot.repositories = self.config.addons_repositories
52+
4753
return snapshot
4854

4955
async def reload(self):
@@ -87,9 +93,6 @@ async def do_snapshot_full(self, name=""):
8793
await self._lock.acquire()
8894

8995
async with snapshot:
90-
snapshot.homeassistant = self.homeassistant.version
91-
snapshot.repositories = self.config.addons_repositories
92-
9396
# snapshot addons
9497
tasks = []
9598
for addon in self.addons.list_addons:
@@ -110,7 +113,7 @@ async def do_snapshot_full(self, name=""):
110113
self.snapshots[snapshot.slug] = snapshot
111114
return True
112115

113-
except (OSError, tarfile.TarError) as err:
116+
except (OSError, ValueError, tarfile.TarError) as err:
114117
_LOGGER.info("Full-Snapshot %s error -> %s", snapshot.slug, err)
115118
return False
116119

@@ -134,9 +137,6 @@ async def do_snapshot_partial(self, name="", addons=None, folders=None):
134137
await self._lock.acquire()
135138

136139
async with snapshot:
137-
snapshot.homeassistant = self.homeassistant.version
138-
snapshot.repositories = self.config.addons_repositories
139-
140140
# snapshot addons
141141
tasks = []
142142
for slug in addons:
@@ -158,7 +158,7 @@ async def do_snapshot_partial(self, name="", addons=None, folders=None):
158158
self.snapshots[snapshot.slug] = snapshot
159159
return True
160160

161-
except (OSError, tarfile.TarError) as err:
161+
except (OSError, ValueError, tarfile.TarError) as err:
162162
_LOGGER.info("Partial-Snapshot %s error -> %s", snapshot.slug, err)
163163
return False
164164

@@ -198,8 +198,10 @@ async def do_restore_full(self, snapshot):
198198
await snapshot.restore_folders()
199199

200200
# start homeassistant restore
201+
self.config.homeassistant_devices = \
202+
snapshot.homeassistant_devices
201203
task_hass = self.loop.create_task(
202-
self.homeassistant.update(snapshot.homeassistant))
204+
self.homeassistant.update(snapshot.homeassistant_version))
203205

204206
# restore repositories
205207
await self.addons.load_repositories(snapshot.repositories)
@@ -244,7 +246,7 @@ async def do_restore_full(self, snapshot):
244246
_LOGGER.info("Full-Restore %s done", snapshot.slug)
245247
return True
246248

247-
except (OSError, tarfile.TarError) as err:
249+
except (OSError, ValueError, tarfile.TarError) as err:
248250
_LOGGER.info("Full-Restore %s error -> %s", slug, err)
249251
return False
250252

@@ -279,8 +281,10 @@ async def do_restore_partial(self, snapshot, homeassistant=False,
279281
await snapshot.restore_folders(folders)
280282

281283
if homeassistant:
284+
self.config.homeassistant_devices = \
285+
snapshot.homeassistant_devices
282286
tasks.append(self.homeassistant.update(
283-
snapshot.homeassistant))
287+
snapshot.homeassistant_version))
284288

285289
for slug in addons:
286290
addon = self.addons.get(slug)
@@ -300,7 +304,7 @@ async def do_restore_partial(self, snapshot, homeassistant=False,
300304
_LOGGER.info("Partial-Restore %s done", snapshot.slug)
301305
return True
302306

303-
except (OSError, tarfile.TarError) as err:
307+
except (OSError, ValueError, tarfile.TarError) as err:
304308
_LOGGER.info("Partial-Restore %s error -> %s", slug, err)
305309
return False
306310

hassio/snapshots/snapshot.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .util import remove_folder
1414
from ..const import (
1515
ATTR_SLUG, ATTR_NAME, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES,
16-
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE)
16+
ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_DEVICES)
1717
from ..tools import write_json_file
1818

1919
_LOGGER = logging.getLogger(__name__)
@@ -43,42 +43,52 @@ def sys_type(self):
4343
@property
4444
def name(self):
4545
"""Return snapshot name."""
46-
return self._data.get(ATTR_NAME)
46+
return self._data[ATTR_NAME]
4747

4848
@property
4949
def date(self):
5050
"""Return snapshot date."""
51-
return self._data.get(ATTR_DATE)
51+
return self._data[ATTR_DATE]
5252

5353
@property
5454
def addons(self):
5555
"""Return snapshot date."""
56-
return self._data.get(ATTR_ADDONS, [])
56+
return self._data[ATTR_ADDONS]
5757

5858
@property
5959
def folders(self):
6060
"""Return list of saved folders."""
61-
return self._data.get(ATTR_FOLDERS, [])
61+
return self._data[ATTR_FOLDERS]
6262

6363
@property
6464
def repositories(self):
6565
"""Return snapshot date."""
66-
return self._data.get(ATTR_REPOSITORIES, [])
66+
return self._data[ATTR_REPOSITORIES]
6767

6868
@repositories.setter
6969
def repositories(self, value):
7070
"""Set snapshot date."""
7171
self._data[ATTR_REPOSITORIES] = value
7272

7373
@property
74-
def homeassistant(self):
74+
def homeassistant_version(self):
7575
"""Return snapshot homeassistant version."""
76-
return self._data.get(ATTR_HOMEASSISTANT)
76+
return self._data[ATTR_HOMEASSISTANT].get(ATTR_VERSION)
7777

78-
@homeassistant.setter
79-
def homeassistant(self, value):
78+
@homeassistant_version.setter
79+
def homeassistant_version(self, value):
8080
"""Set snapshot homeassistant version."""
81-
self._data[ATTR_HOMEASSISTANT] = value
81+
self._data[ATTR_HOMEASSISTANT][ATTR_VERSION] = value
82+
83+
@property
84+
def homeassistant_devices(self):
85+
"""Return snapshot homeassistant devices."""
86+
return self._data[ATTR_HOMEASSISTANT].get(ATTR_DEVICES)
87+
88+
@homeassistant_devices.setter
89+
def homeassistant_devices(self, value):
90+
"""Set snapshot homeassistant devices."""
91+
self._data[ATTR_HOMEASSISTANT][ATTR_DEVICES] = value
8292

8393
@property
8494
def size(self):
@@ -96,6 +106,7 @@ def create(self, slug, name, date, sys_type):
96106
self._data[ATTR_TYPE] = sys_type
97107

98108
# init other constructs
109+
self._data[ATTR_HOMEASSISTANT] = {}
99110
self._data[ATTR_ADDONS] = []
100111
self._data[ATTR_REPOSITORIES] = []
101112
self._data[ATTR_FOLDERS] = []
@@ -159,17 +170,22 @@ async def __aexit__(self, exception_type, exception_value, traceback):
159170
if self.tar_file.is_file() or exception_type is not None:
160171
return self._tmp.cleanup()
161172

173+
# validate data
174+
try:
175+
self._data = SCHEMA_SNAPSHOT(self._data)
176+
except vol.Invalid as err:
177+
_LOGGER.error("Invalid data for %s -> %s", self.tar_file,
178+
humanize_error(self._data, err))
179+
raise ValueError("Invalid config") from None
180+
162181
# new snapshot, build it
163182
def _create_snapshot():
164183
"""Create a new snapshot."""
165184
with tarfile.open(self.tar_file, "w:") as tar:
166185
tar.add(self._tmp.name, arcname=".")
167186

168187
if write_json_file(Path(self._tmp.name, "snapshot.json"), self._data):
169-
try:
170-
await self.loop.run_in_executor(None, _create_snapshot)
171-
except tarfile.TarError as err:
172-
_LOGGER.error("Can't create tar %s", err)
188+
await self.loop.run_in_executor(None, _create_snapshot)
173189
else:
174190
_LOGGER.error("Can't write snapshot.json")
175191

hassio/snapshots/validate.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
from ..const import (
66
ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_NAME, ATTR_SLUG, ATTR_DATE,
7-
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, FOLDER_SHARE,
8-
FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, SNAPSHOT_FULL,
9-
SNAPSHOT_PARTIAL)
7+
ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_DEVICES,
8+
FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL,
9+
SNAPSHOT_FULL, SNAPSHOT_PARTIAL)
1010

1111
ALL_FOLDERS = [FOLDER_HOMEASSISTANT, FOLDER_SHARE, FOLDER_ADDONS, FOLDER_SSL]
1212

@@ -16,12 +16,15 @@
1616
vol.Required(ATTR_TYPE): vol.In([SNAPSHOT_FULL, SNAPSHOT_PARTIAL]),
1717
vol.Required(ATTR_NAME): vol.Coerce(str),
1818
vol.Required(ATTR_DATE): vol.Coerce(str),
19-
vol.Required(ATTR_HOMEASSISTANT): vol.Coerce(str),
20-
vol.Required(ATTR_FOLDERS): [vol.In(ALL_FOLDERS)],
21-
vol.Required(ATTR_ADDONS): [vol.Schema({
19+
vol.Required(ATTR_HOMEASSISTANT): vol.Schema({
20+
vol.Required(ATTR_VERSION): vol.Coerce(str),
21+
vol.Optional(ATTR_DEVICES, default=[]): [vol.Match(r"^[^/]*$")],
22+
}),
23+
vol.Optional(ATTR_FOLDERS, default=[]): [vol.In(ALL_FOLDERS)],
24+
vol.Optional(ATTR_ADDONS, default=[]): [vol.Schema({
2225
vol.Required(ATTR_SLUG): vol.Coerce(str),
2326
vol.Required(ATTR_NAME): vol.Coerce(str),
2427
vol.Required(ATTR_VERSION): vol.Coerce(str),
2528
})],
26-
vol.Required(ATTR_REPOSITORIES): [vol.Url()],
29+
vol.Optional(ATTR_REPOSITORIES, default=[]): [vol.Url()],
2730
}, extra=vol.ALLOW_EXTRA)

0 commit comments

Comments
 (0)