Create backup of add-ons and core before update (#3369)

* Create backup of addons and core before update

* Move responsibility
This commit is contained in:
Joakim Sørensen 2021-12-28 11:13:54 +01:00 committed by GitHub
parent 22238c9c0e
commit e0fd31c390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 11 deletions

View File

@ -252,7 +252,7 @@ class AddonManager(CoreSysAttributes):
],
on_condition=AddonsJobError,
)
async def update(self, slug: str) -> None:
async def update(self, slug: str, backup: Optional[bool] = False) -> None:
"""Update add-on."""
if slug not in self.local:
raise AddonsError(f"Add-on {slug} is not installed", _LOGGER.error)
@ -273,6 +273,13 @@ class AddonManager(CoreSysAttributes):
f"Add-on {slug} not supported on that platform", _LOGGER.error
)
if backup:
await self.sys_backups.do_backup_partial(
name=f"addon_{addon.slug}_{addon.version}",
homeassistant=False,
addons=[addon.slug],
)
# Update instance
last_state: AddonState = addon.state
old_image = addon.image

View File

@ -622,9 +622,9 @@ class AddonModel(CoreSysAttributes, ABC):
"""Uninstall this add-on."""
return self.sys_addons.uninstall(self.slug)
def update(self) -> Awaitable[None]:
def update(self, backup: Optional[bool] = False) -> Awaitable[None]:
"""Update this add-on."""
return self.sys_addons.update(self.slug)
return self.sys_addons.update(self.slug, backup=backup)
def rebuild(self) -> Awaitable[None]:
"""Rebuild this add-on."""

View File

@ -10,6 +10,7 @@ from ..const import (
ATTR_ARCH,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BACKUP,
ATTR_BLK_READ,
ATTR_BLK_WRITE,
ATTR_BOOT,
@ -54,7 +55,12 @@ SCHEMA_OPTIONS = vol.Schema(
}
)
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
SCHEMA_UPDATE = vol.Schema(
{
vol.Optional(ATTR_VERSION): version_tag,
vol.Optional(ATTR_BACKUP): bool,
}
)
class APIHomeAssistant(CoreSysAttributes):
@ -137,10 +143,14 @@ class APIHomeAssistant(CoreSysAttributes):
@api_process
async def update(self, request: web.Request) -> None:
"""Update Home Assistant."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
body = await api_validate(SCHEMA_UPDATE, request)
await asyncio.shield(self.sys_homeassistant.core.update(version))
await asyncio.shield(
self.sys_homeassistant.core.update(
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
backup=body.get(ATTR_BACKUP),
)
)
@api_process
def stop(self, request: web.Request) -> Awaitable[None]:

View File

@ -3,13 +3,15 @@ import asyncio
from typing import Any, Awaitable
from aiohttp import web
import voluptuous as vol
from ..addons import AnyAddon
from ..api.utils import api_process
from ..api.utils import api_process, api_validate
from ..const import (
ATTR_ADDONS,
ATTR_ADVANCED,
ATTR_AVAILABLE,
ATTR_BACKUP,
ATTR_BUILD,
ATTR_DESCRIPTON,
ATTR_HOMEASSISTANT,
@ -34,6 +36,12 @@ from ..exceptions import APIError, APIForbidden
from ..store.addon import AddonStore
from ..store.repository import Repository
SCHEMA_UPDATE = vol.Schema(
{
vol.Optional(ATTR_BACKUP): bool,
}
)
class APIStore(CoreSysAttributes):
"""Handle RESTful API for store functions."""
@ -134,12 +142,15 @@ class APIStore(CoreSysAttributes):
return asyncio.shield(addon.install())
@api_process
def addons_addon_update(self, request: web.Request) -> Awaitable[None]:
async def addons_addon_update(self, request: web.Request) -> None:
"""Update add-on."""
addon = self._extract_addon(request, installed=True)
if addon == request.get(REQUEST_FROM):
raise APIForbidden(f"Add-on {addon.slug} can't update itself!")
return asyncio.shield(addon.update())
body = await api_validate(SCHEMA_UPDATE, request)
return await asyncio.shield(addon.update(backup=body.get(ATTR_BACKUP)))
@api_process
async def addons_addon_info(self, request: web.Request) -> dict[str, Any]:

View File

@ -114,6 +114,7 @@ ATTR_AUTH = "auth"
ATTR_AUTH_API = "auth_api"
ATTR_AUTO_UPDATE = "auto_update"
ATTR_AVAILABLE = "available"
ATTR_BACKUP = "backup"
ATTR_BACKUP_EXCLUDE = "backup_exclude"
ATTR_BACKUP_POST = "backup_post"
ATTR_BACKUP_PRE = "backup_pre"

View File

@ -12,6 +12,8 @@ from typing import Awaitable, Optional
import attr
from awesomeversion import AwesomeVersion, AwesomeVersionException
from supervisor.const import ATTR_HOMEASSISTANT
from ..coresys import CoreSys, CoreSysAttributes
from ..docker.homeassistant import DockerHomeAssistant
from ..docker.stats import DockerStats
@ -173,7 +175,11 @@ class HomeAssistantCore(CoreSysAttributes):
],
on_condition=HomeAssistantJobError,
)
async def update(self, version: Optional[AwesomeVersion] = None) -> None:
async def update(
self,
version: Optional[AwesomeVersion] = None,
backup: Optional[bool] = False,
) -> None:
"""Update HomeAssistant version."""
version = version or self.sys_homeassistant.latest_version
old_image = self.sys_homeassistant.image
@ -186,6 +192,13 @@ class HomeAssistantCore(CoreSysAttributes):
f"Version {version!s} is already installed", _LOGGER.warning
)
if backup:
await self.sys_backups.do_backup_partial(
name=f"core_{self.instance.version}",
homeassistant=True,
folders=[ATTR_HOMEASSISTANT],
)
# process an update
async def _update(to_version: AwesomeVersion) -> None:
"""Run Home Assistant update."""