Make supervisor backup file names more user friendly (#137020)
parent
e0bf248867
commit
64f679ba8f
|
@ -35,6 +35,7 @@ from .manager import (
|
|||
WrittenBackup,
|
||||
)
|
||||
from .models import AddonInfo, AgentBackup, Folder
|
||||
from .util import suggested_filename, suggested_filename_from_name_date
|
||||
from .websocket import async_register_websocket_handlers
|
||||
|
||||
__all__ = [
|
||||
|
@ -58,6 +59,8 @@ __all__ = [
|
|||
"RestoreBackupState",
|
||||
"WrittenBackup",
|
||||
"async_get_manager",
|
||||
"suggested_filename",
|
||||
"suggested_filename_from_name_date",
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
|
|
@ -118,12 +118,15 @@ def read_backup(backup_path: Path) -> AgentBackup:
|
|||
)
|
||||
|
||||
|
||||
def suggested_filename_from_name_date(name: str, date_str: str) -> str:
|
||||
"""Suggest a filename for the backup."""
|
||||
date = dt_util.parse_datetime(date_str, raise_on_error=True)
|
||||
return "_".join(f"{name} - {date.strftime('%Y-%m-%d %H.%M %S%f')}.tar".split())
|
||||
|
||||
|
||||
def suggested_filename(backup: AgentBackup) -> str:
|
||||
"""Suggest a filename for the backup."""
|
||||
date = dt_util.parse_datetime(backup.date, raise_on_error=True)
|
||||
return "_".join(
|
||||
f"{backup.name} - {date.strftime('%Y-%m-%d %H.%M %S%f')}.tar".split()
|
||||
)
|
||||
return suggested_filename_from_name_date(backup.name, backup.date)
|
||||
|
||||
|
||||
def validate_password(path: Path, password: str | None) -> bool:
|
||||
|
|
|
@ -6,7 +6,7 @@ import asyncio
|
|||
from collections.abc import AsyncIterator, Callable, Coroutine, Mapping
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Any, cast
|
||||
from uuid import UUID
|
||||
|
||||
|
@ -38,11 +38,14 @@ from homeassistant.components.backup import (
|
|||
RestoreBackupState,
|
||||
WrittenBackup,
|
||||
async_get_manager as async_get_backup_manager,
|
||||
suggested_filename as suggested_backup_filename,
|
||||
suggested_filename_from_name_date,
|
||||
)
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN, EVENT_SUPERVISOR_EVENT
|
||||
from .handler import get_supervisor_client
|
||||
|
@ -113,12 +116,15 @@ def _backup_details_to_agent_backup(
|
|||
AddonInfo(name=addon.name, slug=addon.slug, version=addon.version)
|
||||
for addon in details.addons
|
||||
]
|
||||
extra_metadata = details.extra or {}
|
||||
location = location or LOCATION_LOCAL
|
||||
return AgentBackup(
|
||||
addons=addons,
|
||||
backup_id=details.slug,
|
||||
database_included=database_included,
|
||||
date=details.date.isoformat(),
|
||||
date=extra_metadata.get(
|
||||
"supervisor.backup_request_date", details.date.isoformat()
|
||||
),
|
||||
extra_metadata=details.extra or {},
|
||||
folders=[Folder(folder) for folder in details.folders],
|
||||
homeassistant_included=homeassistant_included,
|
||||
|
@ -174,7 +180,8 @@ class SupervisorBackupAgent(BackupAgent):
|
|||
return
|
||||
stream = await open_stream()
|
||||
upload_options = supervisor_backups.UploadBackupOptions(
|
||||
location={self.location}
|
||||
location={self.location},
|
||||
filename=PurePath(suggested_backup_filename(backup)),
|
||||
)
|
||||
await self._client.backups.upload_backup(
|
||||
stream,
|
||||
|
@ -301,6 +308,9 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||
locations = []
|
||||
locations = locations or [LOCATION_CLOUD_BACKUP]
|
||||
|
||||
date = dt_util.now().isoformat()
|
||||
extra_metadata = extra_metadata | {"supervisor.backup_request_date": date}
|
||||
filename = suggested_filename_from_name_date(backup_name, date)
|
||||
try:
|
||||
backup = await self._client.backups.partial_backup(
|
||||
supervisor_backups.PartialBackupOptions(
|
||||
|
@ -314,6 +324,7 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
|
|||
homeassistant_exclude_database=not include_database,
|
||||
background=True,
|
||||
extra=extra_metadata,
|
||||
filename=PurePath(filename),
|
||||
)
|
||||
)
|
||||
except SupervisorError as err:
|
||||
|
|
|
@ -11,6 +11,7 @@ from dataclasses import replace
|
|||
from datetime import datetime
|
||||
from io import StringIO
|
||||
import os
|
||||
from pathlib import PurePath
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||
from uuid import UUID
|
||||
|
@ -26,6 +27,7 @@ from aiohasupervisor.models import (
|
|||
mounts as supervisor_mounts,
|
||||
)
|
||||
from aiohasupervisor.models.mounts import MountsInfo
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
|
@ -854,8 +856,10 @@ DEFAULT_BACKUP_OPTIONS = supervisor_backups.PartialBackupOptions(
|
|||
compressed=True,
|
||||
extra={
|
||||
"instance_id": ANY,
|
||||
"supervisor.backup_request_date": "2025-01-30T05:42:12.345678-08:00",
|
||||
"with_automatic_settings": False,
|
||||
},
|
||||
filename=PurePath("Test_-_2025-01-30_05.42_12345678.tar"),
|
||||
folders={"ssl"},
|
||||
homeassistant_exclude_database=False,
|
||||
homeassistant=True,
|
||||
|
@ -907,12 +911,14 @@ DEFAULT_BACKUP_OPTIONS = supervisor_backups.PartialBackupOptions(
|
|||
async def test_reader_writer_create(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
supervisor_client: AsyncMock,
|
||||
extra_generate_options: dict[str, Any],
|
||||
expected_supervisor_options: supervisor_backups.PartialBackupOptions,
|
||||
) -> None:
|
||||
"""Test generating a backup."""
|
||||
client = await hass_ws_client(hass)
|
||||
freezer.move_to("2025-01-30 13:42:12.345678")
|
||||
supervisor_client.backups.partial_backup.return_value.job_id = TEST_JOB_ID
|
||||
supervisor_client.backups.backup_info.return_value = TEST_BACKUP_DETAILS
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
|
@ -982,10 +988,12 @@ async def test_reader_writer_create(
|
|||
async def test_reader_writer_create_job_done(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test generating a backup, and backup job finishes early."""
|
||||
client = await hass_ws_client(hass)
|
||||
freezer.move_to("2025-01-30 13:42:12.345678")
|
||||
supervisor_client.backups.partial_backup.return_value.job_id = TEST_JOB_ID
|
||||
supervisor_client.backups.backup_info.return_value = TEST_BACKUP_DETAILS
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_DONE
|
||||
|
@ -1140,6 +1148,7 @@ async def test_reader_writer_create_job_done(
|
|||
async def test_reader_writer_create_per_agent_encryption(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
supervisor_client: AsyncMock,
|
||||
commands: dict[str, Any],
|
||||
password: str | None,
|
||||
|
@ -1151,6 +1160,7 @@ async def test_reader_writer_create_per_agent_encryption(
|
|||
) -> None:
|
||||
"""Test generating a backup."""
|
||||
client = await hass_ws_client(hass)
|
||||
freezer.move_to("2025-01-30 13:42:12.345678")
|
||||
mounts = MountsInfo(
|
||||
default_backup_mount=None,
|
||||
mounts=[
|
||||
|
@ -1170,6 +1180,7 @@ async def test_reader_writer_create_per_agent_encryption(
|
|||
supervisor_client.backups.partial_backup.return_value.job_id = TEST_JOB_ID
|
||||
supervisor_client.backups.backup_info.return_value = replace(
|
||||
TEST_BACKUP_DETAILS,
|
||||
extra=DEFAULT_BACKUP_OPTIONS.extra,
|
||||
locations=create_locations,
|
||||
location_attributes={
|
||||
location or LOCATION_LOCAL: supervisor_backups.BackupLocationAttributes(
|
||||
|
@ -1254,6 +1265,7 @@ async def test_reader_writer_create_per_agent_encryption(
|
|||
upload_locations
|
||||
)
|
||||
for call in supervisor_client.backups.upload_backup.mock_calls:
|
||||
assert call.args[1].filename == PurePath("Test_-_2025-01-30_05.42_12345678.tar")
|
||||
upload_call_locations: set = call.args[1].location
|
||||
assert len(upload_call_locations) == 1
|
||||
assert upload_call_locations.pop() in upload_locations
|
||||
|
@ -1569,10 +1581,12 @@ async def test_reader_writer_create_info_error(
|
|||
async def test_reader_writer_create_remote_backup(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
supervisor_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test generating a backup which will be uploaded to a remote agent."""
|
||||
client = await hass_ws_client(hass)
|
||||
freezer.move_to("2025-01-30 13:42:12.345678")
|
||||
supervisor_client.backups.partial_backup.return_value.job_id = TEST_JOB_ID
|
||||
supervisor_client.backups.backup_info.return_value = TEST_BACKUP_DETAILS_5
|
||||
supervisor_client.jobs.get_job.return_value = TEST_JOB_NOT_DONE
|
||||
|
|
Loading…
Reference in New Issue