Files
helm-charts/ytdlp-web-player/templates/ytdlp-web-player.httproute.yaml
T
jklein e4b41577ae feat(ytdlp-web-player): Gateway-API as an alternative to the Ingress
Group 3 of the #114 sweep, last chart - and the one that carried the group-2
pattern this sweep was told to look for. It reads ingress.enabled OUTSIDE its
Ingress template, in ytdlp-web-player.validate, and in Gateway mode
ingress.enabled is false by construction, so that guard would have gone
silent. Fixed with ytdlp-web-player.published = ingress.enabled OR
httpRoute.enabled - the freesailarr.published / sure.publishedOverTls shape,
identical to ingress.enabled while httpRoute.enabled is false, which is why it
changes no existing render.

AND THE GUARD IS NEEDED MORE IN GATEWAY MODE, NOT LESS. It refuses to publish
the application and the oauth2-proxy on the same host:

  - In INGRESS mode the consequence of ignoring it is loud: ingress-nginx's
    admission webhook rejects the second Ingress and the Fleet bundle goes to
    ErrApplied.
  - In GATEWAY mode THE WEBHOOK NEVER FIRES. It compares Ingress objects with
    Ingress objects, and an HTTPRoute is a different kind - so nothing rejects
    anything, both objects stay valid, and one hostname is served over two
    data paths that are NOT equivalent: the proxy's Ingress goes through
    AUTHENTICATION and this chart's HTTPRoute goes straight to the
    application. Which one a client reaches depends on what DNS resolves to.
    That is an authentication bypass that comes and goes with a DNS record and
    that no Kubernetes object reports.

So the Gateway-mode abort has its own message saying exactly that. Both
messages exercised, plus the collision reached through httpRoute.hostnames
rather than through the ingress.domain fallback, and the guard still firing
when only the Deployment is rendered.

THE SUBCHART HAS NO GATEWAY SUPPORT, and this chart cannot give it one -
upstream's chart is consumed as it comes (README, "Chart dependencies"). It
renders an Ingress or nothing. So in the RECOMMENDED configuration, where the
proxy sits in the data path and owns the host, the publishing object is the
SUBCHART'S INGRESS in both of this chart's modes, and turning httpRoute on
buys nothing. The route is for the proxy-disabled case, or for the application
on a host of its own - which is the documented unauthenticated bypass. Said in
the template header, in values.yaml, in the schema and in the proxy branch of
NOTES.txt, because someone migrating hosts will otherwise set the value and
wonder why nothing moved.

The 16k proxy-buffer-size this chart recommends is NOT lost by this switch,
and that needed stating rather than assuming: it belongs on the PROXY's
Ingress (oauth2-proxy.ingress.annotations) as the second half of the 4kB
session-cookie trap, and the subchart keeps rendering an Ingress. This chart's
own ingress.annotations default is {} and it sets nothing but the cert-manager
annotation, so there is no chart-side annotation loss to name.

CORRECTED CLAIM: values.yaml and the schema both said that everything the
Ingress needs is read only inside its template, so switching it off freed BOTH
ingress.domain and ingress.clusterIssuer. clusterIssuer is still freed; domain
is now read by the HTTPRoute, so it is freed only with both modes off. The
reason the claim was made still holds and is kept - the application has no
base-URL setting and builds no absolute links.

NOTHING WAS MEASURED ON A LIVE INGRESS OF THIS CHART, because there is none.
Measured 2026-10-12: the release runs in namespace jlab-player with both
Deployments Ready, and NOT ONE of the cluster's 23 Ingress objects is in that
namespace - neither this chart's nor the subchart's. What publishes it instead
could not be determined: this workspace's identity may not list httproutes or
secrets, so neither a hand-written HTTPRoute nor the release's Helm values are
readable. Recorded as the open question it is, in values.yaml and in the
overlay, rather than guessed at.

Minor: additive, defaults unchanged. Empty helm template diff against
origin/main with ci/ytdlp-web-player/test-values.yaml.

Part of #114
2026-10-13 08:59:09 +00:00

135 lines
7.5 KiB
YAML

