initial commit

This commit is contained in:
2022-12-20 21:26:47 +01:00
commit 2962a6db69
722 changed files with 63886 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# Repository validation
This is where the validation rules that run against the various repository categories live.
## Structure
- There is one file pr. rule.
- All rule needs tests to verify every possible outcome for the rule.
- It's better with multiple files than a big rule.
- All rules uses `ActionValidationBase` as the base class.
- Only use `validate` or `async_validate` methods to define validation rules.
- If a rule should fail, raise `ValidationException` with the failure message.
## Example
```python
from .base import (
ActionValidationBase,
ValidationBase,
ValidationException,
)
class SuperAwesomeRepository(ActionValidationBase):
category = "integration"
async def async_validate(self):
if self.repository != "super-awesome":
raise ValidationException("The repository is not super-awesome")
```

View File

@@ -0,0 +1 @@
"""Initialize validation."""

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-archived"
allow_fork = False
async def async_validate(self):
"""Validate the repository."""
if self.repository.data.archived:
raise ValidationException("The repository is archived")

View File

@@ -0,0 +1,53 @@
"""Base class for validation."""
from __future__ import annotations
from typing import TYPE_CHECKING
from ..enums import HacsCategory
from ..exceptions import HacsException
if TYPE_CHECKING:
from ..repositories.base import HacsRepository
class ValidationException(HacsException):
"""Raise when there is a validation issue."""
class ActionValidationBase:
"""Base class for action validation."""
categories: list[HacsCategory] = []
allow_fork: bool = True
more_info: str = "https://hacs.xyz/docs/publish/action"
def __init__(self, repository: HacsRepository) -> None:
self.hacs = repository.hacs
self.repository = repository
self.failed = False
@property
def slug(self) -> str:
"""Return the check slug."""
return self.__class__.__module__.rsplit(".", maxsplit=1)[-1]
async def async_validate(self) -> None:
"""Validate the repository."""
async def execute_validation(self, *_, **__) -> None:
"""Execute the task defined in subclass."""
self.failed = False
try:
await self.async_validate()
except ValidationException as exception:
self.failed = True
self.hacs.log.error(
"<Validation %s> failed: %s (More info: %s )",
self.slug,
exception,
self.more_info,
)
else:
self.hacs.log.info("<Validation %s> completed", self.slug)

View File

@@ -0,0 +1,31 @@
from __future__ import annotations
from custom_components.hacs.enums import HacsCategory
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
URL = "https://brands.home-assistant.io/domains.json"
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-brands"
categories = [HacsCategory.INTEGRATION]
async def async_validate(self):
"""Validate the repository."""
response = await self.hacs.session.get(URL)
content = await response.json()
if self.repository.data.domain not in content["custom"]:
raise ValidationException(
"The repository has not been added as a custom domain to the brands repo"
)

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
allow_fork = False
async def async_validate(self):
"""Validate the repository."""
if not self.repository.data.description:
raise ValidationException("The repository has no description")

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
from voluptuous.error import Invalid
from ..enums import RepositoryFile
from ..repositories.base import HacsRepository
from ..utils.validate import HACS_MANIFEST_JSON_SCHEMA
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-hacs-manifest"
async def async_validate(self):
"""Validate the repository."""
if RepositoryFile.HACS_JSON not in [x.filename for x in self.repository.tree]:
raise ValidationException(f"The repository has no '{RepositoryFile.HACS_JSON}' file")
content = await self.repository.async_get_hacs_json(self.repository.ref)
try:
HACS_MANIFEST_JSON_SCHEMA(content)
except Invalid as exception:
raise ValidationException(exception) from exception

View File

@@ -0,0 +1,29 @@
from __future__ import annotations
from ..enums import HacsCategory
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
IGNORED = ["-shield", "img.shields.io", "buymeacoffee.com"]
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
categories = [HacsCategory.PLUGIN, HacsCategory.THEME]
more_info = "https://hacs.xyz/docs/publish/include#check-images"
async def async_validate(self):
"""Validate the repository."""
info = await self.repository.async_get_info_file_contents()
for line in info.split("\n"):
if "<img" in line or "![" in line:
if [ignore for ignore in IGNORED if ignore in line]:
continue
return
raise ValidationException("The repository does not have images in the Readme file")

