Skip to content

Commit ae036ce

Browse files
authored
Don't backup uninstalled addons (#5988)
* Don't backup uninstalled addons * Remove hash in backup
1 parent f0ea0d4 commit ae036ce

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

supervisor/backups/backup.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
from .utils import password_to_key
6464
from .validate import SCHEMA_BACKUP
6565

66+
IGNORED_COMPARISON_FIELDS = {ATTR_PROTECTED, ATTR_CRYPTO, ATTR_DOCKER}
67+
6668
_LOGGER: logging.Logger = logging.getLogger(__name__)
6769

6870

@@ -265,7 +267,7 @@ def __eq__(self, other: Any) -> bool:
265267

266268
# Compare all fields except ones about protection. Current encryption status does not affect equality
267269
keys = self._data.keys() | other._data.keys()
268-
for k in keys - {ATTR_PROTECTED, ATTR_CRYPTO, ATTR_DOCKER}:
270+
for k in keys - IGNORED_COMPARISON_FIELDS:
269271
if (
270272
k not in self._data
271273
or k not in other._data
@@ -577,13 +579,21 @@ def _add_backup_json():
577579
@Job(name="backup_addon_save", cleanup=False)
578580
async def _addon_save(self, addon: Addon) -> asyncio.Task | None:
579581
"""Store an add-on into backup."""
580-
self.sys_jobs.current.reference = addon.slug
582+
self.sys_jobs.current.reference = slug = addon.slug
581583
if not self._outer_secure_tarfile:
582584
raise RuntimeError(
583585
"Cannot backup components without initializing backup tar"
584586
)
585587

586-
tar_name = f"{addon.slug}.tar{'.gz' if self.compressed else ''}"
588+
# Ensure it is still installed and get current data before proceeding
589+
if not (curr_addon := self.sys_addons.get_local_only(slug)):
590+
_LOGGER.warning(
591+
"Skipping backup of add-on %s because it has been uninstalled",
592+
slug,
593+
)
594+
return None
595+
596+
tar_name = f"{slug}.tar{'.gz' if self.compressed else ''}"
587597

588598
addon_file = self._outer_secure_tarfile.create_inner_tar(
589599
f"./{tar_name}",
@@ -592,16 +602,16 @@ async def _addon_save(self, addon: Addon) -> asyncio.Task | None:
592602
)
593603
# Take backup
594604
try:
595-
start_task = await addon.backup(addon_file)
605+
start_task = await curr_addon.backup(addon_file)
596606
except AddonsError as err:
597607
raise BackupError(str(err)) from err
598608

599609
# Store to config
600610
self._data[ATTR_ADDONS].append(
601611
{
602-
ATTR_SLUG: addon.slug,
603-
ATTR_NAME: addon.name,
604-
ATTR_VERSION: addon.version,
612+
ATTR_SLUG: slug,
613+
ATTR_NAME: curr_addon.name,
614+
ATTR_VERSION: curr_addon.version,
605615
# Bug - addon_file.size used to give us this information
606616
# It always returns 0 in current securetar. Skipping until fixed
607617
ATTR_SIZE: 0,

tests/backups/test_manager.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,3 +2244,33 @@ async def test_get_upload_path_for_mount_location(
22442244
result = await manager.get_upload_path_for_location(mount)
22452245

22462246
assert result == mount.local_where
2247+
2248+
2249+
@pytest.mark.usefixtures(
2250+
"supervisor_internet", "tmp_supervisor_data", "path_extern", "install_addon_example"
2251+
)
2252+
async def test_backup_addon_skips_uninstalled(
2253+
coresys: CoreSys, caplog: pytest.LogCaptureFixture
2254+
):
2255+
"""Test restore installing new addon."""
2256+
await coresys.core.set_state(CoreState.RUNNING)
2257+
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
2258+
assert "local_example" in coresys.addons.local
2259+
orig_store_addons = Backup.store_addons
2260+
2261+
async def mock_store_addons(*args, **kwargs):
2262+
# Mock an uninstall during the backup process
2263+
await coresys.addons.uninstall("local_example")
2264+
await orig_store_addons(*args, **kwargs)
2265+
2266+
with patch.object(Backup, "store_addons", new=mock_store_addons):
2267+
backup: Backup = await coresys.backups.do_backup_partial(
2268+
addons=["local_example"], folders=["ssl"]
2269+
)
2270+
2271+
assert "local_example" not in coresys.addons.local
2272+
assert not backup.addons
2273+
assert (
2274+
"Skipping backup of add-on local_example because it has been uninstalled"
2275+
in caplog.text
2276+
)

0 commit comments

Comments
 (0)