Fix issue on store git clone (#2331)

This commit is contained in:
Pascal Vizeli 2020-12-03 21:06:48 +01:00 committed by GitHub
parent f8fd7b5933
commit 0c55bf20fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 238 additions and 192 deletions

View File

@ -44,7 +44,7 @@ from .misc.hwmon import HwMonitor
from .misc.scheduler import Scheduler
from .misc.tasks import Tasks
from .plugins import PluginManager
from .resolution import ResolutionManager
from .resolution.module import ResolutionManager
from .services import ServiceManager
from .snapshots import SnapshotManager
from .store import StoreManager

View File

@ -83,6 +83,7 @@ class Core(CoreSysAttributes):
"""Start setting up supervisor orchestration."""
self.state = CoreState.SETUP
# Order can be important!
setup_loads: List[Awaitable[None]] = [
# rest api views
self.sys_api.load(),

View File

@ -30,7 +30,7 @@ if TYPE_CHECKING:
from .misc.scheduler import Scheduler
from .misc.tasks import Tasks
from .plugins import PluginManager
from .resolution import ResolutionManager
from .resolution.module import ResolutionManager
from .services import ServiceManager
from .snapshots import SnapshotManager
from .store import StoreManager

View File

@ -322,6 +322,10 @@ class ResolutionFixupError(HassioError):
"""Rasie if a fixup fails."""
class ResolutionFixupJobError(ResolutionFixupError, JobException):
"""Raise on job error."""
# Store
@ -335,3 +339,7 @@ class StoreGitError(StoreError):
class StoreNotFound(StoreError):
"""Raise if slug is not known."""
class StoreJobError(StoreError, JobException):
"""Raise on job error with git."""

View File

@ -1,181 +1 @@
"""Supervisor resolution center."""
from datetime import time
import logging
from typing import List, Optional
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import ResolutionError, ResolutionNotFound
from .check import ResolutionCheck
from .const import (
SCHEDULED_HEALTHCHECK,
ContextType,
IssueType,
SuggestionType,
UnhealthyReason,
UnsupportedReason,
)
from .data import Issue, Suggestion
from .evaluate import ResolutionEvaluation
from .fixup import ResolutionFixup
from .notify import ResolutionNotify
_LOGGER: logging.Logger = logging.getLogger(__name__)
class ResolutionManager(CoreSysAttributes):
"""Resolution manager for supervisor."""
def __init__(self, coresys: CoreSys):
"""Initialize Resolution manager."""
self.coresys: CoreSys = coresys
self._evaluate = ResolutionEvaluation(coresys)
self._check = ResolutionCheck(coresys)
self._fixup = ResolutionFixup(coresys)
self._notify = ResolutionNotify(coresys)
self._suggestions: List[Suggestion] = []
self._issues: List[Issue] = []
self._unsupported: List[UnsupportedReason] = []
self._unhealthy: List[UnhealthyReason] = []
@property
def evaluate(self) -> ResolutionEvaluation:
"""Return the ResolutionEvaluation class."""
return self._evaluate
@property
def check(self) -> ResolutionCheck:
"""Return the ResolutionCheck class."""
return self._check
@property
def fixup(self) -> ResolutionFixup:
"""Return the ResolutionFixup class."""
return self._fixup
@property
def notify(self) -> ResolutionNotify:
"""Return the ResolutionNotify class."""
return self._notify
@property
def issues(self) -> List[Issue]:
"""Return a list of issues."""
return self._issues
@issues.setter
def issues(self, issue: Issue) -> None:
"""Add issues."""
if issue not in self._issues:
self._issues.append(issue)
@property
def suggestions(self) -> List[Suggestion]:
"""Return a list of suggestions that can handled."""
return self._suggestions
@suggestions.setter
def suggestions(self, suggestion: Suggestion) -> None:
"""Add suggestion."""
if suggestion not in self._suggestions:
self._suggestions.append(suggestion)
@property
def unsupported(self) -> List[UnsupportedReason]:
"""Return a list of unsupported reasons."""
return self._unsupported
@unsupported.setter
def unsupported(self, reason: UnsupportedReason) -> None:
"""Add a reason for unsupported."""
if reason not in self._unsupported:
self._unsupported.append(reason)
@property
def unhealthy(self) -> List[UnhealthyReason]:
"""Return a list of unsupported reasons."""
return self._unhealthy
@unhealthy.setter
def unhealthy(self, reason: UnhealthyReason) -> None:
"""Add a reason for unsupported."""
if reason not in self._unhealthy:
self._unhealthy.append(reason)
def get_suggestion(self, uuid: str) -> Suggestion:
"""Return suggestion with uuid."""
for suggestion in self._suggestions:
if suggestion.uuid != uuid:
continue
return suggestion
raise ResolutionNotFound()
def get_issue(self, uuid: str) -> Issue:
"""Return issue with uuid."""
for issue in self._issues:
if issue.uuid != uuid:
continue
return issue
raise ResolutionNotFound()
def create_issue(
self,
issue: IssueType,
context: ContextType,
reference: Optional[str] = None,
suggestions: Optional[List[SuggestionType]] = None,
) -> None:
"""Create issues and suggestion."""
self.issues = Issue(issue, context, reference)
if not suggestions:
return
# Add suggestions
for suggestion in suggestions:
self.suggestions = Suggestion(suggestion, context, reference)
async def load(self):
"""Load the resoulution manager."""
# Initial healthcheck when the manager is loaded
await self.healthcheck()
# Schedule the healthcheck
self.sys_scheduler.register_task(self.healthcheck, SCHEDULED_HEALTHCHECK)
self.sys_scheduler.register_task(self.fixup.run_autofix, time(hour=2))
async def healthcheck(self):
"""Scheduled task to check for known issues."""
await self.check.check_system()
# Create notification for any known issues
await self.notify.issue_notifications()
async def apply_suggestion(self, suggestion: Suggestion) -> None:
"""Apply suggested action."""
if suggestion not in self._suggestions:
_LOGGER.warning("Suggestion %s is not valid", suggestion.uuid)
raise ResolutionError()
await self.fixup.apply_fixup(suggestion)
await self.healthcheck()
def dismiss_suggestion(self, suggestion: Suggestion) -> None:
"""Dismiss suggested action."""
if suggestion not in self._suggestions:
_LOGGER.warning("The UUID %s is not valid suggestion", suggestion.uuid)
raise ResolutionError()
self._suggestions.remove(suggestion)
def dismiss_issue(self, issue: Issue) -> None:
"""Dismiss suggested action."""
if issue not in self._issues:
_LOGGER.warning("The UUID %s is not a valid issue", issue.uuid)
raise ResolutionError()
self._issues.remove(issue)
def dismiss_unsupported(self, reason: Issue) -> None:
"""Dismiss a reason for unsupported."""
if reason not in self._unsupported:
_LOGGER.warning("The reason %s is not active", reason)
raise ResolutionError()
self._unsupported.remove(reason)
"""Resolution Supervisor module."""

View File

@ -29,7 +29,10 @@ class ResolutionFixup(CoreSysAttributes):
@property
def all_fixes(self) -> List[FixupBase]:
"""Return a list of all fixups."""
"""Return a list of all fixups.
Order can be important!
"""
return [
self._create_full_snapshot,
self._clear_full_snapshot,

View File

@ -2,8 +2,14 @@
import logging
from typing import List, Optional
from supervisor.exceptions import ResolutionFixupError, StoreError, StoreNotFound
from ...exceptions import (
ResolutionFixupError,
ResolutionFixupJobError,
StoreError,
StoreNotFound,
)
from ...jobs.const import JobCondition
from ...jobs.decorator import Job
from ...utils import remove_folder
from ..const import ContextType, IssueType, SuggestionType
from .base import FixupBase
@ -14,6 +20,9 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
class FixupStoreExecuteReset(FixupBase):
"""Storage class for fixup."""
@Job(
conditions=[JobCondition.INTERNET_SYSTEM], on_condition=ResolutionFixupJobError
)
async def process_fixup(self, reference: Optional[str] = None) -> None:
"""Initialize the fixup class."""
_LOGGER.info("Reset corrupt Store: %s", reference)

View File

@ -0,0 +1,181 @@
"""Supervisor resolution center."""
from datetime import time
import logging
from typing import List, Optional
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import ResolutionError, ResolutionNotFound
from .check import ResolutionCheck
from .const import (
SCHEDULED_HEALTHCHECK,
ContextType,
IssueType,
SuggestionType,
UnhealthyReason,
UnsupportedReason,
)
from .data import Issue, Suggestion
from .evaluate import ResolutionEvaluation
from .fixup import ResolutionFixup
from .notify import ResolutionNotify
_LOGGER: logging.Logger = logging.getLogger(__name__)
class ResolutionManager(CoreSysAttributes):
"""Resolution manager for supervisor."""
def __init__(self, coresys: CoreSys):
"""Initialize Resolution manager."""
self.coresys: CoreSys = coresys
self._evaluate = ResolutionEvaluation(coresys)
self._check = ResolutionCheck(coresys)
self._fixup = ResolutionFixup(coresys)
self._notify = ResolutionNotify(coresys)
self._suggestions: List[Suggestion] = []
self._issues: List[Issue] = []
self._unsupported: List[UnsupportedReason] = []
self._unhealthy: List[UnhealthyReason] = []
@property
def evaluate(self) -> ResolutionEvaluation:
"""Return the ResolutionEvaluation class."""
return self._evaluate
@property
def check(self) -> ResolutionCheck:
"""Return the ResolutionCheck class."""
return self._check
@property
def fixup(self) -> ResolutionFixup:
"""Return the ResolutionFixup class."""
return self._fixup
@property
def notify(self) -> ResolutionNotify:
"""Return the ResolutionNotify class."""
return self._notify
@property
def issues(self) -> List[Issue]:
"""Return a list of issues."""
return self._issues
@issues.setter
def issues(self, issue: Issue) -> None:
"""Add issues."""
if issue not in self._issues:
self._issues.append(issue)
@property
def suggestions(self) -> List[Suggestion]:
"""Return a list of suggestions that can handled."""
return self._suggestions
@suggestions.setter
def suggestions(self, suggestion: Suggestion) -> None:
"""Add suggestion."""
if suggestion not in self._suggestions:
self._suggestions.append(suggestion)
@property
def unsupported(self) -> List[UnsupportedReason]:
"""Return a list of unsupported reasons."""
return self._unsupported
@unsupported.setter
def unsupported(self, reason: UnsupportedReason) -> None:
"""Add a reason for unsupported."""
if reason not in self._unsupported:
self._unsupported.append(reason)
@property
def unhealthy(self) -> List[UnhealthyReason]:
"""Return a list of unsupported reasons."""
return self._unhealthy
@unhealthy.setter
def unhealthy(self, reason: UnhealthyReason) -> None:
"""Add a reason for unsupported."""
if reason not in self._unhealthy:
self._unhealthy.append(reason)
def get_suggestion(self, uuid: str) -> Suggestion:
"""Return suggestion with uuid."""
for suggestion in self._suggestions:
if suggestion.uuid != uuid:
continue
return suggestion
raise ResolutionNotFound()
def get_issue(self, uuid: str) -> Issue:
"""Return issue with uuid."""
for issue in self._issues:
if issue.uuid != uuid:
continue
return issue
raise ResolutionNotFound()
def create_issue(
self,
issue: IssueType,
context: ContextType,
reference: Optional[str] = None,
suggestions: Optional[List[SuggestionType]] = None,
) -> None:
"""Create issues and suggestion."""
self.issues = Issue(issue, context, reference)
if not suggestions:
return
# Add suggestions
for suggestion in suggestions:
self.suggestions = Suggestion(suggestion, context, reference)
async def load(self):
"""Load the resoulution manager."""
# Initial healthcheck when the manager is loaded
await self.healthcheck()
# Schedule the healthcheck
self.sys_scheduler.register_task(self.healthcheck, SCHEDULED_HEALTHCHECK)
self.sys_scheduler.register_task(self.fixup.run_autofix, time(hour=2))
async def healthcheck(self):
"""Scheduled task to check for known issues."""
await self.check.check_system()
# Create notification for any known issues
await self.notify.issue_notifications()
async def apply_suggestion(self, suggestion: Suggestion) -> None:
"""Apply suggested action."""
if suggestion not in self._suggestions:
_LOGGER.warning("Suggestion %s is not valid", suggestion.uuid)
raise ResolutionError()
await self.fixup.apply_fixup(suggestion)
await self.healthcheck()
def dismiss_suggestion(self, suggestion: Suggestion) -> None:
"""Dismiss suggested action."""
if suggestion not in self._suggestions:
_LOGGER.warning("The UUID %s is not valid suggestion", suggestion.uuid)
raise ResolutionError()
self._suggestions.remove(suggestion)
def dismiss_issue(self, issue: Issue) -> None:
"""Dismiss suggested action."""
if issue not in self._issues:
_LOGGER.warning("The UUID %s is not a valid issue", issue.uuid)
raise ResolutionError()
self._issues.remove(issue)
def dismiss_unsupported(self, reason: Issue) -> None:
"""Dismiss a reason for unsupported."""
if reason not in self._unsupported:
_LOGGER.warning("The reason %s is not active", reason)
raise ResolutionError()
self._unsupported.remove(reason)

View File

@ -4,7 +4,7 @@ import logging
from typing import Dict, List
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import StoreGitError, StoreNotFound
from ..exceptions import StoreGitError, StoreJobError, StoreNotFound
from ..jobs.decorator import Job, JobCondition
from ..resolution.const import ContextType, IssueType, SuggestionType
from .addon import AddonStore
@ -83,6 +83,14 @@ class StoreManager(CoreSysAttributes):
await repository.load()
except StoreGitError:
_LOGGER.error("Can't load data from repository %s", url)
except StoreJobError:
_LOGGER.warning("Skip update to later for %s", repository.slug)
self.sys_resolution.create_issue(
IssueType.FATAL_ERROR,
ContextType.STORE,
refrence=repository.slug,
suggestions=[SuggestionType.EXECUTE_RELOAD],
)
else:
if not repository.validate():
_LOGGER.error("%s is not a valid add-on repository", url)

View File

@ -9,7 +9,7 @@ import git
from ..const import ATTR_BRANCH, ATTR_URL, URL_HASSIO_ADDONS
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import StoreGitError
from ..exceptions import StoreGitError, StoreJobError
from ..jobs.decorator import Job, JobCondition
from ..resolution.const import ContextType, IssueType, SuggestionType
from ..utils import remove_folder
@ -22,6 +22,8 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
class GitRepo(CoreSysAttributes):
"""Manage Add-on Git repository."""
builtin: bool
def __init__(self, coresys: CoreSys, path: Path, url: str):
"""Initialize Git base wrapper."""
self.coresys: CoreSys = coresys
@ -82,7 +84,10 @@ class GitRepo(CoreSysAttributes):
)
raise StoreGitError() from err
@Job(conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_SYSTEM])
@Job(
conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_SYSTEM],
on_condition=StoreJobError,
)
async def clone(self) -> None:
"""Clone git add-on repository."""
async with self.lock:
@ -115,11 +120,18 @@ class GitRepo(CoreSysAttributes):
IssueType.FATAL_ERROR,
ContextType.STORE,
reference=self.path.stem,
suggestions=[SuggestionType.EXECUTE_RELOAD],
suggestions=[
SuggestionType.EXECUTE_RELOAD
if self.builtin
else SuggestionType.EXECUTE_REMOVE
],
)
raise StoreGitError() from err
@Job(conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_SYSTEM])
@Job(
conditions=[JobCondition.FREE_SPACE, JobCondition.INTERNET_SYSTEM],
on_condition=StoreJobError,
)
async def pull(self):
"""Pull Git add-on repo."""
if self.lock.locked():
@ -175,6 +187,8 @@ class GitRepo(CoreSysAttributes):
class GitRepoHassIO(GitRepo):
"""Supervisor add-ons repository."""
builtin: bool = False
def __init__(self, coresys):
"""Initialize Git Supervisor add-on repository."""
super().__init__(coresys, coresys.config.path_addons_core, URL_HASSIO_ADDONS)
@ -183,6 +197,8 @@ class GitRepoHassIO(GitRepo):
class GitRepoCustom(GitRepo):
"""Custom add-ons repository."""
builtin: bool = False
def __init__(self, coresys, url):
"""Initialize custom Git Supervisor addo-n repository."""
path = Path(coresys.config.path_addons_git, get_hash_from_repository(url))

View File

@ -100,7 +100,7 @@ class Repository(CoreSysAttributes):
async def update(self) -> None:
"""Update add-on repository."""
if self.type == StoreType.LOCAL:
if self.type == StoreType.LOCAL or not self.validate():
return
await self.git.pull()