Build hassfest docker image and pushlish it on beta/stable releases (#124706)

pull/124396/head^2
Robert Resch 2024-08-28 16:38:12 +02:00 committed by GitHub
parent a4bfb0e92c
commit 45bb2cdd82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 145 additions and 11 deletions

View File

@ -482,3 +482,56 @@ jobs:
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
twine upload dist/* --skip-existing
hassfest-image:
name: Build and test hassfest image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
needs: ["init", "build_base"]
if: github.repository_owner == 'home-assistant'
env:
HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
with:
context: ./script/hassfest/docker
build-args: BASE_IMAGE=ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}
load: true
tags: ${{ env.HASSFEST_IMAGE_TAG }}
- name: Run hassfest against core
run: docker run --rm -v ${{ github.workspace }}/homeassistant:/github/workspace/homeassistant ${{ env.HASSFEST_IMAGE_TAG }} --core-integrations-path=/github/workspace/homeassistant/components
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
with:
context: ./script/hassfest/docker
build-args: BASE_IMAGE=ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}
push: true
tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View File

@ -15,7 +15,7 @@ import tomllib
from typing import Any
from homeassistant.util.yaml.loader import load_yaml
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
# Requirements which can't be installed on all systems because they rely on additional
# system packages. Requirements listed in EXCLUDED_REQUIREMENTS_ALL will be commented-out
@ -270,7 +270,9 @@ def gather_recursive_requirements(
seen = set()
seen.add(domain)
integration = Integration(Path(f"homeassistant/components/{domain}"))
integration = Integration(
Path(f"homeassistant/components/{domain}"), _get_hassfest_config()
)
integration.load_manifest()
reqs = {x for x in integration.requirements if x not in CONSTRAINT_BASE}
for dep_domain in integration.dependencies:
@ -336,7 +338,8 @@ def gather_requirements_from_manifests(
errors: list[str], reqs: dict[str, list[str]]
) -> None:
"""Gather all of the requirements from manifests."""
integrations = Integration.load_dir(Path("homeassistant/components"))
config = _get_hassfest_config()
integrations = Integration.load_dir(config.core_integrations_path, config)
for domain in sorted(integrations):
integration = integrations[domain]
@ -584,6 +587,17 @@ def main(validate: bool, ci: bool) -> int:
return 0
def _get_hassfest_config() -> Config:
"""Get hassfest config."""
return Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
)
if __name__ == "__main__":
_VAL = sys.argv[-1] == "validate"
_CI = sys.argv[-1] == "ci"

View File

@ -107,6 +107,12 @@ def get_config() -> Config:
default=ALL_PLUGIN_NAMES,
help="Comma-separate list of plugins to run. Valid plugin names: %(default)s",
)
parser.add_argument(
"--core-integrations-path",
type=pathlib.Path,
default=pathlib.Path("homeassistant/components"),
help="Path to core integrations",
)
parsed = parser.parse_args()
if parsed.action is None:
@ -129,6 +135,7 @@ def get_config() -> Config:
action=parsed.action,
requirements=parsed.requirements,
plugins=set(parsed.plugins),
core_integrations_path=parsed.core_integrations_path,
)
@ -146,12 +153,12 @@ def main() -> int:
integrations = {}
for int_path in config.specific_integrations:
integration = Integration(int_path)
integration = Integration(int_path, config)
integration.load_manifest()
integrations[integration.domain] = integration
else:
integrations = Integration.load_dir(pathlib.Path("homeassistant/components"))
integrations = Integration.load_dir(config.core_integrations_path, config)
plugins += HASS_PLUGINS
for plugin in plugins:

View File

@ -0,0 +1,22 @@
ARG BASE_IMAGE=ghcr.io/home-assistant/home-assistant:beta
FROM $BASE_IMAGE
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
COPY entrypoint.sh /entrypoint.sh
RUN \
uv pip install stdlib-list==0.10.0 \
$(grep -e "^pipdeptree" -e "^tqdm" /usr/src/homeassistant/requirements_test.txt) \
$(grep -e "^ruff" /usr/src/homeassistant/requirements_test_pre_commit.txt)
WORKDIR "/github/workspace"
ENTRYPOINT ["/entrypoint.sh"]
LABEL "name"="hassfest"
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"
LABEL "com.github.actions.name"="hassfest"
LABEL "com.github.actions.description"="Run hassfest to validate standalone integration repositories"
LABEL "com.github.actions.icon"="terminal"
LABEL "com.github.actions.color"="gray-dark"

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bashio
declare -a integrations
declare integration_path
shopt -s globstar nullglob
for manifest in **/manifest.json; do
manifest_path=$(realpath "${manifest}")
integrations+=(--integration-path "${manifest_path%/*}")
done
if [[ ${#integrations[@]} -eq 0 ]]; then
bashio::exit.nok "No integrations found!"
fi
cd /usr/src/homeassistant
exec python3 -m script.hassfest --action validate "${integrations[@]}" "$@"

View File

@ -29,6 +29,7 @@ class Config:
root: pathlib.Path
action: Literal["validate", "generate"]
requirements: bool
core_integrations_path: pathlib.Path
errors: list[Error] = field(default_factory=list)
cache: dict[str, Any] = field(default_factory=dict)
plugins: set[str] = field(default_factory=set)
@ -105,7 +106,7 @@ class Integration:
"""Represent an integration in our validator."""
@classmethod
def load_dir(cls, path: pathlib.Path) -> dict[str, Integration]:
def load_dir(cls, path: pathlib.Path, config: Config) -> dict[str, Integration]:
"""Load all integrations in a directory."""
assert path.is_dir()
integrations: dict[str, Integration] = {}
@ -123,13 +124,14 @@ class Integration:
)
continue
integration = cls(fil)
integration = cls(fil, config)
integration.load_manifest()
integrations[integration.domain] = integration
return integrations
path: pathlib.Path
_config: Config
_manifest: dict[str, Any] | None = None
manifest_path: pathlib.Path | None = None
errors: list[Error] = field(default_factory=list)
@ -150,7 +152,9 @@ class Integration:
@property
def core(self) -> bool:
"""Core integration."""
return self.path.as_posix().startswith("homeassistant/components")
return self.path.as_posix().startswith(
self._config.core_integrations_path.as_posix()
)
@property
def disabled(self) -> str | None:

View File

@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
from script.hassfest.requirements import validate_requirements_format
@ -13,6 +13,13 @@ def integration():
"""Fixture for hassfest integration model."""
return Integration(
path=Path("homeassistant/components/test"),
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
_manifest={
"domain": "test",
"documentation": "https://example.com",

View File

@ -1,5 +1,7 @@
"""Tests for hassfest version."""
from pathlib import Path
import pytest
import voluptuous as vol
@ -7,13 +9,22 @@ from script.hassfest.manifest import (
CUSTOM_INTEGRATION_MANIFEST_SCHEMA,
validate_version,
)
from script.hassfest.model import Integration
from script.hassfest.model import Config, Integration
@pytest.fixture
def integration():
"""Fixture for hassfest integration model."""
integration = Integration("")
integration = Integration(
"",
_config=Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
core_integrations_path=Path("homeassistant/components"),
),
)
integration._manifest = {
"domain": "test",
"documentation": "https://example.com",