mirror of
https://github.com/home-assistant/core.git
synced 2026-05-14 20:41:24 +01:00
249 lines
8.2 KiB
Python
249 lines
8.2 KiB
Python
"""Generate and validate the dockerfile."""
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from homeassistant import core
|
|
from homeassistant.util import executor, thread
|
|
|
|
from .model import Config, Integration
|
|
|
|
# Don't forget to update also Dockerfile.dev when updating this.
|
|
_DOCKERFILE_SYNTAX_SHA = (
|
|
"2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769" # 1.23.0
|
|
)
|
|
|
|
_GO2RTC_SHA = (
|
|
"675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae" # 1.9.14
|
|
)
|
|
|
|
DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
|
|
# Automatically generated by hassfest.
|
|
#
|
|
# To update, run python3 -m script.hassfest -p docker
|
|
ARG BUILD_FROM
|
|
FROM ${{BUILD_FROM}}
|
|
|
|
LABEL \
|
|
io.hass.type="core" \
|
|
org.opencontainers.image.authors="The Home Assistant Authors" \
|
|
org.opencontainers.image.description="Open-source home automation platform running on Python 3" \
|
|
org.opencontainers.image.documentation="https://www.home-assistant.io/docs/" \
|
|
org.opencontainers.image.licenses="Apache-2.0" \
|
|
org.opencontainers.image.title="Home Assistant" \
|
|
org.opencontainers.image.url="https://www.home-assistant.io/"
|
|
|
|
# Synchronize with homeassistant/core.py:async_stop
|
|
ENV \
|
|
S6_SERVICES_GRACETIME={timeout} \
|
|
UV_SYSTEM_PYTHON=true \
|
|
UV_NO_CACHE=true
|
|
|
|
WORKDIR /usr/src
|
|
|
|
# Home Assistant S6-Overlay
|
|
COPY rootfs /
|
|
|
|
# Add go2rtc binary
|
|
COPY --from=ghcr.io/alexxit/go2rtc@sha256:{go2rtc} /usr/local/bin/go2rtc /bin/go2rtc
|
|
|
|
## Setup Home Assistant Core dependencies
|
|
COPY --parents requirements.txt homeassistant/package_constraints.txt homeassistant/
|
|
RUN \
|
|
# Verify go2rtc can be executed
|
|
go2rtc --version \
|
|
# Install uv at the version pinned in the requirements file
|
|
&& pip3 install --no-cache-dir "uv==$(awk -F'==' '/^uv==/{{print $2}}' homeassistant/requirements.txt)" \
|
|
&& uv pip install \
|
|
--no-build \
|
|
-r homeassistant/requirements.txt
|
|
|
|
COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
|
|
RUN \
|
|
if ls homeassistant/home_assistant_*.whl 1> /dev/null 2>&1; then \
|
|
uv pip install homeassistant/home_assistant_*.whl; \
|
|
fi \
|
|
&& uv pip install \
|
|
--no-build \
|
|
-r homeassistant/requirements_all.txt
|
|
|
|
## Setup Home Assistant Core
|
|
COPY --parents LICENSE* README* homeassistant/ pyproject.toml homeassistant/
|
|
RUN \
|
|
uv pip install \
|
|
-e ./homeassistant \
|
|
&& python3 -m compileall \
|
|
homeassistant/homeassistant
|
|
|
|
WORKDIR /config
|
|
"""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _MachineConfig:
|
|
"""Machine-specific Dockerfile configuration."""
|
|
|
|
arch: str
|
|
packages: tuple[str, ...] = ()
|
|
|
|
|
|
_MACHINES = {
|
|
"generic-x86-64": _MachineConfig(arch="amd64", packages=("libva-intel-driver",)),
|
|
"green": _MachineConfig(arch="aarch64"),
|
|
"khadas-vim3": _MachineConfig(arch="aarch64"),
|
|
"odroid-c2": _MachineConfig(arch="aarch64"),
|
|
"odroid-c4": _MachineConfig(arch="aarch64"),
|
|
"odroid-m1": _MachineConfig(arch="aarch64"),
|
|
"odroid-n2": _MachineConfig(arch="aarch64"),
|
|
"qemuarm-64": _MachineConfig(arch="aarch64"),
|
|
"qemux86-64": _MachineConfig(arch="amd64"),
|
|
"raspberrypi3-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
|
|
"raspberrypi4-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
|
|
"raspberrypi5-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
|
|
"yellow": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
|
|
}
|
|
|
|
_MACHINE_DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
|
|
# Automatically generated by hassfest.
|
|
#
|
|
# To update, run python3 -m script.hassfest -p docker
|
|
ARG BUILD_FROM=ghcr.io/home-assistant/{arch}-homeassistant:latest
|
|
FROM ${{BUILD_FROM}}
|
|
{extra_packages}
|
|
LABEL io.hass.machine="{machine}"
|
|
"""
|
|
|
|
|
|
def _generate_machine_dockerfile(
|
|
machine_name: str, machine_config: _MachineConfig
|
|
) -> str:
|
|
"""Generate a machine Dockerfile from configuration."""
|
|
if machine_config.packages:
|
|
pkg_lines = " \\\n ".join(machine_config.packages)
|
|
extra_packages = f"\nRUN apk --no-cache add \\\n {pkg_lines}\n"
|
|
else:
|
|
extra_packages = ""
|
|
|
|
return _MACHINE_DOCKERFILE_TEMPLATE.format(
|
|
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
|
|
arch=machine_config.arch,
|
|
extra_packages=extra_packages,
|
|
machine=machine_name,
|
|
)
|
|
|
|
|
|
_HASSFEST_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
|
|
# Automatically generated by hassfest.
|
|
#
|
|
# To update, run python3 -m script.hassfest -p docker
|
|
FROM python:{python_version}-alpine
|
|
|
|
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"
|
|
|
|
COPY --parents requirements.txt homeassistant/ script /usr/src/homeassistant/
|
|
|
|
# Uv creates a lock file in /tmp
|
|
RUN --mount=type=tmpfs,target=/tmp \
|
|
--mount=type=bind,source=requirements_test.txt,target=/tmp/requirements_test.txt,readonly \
|
|
--mount=type=bind,source=requirements_test_pre_commit.txt,target=/tmp/requirements_test_pre_commit.txt,readonly \
|
|
# Required for PyTurboJPEG
|
|
apk add --no-cache libturbojpeg \
|
|
# Install uv at the version pinned in the requirements file
|
|
&& pip install --no-cache-dir "uv==$(awk -F'==' '/^uv==/{{print $2}}' /usr/src/homeassistant/requirements.txt)" \
|
|
&& uv pip install \
|
|
--no-build \
|
|
--no-cache \
|
|
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
|
|
-r /usr/src/homeassistant/requirements.txt \
|
|
"pipdeptree==$(awk -F'==' '/^pipdeptree==/{{print $2}}' /tmp/requirements_test.txt)" \
|
|
"tqdm==$(awk -F'==' '/^tqdm==/{{print $2}}' /tmp/requirements_test.txt)" \
|
|
"ruff==$(awk -F'==' '/^ruff==/{{print $2}}' /tmp/requirements_test_pre_commit.txt)"
|
|
|
|
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"
|
|
"""
|
|
|
|
|
|
def _get_python_version(root: Path) -> str:
|
|
"""Extract the Python version from .python-version."""
|
|
return (root / ".python-version").read_text(encoding="UTF-8").strip()
|
|
|
|
|
|
@dataclass
|
|
class File:
|
|
"""File."""
|
|
|
|
content: str
|
|
path: Path
|
|
|
|
|
|
def _generate_files(config: Config) -> list[File]:
|
|
timeout = (
|
|
core.STOPPING_STAGE_SHUTDOWN_TIMEOUT
|
|
+ core.STOP_STAGE_SHUTDOWN_TIMEOUT
|
|
+ core.FINAL_WRITE_STAGE_SHUTDOWN_TIMEOUT
|
|
+ core.CLOSE_STAGE_SHUTDOWN_TIMEOUT
|
|
+ executor.EXECUTOR_SHUTDOWN_TIMEOUT
|
|
+ thread.THREADING_SHUTDOWN_TIMEOUT
|
|
+ 10
|
|
) * 1000
|
|
|
|
files = [
|
|
File(
|
|
DOCKERFILE_TEMPLATE.format(
|
|
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
|
|
timeout=timeout,
|
|
go2rtc=_GO2RTC_SHA,
|
|
),
|
|
config.root / "Dockerfile",
|
|
),
|
|
File(
|
|
_HASSFEST_TEMPLATE.format(
|
|
dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
|
|
python_version=_get_python_version(config.root),
|
|
),
|
|
config.root / "script/hassfest/docker/Dockerfile",
|
|
),
|
|
]
|
|
|
|
for machine_name, machine_config in sorted(_MACHINES.items()):
|
|
files.append(
|
|
File(
|
|
_generate_machine_dockerfile(machine_name, machine_config),
|
|
config.root / "machine" / machine_name,
|
|
)
|
|
)
|
|
|
|
return files
|
|
|
|
|
|
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
|
"""Validate dockerfile."""
|
|
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."""
|
|
for file in _generate_files(config):
|
|
file.path.write_text(file.content)
|