Build hassfest docker image and pushlish it on beta/stable releases (#124706)
parent
a4bfb0e92c
commit
45bb2cdd82
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
|
@ -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[@]}" "$@"
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue