Compare commits

..
Author SHA1 Message Date
jakman85 990b309cbb Merge pull request 'chore(deps): Update docker Docker tag to v29' (#114) from renovate/docker-29.x into main
gate / gate (push) Canceled after 9m30s
gate / conformance (push) Canceled after 0s
Reviewed-on: #114
2026-11-08 04:39:04 +00:00
jakman85 cedcb01975 Merge pull request 'chore(deps): Update ghcr.io/cloudnative-pg/postgresql Docker tag to v16.15' (#124) from renovate/ghcr.io-cloudnative-pg-postgresql-16.x into main
gate / gate (push) Successful in 3m18s
gate / conformance (push) Skipped
Reviewed-on: #124
2026-11-08 04:28:19 +00:00
jakman85 2b279d4f21 Merge pull request 'chore(deps): Update Helm release headlamp to <0.47.0' (#130) from renovate/headlamp-0.x into main
gate / gate (push) Canceled after 15s
gate / conformance (push) Canceled after 0s
Reviewed-on: #130
2026-11-08 04:28:01 +00:00
jakman85 12c28adf4d Merge pull request 'feat(conformance): L0 guard — external-dns annotationPrefix must match the tree (stacked on #140)' (#141) from feat/l0-externaldns-annotation-prefix into main
gate / gate (push) Successful in 2m55s
gate / conformance (push) Skipped
Reviewed-on: #141
2026-11-08 00:08:29 +00:00
jakman85 b0230ba736 Merge pull request 'fix(dns): pin external-dns annotationPrefix — v0.23 ignored every alpha annotation' (#140) from fix/public-dns-wan-a-record into main
gate / gate (push) Successful in 2m53s
gate / conformance (push) Skipped
Reviewed-on: #140
2026-11-08 00:01:06 +00:00
jakman85andClaude Opus 5.5 9c51f60a35 feat(conformance): L0 guard that the external-dns annotationPrefix matches the tree
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Skipped
Add assertion (8) to internal-services-publish-no-public-dns. The external-dns
HelmRelease must declare annotationPrefix explicitly, and every
external-dns annotation in apps/, platform/, runbooks/ and catalog/render.py
must use that prefix. The v0.23.0 default-prefix change on 2026-11-07
ignored the controller: none, target and hostname annotations while
assertions (1)-(3) stayed green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 17:56:12 -06:00
renovate-bot 3aec6886d8 chore(deps): Update docker Docker tag to v29
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:19:08 +00:00
renovate-bot b7a77a7d1e chore(deps): Update Helm release headlamp to <0.47.0
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:34 +00:00
renovate-bot 3ceaad15e0 chore(deps): Update ghcr.io/cloudnative-pg/postgresql Docker tag to v16.15
gate / gate (pull_request) Successful in 2m58s
gate / conformance (pull_request) Has been skipped
2026-11-07 08:17:11 +00:00
7 changed files with 134 additions and 12 deletions

No files matched your search

+2 -2
View File
@@ -48,7 +48,7 @@ spec:
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
# Stamp app.kubernetes.io/name=nextcloud-db onto every generated pod/service so the rendered
# nextcloud egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
inheritedMetadata:
@@ -300,7 +300,7 @@ spec:
# is already in it and it is already present on every node, so this
# adds no new image and no new supply-chain surface. Keep the tag in
# step with spec.imageName above when Renovate bumps it.
image: ghcr.io/cloudnative-pg/postgresql:16.4
image: ghcr.io/cloudnative-pg/postgresql:16.15
imagePullPolicy: IfNotPresent
command:
- psql
+1 -1
View File
@@ -49,7 +49,7 @@ spec:
# Scaling out is non-disruptive: CNPG creates -2 then -3 sequentially, each seeded by
# pg_basebackup from the running primary, which is neither restarted nor disconnected.
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
# Stamp app.kubernetes.io/name=semaphore-db onto every generated pod/service so the rendered
# semaphore egress CiliumNetworkPolicy's toEndpoints selector matches the primary pod (R-NET-02).
inheritedMetadata:
+1 -1
View File
@@ -133,7 +133,7 @@ spec:
initContainers:
- name: dind
restartPolicy: Always # <- this field is what makes it a SIDECAR
image: docker:27-dind
image: docker:29-dind
securityContext:
privileged: true
env:
@@ -26,7 +26,10 @@
# KNOWN_NON_CATALOG_PUBLISHERS — shrink-only, stale entries FAIL;
# (7) the check is LOAD-BEARING: whole-repo scope declared in chainsaw-test.yaml, honoured by
# ci/l0.sh's --include-scope union, and ci/gate.sh blocks on an l0 failure. NO hand-written
# ci/l0.sh stage (whole-repo-invariants-wired fails the gate if one appears).
# ci/l0.sh stage (whole-repo-invariants-wired fails the gate if one appears);
# (8) the external-dns HelmRelease declares `annotationPrefix` explicitly and it matches the
# prefix of EVERY external-dns annotation in the tree and in catalog/render.py — added after
# the 2026-11-07 v0.23.0 default-prefix change silently voided (1)-(3) at runtime.
#
# WHAT THIS DOES *NOT* PROVE (honest gaps — it is STATIC, offline, cluster-free):
# - That the record is gone from Cloudflare. Live read; belongs beside
@@ -90,9 +93,18 @@ CHECKS = 0
# ControllerValue = "dns-controller" -> the value that CLAIMS an object
# source/annotations/processors.go IsControllerMismatch skips a source object when the key is
# PRESENT and the value is NOT ControllerValue. So "disowned" == key present, value != claimed.
CONTROLLER_KEY = "external-dns.alpha.kubernetes.io/controller"
#
# THE PREFIX IS NOT A CONSTANT OF NATURE. Every key external-dns reads is `<annotationPrefix><name>`,
# and the prefix is a runtime flag whose DEFAULT moved in v0.22.0 (#6424: alpha -> GA, "with no
# fallback"). On 2026-11-07 the chart bump to v0.23.0 made every key below invisible to the running
# controller while this check — which only asserted the keys were PRESENT — stayed green. (8) now
# ties ANNOTATION_PREFIX to the HelmRelease's explicit `annotationPrefix`.
ANNOTATION_PREFIX = "external-dns.alpha.kubernetes.io/"
CONTROLLER_KEY = ANNOTATION_PREFIX + "controller"
CONTROLLER_CLAIMED = "dns-controller"
HOSTNAME_KEY = "external-dns.alpha.kubernetes.io/hostname"
HOSTNAME_KEY = ANNOTATION_PREFIX + "hostname"
# Any key that looks like an external-dns annotation, under ANY prefix variant (alpha, GA, typos).
EXTERNAL_DNS_KEY_RE = re.compile(r"^external-dns(\.[a-z0-9-]+)*\.kubernetes\.io/")
def check(cond, msg):
@@ -231,9 +243,7 @@ for sub in SCAN_DIRS:
ann = md.get("annotations") or {}
if kind == "HTTPRoute":
routes.append((rel, d))
elif kind == "Service" and any(
a.startswith("external-dns.alpha.kubernetes.io/") for a in ann
):
elif kind == "Service" and any(EXTERNAL_DNS_KEY_RE.match(a) for a in ann):
dns_services.append((rel, d))
check(len(routes) > 0, "inventory: no HTTPRoute found under apps/ platform/ runbooks/ — parse "
@@ -444,6 +454,67 @@ check(
"and the whole-repo-invariants-wired meta-test fails the gate on a hand-written stage.",
)
# =============================================================================================
# (8) THE RUNNING CONTROLLER READS THE PREFIX THIS TREE WRITES.
# (a) the external-dns HelmRelease sets `values.annotationPrefix` EXPLICITLY (an upstream
# default is exactly what moved under us) and it equals ANNOTATION_PREFIX;
# (b) it does not ALSO enable `--enable-legacy-annotation-prefix` (dual-read hides a
# half-migration; upstream says the flag will be removed);
# (c) every external-dns-looking annotation key on any object under apps/ platform/ runbooks/
# uses ANNOTATION_PREFIX — a key under another prefix is silently ignored at runtime;
# (d) catalog/render.py emits no external-dns key under another prefix.
# Migrating to the GA prefix = change ANNOTATION_PREFIX, the HelmRelease value, the renderer
# and every manifest in ONE commit; this assertion fails on any partial state.
# =============================================================================================
HR_PATH = ROOT / "platform/externaldns/helmrelease.yaml"
hr = next((d for d in docs_of(HR_PATH) if d.get("kind") == "HelmRelease"
and (d.get("metadata") or {}).get("name") == "external-dns"), None)
check(hr is not None, f"(8) {HR_PATH.relative_to(ROOT)} holds no HelmRelease `external-dns` — "
f"cannot prove which annotation prefix the controller reads")
values = ((hr or {}).get("spec") or {}).get("values") or {}
hr_prefix = values.get("annotationPrefix")
check(
hr_prefix == ANNOTATION_PREFIX,
f"(8a) external-dns HelmRelease `values.annotationPrefix` is {hr_prefix!r}, but every "
f"external-dns annotation in this tree uses {ANNOTATION_PREFIX!r}. A mismatch (or relying on "
f"the upstream default, which changed in v0.22.0) makes the controller IGNORE `controller: "
f"none`, the Gateway `target` and Service `hostname` — internal names go public, public names "
f"answer RFC1918 (incident 2026-11-07).",
)
extra = values.get("extraArgs") or []
extra_s = " ".join(f"{k}={v}" for k, v in extra.items()) if isinstance(extra, dict) \
else " ".join(map(str, extra))
check("enable-legacy-annotation-prefix" not in extra_s and "annotation-prefix" not in extra_s,
"(8b) external-dns extraArgs must not set --annotation-prefix or "
"--enable-legacy-annotation-prefix; the prefix is declared ONLY via values.annotationPrefix "
"so this check can see it")
foreign: list[str] = []
seen_keys = 0
for sub in SCAN_DIRS:
base = ROOT / sub
if not base.is_dir():
continue
for p in sorted(base.rglob("*.yaml")):
rel = p.relative_to(ROOT).as_posix()
for d in docs_of(p):
for k in annotations_of(d):
if isinstance(k, str) and EXTERNAL_DNS_KEY_RE.match(k):
seen_keys += 1
if not k.startswith(ANNOTATION_PREFIX):
foreign.append(f"{rel}:{'/'.join(ident(d))} {k}")
check(seen_keys > 0, "(8c) no external-dns annotation found anywhere — parse contract broken "
"(the Gateway target alone guarantees at least one)")
check(not foreign,
f"(8c) external-dns annotations under a prefix the controller does NOT read "
f"({ANNOTATION_PREFIX!r} is configured): {foreign}")
render_src = read(CATALOG / "render.py")
bad_render = sorted({m for m in re.findall(r"external-dns(?:\.[a-z0-9-]+)*\.kubernetes\.io/",
render_src) if m != ANNOTATION_PREFIX})
check(not bad_render,
f"(8d) catalog/render.py emits external-dns keys under {bad_render}, but the controller "
f"reads {ANNOTATION_PREFIX!r}")
# =============================================================================================
if FAILURES:
for f in FAILURES:
@@ -0,0 +1,51 @@
---
date: 2026-11-07
branch: feat/l0-externaldns-annotation-prefix
title: L0 guard — external-dns annotationPrefix must match every external-dns annotation in the tree
---
## What changed
`conformance/tests/l0-static/internal-services-publish-no-public-dns/check.sh` gains assertion
(8). The check is already whole-repo-scoped, so it runs on every gate.
- **(8a)** `platform/externaldns/helmrelease.yaml` must set `values.annotationPrefix` explicitly,
and it must equal the check's `ANNOTATION_PREFIX` (`external-dns.alpha.kubernetes.io/`). The
check does not trust the upstream default, because that default is what changed.
- **(8b)** `extraArgs` must not carry `--annotation-prefix` or `--enable-legacy-annotation-prefix`,
so the prefix has one declared place.
- **(8c)** Every external-dns-looking annotation key under apps/, platform/ and runbooks/ must use
that prefix. The match is any `external-dns(.x)*.kubernetes.io/` key.
- **(8d)** catalog/render.py must emit no external-dns key under any other prefix.
`CONTROLLER_KEY` and `HOSTNAME_KEY` are now derived from `ANNOTATION_PREFIX`. The Service
inventory matches any external-dns prefix variant, not only the alpha one.
Why: on 2026-11-07, external-dns v0.23.0 (whose default prefix is GA) ignored every alpha
annotation. Assertions (1) to (3) stayed green, because they prove the annotations are present,
not that the controller reads them. See `decisions/2026-11-07-public-dns-wan-a-record.md`.
This branch is stacked on `fix/public-dns-wan-a-record` (PR #140). Without that fix, (8a) fails,
which is correct. Merge #140 first.
## What I verified
Run on the host (PyYAML):
- the check passes on this tree: `check OK: 62 assertions passed`.
- it **fails (8a)** with origin/main's HelmRelease, which has no `annotationPrefix`.
- it **fails (8c)** when `platform/gateway/gateway.yaml`'s `target` key is changed to the GA
prefix.
Toolbox gate: see the PR description.
## What I did NOT verify
- Whether a future chart could read the prefix from somewhere other than `values.annotationPrefix`,
for example a renamed chart value. (8a) would then pass while the flag silently vanished from the
rendered args. Rendering the chart in the gate would close that gap. Not done: it needs network
access or a vendored chart in L0.
- `conformance/**` is a **sensitive path**. This PR needs the operator's approving review or
`/approve-override`. The agent did not post one.
- The header of the sibling chainsaw-test.yaml still says "external-dns v0.21.0". That is cosmetic
and I left it alone.
+1 -1
View File
@@ -40,7 +40,7 @@ spec:
# values (-session-ttl + the -oidc-* args from the externalSecret; useCookie stays default-false
# so `-oidc-use-cookie` is NOT emitted) is supported by the v0.41.0 backend. Renovate maintains
# within 0.41.x; the appVersion continues to pin the Headlamp image.
version: ">=0.41.0 <0.42.0"
version: "<0.47.0"
sourceRef:
kind: HelmRepository
name: headlamp
+1 -1
View File
@@ -41,7 +41,7 @@ spec:
# not disconnected. (The anti-affinity change is a separate commit precisely because that one DOES
# cause a switchover — see it for the blast radius.)
instances: 3
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
imageName: ghcr.io/cloudnative-pg/postgresql:16.15
# HARD anti-affinity — the three instances MUST land on three DIFFERENT nodes (2026-09-07).
#
# CNPG's default is `preferred`, which is only a scheduler HINT: the scheduler may put two