diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 789e1ed6079..0d0b9cd27ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -94,7 +94,7 @@ repos: pass_filenames: false language: script types: [text] - files: ^(script/hassfest/metadata\.py|homeassistant/const\.py$|pyproject\.toml|homeassistant/components/go2rtc/const\.py)$ + files: ^(script/hassfest/(metadata|docker)\.py|homeassistant/const\.py$|pyproject\.toml)$ - id: hassfest-mypy-config name: hassfest-mypy-config entry: script/run-in-env.sh python3 -m script.hassfest -p mypy_config diff --git a/Dockerfile b/Dockerfile index 75153cef75e..987521ab8de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,21 +15,14 @@ ARG QEMU_CPU # Home Assistant S6-Overlay COPY rootfs / -# Needs to be redefined inside the FROM statement to be set for RUN commands -ARG BUILD_ARCH -# Get go2rtc binary -RUN \ - case "${BUILD_ARCH}" in \ - "aarch64") go2rtc_suffix='arm64' ;; \ - *) go2rtc_suffix=${BUILD_ARCH} ;; \ - esac \ - && curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.12/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \ - && chmod +x /bin/go2rtc \ - # Verify go2rtc can be executed - && go2rtc --version +# Add go2rtc binary +COPY --from=ghcr.io/alexxit/go2rtc@sha256:baef0aa19d759fcfd31607b34ce8eaf039d496282bba57731e6ae326896d7640 /usr/local/bin/go2rtc /bin/go2rtc -# Install uv -RUN pip3 install uv==0.9.6 +RUN \ + # Verify go2rtc can be executed + go2rtc --version \ + # Install uv + && pip3 install uv==0.9.6 WORKDIR /usr/src diff --git a/homeassistant/components/go2rtc/const.py b/homeassistant/components/go2rtc/const.py index f6d95b855c8..b827c59fe6d 100644 --- a/homeassistant/components/go2rtc/const.py +++ b/homeassistant/components/go2rtc/const.py @@ -7,4 +7,6 @@ DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time." HA_MANAGED_API_PORT = 11984 HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/" HA_MANAGED_UNIX_SOCKET = "/run/go2rtc.sock" +# When changing this version, also update the corresponding SHA hash (_GO2RTC_SHA) +# in script/hassfest/docker.py. RECOMMENDED_VERSION = "1.9.12" diff --git a/script/hassfest/docker.py b/script/hassfest/docker.py index e55465923a4..d291024d428 100644 --- a/script/hassfest/docker.py +++ b/script/hassfest/docker.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from pathlib import Path from homeassistant import core -from homeassistant.components.go2rtc.const import RECOMMENDED_VERSION as GO2RTC_VERSION from homeassistant.const import Platform from homeassistant.util import executor, thread from script.gen_requirements_all import gather_recursive_requirements @@ -12,6 +11,10 @@ from script.gen_requirements_all import gather_recursive_requirements from .model import Config, Integration from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR +_GO2RTC_SHA = ( + "baef0aa19d759fcfd31607b34ce8eaf039d496282bba57731e6ae326896d7640" # 1.9.12 +) + DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest. # # To update, run python3 -m script.hassfest -p docker @@ -29,21 +32,14 @@ ARG QEMU_CPU # Home Assistant S6-Overlay COPY rootfs / -# Needs to be redefined inside the FROM statement to be set for RUN commands -ARG BUILD_ARCH -# Get go2rtc binary -RUN \ - case "${{BUILD_ARCH}}" in \ - "aarch64") go2rtc_suffix='arm64' ;; \ - *) go2rtc_suffix=${{BUILD_ARCH}} ;; \ - esac \ - && curl -L https://github.com/AlexxIT/go2rtc/releases/download/v{go2rtc}/go2rtc_linux_${{go2rtc_suffix}} --output /bin/go2rtc \ - && chmod +x /bin/go2rtc \ - # Verify go2rtc can be executed - && go2rtc --version +# Add go2rtc binary +COPY --from=ghcr.io/alexxit/go2rtc@sha256:{go2rtc} /usr/local/bin/go2rtc /bin/go2rtc -# Install uv -RUN pip3 install uv=={uv} +RUN \ + # Verify go2rtc can be executed + go2rtc --version \ + # Install uv + && pip3 install uv=={uv} WORKDIR /usr/src @@ -164,8 +160,6 @@ def _generate_hassfest_dockerimage( packages.update( gather_recursive_requirements(platform.value, already_checked_domains) ) - # Add go2rtc requirements as this file needs the go2rtc integration - packages.update(gather_recursive_requirements("go2rtc", already_checked_domains)) return File( _HASSFEST_TEMPLATE.format( @@ -201,7 +195,7 @@ def _generate_files(config: Config) -> list[File]: DOCKERFILE_TEMPLATE.format( timeout=timeout, **package_versions, - go2rtc=GO2RTC_VERSION, + go2rtc=_GO2RTC_SHA, ), config.root / "Dockerfile", ), diff --git a/script/hassfest/docker/Dockerfile b/script/hassfest/docker/Dockerfile index 257a60799b4..f828ef6beec 100644 --- a/script/hassfest/docker/Dockerfile +++ b/script/hassfest/docker/Dockerfile @@ -29,7 +29,6 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.9.6,source=/uv,target=/bin/uv \ tqdm==4.67.1 \ ruff==0.13.0 \ PyTurboJPEG==1.8.0 \ - go2rtc-client==0.3.0 \ ha-ffmpeg==3.2.2 \ hassil==3.4.0 \ home-assistant-intents==2025.11.7 \ diff --git a/tests/components/go2rtc/test_docker_version.py b/tests/components/go2rtc/test_docker_version.py new file mode 100644 index 00000000000..a67c8b4ac1d --- /dev/null +++ b/tests/components/go2rtc/test_docker_version.py @@ -0,0 +1,85 @@ +"""Test that the go2rtc Docker image version matches or exceeds the recommended version. + +This test ensures that the go2rtc Docker image SHA pinned in +script/hassfest/docker.py corresponds to a version that is equal to or +greater than the RECOMMENDED_VERSION defined in homeassistant/components/go2rtc/const.py. + +The test pulls the Docker image using the pinned SHA and runs the +`go2rtc --version` command inside the container to extract the version, +then compares it against RECOMMENDED_VERSION. +""" + +import asyncio +import os +import re + +from awesomeversion import AwesomeVersion +import pytest + +from homeassistant.components.go2rtc.const import RECOMMENDED_VERSION +from script.hassfest.docker import _GO2RTC_SHA as DOCKER_SHA + + +async def _get_version_from_docker_sha() -> str: + """Extract go2rtc version from Docker image using the pinned SHA.""" + + image = f"ghcr.io/alexxit/go2rtc@sha256:{DOCKER_SHA}" + + pull_process = await asyncio.create_subprocess_exec( + "docker", + "pull", + image, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + _, pull_stderr = await pull_process.communicate() + + if pull_process.returncode != 0: + raise RuntimeError(f"Failed to pull go2rtc image: {pull_stderr.decode()}") + + # Run the container to get version + run_process = await asyncio.create_subprocess_exec( + "docker", + "run", + "--rm", + image, + "go2rtc", + "--version", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + run_stdout, run_stderr = await run_process.communicate() + + if run_process.returncode != 0: + raise RuntimeError(f"Failed to run go2rtc --version: {run_stderr.decode()}") + + # Parse version from output + # Expected output format: "go2rtc version 1.9.12 (commit) linux/amd64" or similar + output = run_stdout.decode().strip() + version_match = re.search(r"version\s+([\d.]+)", output) + + if not version_match: + raise RuntimeError(f"Could not parse version from go2rtc output: {output}") + + return version_match.group(1) + + +@pytest.mark.skipif( + not os.environ.get("CI"), + reason="This test requires Docker and only runs in CI", +) +async def test_docker_version_matches_recommended() -> None: + """Test that the go2rtc Docker SHA version matches or exceeds RECOMMENDED_VERSION.""" + # Extract version from the actual Docker container + docker_version_str = await _get_version_from_docker_sha() + + # Parse versions + docker_version = AwesomeVersion(docker_version_str) + recommended_version = AwesomeVersion(RECOMMENDED_VERSION) + + # Assert that Docker version is equal to or greater than recommended version + assert docker_version >= recommended_version, ( + f"go2rtc Docker version ({docker_version}) is less than " + f"RECOMMENDED_VERSION ({recommended_version}). " + "Please update _GO2RTC_SHA in script/hassfest/docker.py to a newer version" + )