View File

@@ -0,0 +1,29 @@
from __future__ import annotations
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-info"
async def async_validate(self):
"""Validate the repository."""
filenames = [x.filename.lower() for x in self.repository.tree]
if "readme" in filenames:
pass
elif "readme.md" in filenames:
pass
elif "info" in filenames:
pass
elif "info.md" in filenames:
pass
else:
raise ValidationException("The repository has no information file")

View File

@@ -0,0 +1,35 @@
from __future__ import annotations
from voluptuous.error import Invalid
from ..enums import HacsCategory, RepositoryFile
from ..repositories.base import HacsRepository
from ..repositories.integration import HacsIntegrationRepository
from ..utils.validate import INTEGRATION_MANIFEST_JSON_SCHEMA
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
repository: HacsIntegrationRepository
more_info = "https://hacs.xyz/docs/publish/include#check-manifest"
categories = [HacsCategory.INTEGRATION]
async def async_validate(self):
"""Validate the repository."""
if RepositoryFile.MAINIFEST_JSON not in [x.filename for x in self.repository.tree]:
raise ValidationException(
f"The repository has no '{RepositoryFile.MAINIFEST_JSON}' file"
)
content = await self.repository.async_get_integration_manifest(self.repository.ref)
try:
INTEGRATION_MANIFEST_JSON_SCHEMA(content)
except Invalid as exception:
raise ValidationException(exception) from exception

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
allow_fork = False
async def async_validate(self):
"""Validate the repository."""
if not self.repository.data.has_issues:
raise ValidationException("The repository does not have issues enabled")

View File

@@ -0,0 +1,81 @@
"""Hacs validation manager."""
from __future__ import annotations
import asyncio
from importlib import import_module
import os
from pathlib import Path
from typing import TYPE_CHECKING
from homeassistant.core import HomeAssistant
from ..repositories.base import HacsRepository
from .base import ActionValidationBase
if TYPE_CHECKING:
from ..base import HacsBase
class ValidationManager:
"""Hacs validation manager."""
def __init__(self, hacs: HacsBase, hass: HomeAssistant) -> None:
"""Initialize the setup manager class."""
self.hacs = hacs
self.hass = hass
self._validatiors: dict[str, ActionValidationBase] = {}
@property
def validatiors(self) -> list[ActionValidationBase]:
"""Return all list of all tasks."""
return list(self._validatiors.values())
async def async_load(self, repository: HacsRepository) -> None:
"""Load all tasks."""
self._validatiors = {}
validator_files = Path(__file__).parent
validator_modules = (
module.stem
for module in validator_files.glob("*.py")
if module.name not in ("base.py", "__init__.py", "manager.py")
)
async def _load_module(module: str):
task_module = import_module(f"{__package__}.{module}")
if task := await task_module.async_setup_validator(repository=repository):
self._validatiors[task.slug] = task
await asyncio.gather(*[_load_module(task) for task in validator_modules])
async def async_run_repository_checks(self, repository: HacsRepository) -> None:
"""Run all validators for a repository."""
if not self.hacs.system.action:
return
await self.async_load(repository)
is_pull_from_fork = (
not os.getenv("INPUT_REPOSITORY")
and os.getenv("GITHUB_REPOSITORY") != repository.data.full_name
)
validatiors = [
validator
for validator in self.validatiors or []
if (
(not validator.categories or repository.data.category in validator.categories)
and validator.slug not in os.getenv("INPUT_IGNORE", "").split(" ")
and (not is_pull_from_fork or validator.allow_fork)
)
]
await asyncio.gather(*[validator.execute_validation() for validator in validatiors])
total = len(validatiors)
failed = len([x for x in validatiors if x.failed])
if failed != 0:
repository.logger.error("%s %s/%s checks failed", repository.string, failed, total)
exit(1)
else:
repository.logger.info("%s All (%s) checks passed", repository.string, total)

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from ..repositories.base import HacsRepository
from .base import ActionValidationBase, ValidationException
async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)
class Validator(ActionValidationBase):
"""Validate the repository."""
more_info = "https://hacs.xyz/docs/publish/include#check-repository"
allow_fork = False
async def async_validate(self):
"""Validate the repository."""
if not self.repository.data.topics:
raise ValidationException("The repository has no valid topics")