{{/*
HTTPRoute - the Gateway-API alternative to ytdlp-web-player.ingress.yaml
(Gitea #114).
READ THE SUBCHART PARAGRAPH FIRST, because it decides whether this mode is
useful at all for a given installation.
THE oauth2-proxy SUBCHART HAS NO GATEWAY SUPPORT, AND THIS CHART CANNOT GIVE
IT ONE. Upstream's chart is consumed as it comes - that is the standing rule
for dependencies (README, "Chart dependencies"): where its defaults fall short
of this repository's conventions, the shortfall is DOCUMENTED and never
patched. It renders an Ingress or nothing.
So in the RECOMMENDED configuration - oauth2-proxy.enabled true, the proxy in
the data path, the proxy owning the host and this chart's own routing object
switched off - the object that publishes the release is the SUBCHART'S
INGRESS, in both of this chart's modes. Setting httpRoute.enabled there buys
nothing and changes nothing about how the host is served. This route is for
the other two cases:
- the proxy switched off entirely (oauth2-proxy.enabled false), the chart
publishing the application itself;
- the application on a host of ITS OWN, next to the proxy's host - which is
allowed and means exactly what it says: an unauthenticated way past the
proxy. NOTES.txt says so in both modes.
AND THAT SECOND CASE IS WHY THE HOST GUARD CHANGED. ytdlp-web-player.validate
used to ask ingress.enabled, which is false by construction in this mode, so
the guard went silent exactly where it matters most. It now asks
ytdlp-web-player.published (ingress.enabled OR httpRoute.enabled). The
difference is not cosmetic: in Ingress mode a host claimed twice is rejected
LOUDLY by ingress-nginx's admission webhook, while in Gateway mode the webhook
never fires - it compares Ingress objects with Ingress objects, and an
HTTPRoute is a different kind. Two valid objects then serve one hostname over
two data paths, one through authentication and one straight to the
application, and which one a client reaches depends on DNS. See the helper's
comment for the full argument.
WHAT THIS RENDERS, AND WHAT IT MUST NOT
Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no
Certificate, no implementation-specific policy object - the subset that looks
identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio (decision 1
of #114). Class and certificate belong to the PLATFORM, on the Gateway and its
listeners, in cluster-config.
WHAT THIS MODE DROPS, AND WHAT IT DOES NOT
ingress.annotations is inert here - no annotation channel in the Gateway API.
For this chart the measured picture is unusual, so it is worth being exact
about which annotation lives where:
- The 16k proxy-buffer-size this chart's values recommend is NOT on this
chart's Ingress. It belongs on the PROXY's Ingress
(oauth2-proxy.ingress.annotations), because it is the second half of the
4kB session-cookie trap: the response carrying the proxy's session cookie
does not fit ingress-nginx's default 4k buffer and the browser gets a 502
AFTER a successful login. The subchart still renders an Ingress in this
mode, so that annotation is UNAFFECTED by this switch. It would only be
lost if the proxy itself ever moved behind a Gateway, which this chart
cannot do.
- This chart's own ingress.annotations default is {} and the chart sets
nothing but the cert-manager annotation, so there is no chart-side loss to
name.
NOTHING WAS MEASURED ON A LIVE INGRESS OF THIS CHART, because there is none.
Checked on 2026-10-12: the release runs in namespace jlab-player (Services
jlab-player-ytdlp-web-player-service and jlab-player-oauth2-proxy, both
Deployments Ready) and NOT ONE of the 23 Ingress objects in the cluster is in
that namespace - neither this chart's nor the subchart's. What publishes it
instead could NOT be determined: this workspace's identity may not list
httproutes or secrets, so neither a hand-written HTTPRoute nor the release's
own Helm values are readable. Stated as the open question it is, rather than
guessed at.
*/}}
{{- /*
Null-safe like every other block a template here dereferences (#99): an
override written without children - "httpRoute:" with its keys deleted - is
YAML null, and .Values.httpRoute.enabled would abort with a nil pointer.
*/ -}}
{{- $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 hostname 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. ingress.enabled defaults to TRUE in this chart, so turning the Gateway mode on means setting ingress.enabled: false in the same values file. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both - and if the oauth2-proxy subchart is in the data path, mind that it has no Gateway support and publishes over an Ingress regardless." -}}
{{- end -}}
{{- if $httpRoute.enabled -}}
{{- include "ytdlp-web-player.validate" . -}}
{{- $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 -}}
{{- /*
One source of truth for the hostname: an empty httpRoute.hostnames means
[ingress.domain]. Two values that have to agree drift apart.
AND THIS READ CHANGES THE CHART'S OWN ACCOUNT OF ITSELF. values.yaml and the
schema both stated that everything the Ingress needs is read only inside the
Ingress template, which is why switching it off freed BOTH ingress.domain and
ingress.clusterIssuer. The second half still holds - the application has no
base-URL setting and builds no absolute links, so it needs no hostname of its
own - but ingress.domain is now read HERE as well. Both have been corrected
rather than left standing. clusterIssuer is still freed by this mode: no
Certificate is rendered.
*/ -}}
{{- $hostnames := $httpRoute.hostnames | default list -}}
{{- if not $hostnames -}}
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the HTTPRoute's single hostname when httpRoute.hostnames is empty) or set httpRoute.hostnames explicitly" .Values.ingress.domain) -}}
{{- 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
application Service. The port is the Service's port NUMBER and not its
name "http": backendRefs takes a PortNumber, so unlike the Ingress this
reference cannot be made by name. 80 is what the Service publishes (and
what ytdlp-web-player.appServiceAddress hands the proxy as its
upstream), so the three agree; ci/run-httproute-match-test.sh holds the
pair for the case where they stop agreeing.
*/}}
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-service
port: 80
{{- end }}