{{/* The Gateway-API alternative to garage.ingress.yaml (Gitea #114). Form and reasoning: template-chart/templates/xxx.httproute.yaml and README, "Ingress and Gateway API". Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no Certificate, and no implementation-specific policy object - that subset looks identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is the whole reason the cluster's implementation can be swapped without touching this chart. Class and certificate therefore belong to the PLATFORM, on the Gateway and its listeners. OFF BY DEFAULT, AND SO IS THE INGRESS. This chart publishes nothing unless asked (ingress.enabled: false, httpRoute.enabled: false), because the consumer it was built for speaks S3 inside the cluster. Both routing modes are therefore a values change, and both expose THE S3 API - not a web UI. Garage's admin API is JSON and its authenticated half is closed in this chart, and the project's web interface is a separate program that is not in this image. Whoever turns either mode on is putting an S3 endpoint on the internet; it authenticates every request with a signature, and the credentials are the two fields of the s3 vault item. WHAT CHANGES FOR ingress.domain, AND IT IS THE ONE CLAIM THIS FILE INVALIDATES garage.ingress.yaml used to be able to say that NOTHING outside it reads ingress.domain, so that switching the Ingress off freed domain as well as clusterIssuer. This file is now a second reader (when httpRoute.hostnames is empty), so the sentence holds only while BOTH modes are off. That is corrected in that template's header and in values.schema.json rather than left standing. Unchanged: garage.s3.rootDomain is still its own value and still not coupled to ingress.domain. A wildcard vhost suffix for bucket addressing and the single host a route answers on are different things, and the Gateway API does not make them the same. ingress.clusterIssuer is meaningless here - this chart renders no Certificate in this mode - and it is not demanded either: its only required() sits inside garage.ingress.yaml, behind ingress.enabled. */}} {{- /* 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 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 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. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both." -}} {{- end -}} {{- if $httpRoute.enabled -}} {{- $settings := fromYaml (include "garage.settings" .) -}} {{- $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], so the two cannot drift apart. ingress.domain is read here even though no Ingress is rendered in this mode - the value names the public hostname of this release, which both modes need; it is not an Ingress switch. */ -}} {{- $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 labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs spec: parentRefs: {{- toYaml $parentRefs | nindent 4 }} hostnames: {{- toYaml $hostnames | nindent 4 }} rules: # The Ingress counterpart is `path: / , pathType: Prefix` on the S3 port of # the one Service. # # THE PORT IS WHERE THIS CHART DIFFERS FROM EVERY OTHER ONE IN THE SWEEP. # backendRefs takes a PortNumber and cannot name a port, while # garage.ingress.yaml references it BY NAME ("s3") and is therefore immune # to the number changing. Here the number has to be rendered - and # garage.ports.s3 is a value, so a literal 3900 would be a lie the moment # anyone changes it. It comes out of the same garage.settings helper that # garage.service.yaml renders its port from, which is what keeps the two # in step by construction instead of by hand. # # The admin port is deliberately NOT routed: with this chart's config it # serves /health and /metrics without authentication and answers everything # else 401/403. That is a monitoring surface for the cluster, not something # to publish. # # ci/run-httproute-match-test.sh holds the pair either way - 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: {{ $settings.ports.s3 | int64 }} {{- end }}