fix(helm): apply image.pullSecrets to the nginx Deployment (#8123)

The frontend, gateway, provisioner, Postgres and Redis pods include
deer-flow.imagePullSecrets, but the nginx Deployment did not. With
nginx.image mirrored to a private registry, the nginx pod stayed in
ImagePullBackOff while every other pod started.

Add a helm-template test that renders every workload and checks that
each one uses the configured pull secrets and none sets them by default.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bas Cancrinus 2026-11-11 04:10:31 +01:00 committed by GitHub
parent 3df3b3c630
commit 4a27b133f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 0 deletions

View File

@ -0,0 +1,50 @@
"""Every chart workload must honour ``image.pullSecrets``.
``values.yaml`` documents ``image.pullSecrets`` as the way to pull from a private
registry, and the frontend, gateway, provisioner, Postgres and Redis pods all
include ``deer-flow.imagePullSecrets``. The nginx Deployment did not, so with a
mirrored ``nginx.image`` in a private registry the nginx pod stayed in
``ImagePullBackOff`` while every other pod started.
"""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
import pytest
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
CHART = REPO_ROOT / "deploy" / "helm" / "deer-flow"
# Enable every optional workload so the test covers all pod templates.
ALL_WORKLOADS = ("provisioner.enabled=true", "postgresql.enabled=true", "redis.enabled=true")
def _render_chart(*settings: str) -> list[dict]:
helm = shutil.which("helm")
if helm is None:
pytest.skip("helm is unavailable")
command = [helm, "template", "deer-flow", str(CHART)]
for setting in settings:
command.extend(["--set", setting])
rendered = subprocess.run(command, check=True, capture_output=True, text=True, encoding="utf-8").stdout
return [document for document in yaml.safe_load_all(rendered) if isinstance(document, dict)]
def _workloads(documents: list[dict]) -> dict[str, dict]:
return {document["metadata"]["name"]: document["spec"]["template"]["spec"] for document in documents if document.get("kind") in {"Deployment", "StatefulSet"}}
def test_every_workload_uses_configured_pull_secrets() -> None:
workloads = _workloads(_render_chart(*ALL_WORKLOADS, "image.pullSecrets[0].name=registry-credentials"))
assert any(name.endswith("-nginx") for name in workloads)
missing = sorted(name for name, pod in workloads.items() if pod.get("imagePullSecrets") != [{"name": "registry-credentials"}])
assert missing == []
def test_no_workload_sets_pull_secrets_by_default() -> None:
workloads = _workloads(_render_chart(*ALL_WORKLOADS))
assert [name for name, pod in workloads.items() if "imagePullSecrets" in pod] == []

View File

@ -21,6 +21,7 @@ spec:
# Roll pods when the nginx ConfigMap changes (subPath mount).
checksum/nginx: {{ include "deer-flow.nginxChecksum" . }}
spec:
{{- include "deer-flow.imagePullSecrets" . | nindent 6 }}
securityContext:
# Non-root: nginx:alpine ships a `nginx` user (uid 101). /etc/nginx and
# /var/cache/nginx are root-owned, so the command writes the rendered