reconciler: scope the drift check to svcforge's own releases
ci / lint (push) Failing after 10s
ci / types (push) Has been skipped
ci / unit (push) Has been skipped
ci / integration (push) Has been skipped
ci / security (push) Has been skipped
ci / dockerfile (push) Has been skipped
ci / chart (push) Has been skipped
ci / image (api) (push) Has been skipped
ci / image (reconciler) (push) Has been skipped
ci / image (worker) (push) Has been skipped
ci / bump (push) Has been skipped

check_drift diffs helm against the database in both directions, and the second
one is `live - known` -> drift.orphan_release at ERROR. `live` was every
release in the cluster, so argocd, longhorn, gitea and cert-manager were all
reported as orphans svcforge is failing to account for, on every sweep. They
are not orphans; they were never svcforge's to know about.

install() now stamps app.kubernetes.io/managed-by=svcforge and list_releases()
selects on it. Releases provisioned before this see one sweep as missing and
get re-provisioned, which is safe by construction — `helm upgrade --install`
against a deterministic release name — and that re-provision applies the label.

This does NOT fix the timeout, and the docstring says so. Measured, not
assumed: unscoped 23 releases in 4392ms, scoped to 0 in 3988ms. `--selector` is
not pushed down as a server-side selector, so helm still fetches and
decompresses every release secret and filters what it already parsed. Around
10%, not the order of magnitude the flag's shape suggests. The 330s timeouts
need CPU for the container or a different read path.

The two sides of the label are asserted against each other rather than a
literal, so a rename that updates only one fails in tests instead of in
production as a silently empty drift check. Control-tested: renaming the read
side alone fails two of the three.
This commit is contained in:
Nguyen Minh Phuc
2026-07-20 12:53:16 +00:00
parent c2a27952d1
commit 2356ac4ef3
2 changed files with 145 additions and 2 deletions
+93
View File
@@ -0,0 +1,93 @@
"""The helm argv that the reconciler's correctness depends on.
install() writes a label; list_releases() reads it back. Neither is checked by anything at
runtime — if the two ever disagree, `helm list --selector` matches nothing, the reconciler
sees an empty cluster, and every ready instance looks like it lost its release. That failure
is silent and reads as "no drift", so it gets a test rather than a comment.
These assert argv, not behaviour against a real cluster: tests/e2e covers that. The point
here is that the two sides of the label agree, and that neither drops out under a refactor.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from svcforge_core.adapters.helm import (
MANAGED_BY_LABEL,
MANAGED_BY_VALUE,
HelmProvisioner,
)
from svcforge_core.domain.models import CatalogEntry, SizeSpec
ENTRY = CatalogEntry(
service_type="redis",
chart="oci://example/redis",
chart_version="1.2.3",
sizes={"small": SizeSpec(replicas=1, resources={})},
)
def _capture(monkeypatch: pytest.MonkeyPatch) -> list[list[str]]:
"""Record every argv HelmProvisioner would exec, and run none of them."""
seen: list[list[str]] = []
async def fake_run_helm(self: HelmProvisioner, argv: list[str]) -> str:
seen.append(list(argv))
return "[]"
monkeypatch.setattr(HelmProvisioner, "_run_helm", fake_run_helm, raising=True)
return seen
def _pair(argv: list[str], flag: str) -> str | None:
"""The value following `flag`, or None. Positional, because helm takes `--flag value`."""
return argv[argv.index(flag) + 1] if flag in argv else None
@pytest.mark.asyncio
async def test_install_labels_the_release_as_svcforge_managed(
monkeypatch: pytest.MonkeyPatch,
) -> None:
seen = _capture(monkeypatch)
await HelmProvisioner(kubeconfig=Path("/dev/null")).install(
"acme-redis", "tenant-acme", ENTRY, {"replicas": 1}
)
assert len(seen) == 1
assert _pair(seen[0], "--labels") == f"{MANAGED_BY_LABEL}={MANAGED_BY_VALUE}"
@pytest.mark.asyncio
async def test_list_releases_asks_only_for_svcforge_releases(
monkeypatch: pytest.MonkeyPatch,
) -> None:
seen = _capture(monkeypatch)
await HelmProvisioner(kubeconfig=Path("/dev/null")).list_releases()
assert len(seen) == 1
argv = seen[0]
assert _pair(argv, "--selector") == f"{MANAGED_BY_LABEL}={MANAGED_BY_VALUE}"
# Still every namespace. Tenants get their own, so scoping to one would hide releases
# rather than the cluster's; the label is what narrows this, not the namespace.
assert "--all-namespaces" in argv
@pytest.mark.asyncio
async def test_the_label_written_is_the_label_read(monkeypatch: pytest.MonkeyPatch) -> None:
"""The regression that motivated this file.
Asserted against each other rather than against a literal on both sides, so a rename
that updates only one of install/list_releases fails here instead of in production as
an empty drift check.
"""
seen = _capture(monkeypatch)
prov = HelmProvisioner(kubeconfig=Path("/dev/null"))
await prov.install("acme-redis", "tenant-acme", ENTRY, {})
await prov.list_releases()
assert _pair(seen[0], "--labels") == _pair(seen[1], "--selector")