@@ -1238,46 +1238,45 @@ async def backup(self, tar_file: tarfile.TarFile) -> asyncio.Task | None:
1238
1238
Returns a Task that completes when addon has state 'started' (see start)
1239
1239
for cold backup. Else nothing is returned.
1240
1240
"""
1241
- wait_for_start : Awaitable [None ] | None = None
1242
-
1243
- with TemporaryDirectory (dir = self .sys_config .path_tmp ) as temp :
1244
- temp_path = Path (temp )
1245
1241
1246
- # store local image
1247
- if self .need_build :
1248
- try :
1249
- await self .instance .export_image (temp_path .joinpath ("image.tar" ))
1250
- except DockerError as err :
1251
- raise AddonsError () from err
1252
-
1253
- data = {
1254
- ATTR_USER : self .persist ,
1255
- ATTR_SYSTEM : self .data ,
1256
- ATTR_VERSION : self .version ,
1257
- ATTR_STATE : _MAP_ADDON_STATE .get (self .state , self .state ),
1258
- }
1242
+ def _addon_backup (
1243
+ store_image : bool ,
1244
+ metadata : dict [str , Any ],
1245
+ apparmor_profile : str | None ,
1246
+ addon_config_used : bool ,
1247
+ ):
1248
+ """Start the backup process."""
1249
+ with TemporaryDirectory (dir = self .sys_config .path_tmp ) as temp :
1250
+ temp_path = Path (temp )
1259
1251
1260
- # Store local configs/state
1261
- try :
1262
- write_json_file (temp_path .joinpath ("addon.json" ), data )
1263
- except ConfigurationFileError as err :
1264
- raise AddonsError (
1265
- f"Can't save meta for { self .slug } " , _LOGGER .error
1266
- ) from err
1252
+ # store local image
1253
+ if store_image :
1254
+ try :
1255
+ self .instance .export_image (temp_path .joinpath ("image.tar" ))
1256
+ except DockerError as err :
1257
+ raise AddonsError () from err
1267
1258
1268
- # Store AppArmor Profile
1269
- if self .sys_host .apparmor .exists (self .slug ):
1270
- profile = temp_path .joinpath ("apparmor.txt" )
1259
+ # Store local configs/state
1271
1260
try :
1272
- await self . sys_host . apparmor . backup_profile ( self . slug , profile )
1273
- except HostAppArmorError as err :
1261
+ write_json_file ( temp_path . joinpath ( "addon.json" ), metadata )
1262
+ except ConfigurationFileError as err :
1274
1263
raise AddonsError (
1275
- "Can't backup AppArmor profile " , _LOGGER .error
1264
+ f "Can't save meta for { self . slug } " , _LOGGER .error
1276
1265
) from err
1277
1266
1278
- # write into tarfile
1279
- def _write_tarfile ():
1280
- """Write tar inside loop."""
1267
+ # Store AppArmor Profile
1268
+ if apparmor_profile :
1269
+ profile_backup_file = temp_path .joinpath ("apparmor.txt" )
1270
+ try :
1271
+ self .sys_host .apparmor .backup_profile (
1272
+ apparmor_profile , profile_backup_file
1273
+ )
1274
+ except HostAppArmorError as err :
1275
+ raise AddonsError (
1276
+ "Can't backup AppArmor profile" , _LOGGER .error
1277
+ ) from err
1278
+
1279
+ # Write tarfile
1281
1280
with tar_file as backup :
1282
1281
# Backup metadata
1283
1282
backup .add (temp , arcname = "." )
@@ -1293,7 +1292,7 @@ def _write_tarfile():
1293
1292
)
1294
1293
1295
1294
# Backup config
1296
- if self . addon_config_used :
1295
+ if addon_config_used :
1297
1296
atomic_contents_add (
1298
1297
backup ,
1299
1298
self .path_config ,
@@ -1303,19 +1302,39 @@ def _write_tarfile():
1303
1302
arcname = "config" ,
1304
1303
)
1305
1304
1306
- is_running = await self .begin_backup ()
1307
- try :
1308
- _LOGGER .info ("Building backup for add-on %s" , self .slug )
1309
- await self .sys_run_in_executor (_write_tarfile )
1310
- except (tarfile .TarError , OSError ) as err :
1311
- raise AddonsError (
1312
- f"Can't write tarfile { tar_file } : { err } " , _LOGGER .error
1313
- ) from err
1314
- finally :
1315
- if is_running :
1316
- wait_for_start = await self .end_backup ()
1305
+ wait_for_start : Awaitable [None ] | None = None
1306
+
1307
+ data = {
1308
+ ATTR_USER : self .persist ,
1309
+ ATTR_SYSTEM : self .data ,
1310
+ ATTR_VERSION : self .version ,
1311
+ ATTR_STATE : _MAP_ADDON_STATE .get (self .state , self .state ),
1312
+ }
1313
+ apparmor_profile = (
1314
+ self .slug if self .sys_host .apparmor .exists (self .slug ) else None
1315
+ )
1316
+
1317
+ was_running = await self .begin_backup ()
1318
+ try :
1319
+ _LOGGER .info ("Building backup for add-on %s" , self .slug )
1320
+ await self .sys_run_in_executor (
1321
+ partial (
1322
+ _addon_backup ,
1323
+ store_image = self .need_build ,
1324
+ metadata = data ,
1325
+ apparmor_profile = apparmor_profile ,
1326
+ addon_config_used = self .addon_config_used ,
1327
+ )
1328
+ )
1329
+ _LOGGER .info ("Finish backup for addon %s" , self .slug )
1330
+ except (tarfile .TarError , OSError ) as err :
1331
+ raise AddonsError (
1332
+ f"Can't write tarfile { tar_file } : { err } " , _LOGGER .error
1333
+ ) from err
1334
+ finally :
1335
+ if was_running :
1336
+ wait_for_start = await self .end_backup ()
1317
1337
1318
- _LOGGER .info ("Finish backup for addon %s" , self .slug )
1319
1338
return wait_for_start
1320
1339
1321
1340
@Job (
@@ -1330,30 +1349,36 @@ async def restore(self, tar_file: tarfile.TarFile) -> asyncio.Task | None:
1330
1349
if addon is started after restore. Else nothing is returned.
1331
1350
"""
1332
1351
wait_for_start : Awaitable [None ] | None = None
1333
- with TemporaryDirectory (dir = self .sys_config .path_tmp ) as temp :
1334
- # extract backup
1335
- def _extract_tarfile ():
1336
- """Extract tar backup."""
1352
+
1353
+ # Extract backup
1354
+ def _extract_tarfile () -> tuple [TemporaryDirectory , dict [str , Any ]]:
1355
+ """Extract tar backup."""
1356
+ tmp = TemporaryDirectory (dir = self .sys_config .path_tmp )
1357
+ try :
1337
1358
with tar_file as backup :
1338
1359
backup .extractall (
1339
- path = Path ( temp ) ,
1360
+ path = tmp . name ,
1340
1361
members = secure_path (backup ),
1341
1362
filter = "fully_trusted" ,
1342
1363
)
1343
1364
1344
- try :
1345
- await self .sys_run_in_executor (_extract_tarfile )
1346
- except tarfile .TarError as err :
1347
- raise AddonsError (
1348
- f"Can't read tarfile { tar_file } : { err } " , _LOGGER .error
1349
- ) from err
1365
+ data = read_json_file (Path (tmp .name , "addon.json" ))
1366
+ except :
1367
+ tmp .cleanup ()
1368
+ raise
1350
1369
1351
- # Read backup data
1352
- try :
1353
- data = read_json_file (Path (temp , "addon.json" ))
1354
- except ConfigurationFileError as err :
1355
- raise AddonsError () from err
1370
+ return tmp , data
1371
+
1372
+ try :
1373
+ tmp , data = await self .sys_run_in_executor (_extract_tarfile )
1374
+ except tarfile .TarError as err :
1375
+ raise AddonsError (
1376
+ f"Can't read tarfile { tar_file } : { err } " , _LOGGER .error
1377
+ ) from err
1378
+ except ConfigurationFileError as err :
1379
+ raise AddonsError () from err
1356
1380
1381
+ try :
1357
1382
# Validate
1358
1383
try :
1359
1384
data = SCHEMA_ADDON_BACKUP (data )
@@ -1387,7 +1412,7 @@ def _extract_tarfile():
1387
1412
if not await self .instance .exists ():
1388
1413
_LOGGER .info ("Restore/Install of image for addon %s" , self .slug )
1389
1414
1390
- image_file = Path (temp , "image.tar" )
1415
+ image_file = Path (tmp . name , "image.tar" )
1391
1416
if image_file .is_file ():
1392
1417
with suppress (DockerError ):
1393
1418
await self .instance .import_image (image_file )
@@ -1406,13 +1431,13 @@ def _extract_tarfile():
1406
1431
# Restore data and config
1407
1432
def _restore_data ():
1408
1433
"""Restore data and config."""
1409
- temp_data = Path (temp , "data" )
1434
+ temp_data = Path (tmp . name , "data" )
1410
1435
if temp_data .is_dir ():
1411
1436
shutil .copytree (temp_data , self .path_data , symlinks = True )
1412
1437
else :
1413
1438
self .path_data .mkdir ()
1414
1439
1415
- temp_config = Path (temp , "config" )
1440
+ temp_config = Path (tmp . name , "config" )
1416
1441
if temp_config .is_dir ():
1417
1442
shutil .copytree (temp_config , self .path_config , symlinks = True )
1418
1443
elif self .addon_config_used :
@@ -1432,15 +1457,16 @@ def _restore_data():
1432
1457
) from err
1433
1458
1434
1459
# Restore AppArmor
1435
- profile_file = Path (temp , "apparmor.txt" )
1460
+ profile_file = Path (tmp . name , "apparmor.txt" )
1436
1461
if profile_file .exists ():
1437
1462
try :
1438
1463
await self .sys_host .apparmor .load_profile (
1439
1464
self .slug , profile_file
1440
1465
)
1441
1466
except HostAppArmorError as err :
1442
1467
_LOGGER .error (
1443
- "Can't restore AppArmor profile for add-on %s" , self .slug
1468
+ "Can't restore AppArmor profile for add-on %s" ,
1469
+ self .slug ,
1444
1470
)
1445
1471
raise AddonsError () from err
1446
1472
@@ -1452,7 +1478,8 @@ def _restore_data():
1452
1478
# Run add-on
1453
1479
if data [ATTR_STATE ] == AddonState .STARTED :
1454
1480
wait_for_start = await self .start ()
1455
-
1481
+ finally :
1482
+ tmp .cleanup ()
1456
1483
_LOGGER .info ("Finished restore for add-on %s" , self .slug )
1457
1484
return wait_for_start
1458
1485
0 commit comments