From 924e2a3fab1582ca342342a70ba0d42a1b907a9c Mon Sep 17 00:00:00 2001 From: Felix Delattre Date: Fri, 24 Jul 2026 00:58:52 +0200 Subject: [PATCH] refactor: extract unified ingress helpers. --- .../eoapi/templates/_helpers/networking.tpl | 100 ++++++++++++++++++ .../eoapi/templates/networking/ingress.yaml | 92 ++-------------- .../networking/traefik-middleware.yaml | 29 +---- charts/eoapi/tests/browser_redirect_test.yaml | 1 + charts/eoapi/tests/ingress_test.yaml | 20 ++++ .../tests/stac-auth-proxy-ingress_test.yaml | 7 ++ .../eoapi/tests/traefik_middleware_test.yaml | 3 + 7 files changed, 143 insertions(+), 109 deletions(-) create mode 100644 charts/eoapi/templates/_helpers/networking.tpl diff --git a/charts/eoapi/templates/_helpers/networking.tpl b/charts/eoapi/templates/_helpers/networking.tpl new file mode 100644 index 00000000..0e91acab --- /dev/null +++ b/charts/eoapi/templates/_helpers/networking.tpl @@ -0,0 +1,100 @@ +{{/* +Return JSON array of enabled ingress services with resolved path, backend, and rewrite metadata. +Browser remains on the main ingress; skipStripPrefix excludes it from Traefik strip-prefix only. +*/}} +{{- define "eoapi.enabledIngressServices" -}} +{{- $root := . -}} +{{- $entries := list + (dict "key" "stac" "usesAuthProxy" true) + (dict "key" "raster") + (dict "key" "vector") + (dict "key" "multidim") + (dict "key" "browser" "defaultPath" "/browser" "hasOwnPort" true "skipStripPrefix" true) + (dict "key" "mockOidcServer" "actualName" "mock-oidc-server" "hasOwnPort" true) +-}} +{{- $resolved := list -}} +{{- range $entries }} + {{- $entry := . -}} + {{- $service := ternary (index $root.Values "testing" "mockOidcServer") (index $root.Values $entry.key) (eq $entry.key "mockOidcServer") -}} + {{- $ingress := (($service | default dict).ingress) | default dict -}} + {{- if and $service $service.enabled (or (not $service.ingress) $service.ingress.enabled) }} + {{- $path := $ingress.path | default $entry.defaultPath -}} + {{- $useAuthProxy := and $entry.usesAuthProxy (index $root.Values "stac-auth-proxy" "enabled") -}} + {{/* nginxStrip: NGINX rewrite path shape; stripPrefix: Traefik middleware (matches main, includes "/") */}} + {{- $nginxStrip := and (ne $path "/") (not $useAuthProxy) -}} + {{- $stripPrefix := and (not $entry.skipStripPrefix) (not $useAuthProxy) -}} + {{- $serviceName := $entry.actualName | default $entry.key -}} + {{- $port := $root.Values.service.port -}} + {{- if $entry.hasOwnPort }} + {{- $port = (($service.service).port | default 8080) }} + {{- end }} + {{- $resolved = append $resolved (dict "path" $path "serviceName" $serviceName "port" $port "useAuthProxy" $useAuthProxy "stripPath" $nginxStrip "stripPrefix" $stripPrefix) -}} + {{- end }} +{{- end }} +{{- toJson $resolved -}} +{{- end -}} + +{{/* +Return true when at least one ingress service or doc server is enabled. +*/}} +{{- define "eoapi.hasEnabledService" -}} +{{- if or (include "eoapi.enabledIngressServices" . | fromJsonArray) .Values.docServer.enabled -}}true{{- end -}} +{{- end -}} + +{{/* +Generate ingress path rules for enabled services and doc server. +*/}} +{{- define "eoapi.ingressPaths" -}} +{{- $root := . -}} +{{- $isNginx := eq $root.Values.ingress.className "nginx" -}} +{{- range include "eoapi.enabledIngressServices" $root | fromJsonArray }} +- pathType: {{ if and $isNginx .stripPath }}ImplementationSpecific{{ else }}Prefix{{ end }} + path: {{ .path }}{{ if and $isNginx .stripPath }}(/|$)(.*){{ end }} + backend: + service: + {{- if .useAuthProxy }} + name: {{ $root.Release.Name }}-stac-auth-proxy + {{- else }} + name: {{ $root.Release.Name }}-{{ .serviceName }} + {{- end }} + port: + number: {{ .port }} +{{- end }} +{{- if $root.Values.docServer.enabled }} +- pathType: Prefix + path: "/{{ $root.Values.ingress.rootPath | default "" }}" + backend: + service: + name: {{ $root.Release.Name }}-doc-server + port: + number: 80 +{{- end }} +{{- end -}} + +{{/* +Return JSON array of path prefixes for Traefik strip-prefix middleware. +*/}} +{{- define "eoapi.traefikStripPrefixes" -}} +{{- $prefixes := list -}} +{{- range include "eoapi.enabledIngressServices" . | fromJsonArray }} + {{- if and .stripPrefix .path }} + {{- $prefixes = append $prefixes .path }} + {{- end }} +{{- end }} +{{- toJson $prefixes -}} +{{- end -}} + +{{/* +Return the configured browser ingress path without trailing slash. +*/}} +{{- define "eoapi.browserIngressPath" -}} +{{- trimSuffix "/" ((((.Values.browser).ingress).path) | default "/browser") | default "/" -}} +{{- end -}} + +{{/* +Return true when the Traefik bare-path redirect middleware is needed. +*/}} +{{- define "eoapi.browserRedirectEnabled" -}} +{{- $browser := .Values.browser -}} +{{- if and $browser $browser.enabled (or (not $browser.ingress) $browser.ingress.enabled) -}}true{{- end -}} +{{- end -}} diff --git a/charts/eoapi/templates/networking/ingress.yaml b/charts/eoapi/templates/networking/ingress.yaml index 35ecad7e..20b189ae 100644 --- a/charts/eoapi/templates/networking/ingress.yaml +++ b/charts/eoapi/templates/networking/ingress.yaml @@ -1,96 +1,22 @@ -{{/* -Helper template for generating ingress paths -*/}} -{{- define "eoapi.ingressPaths" -}} -{{- $isNginx := eq .Values.ingress.className "nginx" -}} -{{- $root := . -}} - -{{/* Service list with metadata for special cases - preserves order */}} -{{- $services := list - (dict "key" "stac" "usesAuthProxy" true) - (dict "key" "raster") - (dict "key" "vector") - (dict "key" "multidim") - (dict "key" "browser" "defaultPath" "/browser" "hasOwnPort" true) - (dict "key" "mockOidcServer" "actualName" "mock-oidc-server" "hasOwnPort" true "config" .Values.testing.mockOidcServer) --}} - -{{- range $services }} - {{- $service := .config | default (index $root.Values .key) }} - {{- if and $service $service.enabled (or (not $service.ingress) $service.ingress.enabled) }} - {{- $path := $service.ingress.path | default .defaultPath }} - {{- $useAuthProxy := and .usesAuthProxy (index $root.Values "stac-auth-proxy" "enabled") }} - {{- $stripPath := and (ne $path "/") (not $useAuthProxy) }} - {{- $serviceName := .actualName | default .key }} - {{- $port := $root.Values.service.port }} - {{- if .hasOwnPort }} - {{- if $service.service }} - {{- $port = $service.service.port | default 8080 }} - {{- else }} - {{- $port = 8080 }} - {{- end }} - {{- end }} -- pathType: {{ if and $isNginx $stripPath }}ImplementationSpecific{{ else }}Prefix{{ end }} - path: {{ $path }}{{ if and $isNginx $stripPath }}(/|$)(.*){{ end }} - backend: - service: - {{- if $useAuthProxy }} - name: {{ $root.Release.Name }}-stac-auth-proxy - {{- else }} - name: {{ $root.Release.Name }}-{{ $serviceName }} - {{- end }} - port: - number: {{ $port }} - {{- end }} -{{- end }} - -{{- if .Values.docServer.enabled }} -- pathType: Prefix - path: "/{{ .Values.ingress.rootPath | default "" }}" - backend: - service: - name: {{ .Release.Name }}-doc-server - port: - number: 80 -{{- end }} -{{- end }} - -{{- define "eoapi.hasEnabledService" -}} -{{- $keys := list "stac" "raster" "vector" "multidim" "browser" -}} -{{- $hasService := false -}} -{{- range $keys }} - {{- $s := index $.Values . }} - {{- if and $s $s.enabled (or (not $s.ingress) $s.ingress.enabled) }} - {{- $hasService = true -}} - {{- end }} -{{- end }} -{{- if and $.Values.testing.mockOidcServer $.Values.testing.mockOidcServer.enabled (or (not $.Values.testing.mockOidcServer.ingress) $.Values.testing.mockOidcServer.ingress.enabled) }} - {{- $hasService = true -}} -{{- end }} -{{- or $hasService .Values.docServer.enabled -}} -{{- end }} - {{- if and .Values.ingress.enabled (include "eoapi.hasEnabledService" . | trim | eq "true") }} -{{- $ingressAnnotations := .Values.ingress.annotations | default dict }} -{{- $annotations := dict }} -{{- if and (eq .Values.ingress.className "traefik") .Values.ingress.entrypoints }} -{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.entrypoints" (.Values.ingress.entrypoints | toString) }} -{{- end }} -{{- $annotations = mergeOverwrite $annotations $ingressAnnotations }} +{{- $annotations := dict -}} +{{- if and (eq .Values.ingress.className "traefik") .Values.ingress.entrypoints -}} +{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.entrypoints" (.Values.ingress.entrypoints | toString) -}} +{{- end -}} +{{- $annotations = mergeOverwrite $annotations (.Values.ingress.annotations | default dict) -}} {{- if eq .Values.ingress.className "nginx" }} -{{- $_ := set $annotations "nginx.ingress.kubernetes.io/rewrite-target" "/$2" }} -{{- $_ := set $annotations "nginx.ingress.kubernetes.io/use-regex" "true" }} +{{- $_ := set $annotations "nginx.ingress.kubernetes.io/rewrite-target" "/$2" -}} +{{- $_ := set $annotations "nginx.ingress.kubernetes.io/use-regex" "true" -}} {{- end }} {{- if eq .Values.ingress.className "traefik" }} {{- /* strip-prefix always; also chain the browser bare-path redirect when the browser ingress is on. Keep this condition in sync with the redirect Middleware in traefik-middleware.yaml. */}} {{- $mwPrefix := printf "%s-%s" .Release.Namespace .Release.Name }} {{- $middlewares := list (printf "%s-strip-prefix-middleware@kubernetescrd" $mwPrefix) }} -{{- $browser := .Values.browser }} -{{- if and $browser $browser.enabled (or (not $browser.ingress) $browser.ingress.enabled) }} +{{- if include "eoapi.browserRedirectEnabled" . | trim }} {{- $middlewares = append $middlewares (printf "%s-browser-redirect-middleware@kubernetescrd" $mwPrefix) }} {{- end }} -{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.middlewares" (join "," $middlewares) }} +{{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.middlewares" (join "," $middlewares) -}} {{- end }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} apiVersion: networking.k8s.io/v1 diff --git a/charts/eoapi/templates/networking/traefik-middleware.yaml b/charts/eoapi/templates/networking/traefik-middleware.yaml index dec82047..cd2662c4 100644 --- a/charts/eoapi/templates/networking/traefik-middleware.yaml +++ b/charts/eoapi/templates/networking/traefik-middleware.yaml @@ -1,27 +1,5 @@ {{- if and .Values.ingress.enabled (eq .Values.ingress.className "traefik") }} -{{- $services := list - (dict "key" "stac" "usesAuthProxy" true) - (dict "key" "raster") - (dict "key" "vector") - (dict "key" "multidim") - (dict "key" "browser" "defaultPath" "/browser" "skipStripPrefix" true) - (dict "key" "mockOidcServer" "config" .Values.testing.mockOidcServer) --}} -{{- $prefixes := list -}} - -{{- range $services }} - {{- $service := .config | default (index $.Values .key) }} - {{- if and $service $service.enabled (or (not $service.ingress) $service.ingress.enabled) }} - {{- $stripPath := not (or .skipStripPrefix (and .usesAuthProxy (index $.Values "stac-auth-proxy" "enabled"))) }} - {{- if $stripPath }} - {{- $path := $service.ingress.path | default .defaultPath }} - {{- if $path }} - {{- $prefixes = append $prefixes $path }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} - +{{- $prefixes := include "eoapi.traefikStripPrefixes" . | fromJsonArray -}} {{- if $prefixes }} apiVersion: traefik.io/v1alpha1 kind: Middleware @@ -40,9 +18,8 @@ spec: {{- /* Bare browser path 404s (pathPrefix is baked into the image) and Traefik has no append-slash, so redirect the exact bare path to its trailing-slash form. Keep this condition in sync with the router annotation in ingress.yaml. */}} -{{- $browser := .Values.browser }} -{{- if and $browser $browser.enabled (or (not $browser.ingress) $browser.ingress.enabled) }} -{{- $browserPath := trimSuffix "/" ($browser.ingress.path | default "/browser") }} +{{- if include "eoapi.browserRedirectEnabled" . | trim }} +{{- $browserPath := include "eoapi.browserIngressPath" . }} --- apiVersion: traefik.io/v1alpha1 kind: Middleware diff --git a/charts/eoapi/tests/browser_redirect_test.yaml b/charts/eoapi/tests/browser_redirect_test.yaml index 5b2ff761..60c02317 100644 --- a/charts/eoapi/tests/browser_redirect_test.yaml +++ b/charts/eoapi/tests/browser_redirect_test.yaml @@ -1,5 +1,6 @@ suite: browser bare-path redirect middleware templates: + - templates/_helpers/networking.tpl - templates/networking/traefik-middleware.yaml - templates/networking/ingress.yaml set: diff --git a/charts/eoapi/tests/ingress_test.yaml b/charts/eoapi/tests/ingress_test.yaml index c9a6b8cb..05919c04 100644 --- a/charts/eoapi/tests/ingress_test.yaml +++ b/charts/eoapi/tests/ingress_test.yaml @@ -1,8 +1,10 @@ suite: unified ingress tests templates: + - templates/_helpers/networking.tpl - templates/networking/ingress.yaml tests: - it: "should not create ingress when all services disabled" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -18,6 +20,7 @@ tests: count: 0 - it: "stac-only deployment at root path with nginx" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -47,6 +50,7 @@ tests: value: RELEASE-NAME-stac - it: "stac-only deployment at root path with traefik" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "traefik" @@ -71,6 +75,7 @@ tests: value: "traefik" - it: "vector ingress with nginx controller" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" ingress.annotations: @@ -102,6 +107,7 @@ tests: value: "nginx" - it: "raster ingress with traefik controller" + template: templates/networking/ingress.yaml set: ingress.className: "traefik" ingress.host: "eoapi.local" @@ -131,6 +137,7 @@ tests: value: "eoapi.local" - it: "traefik ingress emits entrypoints when configured" + template: templates/networking/ingress.yaml set: ingress.className: "traefik" ingress.entrypoints: "websecure" @@ -150,6 +157,7 @@ tests: traefik.ingress.kubernetes.io/router.middlewares: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "traefik ingress quotes entrypoints annotation values" + template: templates/networking/ingress.yaml set: ingress.className: "traefik" ingress.entrypoints: "true" @@ -169,6 +177,7 @@ tests: traefik.ingress.kubernetes.io/router.middlewares: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "traefik ingress annotations override configured entrypoints" + template: templates/networking/ingress.yaml set: ingress.className: "traefik" ingress.entrypoints: "websecure" @@ -190,6 +199,7 @@ tests: traefik.ingress.kubernetes.io/router.middlewares: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "all services enabled with custom paths" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" raster.enabled: true @@ -221,6 +231,7 @@ tests: value: "ImplementationSpecific" - it: "multiple hosts with services" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" ingress.hosts: @@ -251,6 +262,7 @@ tests: value: "/vector(/|$)(.*)" - it: "tls enabled with multiple hosts" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" ingress.hosts: @@ -277,6 +289,7 @@ tests: value: "eoapi-tls" - it: "single host with tls" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" ingress.host: "api.eoapi.dev" @@ -301,6 +314,7 @@ tests: value: "eoapi-tls" - it: "stac with auth proxy enabled" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -320,6 +334,7 @@ tests: value: RELEASE-NAME-stac-auth-proxy - it: "browser with default path" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -340,6 +355,7 @@ tests: value: RELEASE-NAME-browser - it: "docServer creates root path entry" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -360,6 +376,7 @@ tests: value: RELEASE-NAME-doc-server - it: "mockOidcServer with custom path" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -383,6 +400,7 @@ tests: value: RELEASE-NAME-mock-oidc-server - it: "custom annotations preserved" + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: "nginx" @@ -429,6 +447,7 @@ tests: traefik.ingress.kubernetes.io/router.middlewares: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "traefik chart middlewares win over user router.middlewares annotation" + template: templates/networking/ingress.yaml set: ingress.className: "traefik" ingress.annotations: @@ -445,6 +464,7 @@ tests: value: NAMESPACE-RELEASE-NAME-strip-prefix-middleware@kubernetescrd - it: "nginx chart rewrite annotations win over user rewrite-target/use-regex" + template: templates/networking/ingress.yaml set: ingress.className: "nginx" ingress.annotations: diff --git a/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml b/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml index baeac16e..e25ae95c 100644 --- a/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml +++ b/charts/eoapi/tests/stac-auth-proxy-ingress_test.yaml @@ -1,9 +1,11 @@ suite: test ingress routing without stripPrefix middleware templates: + - templates/_helpers/networking.tpl - templates/networking/ingress.yaml tests: - it: should route ingress to stac-auth-proxy when enabled + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: nginx @@ -25,6 +27,7 @@ tests: number: 8080 - it: should route ingress directly to stac when auth-proxy is disabled + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: nginx @@ -46,6 +49,7 @@ tests: number: 8080 - it: should not create ingress when both stac and browser are disabled + template: templates/networking/ingress.yaml set: ingress.enabled: true stac.enabled: false @@ -60,6 +64,7 @@ tests: count: 0 - it: should route correctly with experimental profile + template: templates/networking/ingress.yaml values: - ../profiles/experimental.yaml set: @@ -77,6 +82,7 @@ tests: number: 8080 - it: should route ingress to browser + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: nginx @@ -98,6 +104,7 @@ tests: number: 8080 - it: should include both stac and browser when both enabled + template: templates/networking/ingress.yaml set: ingress.enabled: true ingress.className: nginx diff --git a/charts/eoapi/tests/traefik_middleware_test.yaml b/charts/eoapi/tests/traefik_middleware_test.yaml index 64056944..b3339dbe 100644 --- a/charts/eoapi/tests/traefik_middleware_test.yaml +++ b/charts/eoapi/tests/traefik_middleware_test.yaml @@ -1,8 +1,10 @@ suite: traefik strip-prefix middleware templates: + - templates/_helpers/networking.tpl - templates/networking/traefik-middleware.yaml tests: - it: "traefik strip-prefix middleware renders enabled service prefixes" + template: templates/networking/traefik-middleware.yaml set: ingress.className: "traefik" stac.enabled: true @@ -24,6 +26,7 @@ tests: - /multidim - it: "excludes auth-proxied stac from strip-prefix prefixes" + template: templates/networking/traefik-middleware.yaml set: ingress.className: "traefik" stac.enabled: true