diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ee01443..11e84d9 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -13,12 +13,6 @@ jobs: packages: write steps: - uses: actions/checkout@v6.0.2 - - name: Log in to DHI registry - uses: docker/login-action@v3 - with: - registry: dhi.io - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - uses: actions/setup-python@v6.2.0 with: python-version: "3.11" @@ -41,11 +35,18 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Docker metadata id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/${{ github.repository_owner }}/codeforerunner + images: | + ghcr.io/${{ github.repository_owner }}/codeforerunner + heyderekp/codeforerunner tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} diff --git a/Dockerfile b/Dockerfile index 24dbf5c..f931c9f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,27 @@ -FROM dhi.io/python:3.13 +FROM python:3.13-slim AS builder ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 -WORKDIR /workspace +WORKDIR /build +COPY . . + +# Install the wheel into an isolated venv so only it (no build toolchain or +# source tree) is carried into the runtime stage. +RUN python -m venv /opt/venv \ + && /opt/venv/bin/pip install --no-cache-dir . + +FROM python:3.13-slim -COPY . /workspace +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PATH="/opt/venv/bin:$PATH" -RUN python -m pip install --upgrade pip \ - && python -m pip install --no-cache-dir -e . +COPY --from=builder /opt/venv /opt/venv + +# /workspace is a mount point: `forerunner` runs against the caller's repo +# (Path.cwd()), bind-mounted here. Source is not baked in. +WORKDIR /workspace ENTRYPOINT ["forerunner"] diff --git a/tests/test_workflows_yaml.py b/tests/test_workflows_yaml.py index 934764e..73523b5 100644 --- a/tests/test_workflows_yaml.py +++ b/tests/test_workflows_yaml.py @@ -108,12 +108,35 @@ def test_docker_publish_workflow_uses_version_tag_and_ghcr(): step for step in steps if isinstance(step, dict) and step.get("uses") == "docker/login-action@v3" ] - assert any(step.get("with", {}).get("registry") == "dhi.io" for step in login_steps) + # Publishes to GHCR (registry ghcr.io) and Docker Hub (login-action with no + # `registry`, which defaults to docker.io). The distroless DHI base was + # dropped (#72), so there must be no dhi.io login left. Compare registry + # values with equality (not substring `in`) to avoid host-substring checks. + registries = [step.get("with", {}).get("registry") for step in login_steps] + assert any(r == "ghcr.io" for r in registries), ( + f"expected a ghcr.io login, got {registries!r}" + ) + assert any(r is None or r == "docker.io" for r in registries), ( + f"expected a Docker Hub login (no registry / docker.io), got {registries!r}" + ) + assert all(r != "dhi.io" for r in registries), "dhi.io login should be removed (#72)" + steps_text = "\n".join(str(step) for step in publish.get("steps", [])) assert "docker/login-action" in steps_text assert "docker/build-push-action" in steps_text assert "scripts/check_versions.py" in steps_text + # Both publish targets must appear in the image metadata. Parse the + # metadata-action `images` list and match full image refs exactly. + meta_step = next( + step for step in steps + if isinstance(step, dict) and str(step.get("uses", "")).startswith("docker/metadata-action") + ) + images = [ln.strip() for ln in str(meta_step["with"]["images"]).splitlines() if ln.strip()] + expected_ghcr = "ghcr.io/${{ github.repository_owner }}/codeforerunner" + assert expected_ghcr in images, f"expected GHCR image, got {images!r}" + assert "heyderekp/codeforerunner" in images, f"expected Docker Hub image, got {images!r}" + def test_release_pr_workflow_requires_release_signal_and_uploads_artifacts(): wf = WORKFLOWS_DIR / "release-pr.yml"