Skip to content

Commit

Permalink
Extend backup API with file name field
Browse files Browse the repository at this point in the history
Allow to specify a backup file name when creating a backup. This allows
for user friendly backup file names. If none is specified, the current
behavior remains (backup file name is the backup slug).
  • Loading branch information
agners committed Jan 21, 2025
1 parent 805017e commit 533a61c
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 4 deletions.
2 changes: 2 additions & 0 deletions supervisor/api/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ATTR_DATE,
ATTR_DAYS_UNTIL_STALE,
ATTR_EXTRA,
ATTR_FILENAME,
ATTR_FOLDERS,
ATTR_HOMEASSISTANT,
ATTR_HOMEASSISTANT_EXCLUDE_DATABASE,
Expand Down Expand Up @@ -98,6 +99,7 @@ def _ensure_list(item: Any) -> list:
SCHEMA_BACKUP_FULL = vol.Schema(
{
vol.Optional(ATTR_NAME): str,
vol.Optional(ATTR_FILENAME): str,
vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
vol.Optional(ATTR_COMPRESSED): vol.Maybe(vol.Boolean()),
vol.Optional(ATTR_LOCATION): vol.All(
Expand Down
13 changes: 10 additions & 3 deletions supervisor/backups/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def _list_backup_files(self, path: Path) -> Iterable[Path]:
def _create_backup(
self,
name: str,
filename: str | None,
sys_type: BackupType,
password: str | None,
compressed: bool = True,
Expand All @@ -196,7 +197,11 @@ def _create_backup(
"""
date_str = utcnow().isoformat()
slug = create_slug(name, date_str)
tar_file = Path(self._get_base_path(location), f"{slug}.tar")

if filename:
tar_file = Path(self._get_base_path(location), Path(filename).name)
else:
tar_file = Path(self._get_base_path(location), f"{slug}.tar")

# init object
backup = Backup(self.coresys, tar_file, slug, self._get_location_name(location))
Expand Down Expand Up @@ -482,6 +487,7 @@ async def _do_backup(
async def do_backup_full(
self,
name: str = "",
filename: str | None = None,
*,
password: str | None = None,
compressed: bool = True,
Expand All @@ -500,7 +506,7 @@ async def do_backup_full(
)

backup = self._create_backup(
name, BackupType.FULL, password, compressed, location, extra
name, filename, BackupType.FULL, password, compressed, location, extra
)

_LOGGER.info("Creating new full backup with slug %s", backup.slug)
Expand All @@ -526,6 +532,7 @@ async def do_backup_full(
async def do_backup_partial(
self,
name: str = "",
filename: str | None = None,
*,
addons: list[str] | None = None,
folders: list[str] | None = None,
Expand Down Expand Up @@ -558,7 +565,7 @@ async def do_backup_partial(
_LOGGER.error("Nothing to create backup for")

backup = self._create_backup(
name, BackupType.PARTIAL, password, compressed, location, extra
name, filename, BackupType.PARTIAL, password, compressed, location, extra
)

_LOGGER.info("Creating new partial backup with slug %s", backup.slug)
Expand Down
2 changes: 1 addition & 1 deletion tests/backups/test_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ async def test_new_backup_stays_in_folder(coresys: CoreSys, tmp_path: Path):
assert backup.tarfile.exists()

assert len(listdir(tmp_path)) == 1
assert backup.tarfile.exists()
assert backup.tarfile.exists()
25 changes: 25 additions & 0 deletions tests/backups/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ async def test_do_backup_full(coresys: CoreSys, backup_mock, install_addon_ssh):
assert coresys.core.state == CoreState.RUNNING


@pytest.mark.parametrize(
("filename", "filename_expected"),
[("../my file.tar", "/data/backup/my file.tar"), (None, "/data/backup/{}.tar")],
)
async def test_do_backup_full_with_filename(
coresys: CoreSys,
filename: str,
filename_expected: str,
backup_mock
):
"""Test creating Backup with a specific file name."""
coresys.core.state = CoreState.RUNNING
coresys.hardware.disk.get_disk_free_space = lambda x: 5000

manager = BackupManager(coresys)

# backup_mock fixture causes Backup() to be a MagicMock
await manager.do_backup_full(filename=filename)

slug = backup_mock.call_args[0][2]
assert str(backup_mock.call_args[0][1]) == filename_expected.format(slug)

assert coresys.core.state == CoreState.RUNNING


async def test_do_backup_full_uncompressed(
coresys: CoreSys, backup_mock, install_addon_ssh
):
Expand Down

0 comments on commit 533a61c

Please sign in to comment.