mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-12 19:56:29 +00:00
Check & fix git repository integrity (#2245)
* Check & fix git repository integrity * fix typing * New style error handling * Fix return * tag more issue * Fix black
This commit is contained in:
parent
c209d2fa8d
commit
e4bf820038
@ -295,7 +295,18 @@ class ResolutionNotFound(ResolutionError):
|
||||
"""Raise if suggestion/issue was not found."""
|
||||
|
||||
|
||||
# Job
|
||||
# Store
|
||||
|
||||
|
||||
class StoreError(HassioError):
|
||||
"""Raise if an error on store is happening."""
|
||||
|
||||
|
||||
class StoreGitError(StoreError):
|
||||
"""Raise if something on git is happening."""
|
||||
|
||||
|
||||
# JobManager
|
||||
|
||||
|
||||
class JobException(HassioError):
|
||||
|
@ -10,12 +10,13 @@ MINIMUM_FULL_SNAPSHOTS = 2
|
||||
class ContextType(str, Enum):
|
||||
"""Place where somethings was happening."""
|
||||
|
||||
SYSTEM = "system"
|
||||
SUPERVISOR = "supervisor"
|
||||
PLUGIN = "plugin"
|
||||
ADDON = "addon"
|
||||
CORE = "core"
|
||||
OS = "os"
|
||||
PLUGIN = "plugin"
|
||||
SUPERVISOR = "supervisor"
|
||||
STORE = "store"
|
||||
SYSTEM = "system"
|
||||
|
||||
|
||||
class UnsupportedReason(str, Enum):
|
||||
@ -37,6 +38,7 @@ class IssueType(str, Enum):
|
||||
|
||||
FREE_SPACE = "free_space"
|
||||
CORRUPT_DOCKER = "corrupt_docker"
|
||||
CORRUPT_REPOSITORY = "corrupt_repository"
|
||||
MISSING_IMAGE = "missing_image"
|
||||
UPDATE_FAILED = "update_failed"
|
||||
UPDATE_ROLLBACK = "update_rollback"
|
||||
@ -52,3 +54,5 @@ class SuggestionType(str, Enum):
|
||||
EXECUTE_UPDATE = "execute_update"
|
||||
EXECUTE_REPAIR = "execute_repair"
|
||||
EXECUTE_RESET = "execute_reset"
|
||||
EXECUTE_RELOAD = "execute_reload"
|
||||
NEW_INITIALIZE = "new_initialize"
|
||||
|
@ -11,7 +11,7 @@ from supervisor.utils.json import read_json_file
|
||||
|
||||
from ..const import REPOSITORY_CORE, REPOSITORY_LOCAL
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import JsonFileError
|
||||
from ..exceptions import JsonFileError, StoreError, StoreGitError
|
||||
from ..job.decorator import Job, JobCondition
|
||||
from .addon import AddonStore
|
||||
from .data import StoreData
|
||||
@ -70,9 +70,11 @@ class StoreManager(CoreSysAttributes):
|
||||
progress=job.progress + step, stage=f"Checking {url} started"
|
||||
)
|
||||
repository = Repository(self.coresys, url)
|
||||
if not await repository.load():
|
||||
try:
|
||||
await repository.load()
|
||||
except StoreGitError as err:
|
||||
_LOGGER.error("Can't load data from repository %s", url)
|
||||
return
|
||||
raise StoreError() from err
|
||||
|
||||
# don't add built-in repository to config
|
||||
if url not in BUILTIN_REPOSITORIES:
|
||||
|
@ -4,11 +4,14 @@ import functools as ft
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import Dict, Optional
|
||||
|
||||
import git
|
||||
|
||||
from ..const import ATTR_BRANCH, ATTR_URL, URL_HASSIO_ADDONS
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import StoreGitError
|
||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||
from ..validate import RE_REPOSITORY
|
||||
from .utils import get_hash_from_repository
|
||||
|
||||
@ -18,30 +21,33 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
class GitRepo(CoreSysAttributes):
|
||||
"""Manage Add-on Git repository."""
|
||||
|
||||
def __init__(self, coresys, path, url):
|
||||
def __init__(self, coresys: CoreSys, path: Path, url: str):
|
||||
"""Initialize Git base wrapper."""
|
||||
self.coresys = coresys
|
||||
self.repo = None
|
||||
self.path = path
|
||||
self.lock = asyncio.Lock()
|
||||
self.coresys: CoreSys = coresys
|
||||
self.repo: Optional[git.Repo] = None
|
||||
self.path: Path = path
|
||||
self.lock: asyncio.Lock = asyncio.Lock()
|
||||
|
||||
self.data = RE_REPOSITORY.match(url).groupdict()
|
||||
self.data: Dict[str, str] = RE_REPOSITORY.match(url).groupdict()
|
||||
self.slug: str = url
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
def url(self) -> str:
|
||||
"""Return repository URL."""
|
||||
return self.data[ATTR_URL]
|
||||
|
||||
@property
|
||||
def branch(self):
|
||||
def branch(self) -> str:
|
||||
"""Return repository branch."""
|
||||
return self.data[ATTR_BRANCH]
|
||||
|
||||
async def load(self):
|
||||
async def load(self) -> None:
|
||||
"""Init Git add-on repository."""
|
||||
if not self.path.is_dir():
|
||||
return await self.clone()
|
||||
await self.clone()
|
||||
return
|
||||
|
||||
# Load repository
|
||||
async with self.lock:
|
||||
try:
|
||||
_LOGGER.info("Loading add-on %s repository", self.path)
|
||||
@ -53,12 +59,29 @@ class GitRepo(CoreSysAttributes):
|
||||
git.GitCommandError,
|
||||
) as err:
|
||||
_LOGGER.error("Can't load %s repo: %s.", self.path, err)
|
||||
await self._remove()
|
||||
return False
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
reference=self.slug,
|
||||
)
|
||||
raise StoreGitError() from err
|
||||
|
||||
return True
|
||||
# Fix possible corruption
|
||||
async with self.lock:
|
||||
try:
|
||||
_LOGGER.debug("Integrity check add-on %s repository", self.path)
|
||||
await self.sys_run_in_executor(self.repo.git.execute, ["git", "fsck"])
|
||||
except git.GitCommandError as err:
|
||||
_LOGGER.error("Integrity check on %s failed: %s.", self.path, err)
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.CORRUPT_REPOSITORY,
|
||||
ContextType.STORE,
|
||||
reference=self.slug,
|
||||
suggestions=[SuggestionType.EXECUTE_RESET],
|
||||
)
|
||||
raise StoreGitError() from err
|
||||
|
||||
async def clone(self):
|
||||
async def clone(self) -> None:
|
||||
"""Clone git add-on repository."""
|
||||
async with self.lock:
|
||||
git_args = {
|
||||
@ -86,16 +109,19 @@ class GitRepo(CoreSysAttributes):
|
||||
git.GitCommandError,
|
||||
) as err:
|
||||
_LOGGER.error("Can't clone %s repository: %s.", self.url, err)
|
||||
await self._remove()
|
||||
return False
|
||||
|
||||
return True
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
reference=self.slug,
|
||||
suggestions=[SuggestionType.NEW_INITIALIZE],
|
||||
)
|
||||
raise StoreGitError() from err
|
||||
|
||||
async def pull(self):
|
||||
"""Pull Git add-on repo."""
|
||||
if self.lock.locked():
|
||||
_LOGGER.warning("There is already a task in progress")
|
||||
return False
|
||||
return
|
||||
|
||||
async with self.lock:
|
||||
_LOGGER.info("Update add-on %s repository", self.url)
|
||||
@ -124,9 +150,13 @@ class GitRepo(CoreSysAttributes):
|
||||
git.GitCommandError,
|
||||
) as err:
|
||||
_LOGGER.error("Can't update %s repo: %s.", self.url, err)
|
||||
return False
|
||||
|
||||
return True
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.CORRUPT_REPOSITORY,
|
||||
ContextType.STORE,
|
||||
reference=self.slug,
|
||||
suggestions=[SuggestionType.EXECUTE_RELOAD],
|
||||
)
|
||||
raise StoreGitError() from err
|
||||
|
||||
async def _remove(self):
|
||||
"""Remove a repository."""
|
||||
|
@ -57,15 +57,15 @@ class Repository(CoreSysAttributes):
|
||||
|
||||
async def load(self):
|
||||
"""Load addon repository."""
|
||||
if self.git:
|
||||
return await self.git.load()
|
||||
return True
|
||||
if not self.git:
|
||||
return
|
||||
await self.git.load()
|
||||
|
||||
async def update(self):
|
||||
"""Update add-on repository."""
|
||||
if self.git:
|
||||
return await self.git.pull()
|
||||
return True
|
||||
if not self.git:
|
||||
return
|
||||
await self.git.pull()
|
||||
|
||||
async def remove(self):
|
||||
"""Remove add-on repository."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user