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, on_condition=AddonsJobError,
) )
async def update(self, slug: str) -> None: async def update(self, slug: str, backup: Optional[bool] = False) -> None:
"""Update add-on.""" """Update add-on."""
if slug not in self.local: if slug not in self.local:
raise AddonsError(f"Add-on {slug} is not installed", _LOGGER.error) 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 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 # Update instance
last_state: AddonState = addon.state last_state: AddonState = addon.state
old_image = addon.image old_image = addon.image

View File

@ -622,9 +622,9 @@ class AddonModel(CoreSysAttributes, ABC):
"""Uninstall this add-on.""" """Uninstall this add-on."""
return self.sys_addons.uninstall(self.slug) 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.""" """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]: def rebuild(self) -> Awaitable[None]:
"""Rebuild this add-on.""" """Rebuild this add-on."""

View File

@ -10,6 +10,7 @@ from ..const import (
ATTR_ARCH, ATTR_ARCH,
ATTR_AUDIO_INPUT, ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT, ATTR_AUDIO_OUTPUT,
ATTR_BACKUP,
ATTR_BLK_READ, ATTR_BLK_READ,
ATTR_BLK_WRITE, ATTR_BLK_WRITE,
ATTR_BOOT, 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): class APIHomeAssistant(CoreSysAttributes):
@ -137,10 +143,14 @@ class APIHomeAssistant(CoreSysAttributes):
@api_process @api_process
async def update(self, request: web.Request) -> None: async def update(self, request: web.Request) -> None:
"""Update Home Assistant.""" """Update Home Assistant."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_UPDATE, request)
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
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 @api_process
def stop(self, request: web.Request) -> Awaitable[None]: def stop(self, request: web.Request) -> Awaitable[None]:

View File

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

View File

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