Fix issue on store git clone (#2331)
This commit is contained in:
parent
f8fd7b5933
commit
0c55bf20fc
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue