Skip to content

Commit 463f196

Browse files
Bump securetar from 2024.11.0 to 2025.1.3 (#5553)
* Bump securetar from 2024.11.0 to 2025.1.3 Bumps [securetar](https://github.com/pvizeli/securetar) from 2024.11.0 to 2025.1.3. - [Release notes](https://github.com/pvizeli/securetar/releases) - [Commits](pvizeli/securetar@2024.11.0...2025.1.3) --- updated-dependencies: - dependency-name: securetar dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Use file_filter and add test for addon backup_exclude --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
1 parent 52d5df6 commit 463f196

File tree

5 files changed

+84
-10
lines changed

5 files changed

+84
-10
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pulsectl==24.12.0
2020
pyudev==0.24.3
2121
PyYAML==6.0.2
2222
requests==2.32.3
23-
securetar==2024.11.0
23+
securetar==2025.1.3
2424
sentry-sdk==2.20.0
2525
setuptools==75.8.0
2626
voluptuous==0.15.2

supervisor/addons/addon.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from copy import deepcopy
77
from datetime import datetime
88
import errno
9+
from functools import partial
910
from ipaddress import IPv4Address
1011
import logging
1112
from pathlib import Path, PurePath
@@ -1207,6 +1208,25 @@ async def end_backup(self) -> asyncio.Task | None:
12071208
await self._backup_command(self.backup_post)
12081209
return None
12091210

1211+
def _is_excluded_by_filter(
1212+
self, origin_path: Path, arcname: str, item_arcpath: PurePath
1213+
) -> bool:
1214+
"""Filter out files from backup based on filters provided by addon developer.
1215+
1216+
This tests the dev provided filters against the full path of the file as
1217+
Supervisor sees them using match. This is done for legacy reasons, testing
1218+
against the relative path makes more sense and may be changed in the future.
1219+
"""
1220+
full_path = origin_path / item_arcpath.relative_to(arcname)
1221+
1222+
for exclude in self.backup_exclude:
1223+
if not full_path.match(exclude):
1224+
continue
1225+
_LOGGER.debug("Ignoring %s because of %s", full_path, exclude)
1226+
return True
1227+
1228+
return False
1229+
12101230
@Job(
12111231
name="addon_backup",
12121232
limit=JobExecutionLimit.GROUP_ONCE,
@@ -1266,7 +1286,9 @@ def _write_tarfile():
12661286
atomic_contents_add(
12671287
backup,
12681288
self.path_data,
1269-
excludes=self.backup_exclude,
1289+
file_filter=partial(
1290+
self._is_excluded_by_filter, self.path_data, "data"
1291+
),
12701292
arcname="data",
12711293
)
12721294

@@ -1275,7 +1297,9 @@ def _write_tarfile():
12751297
atomic_contents_add(
12761298
backup,
12771299
self.path_config,
1278-
excludes=self.backup_exclude,
1300+
file_filter=partial(
1301+
self._is_excluded_by_filter, self.path_config, "config"
1302+
),
12791303
arcname="config",
12801304
)
12811305

supervisor/backups/backup.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import io
1212
import json
1313
import logging
14-
from pathlib import Path
14+
from pathlib import Path, PurePath
1515
import tarfile
1616
from tarfile import TarFile
1717
from tempfile import TemporaryDirectory
@@ -640,6 +640,22 @@ def _save() -> None:
640640
# Take backup
641641
_LOGGER.info("Backing up folder %s", name)
642642

643+
def is_excluded_by_filter(item_arcpath: PurePath) -> bool:
644+
"""Filter out bind mounts in folders being backed up."""
645+
full_path = origin_dir / item_arcpath.relative_to(".")
646+
647+
for bound in self.sys_mounts.bound_mounts:
648+
if full_path != bound.bind_mount.local_where:
649+
continue
650+
_LOGGER.debug(
651+
"Ignoring %s because of %s",
652+
full_path,
653+
bound.bind_mount.local_where.as_posix(),
654+
)
655+
return True
656+
657+
return False
658+
643659
with self._outer_secure_tarfile.create_inner_tar(
644660
f"./{tar_name}",
645661
gzip=self.compressed,
@@ -648,11 +664,7 @@ def _save() -> None:
648664
atomic_contents_add(
649665
tar_file,
650666
origin_dir,
651-
excludes=[
652-
bound.bind_mount.local_where.as_posix()
653-
for bound in self.sys_mounts.bound_mounts
654-
if bound.bind_mount.local_where
655-
],
667+
file_filter=is_excluded_by_filter,
656668
arcname=".",
657669
)
658670

supervisor/homeassistant/module.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,11 +416,23 @@ def _write_tarfile():
416416
if exclude_database:
417417
excludes += HOMEASSISTANT_BACKUP_EXCLUDE_DATABASE
418418

419+
def is_excluded_by_filter(path: PurePath) -> bool:
420+
"""Filter to filter excludes."""
421+
for exclude in excludes:
422+
if not path.match(exclude):
423+
continue
424+
_LOGGER.debug(
425+
"Ignoring %s because of %s", path, exclude
426+
)
427+
return True
428+
429+
return False
430+
419431
# Backup data
420432
atomic_contents_add(
421433
backup,
422434
self.sys_config.path_homeassistant,
423-
excludes=excludes,
435+
file_filter=is_excluded_by_filter,
424436
arcname="data",
425437
)
426438

tests/backups/test_manager.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,3 +2013,29 @@ async def test_backup_remove_one_location_of_multiple(coresys: CoreSys):
20132013
assert not location_2.exists()
20142014
assert coresys.backups.get("7fed74c8")
20152015
assert backup.all_locations == {None: location_1}
2016+
2017+
2018+
@pytest.mark.usefixtures("tmp_supervisor_data")
2019+
async def test_addon_backup_excludes(coresys: CoreSys, install_addon_example: Addon):
2020+
"""Test backup excludes option for addons."""
2021+
coresys.core.state = CoreState.RUNNING
2022+
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
2023+
2024+
install_addon_example.path_data.mkdir(parents=True)
2025+
(test1 := install_addon_example.path_data / "test1").touch()
2026+
(test_dir := install_addon_example.path_data / "test_dir").mkdir()
2027+
(test2 := test_dir / "test2").touch()
2028+
(test3 := test_dir / "test3").touch()
2029+
2030+
install_addon_example.data["backup_exclude"] = ["test1", "*/test2"]
2031+
backup = await coresys.backups.do_backup_partial(addons=["local_example"])
2032+
test1.unlink()
2033+
test2.unlink()
2034+
test3.unlink()
2035+
test_dir.rmdir()
2036+
2037+
await coresys.backups.do_restore_partial(backup, addons=["local_example"])
2038+
assert not test1.exists()
2039+
assert not test2.exists()
2040+
assert test_dir.is_dir()
2041+
assert test3.exists()

0 commit comments

Comments
 (0)