{{/* The Gateway-API alternative to rss-bridge.ingress.yaml (Gitea #114), and the LAST chart of that sweep: 22 charts carried an Ingress template and 21 an HTTPRoute template, and the difference was this one. It was held back because its install-test had been failing on the Gitea runner (#51), not for anything about its shape - and that test went green in the very PR that converted it, on a render byte-identical to main, which is what ruled the chart out and left the runner. #51 was closed on the strength of that round; the cause was never identified, so a recurrence is a fresh finding rather than a known quirk. WHAT THIS RENDERS, AND WHAT IT DELIBERATELY DOES NOT Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no Certificate, and no implementation-specific policy object (nginx-gateway- fabric's ClientSettingsPolicy, Envoy Gateway's ClientTrafficPolicy or whatever the next one calls it). That subset is the part which looks IDENTICAL on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio - keeping to it is what lets the cluster's implementation be chosen, or changed, without touching this chart again. A policy object here would bind the chart to one implementation and to a CRD whose presence it cannot check. So the class and the certificate move from this chart to the PLATFORM: they sit on the Gateway and its listeners, which belong in cluster-config, not here. ONE OBJECT FOR ALL THREE HOSTNAMES, BECAUSE NOTHING DIFFERS PER HOST spec.hostnames is a LIST and its rules apply to every hostname of the object. That is a problem for a chart whose paths differ per host, and this chart is the opposite case: every hostname serves the SAME Service at "/" - measured on the live Ingress, which carries three host rules with byte-identical path and backend. So one object with three hostnames, not three objects. Contrast, because both shapes exist in this repository and the difference is not style: homeassistant renders one route PER host entry (its paths differ per host), matrix-synapse renders two (homeserver and delegation want different paths and different backends), and the keycloak realms of Gitea #122 would need one each (app-root differs per host). Here a second object would carry the same rule twice. WHY httpRoute.hostnames IS ITS OWN VALUE AND DOES NOT REUSE ingress.hosts The other charts of the sweep fall into two camps, and this chart is in neither. Where a chart has ingress.domain - a single public name - empty hostnames falls back to it, so there is one source of truth (cyberchef). Where a chart has no ingress.domain, httpRoute has no hostnames key at all and the names come from the values that already hold them, because a chart-global list could not say which app or which host entry it belonged to (freesailarr, homeassistant). This chart has no ingress.domain either, but its hostnames are unambiguous: one Service, one path, so a chart-global list means exactly one thing. What makes reuse of ingress.hosts wrong here is the other half of those entries - each carries an OPTIONAL tlsSecretName, which exists only to keep three grown certificates alive on their existing secret names (see the deviation note in values.yaml). In Gateway mode TLS sits on the Gateway's listener, so those names address nothing. Reusing the list would carry a structure whose second field is meaningless into the new mode and invite somebody to set it there. Hence: httpRoute.hostnames is a plain list of strings and is MANDATORY in this mode. There is no ingress.domain to fall back to, and an empty list would render a route matching every request that reaches its listener - on a shared Gateway that means answering for hostnames belonging to other releases. AND THEREFORE rss-bridge.tlsGroups IS INERT IN THIS MODE That helper exists for one purpose: group hosts by TLS secret name so that hosts sharing a name share one certificate with several SANs. With the certificate on the listener there is nothing left for it to group. It is read by the Ingress template and by NOTES.txt's Ingress branch, both behind ingress.enabled, so this mode simply never calls it. That is a SIMPLIFICATION and is named as one rather than quietly translated: the most intricate part of this chart's Ingress has no counterpart here because it has no job here. NOTHING IS LOST WITH THE ANNOTATIONS, AND THAT IS MEASURED, NOT ASSUMED Group 3 of the sweep screened the LIVE Ingress objects, not just the charts, because an operator's annotation can fill a gap the chart never sets and this mode drops ingress.annotations - four of its six charts carried exactly such an annotation. The same screen was run here against the running object (2026-10-13, read-only kubectl): it carries the cert-manager cluster-issuer annotation plus Helm and Rancher/Fleet bookkeeping, and NOT ONE nginx.ingress.kubernetes.io annotation. So this is the first chart of the sweep whose Gateway branch in NOTES.txt has no loss to explain. ingress.annotations is still inert in this mode - whatever a future installation puts there is gone - but there is nothing there today to lose. WHY A VALUE AND NOT AUTO-DETECTION .Capabilities.APIVersions is unreliable at template time (it answers from the cluster helm happens to be pointed at, and not at all for `helm template`), while an HTTPRoute without the Gateway-API CRDs fails at INSTALL time. A chart that guessed would therefore sometimes render an object the cluster rejects and sometimes silently render nothing. So the mode is a value. */}} {{- /* Null-safe like every other block this chart dereferences (#99): an override written without children - "httpRoute:" with the keys below it deleted - is YAML null, and `.Values.httpRoute.enabled` would abort the render with a nil pointer. An empty block means "chart defaults apply", and all three defaults here are empty or false, so the whole block collapses to "no HTTPRoute". */ -}} {{- $httpRoute := .Values.httpRoute | default dict -}} {{- if and $httpRoute.enabled .Values.ingress.enabled -}} {{- fail "ingress.enabled and httpRoute.enabled are both true - set exactly one of them. The Ingress and the HTTPRoute would publish the same Service under the same hostnames over two different data paths, which is already a conflict at DNS level: one name, two addresses, and which one a client reaches is luck. Note that ingress.enabled DEFAULTS TO TRUE in this chart, so switching to Gateway mode means setting ingress.enabled: false explicitly. A migration runs over a SECOND hostname (give httpRoute.hostnames a name the Ingress does not serve) and never over parallel operation of both." -}} {{- end -}} {{- if $httpRoute.enabled -}} {{- $parentRefs := $httpRoute.parentRefs | default list -}} {{- if not $parentRefs -}} {{- fail "httpRoute.enabled is true but httpRoute.parentRefs is empty - an HTTPRoute without a parent attaches to no Gateway. It is created, it is schema-valid, and it routes NOTHING, with no event and no condition that says so; the only symptom is a hostname that does not answer. Name the cluster's Gateway, e.g. httpRoute.parentRefs: [{name: public, namespace: gateway}]." -}} {{- end -}} {{- $hostnames := $httpRoute.hostnames | default list -}} {{- if not $hostnames -}} {{- fail "httpRoute.enabled is true but httpRoute.hostnames is empty - there is no hostname to publish. Unlike most charts here this one has NO ingress.domain to fall back to, and ingress.hosts is deliberately not reused: its entries carry tlsSecretName values that address nothing once TLS sits on the Gateway's listener. An HTTPRoute with no hostnames also does not render nothing - it matches EVERY request reaching its listener, so on a shared Gateway this release would answer for hostnames belonging to other releases. Set the public names explicitly, e.g. httpRoute.hostnames: [rss-bridge.example.com, feeds.example.com]." -}} {{- end -}} apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-httproute spec: parentRefs: {{- toYaml $parentRefs | nindent 4 }} hostnames: {{- toYaml $hostnames | nindent 4 }} rules: {{- /* The Ingress counterpart is `path: / , pathType: Prefix` on the one backend Service, repeated once per host rule - RSS-Bridge is served at the root and has no second path, so one rule covers all hostnames of this object. The port is the Service's port NUMBER and not its name: backendRefs takes a PortNumber, so unlike the Ingress - which references the targetPort by the name rss-bridge-port - this reference cannot be made by name, and the 80 below has to track rss-bridge.service.yaml by hand. ci/run-httproute-match-test.sh is what holds that pair: it fails when a backendRef names a Service, or a port, the same render does not create. */}} - matches: - path: type: PathPrefix value: / backendRefs: - name: {{ .Release.Name }}-{{ .Chart.Name }}-service port: 80 {{- end }}