-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Optimize hassfest docker image * Adjust CI * Use dynamic uv version * Remove workaround
- Loading branch information
Showing
5 changed files
with
164 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
"""Generate and validate the dockerfile.""" | ||
|
||
from dataclasses import dataclass | ||
from pathlib import Path | ||
|
||
from homeassistant import core | ||
from homeassistant.const import Platform | ||
from homeassistant.util import executor, thread | ||
from script.gen_requirements_all import gather_recursive_requirements | ||
|
||
from .model import Config, Integration | ||
from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR | ||
|
@@ -20,7 +25,7 @@ | |
ARG QEMU_CPU | ||
# Install uv | ||
RUN pip3 install uv=={uv_version} | ||
RUN pip3 install uv=={uv} | ||
WORKDIR /usr/src | ||
|
@@ -61,30 +66,105 @@ | |
WORKDIR /config | ||
""" | ||
|
||
_HASSFEST_TEMPLATE = r"""# Automatically generated by hassfest. | ||
# | ||
# To update, run python3 -m script.hassfest -p docker | ||
FROM python:alpine3.20 | ||
ENV \ | ||
UV_SYSTEM_PYTHON=true \ | ||
UV_EXTRA_INDEX_URL="https://wheels.home-assistant.io/musllinux-index/" | ||
SHELL ["/bin/sh", "-o", "pipefail", "-c"] | ||
ENTRYPOINT ["/usr/src/homeassistant/script/hassfest/docker/entrypoint.sh"] | ||
WORKDIR "/github/workspace" | ||
# Install uv | ||
COPY --from=ghcr.io/astral-sh/uv:{uv} /uv /bin/uv | ||
COPY . /usr/src/homeassistant | ||
RUN \ | ||
# Required for PyTurboJPEG | ||
apk add --no-cache libturbojpeg \ | ||
&& cd /usr/src/homeassistant \ | ||
&& uv pip install \ | ||
--no-build \ | ||
--no-cache \ | ||
-c homeassistant/package_constraints.txt \ | ||
-r requirements.txt \ | ||
stdlib-list==0.10.0 pipdeptree=={pipdeptree} tqdm=={tqdm} ruff=={ruff} \ | ||
{required_components_packages} | ||
LABEL "name"="hassfest" | ||
LABEL "maintainer"="Home Assistant <[email protected]>" | ||
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" | ||
""" | ||
|
||
|
||
def _get_uv_version() -> str: | ||
with open("requirements_test.txt") as fp: | ||
def _get_package_versions(file: str, packages: set[str]) -> dict[str, str]: | ||
package_versions: dict[str, str] = {} | ||
with open(file, encoding="UTF-8") as fp: | ||
for _, line in enumerate(fp): | ||
if package_versions.keys() == packages: | ||
return package_versions | ||
|
||
if match := PACKAGE_REGEX.match(line): | ||
pkg, sep, version = match.groups() | ||
|
||
if pkg != "uv": | ||
if pkg not in packages: | ||
continue | ||
|
||
if sep != "==" or not version: | ||
raise RuntimeError( | ||
'Requirement uv need to be pinned "uv==<version>".' | ||
f'Requirement {pkg} need to be pinned "{pkg}==<version>".' | ||
) | ||
|
||
for part in version.split(";", 1)[0].split(","): | ||
version_part = PIP_VERSION_RANGE_SEPARATOR.match(part) | ||
if version_part: | ||
return version_part.group(2) | ||
package_versions[pkg] = version_part.group(2) | ||
break | ||
|
||
if package_versions.keys() == packages: | ||
return package_versions | ||
|
||
raise RuntimeError("At least one package was not found in the requirements file.") | ||
|
||
|
||
@dataclass | ||
class File: | ||
"""File.""" | ||
|
||
content: str | ||
path: Path | ||
|
||
|
||
raise RuntimeError("Invalid uv requirement in requirements_test.txt") | ||
def _generate_hassfest_dockerimage( | ||
config: Config, timeout: int, package_versions: dict[str, str] | ||
) -> File: | ||
packages = set() | ||
already_checked_domains = set() | ||
for platform in Platform: | ||
packages.update( | ||
gather_recursive_requirements(platform.value, already_checked_domains) | ||
) | ||
|
||
return File( | ||
_HASSFEST_TEMPLATE.format( | ||
timeout=timeout, | ||
required_components_packages=" ".join(sorted(packages)), | ||
**package_versions, | ||
), | ||
config.root / "script/hassfest/docker/Dockerfile", | ||
) | ||
|
||
|
||
def _generate_dockerfile() -> str: | ||
def _generate_files(config: Config) -> list[File]: | ||
timeout = ( | ||
core.STOPPING_STAGE_SHUTDOWN_TIMEOUT | ||
+ core.STOP_STAGE_SHUTDOWN_TIMEOUT | ||
|
@@ -93,27 +173,39 @@ def _generate_dockerfile() -> str: | |
+ executor.EXECUTOR_SHUTDOWN_TIMEOUT | ||
+ thread.THREADING_SHUTDOWN_TIMEOUT | ||
+ 10 | ||
) * 1000 | ||
|
||
package_versions = _get_package_versions( | ||
"requirements_test.txt", {"pipdeptree", "tqdm", "uv"} | ||
) | ||
return DOCKERFILE_TEMPLATE.format( | ||
timeout=timeout * 1000, uv_version=_get_uv_version() | ||
package_versions |= _get_package_versions( | ||
"requirements_test_pre_commit.txt", {"ruff"} | ||
) | ||
|
||
return [ | ||
File( | ||
DOCKERFILE_TEMPLATE.format(timeout=timeout, **package_versions), | ||
config.root / "Dockerfile", | ||
), | ||
_generate_hassfest_dockerimage(config, timeout, package_versions), | ||
] | ||
|
||
|
||
def validate(integrations: dict[str, Integration], config: Config) -> None: | ||
"""Validate dockerfile.""" | ||
dockerfile_content = _generate_dockerfile() | ||
config.cache["dockerfile"] = dockerfile_content | ||
|
||
dockerfile_path = config.root / "Dockerfile" | ||
if dockerfile_path.read_text() != dockerfile_content: | ||
config.add_error( | ||
"docker", | ||
"File Dockerfile is not up to date. Run python3 -m script.hassfest", | ||
fixable=True, | ||
) | ||
docker_files = _generate_files(config) | ||
config.cache["docker"] = docker_files | ||
|
||
for file in docker_files: | ||
if file.content != file.path.read_text(): | ||
config.add_error( | ||
"docker", | ||
f"File {file.path} is not up to date. Run python3 -m script.hassfest", | ||
fixable=True, | ||
) | ||
|
||
|
||
def generate(integrations: dict[str, Integration], config: Config) -> None: | ||
"""Generate dockerfile.""" | ||
dockerfile_path = config.root / "Dockerfile" | ||
dockerfile_path.write_text(config.cache["dockerfile"]) | ||
for file in _generate_files(config): | ||
file.path.write_text(file.content) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,32 @@ | ||
ARG BASE_IMAGE=ghcr.io/home-assistant/home-assistant:beta | ||
FROM $BASE_IMAGE | ||
# Automatically generated by hassfest. | ||
# | ||
# To update, run python3 -m script.hassfest -p docker | ||
FROM python:alpine3.20 | ||
|
||
SHELL ["/bin/bash", "-o", "pipefail", "-c"] | ||
ENV \ | ||
UV_SYSTEM_PYTHON=true \ | ||
UV_EXTRA_INDEX_URL="https://wheels.home-assistant.io/musllinux-index/" | ||
|
||
COPY entrypoint.sh /entrypoint.sh | ||
SHELL ["/bin/sh", "-o", "pipefail", "-c"] | ||
ENTRYPOINT ["/usr/src/homeassistant/script/hassfest/docker/entrypoint.sh"] | ||
WORKDIR "/github/workspace" | ||
|
||
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) | ||
# Install uv | ||
COPY --from=ghcr.io/astral-sh/uv:0.2.27 /uv /bin/uv | ||
|
||
WORKDIR "/github/workspace" | ||
ENTRYPOINT ["/entrypoint.sh"] | ||
COPY . /usr/src/homeassistant | ||
|
||
RUN \ | ||
# Required for PyTurboJPEG | ||
apk add --no-cache libturbojpeg \ | ||
&& cd /usr/src/homeassistant \ | ||
&& uv pip install \ | ||
--no-build \ | ||
--no-cache \ | ||
-c homeassistant/package_constraints.txt \ | ||
-r requirements.txt \ | ||
stdlib-list==0.10.0 pipdeptree==2.23.1 tqdm==4.66.4 ruff==0.6.2 \ | ||
PyTurboJPEG==1.7.5 ha-ffmpeg==3.2.0 hassil==1.7.4 home-assistant-intents==2024.8.29 mutagen==1.47.0 | ||
|
||
LABEL "name"="hassfest" | ||
LABEL "maintainer"="Home Assistant <[email protected]>" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Ignore everything except the specified files | ||
* | ||
|
||
!homeassistant/ | ||
!requirements.txt | ||
!script/ | ||
script/hassfest/docker/ | ||
!script/hassfest/docker/entrypoint.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,18 @@ | ||
#!/usr/bin/env bashio | ||
declare -a integrations | ||
declare integration_path | ||
#!/bin/sh | ||
|
||
shopt -s globstar nullglob | ||
for manifest in **/manifest.json; do | ||
integrations="" | ||
integration_path="" | ||
|
||
# Enable recursive globbing using find | ||
for manifest in $(find . -name "manifest.json"); do | ||
manifest_path=$(realpath "${manifest}") | ||
integrations+=(--integration-path "${manifest_path%/*}") | ||
integrations="$integrations --integration-path ${manifest_path%/*}" | ||
done | ||
|
||
if [[ ${#integrations[@]} -eq 0 ]]; then | ||
bashio::exit.nok "No integrations found!" | ||
if [ -z "$integrations" ]; then | ||
echo "Error: No integrations found!" | ||
exit 1 | ||
fi | ||
|
||
cd /usr/src/homeassistant | ||
exec python3 -m script.hassfest --action validate "${integrations[@]}" "$@" | ||
cd /usr/src/homeassistant || exit 1 | ||
exec python3 -m script.hassfest --action validate $integrations "$@" |