Rename snapshot -> backup (#53851)

pull/49395/head
Joakim Sørensen 2021-08-02 11:07:21 +02:00 committed by GitHub
parent 38832618bf
commit da3419945c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 181 additions and 82 deletions

View File

@ -89,6 +89,8 @@ SERVICE_HOST_SHUTDOWN = "host_shutdown"
SERVICE_HOST_REBOOT = "host_reboot"
SERVICE_SNAPSHOT_FULL = "snapshot_full"
SERVICE_SNAPSHOT_PARTIAL = "snapshot_partial"
SERVICE_BACKUP_FULL = "backup_full"
SERVICE_BACKUP_PARTIAL = "backup_partial"
SERVICE_RESTORE_FULL = "restore_full"
SERVICE_RESTORE_PARTIAL = "restore_partial"
@ -101,11 +103,11 @@ SCHEMA_ADDON_STDIN = SCHEMA_ADDON.extend(
{vol.Required(ATTR_INPUT): vol.Any(dict, cv.string)}
)
SCHEMA_SNAPSHOT_FULL = vol.Schema(
SCHEMA_BACKUP_FULL = vol.Schema(
{vol.Optional(ATTR_NAME): cv.string, vol.Optional(ATTR_PASSWORD): cv.string}
)
SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend(
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
{
vol.Optional(ATTR_FOLDERS): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(ATTR_ADDONS): vol.All(cv.ensure_list, [cv.string]),
@ -113,7 +115,12 @@ SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend(
)
SCHEMA_RESTORE_FULL = vol.Schema(
{vol.Required(ATTR_SNAPSHOT): cv.slug, vol.Optional(ATTR_PASSWORD): cv.string}
{
vol.Exclusive(ATTR_SLUG, ATTR_SLUG): cv.slug,
vol.Exclusive(ATTR_SNAPSHOT, ATTR_SLUG): cv.slug,
vol.Optional(ATTR_PASSWORD): cv.string,
},
cv.has_at_least_one_key(ATTR_SLUG, ATTR_SNAPSHOT),
)
SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL.extend(
@ -133,25 +140,32 @@ MAP_SERVICE_API = {
SERVICE_ADDON_STDIN: ("/addons/{addon}/stdin", SCHEMA_ADDON_STDIN, 60, False),
SERVICE_HOST_SHUTDOWN: ("/host/shutdown", SCHEMA_NO_DATA, 60, False),
SERVICE_HOST_REBOOT: ("/host/reboot", SCHEMA_NO_DATA, 60, False),
SERVICE_SNAPSHOT_FULL: ("/snapshots/new/full", SCHEMA_SNAPSHOT_FULL, 300, True),
SERVICE_SNAPSHOT_PARTIAL: (
"/snapshots/new/partial",
SCHEMA_SNAPSHOT_PARTIAL,
SERVICE_BACKUP_FULL: ("/backups/new/full", SCHEMA_BACKUP_FULL, 300, True),
SERVICE_BACKUP_PARTIAL: (
"/backups/new/partial",
SCHEMA_BACKUP_PARTIAL,
300,
True,
),
SERVICE_RESTORE_FULL: (
"/snapshots/{snapshot}/restore/full",
"/backups/{slug}/restore/full",
SCHEMA_RESTORE_FULL,
300,
True,
),
SERVICE_RESTORE_PARTIAL: (
"/snapshots/{snapshot}/restore/partial",
"/backups/{slug}/restore/partial",
SCHEMA_RESTORE_PARTIAL,
300,
True,
),
SERVICE_SNAPSHOT_FULL: ("/backups/new/full", SCHEMA_BACKUP_FULL, 300, True),
SERVICE_SNAPSHOT_PARTIAL: (
"/backups/new/partial",
SCHEMA_BACKUP_PARTIAL,
300,
True,
),
}
@ -272,16 +286,16 @@ async def async_get_addon_discovery_info(hass: HomeAssistant, slug: str) -> dict
@bind_hass
@api_data
async def async_create_snapshot(
async def async_create_backup(
hass: HomeAssistant, payload: dict, partial: bool = False
) -> dict:
"""Create a full or partial snapshot.
"""Create a full or partial backup.
The caller of the function should handle HassioAPIError.
"""
hassio = hass.data[DOMAIN]
snapshot_type = "partial" if partial else "full"
command = f"/snapshots/new/{snapshot_type}"
backup_type = "partial" if partial else "full"
command = f"/backups/new/{backup_type}"
return await hassio.send_command(command, payload=payload, timeout=None)
@ -453,9 +467,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
async def async_service_handler(service):
"""Handle service calls for Hass.io."""
api_command = MAP_SERVICE_API[service.service][0]
if "snapshot" in service.service:
_LOGGER.warning(
"The service '%s' is deprecated and will be removed in Home Assistant 2021.11, use '%s' instead",
service.service,
service.service.replace("snapshot", "backup"),
)
data = service.data.copy()
addon = data.pop(ATTR_ADDON, None)
slug = data.pop(ATTR_SLUG, None)
snapshot = data.pop(ATTR_SNAPSHOT, None)
if snapshot is not None:
_LOGGER.warning(
"Using 'snapshot' is deprecated and will be removed in Home Assistant 2021.11, use 'slug' instead"
)
slug = snapshot
payload = None
# Pass data to Hass.io API
@ -467,12 +494,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
# Call API
try:
await hassio.send_command(
api_command.format(addon=addon, snapshot=snapshot),
api_command.format(addon=addon, slug=slug),
payload=payload,
timeout=MAP_SERVICE_API[service.service][2],
)
except HassioAPIError as err:
_LOGGER.error("Error on Hass.io API: %s", err)
_LOGGER.error("Error on Supervisor API: %s", err)
for service, settings in MAP_SERVICE_API.items():
hass.services.async_register(

View File

@ -29,6 +29,9 @@ NO_TIMEOUT = re.compile(
r"|hassos/update/cli"
r"|supervisor/update"
r"|addons/[^/]+/(?:update|install|rebuild)"
r"|backups/.+/full"
r"|backups/.+/partial"
r"|backups/[^/]+/(?:upload|download)"
r"|snapshots/.+/full"
r"|snapshots/.+/partial"
r"|snapshots/[^/]+/(?:upload|download)"
@ -36,7 +39,7 @@ NO_TIMEOUT = re.compile(
)
NO_AUTH_ONBOARDING = re.compile(
r"^(?:" r"|supervisor/logs" r"|snapshots/[^/]+/.+" r")$"
r"^(?:" r"|supervisor/logs" r"|backups/[^/]+/.+" r"|snapshots/[^/]+/.+" r")$"
)
NO_AUTH = re.compile(
@ -81,13 +84,13 @@ class HassIOView(HomeAssistantView):
client_timeout = 10
data = None
headers = _init_header(request)
if path == "snapshots/new/upload":
if path in ("snapshots/new/upload", "backups/new/upload"):
# We need to reuse the full content type that includes the boundary
headers[
"Content-Type"
] = request._stored_content_type # pylint: disable=protected-access
# Snapshots are big, so we need to adjust the allowed size
# Backups are big, so we need to adjust the allowed size
request._client_max_size = ( # pylint: disable=protected-access
MAX_UPLOAD_SIZE
)

View File

@ -67,13 +67,13 @@ host_shutdown:
description: Poweroff the host system.
snapshot_full:
name: Create a full snapshot.
description: Create a full snapshot.
name: Create a full backup.
description: Create a full backup (deprecated, use backup_full instead).
fields:
name:
name: Name
description: Optional or it will be the current date and time.
example: "Snapshot 1"
example: "backup 1"
selector:
text:
password:
@ -84,8 +84,8 @@ snapshot_full:
text:
snapshot_partial:
name: Create a partial snapshot.
description: Create a partial snapshot.
name: Create a partial backup.
description: Create a partial backup (deprecated, use backup_partial instead).
fields:
addons:
name: Add-ons
@ -102,7 +102,53 @@ snapshot_partial:
name:
name: Name
description: Optional or it will be the current date and time.
example: "Partial Snapshot 1"
example: "Partial backup 1"
selector:
text:
password:
name: Password
description: Optional password.
example: "password"
selector:
text:
backup_full:
name: Create a full backup.
description: Create a full backup.
fields:
name:
name: Name
description: Optional or it will be the current date and time.
example: "backup 1"
selector:
text:
password:
name: Password
description: Optional password.
example: "password"
selector:
text:
backup_partial:
name: Create a partial backup.
description: Create a partial backup.
fields:
addons:
name: Add-ons
description: Optional list of addon slugs.
example: ["core_ssh", "core_samba", "core_mosquitto"]
selector:
object:
folders:
name: Folders
description: Optional list of directories.
example: ["homeassistant", "share"]
selector:
object:
name:
name: Name
description: Optional or it will be the current date and time.
example: "Partial backup 1"
selector:
text:
password:

View File

@ -547,7 +547,7 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
LOGGER.error(err)
return
try:
await addon_manager.async_create_snapshot()
await addon_manager.async_create_backup()
except AddonError as err:
LOGGER.error(err)
return

View File

@ -8,7 +8,7 @@ from functools import partial
from typing import Any, Callable, TypeVar, cast
from homeassistant.components.hassio import (
async_create_snapshot,
async_create_backup,
async_get_addon_discovery_info,
async_get_addon_info,
async_install_addon,
@ -202,7 +202,7 @@ class AddonManager:
if not addon_info.update_available:
return
await self.async_create_snapshot()
await self.async_create_backup()
await async_update_addon(self._hass, ADDON_SLUG)
@callback
@ -289,14 +289,14 @@ class AddonManager:
)
return self._start_task
@api_error("Failed to create a snapshot of the Z-Wave JS add-on.")
async def async_create_snapshot(self) -> None:
"""Create a partial snapshot of the Z-Wave JS add-on."""
@api_error("Failed to create a backup of the Z-Wave JS add-on.")
async def async_create_backup(self) -> None:
"""Create a partial backup of the Z-Wave JS add-on."""
addon_info = await self.async_get_addon_info()
name = f"addon_{ADDON_SLUG}_{addon_info.version}"
LOGGER.debug("Creating snapshot: %s", name)
await async_create_snapshot(
LOGGER.debug("Creating backup: %s", name)
await async_create_backup(
self._hass,
{"name": name, "addons": [ADDON_SLUG]},
partial=True,

View File

@ -132,13 +132,13 @@ async def test_forwarding_user_info(hassio_client, hass_admin_user, aioclient_mo
assert req_headers["X-Hass-Is-Admin"] == "1"
async def test_snapshot_upload_headers(hassio_client, aioclient_mock):
"""Test that we forward the full header for snapshot upload."""
async def test_backup_upload_headers(hassio_client, aioclient_mock, caplog):
"""Test that we forward the full header for backup upload."""
content_type = "multipart/form-data; boundary='--webkit'"
aioclient_mock.get("http://127.0.0.1/snapshots/new/upload")
aioclient_mock.get("http://127.0.0.1/backups/new/upload")
resp = await hassio_client.get(
"/api/hassio/snapshots/new/upload", headers={"Content-Type": content_type}
"/api/hassio/backups/new/upload", headers={"Content-Type": content_type}
)
# Check we got right response
@ -150,18 +150,18 @@ async def test_snapshot_upload_headers(hassio_client, aioclient_mock):
assert req_headers["Content-Type"] == content_type
async def test_snapshot_download_headers(hassio_client, aioclient_mock):
"""Test that we forward the full header for snapshot download."""
async def test_backup_download_headers(hassio_client, aioclient_mock):
"""Test that we forward the full header for backup download."""
content_disposition = "attachment; filename=test.tar"
aioclient_mock.get(
"http://127.0.0.1/snapshots/slug/download",
"http://127.0.0.1/backups/slug/download",
headers={
"Content-Length": "50000000",
"Content-Disposition": content_disposition,
},
)
resp = await hassio_client.get("/api/hassio/snapshots/slug/download")
resp = await hassio_client.get("/api/hassio/backups/slug/download")
# Check we got right response
assert resp.status == 200
@ -174,9 +174,9 @@ async def test_snapshot_download_headers(hassio_client, aioclient_mock):
def test_need_auth(hass):
"""Test if the requested path needs authentication."""
assert not _need_auth(hass, "addons/test/logo")
assert _need_auth(hass, "snapshots/new/upload")
assert _need_auth(hass, "backups/new/upload")
assert _need_auth(hass, "supervisor/logs")
hass.data["onboarding"] = False
assert not _need_auth(hass, "snapshots/new/upload")
assert not _need_auth(hass, "backups/new/upload")
assert not _need_auth(hass, "supervisor/logs")

View File

@ -303,11 +303,13 @@ async def test_service_register(hassio_env, hass):
assert hass.services.has_service("hassio", "host_reboot")
assert hass.services.has_service("hassio", "snapshot_full")
assert hass.services.has_service("hassio", "snapshot_partial")
assert hass.services.has_service("hassio", "backup_full")
assert hass.services.has_service("hassio", "backup_partial")
assert hass.services.has_service("hassio", "restore_full")
assert hass.services.has_service("hassio", "restore_partial")
async def test_service_calls(hassio_env, hass, aioclient_mock):
async def test_service_calls(hassio_env, hass, aioclient_mock, caplog):
"""Call service and check the API calls behind that."""
assert await async_setup_component(hass, "hassio", {})
@ -318,13 +320,13 @@ async def test_service_calls(hassio_env, hass, aioclient_mock):
aioclient_mock.post("http://127.0.0.1/addons/test/stdin", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/host/shutdown", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/host/reboot", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/snapshots/new/full", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/snapshots/new/partial", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/backups/new/full", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/backups/new/partial", json={"result": "ok"})
aioclient_mock.post(
"http://127.0.0.1/snapshots/test/restore/full", json={"result": "ok"}
"http://127.0.0.1/backups/test/restore/full", json={"result": "ok"}
)
aioclient_mock.post(
"http://127.0.0.1/snapshots/test/restore/partial", json={"result": "ok"}
"http://127.0.0.1/backups/test/restore/partial", json={"result": "ok"}
)
await hass.services.async_call("hassio", "addon_start", {"addon": "test"})
@ -345,27 +347,48 @@ async def test_service_calls(hassio_env, hass, aioclient_mock):
assert aioclient_mock.call_count == 10
await hass.services.async_call("hassio", "backup_full", {})
await hass.services.async_call(
"hassio",
"backup_partial",
{"addons": ["test"], "folders": ["ssl"], "password": "123456"},
)
await hass.services.async_call("hassio", "snapshot_full", {})
await hass.services.async_call(
"hassio",
"snapshot_partial",
{"addons": ["test"], "folders": ["ssl"], "password": "123456"},
{"addons": ["test"], "folders": ["ssl"]},
)
await hass.async_block_till_done()
assert (
"The service 'snapshot_full' is deprecated and will be removed in Home Assistant 2021.11, use 'backup_full' instead"
in caplog.text
)
assert (
"The service 'snapshot_partial' is deprecated and will be removed in Home Assistant 2021.11, use 'backup_partial' instead"
in caplog.text
)
assert aioclient_mock.call_count == 12
assert aioclient_mock.mock_calls[-1][2] == {
assert aioclient_mock.call_count == 14
assert aioclient_mock.mock_calls[-3][2] == {
"addons": ["test"],
"folders": ["ssl"],
"password": "123456",
}
await hass.services.async_call("hassio", "restore_full", {"slug": "test"})
await hass.services.async_call("hassio", "restore_full", {"snapshot": "test"})
await hass.async_block_till_done()
assert (
"Using 'snapshot' is deprecated and will be removed in Home Assistant 2021.11, use 'slug' instead"
in caplog.text
)
await hass.services.async_call(
"hassio",
"restore_partial",
{
"snapshot": "test",
"slug": "test",
"homeassistant": False,
"addons": ["test"],
"folders": ["ssl"],
@ -374,7 +397,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock):
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 14
assert aioclient_mock.call_count == 17
assert aioclient_mock.mock_calls[-1][2] == {
"addons": ["test"],
"folders": ["ssl"],

View File

@ -61,7 +61,7 @@ async def test_websocket_supervisor_api(
assert await async_setup_component(hass, "hassio", {})
websocket_client = await hass_ws_client(hass)
aioclient_mock.post(
"http://127.0.0.1/snapshots/new/partial",
"http://127.0.0.1/backups/new/partial",
json={"result": "ok", "data": {"slug": "sn_slug"}},
)
@ -69,7 +69,7 @@ async def test_websocket_supervisor_api(
{
WS_ID: 1,
WS_TYPE: WS_TYPE_API,
ATTR_ENDPOINT: "/snapshots/new/partial",
ATTR_ENDPOINT: "/backups/new/partial",
ATTR_METHOD: "post",
}
)

View File

@ -171,13 +171,13 @@ def uninstall_addon_fixture():
yield uninstall_addon
@pytest.fixture(name="create_shapshot")
def create_snapshot_fixture():
"""Mock create snapshot."""
@pytest.fixture(name="create_backup")
def create_backup_fixture():
"""Mock create backup."""
with patch(
"homeassistant.components.zwave_js.addon.async_create_snapshot"
) as create_shapshot:
yield create_shapshot
"homeassistant.components.zwave_js.addon.async_create_backup"
) as create_backup:
yield create_backup
@pytest.fixture(name="controller_state", scope="session")

View File

@ -365,8 +365,8 @@ async def test_addon_options_changed(
@pytest.mark.parametrize(
"addon_version, update_available, update_calls, snapshot_calls, "
"update_addon_side_effect, create_shapshot_side_effect",
"addon_version, update_available, update_calls, backup_calls, "
"update_addon_side_effect, create_backup_side_effect",
[
("1.0", True, 1, 1, None, None),
("1.0", False, 0, 0, None, None),
@ -380,15 +380,15 @@ async def test_update_addon(
addon_info,
addon_installed,
addon_running,
create_shapshot,
create_backup,
update_addon,
addon_options,
addon_version,
update_available,
update_calls,
snapshot_calls,
backup_calls,
update_addon_side_effect,
create_shapshot_side_effect,
create_backup_side_effect,
):
"""Test update the Z-Wave JS add-on during entry setup."""
device = "/test"
@ -397,7 +397,7 @@ async def test_update_addon(
addon_options["network_key"] = network_key
addon_info.return_value["version"] = addon_version
addon_info.return_value["update_available"] = update_available
create_shapshot.side_effect = create_shapshot_side_effect
create_backup.side_effect = create_backup_side_effect
update_addon.side_effect = update_addon_side_effect
client.connect.side_effect = InvalidServerVersion("Invalid version")
entry = MockConfigEntry(
@ -416,7 +416,7 @@ async def test_update_addon(
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_RETRY
assert create_shapshot.call_count == snapshot_calls
assert create_backup.call_count == backup_calls
assert update_addon.call_count == update_calls
@ -469,7 +469,7 @@ async def test_stop_addon(
async def test_remove_entry(
hass, addon_installed, stop_addon, create_shapshot, uninstall_addon, caplog
hass, addon_installed, stop_addon, create_backup, uninstall_addon, caplog
):
"""Test remove the config entry."""
# test successful remove without created add-on
@ -500,8 +500,8 @@ async def test_remove_entry(
assert stop_addon.call_count == 1
assert stop_addon.call_args == call(hass, "core_zwave_js")
assert create_shapshot.call_count == 1
assert create_shapshot.call_args == call(
assert create_backup.call_count == 1
assert create_backup.call_args == call(
hass,
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
partial=True,
@ -511,7 +511,7 @@ async def test_remove_entry(
assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
stop_addon.reset_mock()
create_shapshot.reset_mock()
create_backup.reset_mock()
uninstall_addon.reset_mock()
# test add-on stop failure
@ -523,27 +523,27 @@ async def test_remove_entry(
assert stop_addon.call_count == 1
assert stop_addon.call_args == call(hass, "core_zwave_js")
assert create_shapshot.call_count == 0
assert create_backup.call_count == 0
assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to stop the Z-Wave JS add-on" in caplog.text
stop_addon.side_effect = None
stop_addon.reset_mock()
create_shapshot.reset_mock()
create_backup.reset_mock()
uninstall_addon.reset_mock()
# test create snapshot failure
# test create backup failure
entry.add_to_hass(hass)
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
create_shapshot.side_effect = HassioAPIError()
create_backup.side_effect = HassioAPIError()
await hass.config_entries.async_remove(entry.entry_id)
assert stop_addon.call_count == 1
assert stop_addon.call_args == call(hass, "core_zwave_js")
assert create_shapshot.call_count == 1
assert create_shapshot.call_args == call(
assert create_backup.call_count == 1
assert create_backup.call_args == call(
hass,
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
partial=True,
@ -551,10 +551,10 @@ async def test_remove_entry(
assert uninstall_addon.call_count == 0
assert entry.state is ConfigEntryState.NOT_LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
assert "Failed to create a snapshot of the Z-Wave JS add-on" in caplog.text
create_shapshot.side_effect = None
assert "Failed to create a backup of the Z-Wave JS add-on" in caplog.text
create_backup.side_effect = None
stop_addon.reset_mock()
create_shapshot.reset_mock()
create_backup.reset_mock()
uninstall_addon.reset_mock()
# test add-on uninstall failure
@ -566,8 +566,8 @@ async def test_remove_entry(
assert stop_addon.call_count == 1
assert stop_addon.call_args == call(hass, "core_zwave_js")
assert create_shapshot.call_count == 1
assert create_shapshot.call_args == call(
assert create_backup.call_count == 1
assert create_backup.call_args == call(
hass,
{"name": "addon_core_zwave_js_1.0", "addons": ["core_zwave_js"]},
partial=True,