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:
Pascal Vizeli 2020-11-13 08:35:45 +01:00 committed by GitHub
parent c209d2fa8d
commit e4bf820038
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 36 deletions

View File

@ -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):

View File

@ -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"

View File

@ -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:

View File

@ -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."""

View File

@ -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."""