mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
Create backups files without having to copy inner tarballs (#110267)
This commit is contained in:
parent
0e833c5fe3
commit
3a053afac6
@ -4,11 +4,12 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import tarfile
|
import tarfile
|
||||||
from tarfile import TarError
|
from tarfile import TarError
|
||||||
from tempfile import TemporaryDirectory
|
import time
|
||||||
from typing import Any, Protocol, cast
|
from typing import Any, Protocol, cast
|
||||||
|
|
||||||
from securetar import SecureTarFile, atomic_contents_add
|
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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import integration_platform
|
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 import dt as dt_util
|
||||||
from homeassistant.util.json import json_loads_object
|
from homeassistant.util.json import json_loads_object
|
||||||
|
|
||||||
@ -228,18 +229,18 @@ class BackupManager:
|
|||||||
LOGGER.debug("Creating backup directory")
|
LOGGER.debug("Creating backup directory")
|
||||||
self.backup_dir.mkdir()
|
self.backup_dir.mkdir()
|
||||||
|
|
||||||
with TemporaryDirectory() as tmp_dir, SecureTarFile(
|
outer_secure_tarfile = SecureTarFile(
|
||||||
tar_file_path, "w", gzip=False, bufsize=BUF_SIZE
|
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(
|
with outer_secure_tarfile as outer_secure_tarfile_tarfile:
|
||||||
tmp_dir_path.joinpath("./homeassistant.tar.gz").as_posix(),
|
raw_bytes = json_bytes(backup_data)
|
||||||
"w",
|
fileobj = io.BytesIO(raw_bytes)
|
||||||
bufsize=BUF_SIZE,
|
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:
|
) as core_tar:
|
||||||
atomic_contents_add(
|
atomic_contents_add(
|
||||||
tar_file=core_tar,
|
tar_file=core_tar,
|
||||||
@ -247,7 +248,7 @@ class BackupManager:
|
|||||||
excludes=EXCLUDE_FROM_BACKUP,
|
excludes=EXCLUDE_FROM_BACKUP,
|
||||||
arcname="data",
|
arcname="data",
|
||||||
)
|
)
|
||||||
tar_file.add(tmp_dir_path, arcname=".")
|
|
||||||
return tar_file_path.stat().st_size
|
return tar_file_path.stat().st_size
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,11 +29,11 @@ async def _mock_backup_generation(manager: BackupManager):
|
|||||||
Path(".storage"),
|
Path(".storage"),
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch("tarfile.open", MagicMock()) as mocked_tarfile, patch(
|
with patch(
|
||||||
"pathlib.Path.iterdir", _mock_iterdir
|
"homeassistant.components.backup.manager.SecureTarFile"
|
||||||
), patch("pathlib.Path.stat", MagicMock(st_size=123)), patch(
|
) as mocked_tarfile, patch("pathlib.Path.iterdir", _mock_iterdir), patch(
|
||||||
"pathlib.Path.is_file", lambda x: x.name != ".storage"
|
"pathlib.Path.stat", MagicMock(st_size=123)
|
||||||
), patch(
|
), patch("pathlib.Path.is_file", lambda x: x.name != ".storage"), patch(
|
||||||
"pathlib.Path.is_dir",
|
"pathlib.Path.is_dir",
|
||||||
lambda x: x.name == ".storage",
|
lambda x: x.name == ".storage",
|
||||||
), patch(
|
), patch(
|
||||||
@ -46,21 +46,20 @@ async def _mock_backup_generation(manager: BackupManager):
|
|||||||
"pathlib.Path.mkdir",
|
"pathlib.Path.mkdir",
|
||||||
MagicMock(),
|
MagicMock(),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.backup.manager.save_json"
|
"homeassistant.components.backup.manager.json_bytes",
|
||||||
) as mocked_save_json, patch(
|
return_value=b"{}", # Empty JSON
|
||||||
|
) as mocked_json_bytes, patch(
|
||||||
"homeassistant.components.backup.manager.HAVERSION",
|
"homeassistant.components.backup.manager.HAVERSION",
|
||||||
"2025.1.0",
|
"2025.1.0",
|
||||||
):
|
):
|
||||||
await manager.generate_backup()
|
await manager.generate_backup()
|
||||||
|
|
||||||
assert mocked_save_json.call_count == 1
|
assert mocked_json_bytes.call_count == 1
|
||||||
assert mocked_save_json.call_args[0][1]["homeassistant"] == {
|
backup_json_dict = mocked_json_bytes.call_args[0][0]
|
||||||
"version": "2025.1.0"
|
assert isinstance(backup_json_dict, dict)
|
||||||
}
|
assert backup_json_dict["homeassistant"] == {"version": "2025.1.0"}
|
||||||
|
assert manager.backup_dir.as_posix() in str(
|
||||||
assert (
|
mocked_tarfile.call_args_list[0][0][0]
|
||||||
manager.backup_dir.as_posix()
|
|
||||||
in mocked_tarfile.call_args_list[0].kwargs["name"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user