Create backups files without having to copy inner tarballs (#110267)

This commit is contained in:
J. Nick Koston 2024-02-14 10:08:22 -06:00 committed by GitHub
parent 0e833c5fe3
commit 3a053afac6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 29 deletions

View File

@ -4,11 +4,12 @@ from __future__ import annotations
import asyncio
from dataclasses import asdict, dataclass
import hashlib
import io
import json
from pathlib import Path
import tarfile
from tarfile import TarError
from tempfile import TemporaryDirectory
import time
from typing import Any, Protocol, cast
from securetar import SecureTarFile, atomic_contents_add
@ -17,7 +18,7 @@ from homeassistant.const import __version__ as HAVERSION
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import integration_platform
from homeassistant.helpers.json import save_json
from homeassistant.helpers.json import json_bytes
from homeassistant.util import dt as dt_util
from homeassistant.util.json import json_loads_object
@ -228,18 +229,18 @@ class BackupManager:
LOGGER.debug("Creating backup directory")
self.backup_dir.mkdir()
with TemporaryDirectory() as tmp_dir, SecureTarFile(
outer_secure_tarfile = SecureTarFile(
tar_file_path, "w", gzip=False, bufsize=BUF_SIZE
) as tar_file:
tmp_dir_path = Path(tmp_dir)
save_json(
tmp_dir_path.joinpath("./backup.json").as_posix(),
backup_data,
)
with SecureTarFile(
tmp_dir_path.joinpath("./homeassistant.tar.gz").as_posix(),
"w",
bufsize=BUF_SIZE,
with outer_secure_tarfile as outer_secure_tarfile_tarfile:
raw_bytes = json_bytes(backup_data)
fileobj = io.BytesIO(raw_bytes)
tar_info = tarfile.TarInfo(name="./backup.json")
tar_info.size = len(raw_bytes)
tar_info.mtime = int(time.time())
outer_secure_tarfile_tarfile.addfile(tar_info, fileobj=fileobj)
with outer_secure_tarfile.create_inner_tar(
"./homeassistant.tar.gz", gzip=True
) as core_tar:
atomic_contents_add(
tar_file=core_tar,
@ -247,7 +248,7 @@ class BackupManager:
excludes=EXCLUDE_FROM_BACKUP,
arcname="data",
)
tar_file.add(tmp_dir_path, arcname=".")
return tar_file_path.stat().st_size

View File

@ -29,11 +29,11 @@ async def _mock_backup_generation(manager: BackupManager):
Path(".storage"),
]
with patch("tarfile.open", MagicMock()) as mocked_tarfile, patch(
"pathlib.Path.iterdir", _mock_iterdir
), patch("pathlib.Path.stat", MagicMock(st_size=123)), patch(
"pathlib.Path.is_file", lambda x: x.name != ".storage"
), patch(
with patch(
"homeassistant.components.backup.manager.SecureTarFile"
) as mocked_tarfile, patch("pathlib.Path.iterdir", _mock_iterdir), patch(
"pathlib.Path.stat", MagicMock(st_size=123)
), patch("pathlib.Path.is_file", lambda x: x.name != ".storage"), patch(
"pathlib.Path.is_dir",
lambda x: x.name == ".storage",
), patch(
@ -46,21 +46,20 @@ async def _mock_backup_generation(manager: BackupManager):
"pathlib.Path.mkdir",
MagicMock(),
), patch(
"homeassistant.components.backup.manager.save_json"
) as mocked_save_json, patch(
"homeassistant.components.backup.manager.json_bytes",
return_value=b"{}", # Empty JSON
) as mocked_json_bytes, patch(
"homeassistant.components.backup.manager.HAVERSION",
"2025.1.0",
):
await manager.generate_backup()
assert mocked_save_json.call_count == 1
assert mocked_save_json.call_args[0][1]["homeassistant"] == {
"version": "2025.1.0"
}
assert (
manager.backup_dir.as_posix()
in mocked_tarfile.call_args_list[0].kwargs["name"]
assert mocked_json_bytes.call_count == 1
backup_json_dict = mocked_json_bytes.call_args[0][0]
assert isinstance(backup_json_dict, dict)
assert backup_json_dict["homeassistant"] == {"version": "2025.1.0"}
assert manager.backup_dir.as_posix() in str(
mocked_tarfile.call_args_list[0][0][0]
)