Correct invalid automatic backup settings when loading from store (#138716)

* Correct invalid automatic backup settings when loading from store

* Improve docstring

* Improve tests
This commit is contained in:
Erik Montnemery 2025-02-19 16:24:30 +01:00 committed by GitHub
parent 600bfed704
commit b70c5710a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 618 additions and 16 deletions

View File

@ -16,6 +16,7 @@ from .agent import (
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig
from .const import DATA_MANAGER, DOMAIN
from .http import async_register_http_views
from .manager import (
@ -47,6 +48,7 @@ __all__ = [
"BackupAgent",
"BackupAgentError",
"BackupAgentPlatformProtocol",
"BackupConfig",
"BackupManagerError",
"BackupNotFound",
"BackupPlatformProtocol",

View File

@ -43,7 +43,11 @@ from .agent import (
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig, delete_backups_exceeding_configured_count
from .config import (
BackupConfig,
CreateBackupParametersDict,
delete_backups_exceeding_configured_count,
)
from .const import (
BUF_SIZE,
DATA_MANAGER,
@ -282,6 +286,10 @@ class BackupReaderWriter(abc.ABC):
) -> None:
"""Get restore events after core restart."""
@abc.abstractmethod
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config."""
class IncorrectPasswordError(BackupReaderWriterError):
"""Raised when the password is incorrect."""
@ -333,6 +341,7 @@ class BackupManager:
self.config.load(stored["config"])
self.known_backups.load(stored["backups"])
await self._reader_writer.async_validate_config(config=self.config)
await self._reader_writer.async_resume_restore_progress_after_restart(
on_progress=self.async_on_backup_event
)
@ -1832,6 +1841,44 @@ class CoreBackupReaderWriter(BackupReaderWriter):
)
on_progress(IdleEvent())
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config.
Update automatic backup settings to not include addons or folders and remove
hassio agents in case a backup created by supervisor was restored.
"""
create_backup = config.data.create_backup
if (
not create_backup.include_addons
and not create_backup.include_all_addons
and not create_backup.include_folders
and not any(a_id.startswith("hassio.") for a_id in create_backup.agent_ids)
):
LOGGER.debug("Backup settings don't need to be adjusted")
return
LOGGER.info(
"Adjusting backup settings to not include addons, folders or supervisor locations"
)
automatic_agents = [
agent_id
for agent_id in create_backup.agent_ids
if not agent_id.startswith("hassio.")
]
if (
self._local_agent_id not in automatic_agents
and "hassio.local" in create_backup.agent_ids
):
automatic_agents = [self._local_agent_id, *automatic_agents]
await config.update(
create_backup=CreateBackupParametersDict(
agent_ids=automatic_agents,
include_addons=None,
include_all_addons=False,
include_folders=None,
)
)
def _generate_backup_id(date: str, name: str) -> str:
"""Generate a backup ID."""

View File

@ -27,6 +27,7 @@ from homeassistant.components.backup import (
AddonInfo,
AgentBackup,
BackupAgent,
BackupConfig,
BackupManagerError,
BackupNotFound,
BackupReaderWriter,
@ -633,6 +634,9 @@ class SupervisorBackupReaderWriter(BackupReaderWriter):
_LOGGER.debug("Could not get restore job %s: %s", restore_job_id, err)
unsub()
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config."""
@callback
def _async_listen_job_events(
self, job_id: UUID, on_event: Callable[[Mapping[str, Any]], None]

View File

@ -251,7 +251,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data0]
# name: test_config_load_config_info[with_hassio-storage_data0]
dict({
'id': 1,
'result': dict({
@ -288,7 +288,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data1]
# name: test_config_load_config_info[with_hassio-storage_data1]
dict({
'id': 1,
'result': dict({
@ -337,7 +337,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data2]
# name: test_config_load_config_info[with_hassio-storage_data2]
dict({
'id': 1,
'result': dict({
@ -375,7 +375,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data3]
# name: test_config_load_config_info[with_hassio-storage_data3]
dict({
'id': 1,
'result': dict({
@ -413,7 +413,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data4]
# name: test_config_load_config_info[with_hassio-storage_data4]
dict({
'id': 1,
'result': dict({
@ -452,7 +452,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data5]
# name: test_config_load_config_info[with_hassio-storage_data5]
dict({
'id': 1,
'result': dict({
@ -490,7 +490,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data6]
# name: test_config_load_config_info[with_hassio-storage_data6]
dict({
'id': 1,
'result': dict({
@ -530,7 +530,7 @@
'type': 'result',
})
# ---
# name: test_config_info[storage_data7]
# name: test_config_load_config_info[with_hassio-storage_data7]
dict({
'id': 1,
'result': dict({
@ -576,6 +576,484 @@
'type': 'result',
})
# ---
# name: test_config_load_config_info[with_hassio-storage_data8]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'hassio.local',
'hassio.share',
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[with_hassio-storage_data9]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'backup.local',
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data0]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
]),
'include_addons': None,
'include_all_addons': False,
'include_database': True,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data1]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': True,
'include_folders': None,
'name': 'test-name',
'password': 'test-password',
}),
'last_attempted_automatic_backup': '2024-10-26T04:45:00+01:00',
'last_completed_automatic_backup': '2024-10-26T04:45:00+01:00',
'next_automatic_backup': '2024-11-14T04:55:00+01:00',
'next_automatic_backup_additional': False,
'retention': dict({
'copies': 3,
'days': 7,
}),
'schedule': dict({
'days': list([
'mon',
'tue',
'wed',
'thu',
'fri',
'sat',
'sun',
]),
'recurrence': 'custom_days',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data2]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': 3,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data3]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': '2024-10-27T04:45:00+01:00',
'last_completed_automatic_backup': '2024-10-26T04:45:00+01:00',
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': 7,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data4]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': '2024-11-18T04:55:00+01:00',
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
'mon',
]),
'recurrence': 'custom_days',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data5]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data6]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': '2024-11-17T04:55:00+01:00',
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
'mon',
'sun',
]),
'recurrence': 'custom_days',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data7]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
'test-agent1': dict({
'protected': True,
}),
'test-agent2': dict({
'protected': False,
}),
}),
'create_backup': dict({
'agent_ids': list([
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': '2024-11-17T04:55:00+01:00',
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
'mon',
'sun',
]),
'recurrence': 'custom_days',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data8]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'backup.local',
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_load_config_info[without_hassio-storage_data9]
dict({
'id': 1,
'result': dict({
'config': dict({
'agents': dict({
}),
'create_backup': dict({
'agent_ids': list([
'backup.local',
'test-agent',
]),
'include_addons': None,
'include_all_addons': False,
'include_database': False,
'include_folders': None,
'name': None,
'password': None,
}),
'last_attempted_automatic_backup': None,
'last_completed_automatic_backup': None,
'next_automatic_backup': None,
'next_automatic_backup_additional': False,
'retention': dict({
'copies': None,
'days': None,
}),
'schedule': dict({
'days': list([
]),
'recurrence': 'never',
'time': None,
}),
}),
}),
'success': True,
'type': 'result',
})
# ---
# name: test_config_update[commands0]
dict({
'id': 1,

View File

@ -42,10 +42,10 @@ BACKUP_CALL = call(
agent_ids=["test.test-agent"],
backup_name="test-name",
extra_metadata={"instance_id": ANY, "with_automatic_settings": True},
include_addons=["test-addon"],
include_addons=[],
include_all_addons=False,
include_database=True,
include_folders=["media"],
include_folders=None,
include_homeassistant=True,
password="test-password",
on_progress=ANY,
@ -1121,25 +1121,96 @@ async def test_agents_info(
"minor_version": store.STORAGE_VERSION_MINOR,
},
},
{
"backup": {
"data": {
"backups": [],
"config": {
"agents": {},
"create_backup": {
"agent_ids": ["hassio.local", "hassio.share", "test-agent"],
"include_addons": None,
"include_all_addons": False,
"include_database": False,
"include_folders": None,
"name": None,
"password": None,
},
"retention": {"copies": None, "days": None},
"last_attempted_automatic_backup": None,
"last_completed_automatic_backup": None,
"schedule": {
"days": [],
"recurrence": "never",
"state": "never",
"time": None,
},
},
},
"key": DOMAIN,
"version": store.STORAGE_VERSION,
"minor_version": store.STORAGE_VERSION_MINOR,
},
},
{
"backup": {
"data": {
"backups": [],
"config": {
"agents": {},
"create_backup": {
"agent_ids": ["backup.local", "test-agent"],
"include_addons": None,
"include_all_addons": False,
"include_database": False,
"include_folders": None,
"name": None,
"password": None,
},
"retention": {"copies": None, "days": None},
"last_attempted_automatic_backup": None,
"last_completed_automatic_backup": None,
"schedule": {
"days": [],
"recurrence": "never",
"state": "never",
"time": None,
},
},
},
"key": DOMAIN,
"version": store.STORAGE_VERSION,
"minor_version": store.STORAGE_VERSION_MINOR,
},
},
],
)
@pytest.mark.parametrize(
("with_hassio"),
[
pytest.param(True, id="with_hassio"),
pytest.param(False, id="without_hassio"),
],
)
@pytest.mark.usefixtures("supervisor_client")
@patch("homeassistant.components.backup.config.random.randint", Mock(return_value=600))
async def test_config_info(
async def test_config_load_config_info(
hass: HomeAssistant,
hass_ws_client: WebSocketGenerator,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
hass_storage: dict[str, Any],
with_hassio: bool,
storage_data: dict[str, Any] | None,
) -> None:
"""Test getting backup config info."""
"""Test loading stored backup config and reading it via config/info."""
client = await hass_ws_client(hass)
await hass.config.async_set_time_zone("Europe/Amsterdam")
freezer.move_to("2024-11-13T12:01:00+01:00")
hass_storage.update(storage_data)
await setup_backup_integration(hass)
await setup_backup_integration(hass, with_hassio=with_hassio)
await hass.async_block_till_done()
await client.send_json_auto_id({"type": "backup/config/info"})
@ -1705,10 +1776,10 @@ async def test_config_schedule_logic(
"agents": {},
"create_backup": {
"agent_ids": ["test.test-agent"],
"include_addons": ["test-addon"],
"include_addons": [],
"include_all_addons": False,
"include_database": True,
"include_folders": ["media"],
"include_folders": [],
"name": "test-name",
"password": "test-password",
},