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