fix(bp-powerdns): hide CRD-emitting resources behind Capabilities gates (refs #190)

Three Catalyst overlay templates emit resources whose CRDs ship in OTHER
charts and were unconditionally rendered, causing a cold install of
bp-powerdns to fail with `no matches for kind X` on a Sovereign that
hasn't yet reconciled the upstream chart:

  - cnpg-cluster.yaml          → postgresql.cnpg.io/v1 Cluster
                                 (CRD ships in bp-cnpg)
  - api-ingress.yaml           → traefik.io/v1alpha1 Middleware
                                 (CRD ships with the Traefik controller;
                                  k3s ships it by default but a Sovereign
                                  overlay MAY disable Traefik in favour
                                  of cilium-only ingress)
  - crossplane-floatingip.yaml → compose.openova.io/v1alpha1 HetznerFloatingIP
                                 (CRD ships when the Catalyst Crossplane
                                  composition family lands — see GAP
                                  DISCLOSURE in that template)

Each is wrapped in `.Capabilities.APIVersions.Has "<group>/<version>"`.
The Traefik router-middleware annotation on the Ingress is similarly
gated so the auth posture cleanly moves to the Sovereign's chosen
ingress controller when Traefik is absent.

Verified locally: `helm template` with default values renders 0 of
these resources; with `--api-versions postgresql.cnpg.io/v1
--api-versions traefik.io/v1alpha1 --api-versions compose.openova.io/v1alpha1`
plus `--set crossplane.floatingIP.enabled=true`, all three render
exactly once. Existing tests/observability-toggle.sh still passes.

Bump version 1.1.1 → 1.1.2.
This commit is contained in:
hatiyildiz 2026-04-29 20:03:03 +02:00
parent 86c8be9153
commit c5a7ff0676
4 changed files with 44 additions and 2 deletions

View File

@ -1,6 +1,6 @@
apiVersion: v2
name: bp-powerdns
version: 1.1.1
version: 1.1.2
description: |
Catalyst-curated Blueprint wrapper for PowerDNS Authoritative.
Carries Catalyst-specific values.yaml + templates (CNPG cluster, dnsdist

View File

@ -12,6 +12,18 @@ or commits) the basicAuth Secret is created out-of-band by the
private-repo cluster manifest at clusters/contabo-mkt/apps/powerdns/.
*/}}
{{- if .Values.api.enabled }}
{{- /*
Capabilities gate (issue #190): the `traefik.io/v1alpha1` Middleware CRD
ships with the Traefik ingress controller. k3s ships Traefik by default
on most Sovereigns, but a Sovereign overlay MAY disable Traefik (e.g. in
favour of cilium-only ingress) — in that case the CRD is absent and the
apiserver rejects this Middleware with `no matches for kind Middleware
in version traefik.io/v1alpha1`. The Capabilities check skips the
Middleware while still rendering the Ingress below, so the auth posture
must then be supplied by the cluster overlay's chosen ingress controller
(per docs/INVIOLABLE-PRINCIPLES.md #4 — never hardcode ingress class).
*/}}
{{- if .Capabilities.APIVersions.Has "traefik.io/v1alpha1" }}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
@ -22,6 +34,7 @@ metadata:
spec:
basicAuth:
secret: {{ .Values.api.basicAuth.secretName | default "powerdns-api-basicauth" | quote }}
{{- end }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
@ -32,7 +45,12 @@ metadata:
{{- include "bp-powerdns.labels" . | nindent 4 }}
annotations:
cert-manager.io/cluster-issuer: {{ .Values.api.tls.issuer | default "letsencrypt-prod" | quote }}
{{- /* Traefik router middleware annotation only renders when the Traefik
Middleware CRD is present (gated above). When Traefik is absent the
auth posture moves to the Sovereign's chosen ingress controller. */}}
{{- if .Capabilities.APIVersions.Has "traefik.io/v1alpha1" }}
traefik.ingress.kubernetes.io/router.middlewares: {{ printf "%s-%s@kubernetescrd" .Release.Namespace (.Values.api.middlewareName | default "powerdns-api-auth") | quote }}
{{- end }}
spec:
ingressClassName: {{ .Values.api.ingressClassName | default "traefik" | quote }}
{{- if .Values.api.tls.enabled }}

View File

@ -18,7 +18,19 @@ password}.secretRef).
`enableSuperuserAccess: true` so that operators can run `pdnsutil`
schema-altering commands inside the database during DNSSEC key
rotations and zone imports.
Capabilities gate (issue #190): bp-cnpg ships the `postgresql.cnpg.io/v1`
CRD that backs Cluster — it is NOT in the bootstrap-kit. On a cold
install of a fresh Sovereign before bp-cnpg is reconciling, the apiserver
rejects this Cluster with `no matches for kind Cluster in version
postgresql.cnpg.io/v1`. The Capabilities check skips this template until
the CRD is registered, at which point the gate becomes a no-op. The
Sovereign's bootstrap order MUST land bp-cnpg before bp-powerdns; the
gate exists so a misordered cluster overlay surfaces as `no Cluster
created yet` rather than a hard install failure of the entire bp-powerdns
release.
*/}}
{{- if .Capabilities.APIVersions.Has "postgresql.cnpg.io/v1" }}
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
@ -161,3 +173,4 @@ spec:
monitoring:
enablePodMonitor: false
{{- end }}

View File

@ -37,7 +37,18 @@ This file is not deleted in the cutover — it stays as the canonical
target-state shape so a fresh chart install on a new Sovereign produces
a Floating IP automatically once the composition exists.
*/}}
{{- if .Values.crossplane.floatingIP.enabled }}
{{- /*
Capabilities gate (issue #190): the `compose.openova.io/v1alpha1`
HetznerFloatingIP CRD is registered by the Catalyst Crossplane
composition family (platform/crossplane/compositions/xrd-floating-ip.yaml,
not yet authored — see GAP DISCLOSURE above). On a Sovereign without
that composition reconciled, the apiserver rejects the XR with `no
matches for kind HetznerFloatingIP in version compose.openova.io/v1alpha1`.
The Capabilities check skips this template until the CRD is registered;
combined with the values toggle (`crossplane.floatingIP.enabled`, default
false) the placeholder ships safely on every Sovereign.
*/}}
{{- if and .Values.crossplane.floatingIP.enabled (.Capabilities.APIVersions.Has "compose.openova.io/v1alpha1") }}
apiVersion: compose.openova.io/v1alpha1
kind: HetznerFloatingIP
metadata: