From ff9a26b55aa008e7f588b6e6c927fca91b19bf95 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 30 Jul 2026 14:55:59 +0200 Subject: [PATCH 01/12] feat: add Gateway API HTTPRoute support for BaSyx services --- charts/basyx/templates/_helpers.tpl | 27 ++++ .../templates/aas-discovery/httproute.yaml | 33 +++++ .../templates/aas-environment/httproute.yaml | 33 +++++ .../templates/aas-registry/httproute.yaml | 33 +++++ .../templates/aas-repository/httproute.yaml | 33 +++++ .../templates/aas-web-gui/httproute.yaml | 33 +++++ .../templates/cd-repository/httproute.yaml | 33 +++++ .../templates/company-lookup/httproute.yaml | 33 +++++ .../digital-twin-registry/httproute.yaml | 33 +++++ charts/basyx/templates/dpp-api/httproute.yaml | 33 +++++ .../basyx/templates/keycloak/httproute.yaml | 33 +++++ .../submodel-registry/httproute.yaml | 33 +++++ .../submodel-repository/httproute.yaml | 33 +++++ charts/basyx/values.schema.json | 18 +++ charts/basyx/values.yaml | 135 ++++++++++++++++++ 15 files changed, 576 insertions(+) create mode 100644 charts/basyx/templates/aas-discovery/httproute.yaml create mode 100644 charts/basyx/templates/aas-environment/httproute.yaml create mode 100644 charts/basyx/templates/aas-registry/httproute.yaml create mode 100644 charts/basyx/templates/aas-repository/httproute.yaml create mode 100644 charts/basyx/templates/aas-web-gui/httproute.yaml create mode 100644 charts/basyx/templates/cd-repository/httproute.yaml create mode 100644 charts/basyx/templates/company-lookup/httproute.yaml create mode 100644 charts/basyx/templates/digital-twin-registry/httproute.yaml create mode 100644 charts/basyx/templates/dpp-api/httproute.yaml create mode 100644 charts/basyx/templates/keycloak/httproute.yaml create mode 100644 charts/basyx/templates/submodel-registry/httproute.yaml create mode 100644 charts/basyx/templates/submodel-repository/httproute.yaml diff --git a/charts/basyx/templates/_helpers.tpl b/charts/basyx/templates/_helpers.tpl index a662530..9b5f769 100644 --- a/charts/basyx/templates/_helpers.tpl +++ b/charts/basyx/templates/_helpers.tpl @@ -1160,6 +1160,33 @@ annotations: {{- end }} {{- end }} +{{/* +Render parentRefs for a Gateway API HTTPRoute. Falls back to the chart-wide +default parentRefs (.Values.gatewayApi.parentRefs) when the component does +not define its own. +*/}} +{{- define "common.httpRoute.parentRefs" -}} +{{- $parentRefs := .parentRefs -}} +{{- if not $parentRefs }} +{{- $parentRefs = .root.Values.gatewayApi.parentRefs }} +{{- end }} +{{- if not $parentRefs }} +{{- fail "httpRoute is enabled but no parentRefs are configured — set .httpRoute.parentRefs or the chart-wide gatewayApi.parentRefs" }} +{{- end }} +{{- range $parentRefs }} +- name: {{ .name | quote }} + {{- if .namespace }} + namespace: {{ .namespace | quote }} + {{- end }} + {{- if .sectionName }} + sectionName: {{ .sectionName | quote }} + {{- end }} + {{- if .port }} + port: {{ .port }} + {{- end }} +{{- end }} +{{- end }} + {{- define "common.ingressTLS.annotations" -}} {{- if .Values.tls.enabled }} {{- if .Values.ingress.certificateIssuer.enabled }} diff --git a/charts/basyx/templates/aas-discovery/httproute.yaml b/charts/basyx/templates/aas-discovery/httproute.yaml new file mode 100644 index 0000000..d0ab032 --- /dev/null +++ b/charts/basyx/templates/aas-discovery/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.aasDiscovery.enabled -}} +{{- if .Values.aasDiscovery.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-aasDiscovery.fullname" . }} + labels: + {{- include "basyx-aasDiscovery.labels" . | nindent 4 }} + {{- with .Values.aasDiscovery.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.aasDiscovery.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.aasDiscovery.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.aasDiscovery.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-aasDiscovery.fullname" $ }} + port: {{ $.Values.aasDiscovery.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/aas-environment/httproute.yaml b/charts/basyx/templates/aas-environment/httproute.yaml new file mode 100644 index 0000000..e7aa1fe --- /dev/null +++ b/charts/basyx/templates/aas-environment/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.aasEnvironment.enabled -}} +{{- if .Values.aasEnvironment.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-aasEnvironment.fullname" . }} + labels: + {{- include "basyx-aasEnvironment.labels" . | nindent 4 }} + {{- with .Values.aasEnvironment.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.aasEnvironment.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.aasEnvironment.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.aasEnvironment.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-aasEnvironment.fullname" $ }} + port: {{ $.Values.aasEnvironment.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/aas-registry/httproute.yaml b/charts/basyx/templates/aas-registry/httproute.yaml new file mode 100644 index 0000000..0169a5c --- /dev/null +++ b/charts/basyx/templates/aas-registry/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.aasRegistry.enabled -}} +{{- if .Values.aasRegistry.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-aasRegistry.fullname" . }} + labels: + {{- include "basyx-aasRegistry.labels" . | nindent 4 }} + {{- with .Values.aasRegistry.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.aasRegistry.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.aasRegistry.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.aasRegistry.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-aasRegistry.fullname" $ }} + port: {{ $.Values.aasRegistry.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/aas-repository/httproute.yaml b/charts/basyx/templates/aas-repository/httproute.yaml new file mode 100644 index 0000000..9ddf23a --- /dev/null +++ b/charts/basyx/templates/aas-repository/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.aasRepository.enabled -}} +{{- if .Values.aasRepository.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-aasRepository.fullname" . }} + labels: + {{- include "basyx-aasRepository.labels" . | nindent 4 }} + {{- with .Values.aasRepository.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.aasRepository.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.aasRepository.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.aasRepository.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-aasRepository.fullname" $ }} + port: {{ $.Values.aasRepository.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/aas-web-gui/httproute.yaml b/charts/basyx/templates/aas-web-gui/httproute.yaml new file mode 100644 index 0000000..913aad5 --- /dev/null +++ b/charts/basyx/templates/aas-web-gui/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.aasWebGui.enabled }} +{{- if .Values.aasWebGui.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-aasWebGui.fullname" . }} + labels: + {{- include "basyx-aasWebGui.labels" . | nindent 4 }} + {{- with .Values.aasWebGui.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.aasWebGui.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.aasWebGui.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.aasWebGui.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-aasWebGui.fullname" $ }} + port: {{ $.Values.aasWebGui.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/cd-repository/httproute.yaml b/charts/basyx/templates/cd-repository/httproute.yaml new file mode 100644 index 0000000..959fefc --- /dev/null +++ b/charts/basyx/templates/cd-repository/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.cdRepository.enabled -}} +{{- if .Values.cdRepository.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-cdRepository.fullname" . }} + labels: + {{- include "basyx-cdRepository.labels" . | nindent 4 }} + {{- with .Values.cdRepository.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.cdRepository.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.cdRepository.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.cdRepository.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-cdRepository.fullname" $ }} + port: {{ $.Values.cdRepository.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/company-lookup/httproute.yaml b/charts/basyx/templates/company-lookup/httproute.yaml new file mode 100644 index 0000000..5073c13 --- /dev/null +++ b/charts/basyx/templates/company-lookup/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.companyLookup.enabled -}} +{{- if .Values.companyLookup.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-companyLookup.fullname" . }} + labels: + {{- include "basyx-companyLookup.labels" . | nindent 4 }} + {{- with .Values.companyLookup.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.companyLookup.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.companyLookup.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.companyLookup.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-companyLookup.fullname" $ }} + port: {{ $.Values.companyLookup.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/digital-twin-registry/httproute.yaml b/charts/basyx/templates/digital-twin-registry/httproute.yaml new file mode 100644 index 0000000..edc8f39 --- /dev/null +++ b/charts/basyx/templates/digital-twin-registry/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.digitalTwinRegistry.enabled -}} +{{- if .Values.digitalTwinRegistry.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-digitalTwinRegistry.fullname" . }} + labels: + {{- include "basyx-digitalTwinRegistry.labels" . | nindent 4 }} + {{- with .Values.digitalTwinRegistry.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.digitalTwinRegistry.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.digitalTwinRegistry.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.digitalTwinRegistry.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-digitalTwinRegistry.fullname" $ }} + port: {{ $.Values.digitalTwinRegistry.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/dpp-api/httproute.yaml b/charts/basyx/templates/dpp-api/httproute.yaml new file mode 100644 index 0000000..6c74154 --- /dev/null +++ b/charts/basyx/templates/dpp-api/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.dppApi.enabled -}} +{{- if .Values.dppApi.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-dppApi.fullname" . }} + labels: + {{- include "basyx-dppApi.labels" . | nindent 4 }} + {{- with .Values.dppApi.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.dppApi.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.dppApi.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.dppApi.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-dppApi.fullname" $ }} + port: {{ $.Values.dppApi.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/keycloak/httproute.yaml b/charts/basyx/templates/keycloak/httproute.yaml new file mode 100644 index 0000000..fbf9c06 --- /dev/null +++ b/charts/basyx/templates/keycloak/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.keycloak.enabled }} +{{- if .Values.keycloak.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-keycloak.fullname" . }} + labels: + {{- include "basyx-keycloak.labels" . | nindent 4 }} + {{- with .Values.keycloak.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.keycloak.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.keycloak.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.keycloak.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-keycloak.fullname" $ }} + port: {{ $.Values.keycloak.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/submodel-registry/httproute.yaml b/charts/basyx/templates/submodel-registry/httproute.yaml new file mode 100644 index 0000000..7d8b0db --- /dev/null +++ b/charts/basyx/templates/submodel-registry/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.submodelRegistry.enabled -}} +{{- if .Values.submodelRegistry.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-submodelRegistry.fullname" . }} + labels: + {{- include "basyx-submodelRegistry.labels" . | nindent 4 }} + {{- with .Values.submodelRegistry.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.submodelRegistry.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.submodelRegistry.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.submodelRegistry.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-submodelRegistry.fullname" $ }} + port: {{ $.Values.submodelRegistry.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/templates/submodel-repository/httproute.yaml b/charts/basyx/templates/submodel-repository/httproute.yaml new file mode 100644 index 0000000..3f93bb4 --- /dev/null +++ b/charts/basyx/templates/submodel-repository/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.submodelRepository.enabled -}} +{{- if .Values.submodelRepository.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "basyx-submodelRepository.fullname" . }} + labels: + {{- include "basyx-submodelRepository.labels" . | nindent 4 }} + {{- with .Values.submodelRepository.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- include "common.httpRoute.parentRefs" (dict "root" $ "parentRefs" .Values.submodelRepository.httpRoute.parentRefs) | nindent 4 }} + hostnames: + {{- range .Values.submodelRepository.httpRoute.hosts }} + - {{ tpl .host $ | quote }} + {{- end }} + rules: + {{- range .Values.submodelRepository.httpRoute.hosts }} + {{- range .paths }} + - matches: + - path: + type: {{ .pathType }} + value: {{ tpl .path $ | quote }} + backendRefs: + - name: {{ include "basyx-submodelRepository.fullname" $ }} + port: {{ $.Values.submodelRepository.service.port }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/basyx/values.schema.json b/charts/basyx/values.schema.json index 7f5a3d9..763cdc9 100644 --- a/charts/basyx/values.schema.json +++ b/charts/basyx/values.schema.json @@ -1338,6 +1338,24 @@ } } }, + "gatewayApi": { + "type": "object", + "properties": { + "parentRefs": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "namespace": { "type": "string" }, + "sectionName": { "type": "string" }, + "port": { "type": "integer" } + } + } + } + } + }, "general": { "type": "object", "properties": { diff --git a/charts/basyx/values.yaml b/charts/basyx/values.yaml index 9ff8bbb..67633b7 100644 --- a/charts/basyx/values.yaml +++ b/charts/basyx/values.yaml @@ -69,6 +69,18 @@ ingress: name: ingress-issuer spec: {} +# Gateway API HTTPRoute - alternative to Ingress for exposing services. +# Requires Gateway API CRDs and a Gateway resource already installed in the +# cluster (see: https://gateway-api.sigs.k8s.io/guides/). Disabled per +# component via .httpRoute.enabled; a component's own +# httpRoute.parentRefs takes precedence, otherwise the list below is used. +gatewayApi: + parentRefs: [] + # parentRefs: + # - name: my-gateway + # namespace: gateway-system + # sectionName: https + paths: dashboard: /aas-dataspace-dashboard aasDiscovery: /aas-discovery @@ -467,6 +479,16 @@ keycloak: - path: "{{ .Values.paths.keycloak }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.keycloak }}" + pathType: PathPrefix + resources: {} # limits: @@ -575,6 +597,17 @@ submodelRepository: paths: - path: "{{ .Values.paths.submodelRepository }}" pathType: Prefix + + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.submodelRepository }}" + pathType: PathPrefix + environment: # Keep the Submodel Registry in sync when submodels are created, updated or deleted. GENERAL_SUBMODELREGISTRYINTEGRATION: true @@ -674,6 +707,17 @@ submodelRegistry: paths: - path: "{{ .Values.paths.submodelRegistry }}" pathType: Prefix + + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.submodelRegistry }}" + pathType: PathPrefix + environment: {} general: {} server: {} @@ -771,6 +815,16 @@ aasRegistry: - path: "{{ .Values.paths.aasRegistry }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.aasRegistry }}" + pathType: PathPrefix + # Content of application.yaml # central environment variables (will be set in configmap.yaml via tpl interpolation) environment: {} @@ -878,6 +932,16 @@ digitalTwinRegistry: - path: "{{ .Values.paths.digitalTwinRegistry }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.digitalTwinRegistry }}" + pathType: PathPrefix + # Content of application.yaml # central environment variables (will be set in configmap.yaml via tpl interpolation) environment: {} @@ -985,6 +1049,16 @@ aasRepository: - path: "{{ .Values.paths.aasRepository }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.aasRepository }}" + pathType: PathPrefix + # Content of application.yaml # central environment variables (will be set in configmap.yaml via tpl interpolation) environment: @@ -1089,6 +1163,16 @@ aasEnvironment: - path: "{{ .Values.paths.aasEnvironment }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.aasEnvironment }}" + pathType: PathPrefix + # AAS Environment bundles the AAS, submodel, concept description, registry # and discovery APIs in one service. Preconfigured AAS files can be imported # by setting general.aasPreconfigPaths and mounting matching files/directories below. @@ -1197,6 +1281,16 @@ dppApi: - path: "{{ .Values.paths.dppApi }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.dppApi }}" + pathType: PathPrefix + # The DPP API composes Digital Product Passport data with the shared BaSyx # database. The default history settings mirror the BaSyx DPP API example. environment: {} @@ -1298,6 +1392,16 @@ cdRepository: - path: "{{ .Values.paths.cdRepository }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.cdRepository }}" + pathType: PathPrefix + # Content of application.yaml # central environment variables (will be set in configmap.yaml via tpl interpolation) environment: {} @@ -1392,6 +1496,17 @@ companyLookup: paths: - path: "{{ .Values.paths.companyLookup }}" pathType: Prefix + + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.companyLookup }}" + pathType: PathPrefix + environment: {} general: {} server: {} @@ -1488,6 +1603,16 @@ aasDiscovery: - path: "{{ .Values.paths.aasDiscovery }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.aasDiscovery }}" + pathType: PathPrefix + # central environment variables (will be set in configmap.yaml via tpl interpolation) environment: {} general: {} @@ -1622,6 +1747,16 @@ aasWebGui: - path: "{{ .Values.paths.aasWebUi }}" pathType: Prefix + httpRoute: + enabled: false + parentRefs: [] + annotations: {} + hosts: + - host: "{{ .Values.host }}" + paths: + - path: "{{ .Values.paths.aasWebUi }}" + pathType: PathPrefix + resources: {} # limits: # cpu: 500m From 0b1cd211dde3a3764450409924f96dc666157d55 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 30 Jul 2026 15:15:47 +0200 Subject: [PATCH 02/12] test: add HTTPRoute rendering and parentRefs precedence tests --- charts/basyx/tests/README.md | 1 + charts/basyx/tests/httproute_test.yaml | 123 +++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 charts/basyx/tests/httproute_test.yaml diff --git a/charts/basyx/tests/README.md b/charts/basyx/tests/README.md index 759132c..c4c1408 100644 --- a/charts/basyx/tests/README.md +++ b/charts/basyx/tests/README.md @@ -31,3 +31,4 @@ The suites cover stable chart contracts such as: - optional CPU and memory HorizontalPodAutoscalers for every shared BaSyx Go backend deployment - shared per-pod PostgreSQL pool defaults and Configuration Service propagation - keycloak init resources and a runtime `helm test` realm check +- Gateway API HTTPRoute rendering, parentRefs precedence and fail-fast validation diff --git a/charts/basyx/tests/httproute_test.yaml b/charts/basyx/tests/httproute_test.yaml new file mode 100644 index 0000000..47fec9c --- /dev/null +++ b/charts/basyx/tests/httproute_test.yaml @@ -0,0 +1,123 @@ +suite: gateway api httproute +release: + name: basyx +templates: + - templates/keycloak/httproute.yaml + - templates/aas-registry/httproute.yaml +tests: + - it: renders no keycloak httproute when httpRoute is disabled by default + template: templates/keycloak/httproute.yaml + asserts: + - hasDocuments: + count: 0 + + - it: renders no aas registry httproute when httpRoute is disabled by default + template: templates/aas-registry/httproute.yaml + set: + aasRegistry.enabled: true + asserts: + - hasDocuments: + count: 0 + + - it: falls back to the chart-wide gatewayApi parentRefs when no local override is set + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + gatewayApi.parentRefs: + - name: shared-gateway + namespace: gateway-system + asserts: + - equal: + path: spec.parentRefs[0].name + value: shared-gateway + - equal: + path: spec.parentRefs[0].namespace + value: gateway-system + + - it: prefers the component-local parentRefs over the chart-wide default + template: templates/aas-registry/httproute.yaml + set: + aasRegistry.enabled: true + aasRegistry.httpRoute.enabled: true + aasRegistry.httpRoute.parentRefs: + - name: aas-registry-gateway + namespace: aas-registry-ns + gatewayApi.parentRefs: + - name: shared-gateway + namespace: gateway-system + asserts: + - equal: + path: spec.parentRefs[0].name + value: aas-registry-gateway + - equal: + path: spec.parentRefs[0].namespace + value: aas-registry-ns + + - it: renders custom httpRoute annotations + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + keycloak.httpRoute.annotations: + example.com/foo: bar + gatewayApi.parentRefs: + - name: shared-gateway + asserts: + - equal: + path: metadata.annotations["example.com/foo"] + value: bar + + - it: renders multiple hosts and paths as hostnames and rule matches + template: templates/aas-registry/httproute.yaml + set: + aasRegistry.enabled: true + aasRegistry.httpRoute.enabled: true + gatewayApi.parentRefs: + - name: shared-gateway + aasRegistry.httpRoute.hosts: + - host: basyx.example.com + paths: + - path: /aas-registry + pathType: PathPrefix + - path: /aas-registry-exact + pathType: Exact + - host: alt.example.com + paths: + - path: /alt-registry + pathType: PathPrefix + asserts: + - equal: + path: spec.hostnames[0] + value: basyx.example.com + - equal: + path: spec.hostnames[1] + value: alt.example.com + - equal: + path: spec.rules[0].matches[0].path.value + value: /aas-registry + - equal: + path: spec.rules[0].matches[0].path.type + value: PathPrefix + - equal: + path: spec.rules[1].matches[0].path.value + value: /aas-registry-exact + - equal: + path: spec.rules[1].matches[0].path.type + value: Exact + - equal: + path: spec.rules[2].matches[0].path.value + value: /alt-registry + - equal: + path: spec.rules[2].matches[0].path.type + value: PathPrefix + + - it: fails when httpRoute is enabled without any parentRefs configured + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + gatewayApi.parentRefs: [] + asserts: + - failedTemplate: + errorPattern: "parentRefs" From b97b3f5f9476cdc83603db45992b1b7139490b79 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 30 Jul 2026 15:19:30 +0200 Subject: [PATCH 03/12] docs: document Gateway API HTTPRoute configuration and add example values --- README.md | 40 ++++++- values/values.gatewayapi.example.yaml | 164 ++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 values/values.gatewayapi.example.yaml diff --git a/README.md b/README.md index 9a300ec..65a2550 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository contains a Helm chart for deploying an Eclipse BaSyx Go based environment on Kubernetes. -The chart installs the BaSyx Go backend services, a PostgreSQL database, optional Keycloak-based authentication, optional ABAC authorization, ingress resources, certificates, the AAS Web UI and optional supporting runtime tests. +The chart installs the BaSyx Go backend services, a PostgreSQL database, optional Keycloak-based authentication, optional ABAC authorization, ingress resources (or Gateway API `HTTPRoute` resources as an opt-in alternative), certificates, the AAS Web UI and optional supporting runtime tests. The repository follows the common Helm multi-chart layout: @@ -50,6 +50,7 @@ Custom values files live outside the chart, for example: ```text values/values.catena-x.example.yaml values/values.example.yaml +values/values.gatewayapi.example.yaml values/values.minimal.yaml values/values.observability.example.yaml values/values.autoscaling.example.yaml @@ -416,6 +417,42 @@ ingress: Use `ingress.className` for AGIC instead of the legacy `kubernetes.io/ingress.class` annotation. Kubernetes rejects manifests when both values are set and do not match exactly. `null` removes an inherited default annotation, which is useful when switching away from nginx and avoiding nginx-specific annotations on non-nginx controllers. +### Gateway API (HTTPRoute) + +As an alternative to Ingress, each service can optionally be exposed through a [Gateway API](https://gateway-api.sigs.k8s.io/) `HTTPRoute` resource instead. This is opt-in and disabled by default per service; Ingress remains the default routing mechanism for the chart. + +Using this feature requires the Gateway API CRDs and an existing `Gateway` resource to already be installed in the cluster. The chart does not create the `Gateway` itself, it only renders `HTTPRoute` resources that attach to one via `spec.parentRefs`. + +Enable `.httpRoute.enabled` per service to render its `HTTPRoute`. Every `HTTPRoute` needs at least one `parentRefs` entry, resolved in this order: + +1. `.httpRoute.parentRefs`, if non-empty. +2. Otherwise the chart-wide `gatewayApi.parentRefs` default. +3. If both are empty while `httpRoute.enabled: true`, the chart fails the render with an explicit error instead of emitting an `HTTPRoute` with an invalid empty `spec.parentRefs`, which the Gateway API rejects. + +Example pointing at an existing Gateway and enabling Gateway API routing for Keycloak: + +```yaml +gatewayApi: + parentRefs: + - name: my-gateway + namespace: gateway-system + sectionName: https + +keycloak: + httpRoute: + enabled: true +``` + +A service can override the chart-wide default by setting its own `.httpRoute.parentRefs`, which then takes precedence over `gatewayApi.parentRefs`. + +| Value | Description | +| --- | --- | +| `gatewayApi.parentRefs` | Chart-wide default `parentRefs`, used by any `HTTPRoute` that does not define its own. | +| `.httpRoute.enabled` | Enables rendering an `HTTPRoute` for the service. Disabled by default. | +| `.httpRoute.parentRefs` | Service-local `parentRefs`. Overrides `gatewayApi.parentRefs` when non-empty. | +| `.httpRoute.annotations` | Annotations added to the service's `HTTPRoute` metadata. | +| `.httpRoute.hosts` | Hostnames and paths routed to the service. Uses the Gateway API `pathType` values (`Exact`, `PathPrefix`, `RegularExpression`), which are not the same enum as the Ingress `pathType`. | + ### Additional CA Certificates BaSyx services sometimes need to call HTTPS endpoints that use private CAs. Add those CAs with `internal.CACertificates.trustStore`. @@ -1974,6 +2011,7 @@ kubectl -n debug -it pod/ \ | `no matches for kind "Certificate"` | cert-manager CRDs are missing. Install cert-manager with CRDs enabled. | | `no matches for kind "Cluster" in version "postgresql.cnpg.io/v1"` | CloudNativePG CRDs are missing. Install CloudNativePG. | | Ingress returns 404 | Check `host`, `paths.*`, ingress class and whether the service itself has a route for that path. | +| HTTPRoute has no effect | Check `.httpRoute.enabled`, that the Gateway API CRDs and the referenced `Gateway` exist in the cluster, and that `parentRefs` (service-local `.httpRoute.parentRefs` or chart-wide `gatewayApi.parentRefs`) resolve to that `Gateway`. | | Pods cannot verify Keycloak TLS | Check the internal CA secret, custom CA mounts and `SSL_CERT_DIR`. | | `Token verification failed: expected audience ...` | Check Keycloak protocol mappers and `environment.common.OIDC_AUDIENCE`. | | `ABAC(model): NO_MATCH` | Check token claims, ABAC object definitions, route patterns and whether pods rolled after config changes. | diff --git a/values/values.gatewayapi.example.yaml b/values/values.gatewayapi.example.yaml new file mode 100644 index 0000000..af5c2b0 --- /dev/null +++ b/values/values.gatewayapi.example.yaml @@ -0,0 +1,164 @@ +# Example values for a BaSyx Go deployment routed through the Kubernetes +# Gateway API (HTTPRoute) instead of a classic Ingress controller. +# +# Prerequisite: the target cluster must already have the Gateway API CRDs +# installed and an existing `Gateway` resource (e.g. named "basyx-gateway" in +# the "gateway-system" namespace, with a listener named "https"). This chart +# does not create the Gateway itself, only HTTPRoute resources that attach to +# it via `gatewayApi.parentRefs`. +# +# Copy this file and adapt it for your own cluster: +# +# cp values/values.gatewayapi.example.yaml values/values.my-environment.yaml +# +# Do not commit real passwords, client secrets or private certificate material. + +instanceName: example +host: basyx.example.com + +fullnameOverride: basyx + +tls: + enabled: true + hosts: + - basyx.example.com + +internal: + CACertificates: + trustStore: + useDefaultCAs: true + chartFileDirectory: "" + additionalCACertificates: "" + certificateIssuer: + name: internal-issuer + autocreateCa: true + autocreateCaSecretName: internal-issuer-ca + spec: + ca: + secretName: internal-issuer-ca + +ingress: + # Use this issuer when the chart should create and use its own internal CA. + issuer: internal-issuer + + # For a production setup with an existing cert-manager ClusterIssuer, use this instead: + # clusterIssuer: letsencrypt-prod + # issuer: "" + +# Chart-wide default parentRefs used by every component's HTTPRoute unless a +# component sets its own .httpRoute.parentRefs. This must point at +# a Gateway that already exists in the cluster. +gatewayApi: + parentRefs: + - name: basyx-gateway + namespace: gateway-system + sectionName: https + +database: + clusterName: basyx-database + instances: 1 + storage: + size: 10Gi + +keycloak: + enabled: false + +abac: + enabled: false + +aasDiscovery: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasRegistry: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasEnvironment: + enabled: false + + +dppApi: + enabled: false + + +submodelRegistry: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +submodelRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +cdRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasWebGui: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + infrastructureConfig: + infrastructures: + default: main + main: + name: "BaSyx Go {{ .Values.instanceName }}" + components: + aasDiscovery: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasDiscovery }}" + aasRegistry: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasRegistry }}" + submodelRegistry: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.submodelRegistry }}" + aasRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasRepository }}" + submodelRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.submodelRepository }}" + conceptDescriptionRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.cdRepository }}" + companyLookup: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.companyLookup }}" + security: + type: None + config: null + +companyLookup: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +digitalTwinRegistry: + enabled: false From 1060ff4edcd1e51e8f875ad9e289c7e2dccbe0a1 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 13 Aug 2026 16:52:02 +0200 Subject: [PATCH 04/12] feat: add optional self-managed Gateway API Gateway support --- charts/basyx/templates/_helpers.tpl | 26 +++++++++++++++--- charts/basyx/templates/gateway.yaml | 42 +++++++++++++++++++++++++++++ charts/basyx/values.schema.json | 18 +++++++++++++ charts/basyx/values.yaml | 23 ++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 charts/basyx/templates/gateway.yaml diff --git a/charts/basyx/templates/_helpers.tpl b/charts/basyx/templates/_helpers.tpl index 9b5f769..10bccc3 100644 --- a/charts/basyx/templates/_helpers.tpl +++ b/charts/basyx/templates/_helpers.tpl @@ -1161,17 +1161,35 @@ annotations: {{- end }} {{/* -Render parentRefs for a Gateway API HTTPRoute. Falls back to the chart-wide -default parentRefs (.Values.gatewayApi.parentRefs) when the component does -not define its own. +Name of the Gateway this chart creates when gatewayApi.gateway.enabled is +true. Shared between templates/gateway.yaml and the parentRefs fallback +below so both agree on the same name. +*/}} +{{- define "common.gatewayApi.gatewayName" -}} +{{- .Values.gatewayApi.gateway.name | default (printf "%s-gateway" .Release.Name) -}} +{{- end }} + +{{/* +Render parentRefs for a Gateway API HTTPRoute. Falls back, in order, to: the +chart-wide default parentRefs (.Values.gatewayApi.parentRefs), then - if the +chart is creating its own Gateway (.Values.gatewayApi.gateway.enabled) - a +parentRefs entry pointing at that Gateway, so enabling gatewayApi.gateway +alone is enough without also duplicating parentRefs. */}} {{- define "common.httpRoute.parentRefs" -}} {{- $parentRefs := .parentRefs -}} {{- if not $parentRefs }} {{- $parentRefs = .root.Values.gatewayApi.parentRefs }} {{- end }} +{{- if and (not $parentRefs) .root.Values.gatewayApi.gateway.enabled }} +{{- $sectionName := "http" -}} +{{- if .root.Values.gatewayApi.gateway.tls.enabled }} +{{- $sectionName = "https" -}} +{{- end }} +{{- $parentRefs = list (dict "name" (include "common.gatewayApi.gatewayName" .root) "namespace" .root.Release.Namespace "sectionName" $sectionName) }} +{{- end }} {{- if not $parentRefs }} -{{- fail "httpRoute is enabled but no parentRefs are configured — set .httpRoute.parentRefs or the chart-wide gatewayApi.parentRefs" }} +{{- fail "httpRoute is enabled but no parentRefs are configured — set .httpRoute.parentRefs, the chart-wide gatewayApi.parentRefs, or gatewayApi.gateway.enabled" }} {{- end }} {{- range $parentRefs }} - name: {{ .name | quote }} diff --git a/charts/basyx/templates/gateway.yaml b/charts/basyx/templates/gateway.yaml new file mode 100644 index 0000000..e55c1e0 --- /dev/null +++ b/charts/basyx/templates/gateway.yaml @@ -0,0 +1,42 @@ +{{- if .Values.gatewayApi.gateway.enabled }} +{{- if not .Values.gatewayApi.gateway.className }} +{{- fail "gatewayApi.gateway.enabled is true but gatewayApi.gateway.className is not set" }} +{{- end }} +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ include "common.gatewayApi.gatewayName" . }} + namespace: {{ .Release.Namespace }} + {{- with .Values.gatewayApi.gateway.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + gatewayClassName: {{ tpl (print .Values.gatewayApi.gateway.className) . | quote }} + listeners: + - name: http + hostname: {{ tpl (print .Values.host) . | quote }} + protocol: HTTP + port: {{ .Values.gatewayApi.gateway.port }} + allowedRoutes: + namespaces: + from: Same + {{- if .Values.gatewayApi.gateway.tls.enabled }} + - name: https + # cert-manager's gateway-shim requires an explicit hostname on the + # listener to know which SAN to request - without it, it silently + # skips the listener ("Skipped a listener block: ... hostname cannot + # be empty") and never issues a Certificate for gatewayApi.gateway + # annotations like cert-manager.io/issuer. + hostname: {{ tpl (print .Values.host) . | quote }} + protocol: HTTPS + port: {{ .Values.gatewayApi.gateway.tls.port }} + tls: + mode: Terminate + certificateRefs: + - name: {{ .Values.gatewayApi.gateway.tls.secretName | default .Values.tls.secretName | default (printf "%s-tls-secret" .Release.Name) | quote }} + allowedRoutes: + namespaces: + from: Same + {{- end }} +{{- end }} diff --git a/charts/basyx/values.schema.json b/charts/basyx/values.schema.json index 763cdc9..44bf531 100644 --- a/charts/basyx/values.schema.json +++ b/charts/basyx/values.schema.json @@ -1353,6 +1353,24 @@ "port": { "type": "integer" } } } + }, + "gateway": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "name": { "type": "string" }, + "className": { "type": "string" }, + "annotations": { "type": "object" }, + "port": { "type": "integer" }, + "tls": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "port": { "type": "integer" }, + "secretName": { "type": "string" } + } + } + } } } }, diff --git a/charts/basyx/values.yaml b/charts/basyx/values.yaml index 67633b7..06f5f0e 100644 --- a/charts/basyx/values.yaml +++ b/charts/basyx/values.yaml @@ -81,6 +81,29 @@ gatewayApi: # namespace: gateway-system # sectionName: https + # Optionally let the chart create its own Gateway instead of requiring one + # to already exist (bring-your-own via parentRefs above remains available + # and still takes precedence when explicitly set). Off by default: a + # Gateway is cluster-operator-owned, shared infrastructure in the Gateway + # API model, and every self-created Gateway typically provisions its own + # LoadBalancer - avoid enabling this across multiple releases that could + # otherwise share one Gateway. + gateway: + enabled: false + # Defaults to "-gateway" when empty. + name: "" + # GatewayClass name, e.g. "envoy-gateway". Required when enabled - unlike + # ingress.className, there is no cluster-portable default. + className: "" + annotations: {} + port: 80 + tls: + enabled: true + port: 443 + # Defaults to tls.secretName / "-tls-secret" when empty, + # the same certificate already used by the Ingress path. + secretName: "" + paths: dashboard: /aas-dataspace-dashboard aasDiscovery: /aas-discovery From 31e3fc3ac03089767034b7eb3c72e53d5352b188 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 13 Aug 2026 16:52:05 +0200 Subject: [PATCH 05/12] test: add self-managed Gateway rendering and parentRefs derivation tests --- charts/basyx/tests/gateway_test.yaml | 152 +++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 charts/basyx/tests/gateway_test.yaml diff --git a/charts/basyx/tests/gateway_test.yaml b/charts/basyx/tests/gateway_test.yaml new file mode 100644 index 0000000..e9a1fdc --- /dev/null +++ b/charts/basyx/tests/gateway_test.yaml @@ -0,0 +1,152 @@ +suite: gateway api gateway +release: + name: basyx + namespace: basyx +templates: + - templates/gateway.yaml + - templates/keycloak/httproute.yaml +tests: + - it: renders no Gateway when gatewayApi.gateway is disabled by default + template: templates/gateway.yaml + asserts: + - hasDocuments: + count: 0 + + - it: fails when gatewayApi.gateway is enabled without a className + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + asserts: + - failedTemplate: + errorPattern: "className" + + - it: renders a Gateway with the default name, class and listeners + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + asserts: + - equal: + path: metadata.name + value: basyx-gateway + - equal: + path: spec.gatewayClassName + value: envoy-gateway + - equal: + path: spec.listeners[0].name + value: http + - equal: + path: spec.listeners[0].port + value: 80 + - equal: + path: spec.listeners[0].hostname + value: basyx.example.com + - equal: + path: spec.listeners[1].name + value: https + - equal: + path: spec.listeners[1].port + value: 443 + - equal: + path: spec.listeners[1].hostname + value: basyx.example.com + - equal: + path: spec.listeners[1].tls.certificateRefs[0].name + value: basyx-tls-secret + + - it: uses a custom Gateway name when set + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.gateway.name: custom-gateway + asserts: + - equal: + path: metadata.name + value: custom-gateway + + - it: uses tls.secretName over the release-name default when set + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.secretName: shared-tls-secret + asserts: + - equal: + path: spec.listeners[1].tls.certificateRefs[0].name + value: shared-tls-secret + + - it: omits the https listener when gateway tls is disabled + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.gateway.tls.enabled: false + asserts: + - lengthEqual: + path: spec.listeners + count: 1 + - equal: + path: spec.listeners[0].name + value: http + + - it: renders custom Gateway annotations + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.gateway.annotations: + example.com/foo: bar + asserts: + - equal: + path: metadata.annotations["example.com/foo"] + value: bar + + - it: derives HTTPRoute parentRefs from the self-created Gateway when none are set + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + asserts: + - equal: + path: spec.parentRefs[0].name + value: basyx-gateway + - equal: + path: spec.parentRefs[0].namespace + value: basyx + - equal: + path: spec.parentRefs[0].sectionName + value: https + + - it: derives HTTPRoute parentRefs with the http sectionName when gateway tls is disabled + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.gateway.tls.enabled: false + asserts: + - equal: + path: spec.parentRefs[0].sectionName + value: http + + - it: prefers explicit gatewayApi.parentRefs over the self-created Gateway + template: templates/keycloak/httproute.yaml + set: + keycloak.enabled: true + keycloak.httpRoute.enabled: true + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.parentRefs: + - name: external-gateway + namespace: gateway-system + asserts: + - equal: + path: spec.parentRefs[0].name + value: external-gateway + - equal: + path: spec.parentRefs[0].namespace + value: gateway-system From a63494e64f34afc80c04f0c70220300b97900a01 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 13 Aug 2026 16:52:08 +0200 Subject: [PATCH 06/12] docs: document self-managed Gateway option and add example values --- README.md | 36 +++- charts/basyx/tests/README.md | 1 + values/values.gatewayapi-managed.example.yaml | 169 ++++++++++++++++++ values/values.gatewayapi.example.yaml | 5 +- 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 values/values.gatewayapi-managed.example.yaml diff --git a/README.md b/README.md index 65a2550..3417fb3 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Custom values files live outside the chart, for example: ```text values/values.catena-x.example.yaml values/values.example.yaml +values/values.gatewayapi-managed.example.yaml values/values.gatewayapi.example.yaml values/values.minimal.yaml values/values.observability.example.yaml @@ -421,13 +422,14 @@ Use `ingress.className` for AGIC instead of the legacy `kubernetes.io/ingress.cl As an alternative to Ingress, each service can optionally be exposed through a [Gateway API](https://gateway-api.sigs.k8s.io/) `HTTPRoute` resource instead. This is opt-in and disabled by default per service; Ingress remains the default routing mechanism for the chart. -Using this feature requires the Gateway API CRDs and an existing `Gateway` resource to already be installed in the cluster. The chart does not create the `Gateway` itself, it only renders `HTTPRoute` resources that attach to one via `spec.parentRefs`. +Using this feature requires the Gateway API CRDs to already be installed in the cluster, and either a `Gateway` resource that already exists (bring-your-own, the default) or `gatewayApi.gateway.enabled: true` to let the chart create its own (see below). Enable `.httpRoute.enabled` per service to render its `HTTPRoute`. Every `HTTPRoute` needs at least one `parentRefs` entry, resolved in this order: 1. `.httpRoute.parentRefs`, if non-empty. 2. Otherwise the chart-wide `gatewayApi.parentRefs` default. -3. If both are empty while `httpRoute.enabled: true`, the chart fails the render with an explicit error instead of emitting an `HTTPRoute` with an invalid empty `spec.parentRefs`, which the Gateway API rejects. +3. Otherwise, if `gatewayApi.gateway.enabled: true`, a `parentRefs` entry pointing at the Gateway the chart itself creates (see below) - no need to also set `gatewayApi.parentRefs` in that case. +4. If all of the above are empty while `httpRoute.enabled: true`, the chart fails the render with an explicit error instead of emitting an `HTTPRoute` with an invalid empty `spec.parentRefs`, which the Gateway API rejects. Example pointing at an existing Gateway and enabling Gateway API routing for Keycloak: @@ -453,6 +455,36 @@ A service can override the chart-wide default by setting its own `.http | `.httpRoute.annotations` | Annotations added to the service's `HTTPRoute` metadata. | | `.httpRoute.hosts` | Hostnames and paths routed to the service. Uses the Gateway API `pathType` values (`Exact`, `PathPrefix`, `RegularExpression`), which are not the same enum as the Ingress `pathType`. | +#### Optionally letting the chart create its own Gateway + +A `Gateway` is cluster-operator-owned, shared infrastructure in the Gateway API model - normally one `Gateway` serves `HTTPRoute`s from many applications, and every self-created `Gateway` typically provisions its own LoadBalancer. For that reason `gatewayApi.gateway.enabled` defaults to `false` and bring-your-own via `gatewayApi.parentRefs` remains the primary, recommended path. Enable it for standalone/demo deployments that don't have a pre-existing shared `Gateway` to attach to: + +```yaml +gatewayApi: + gateway: + enabled: true + className: envoy-gateway # GatewayClass name - required, no cluster-portable default + +keycloak: + httpRoute: + enabled: true +``` + +This renders one `Gateway` (named `-gateway` unless `gatewayApi.gateway.name` is set) with a fixed `http` listener and, unless `gatewayApi.gateway.tls.enabled: false`, a fixed `https` listener terminating TLS with the same certificate already used by the Ingress path (`tls.secretName` / `-tls-secret`). Both listeners are scoped to `host`, matching how the Ingress path is host-scoped too - required for cert-manager's [gateway-shim](https://cert-manager.io/docs/usage/gateway/) to know which hostname to request a certificate for if you annotate the Gateway with `cert-manager.io/issuer`/`cert-manager.io/cluster-issuer` to issue it its own certificate (rather than reusing the Ingress-issued one). If you need more control over listeners than these opinionated defaults offer, use the bring-your-own path (`gatewayApi.parentRefs`) against a `Gateway` you manage yourself instead. + +The derived `parentRefs` (see step 3 above) always target the `https` listener while `gatewayApi.gateway.tls.enabled` is `true` (the default). The `http` listener is still created and its port is still exposed, but no `HTTPRoute` attaches to it automatically, and the chart does not configure an HTTP-to-HTTPS redirect. To route plain HTTP traffic for a service instead, set that service's own `.httpRoute.parentRefs` with `sectionName: http` explicitly. + +| Value | Description | +| --- | --- | +| `gatewayApi.gateway.enabled` | Lets the chart create its own `Gateway` instead of requiring one to already exist. Disabled by default. | +| `gatewayApi.gateway.name` | Name of the chart-created `Gateway`. Defaults to `-gateway`. | +| `gatewayApi.gateway.className` | `GatewayClass` name. Required when enabled. | +| `gatewayApi.gateway.annotations` | Annotations added to the `Gateway` metadata. | +| `gatewayApi.gateway.port` | Port for the fixed `http` listener. Defaults to `80`. | +| `gatewayApi.gateway.tls.enabled` | Whether to also render the fixed `https` listener. Defaults to `true`. | +| `gatewayApi.gateway.tls.port` | Port for the `https` listener. Defaults to `443`. | +| `gatewayApi.gateway.tls.secretName` | TLS certificate secret for the `https` listener. Defaults to `tls.secretName` / `-tls-secret`. | + ### Additional CA Certificates BaSyx services sometimes need to call HTTPS endpoints that use private CAs. Add those CAs with `internal.CACertificates.trustStore`. diff --git a/charts/basyx/tests/README.md b/charts/basyx/tests/README.md index c4c1408..ccc98e3 100644 --- a/charts/basyx/tests/README.md +++ b/charts/basyx/tests/README.md @@ -32,3 +32,4 @@ The suites cover stable chart contracts such as: - shared per-pod PostgreSQL pool defaults and Configuration Service propagation - keycloak init resources and a runtime `helm test` realm check - Gateway API HTTPRoute rendering, parentRefs precedence and fail-fast validation +- optional self-managed Gateway rendering, className validation and derived HTTPRoute parentRefs diff --git a/values/values.gatewayapi-managed.example.yaml b/values/values.gatewayapi-managed.example.yaml new file mode 100644 index 0000000..efc0aa0 --- /dev/null +++ b/values/values.gatewayapi-managed.example.yaml @@ -0,0 +1,169 @@ +# Example values for a BaSyx Go deployment where the chart creates its own +# Gateway API `Gateway` resource, instead of requiring one to already exist +# (see values.gatewayapi.example.yaml for that bring-your-own alternative, +# which remains the recommended path for anything beyond a standalone/demo +# deployment - a Gateway is normally shared cluster infrastructure, and each +# self-created Gateway typically provisions its own LoadBalancer). +# +# Prerequisite: the target cluster must already have the Gateway API CRDs +# and a Gateway API controller (e.g. Envoy Gateway, Traefik, Contour) +# installed, with a matching GatewayClass. This chart still does not create +# the GatewayClass or the controller itself - only the Gateway. +# +# Copy this file and adapt it for your own cluster: +# +# cp values/values.gatewayapi-managed.example.yaml values/values.my-environment.yaml +# +# Do not commit real passwords, client secrets or private certificate material. + +instanceName: example +host: basyx.example.com + +fullnameOverride: basyx + +tls: + enabled: true + hosts: + - basyx.example.com + +internal: + CACertificates: + trustStore: + useDefaultCAs: true + chartFileDirectory: "" + additionalCACertificates: "" + certificateIssuer: + name: internal-issuer + autocreateCa: true + autocreateCaSecretName: internal-issuer-ca + spec: + ca: + secretName: internal-issuer-ca + +ingress: + # Use this issuer when the chart should create and use its own internal CA. + issuer: internal-issuer + + # For a production setup with an existing cert-manager ClusterIssuer, use this instead: + # clusterIssuer: letsencrypt-prod + # issuer: "" + +# The chart creates its own Gateway (named "basyx-gateway" here, defaults to +# "-gateway" if `name` is left empty) with a fixed `http` +# listener and an `https` listener reusing the same TLS certificate the +# Ingress path would otherwise use. No `gatewayApi.parentRefs` needed - it is +# derived automatically to point at this Gateway. +gatewayApi: + gateway: + enabled: true + name: basyx-gateway + className: envoy-gateway # GatewayClass name - matches your installed Gateway API controller + +database: + clusterName: basyx-database + instances: 1 + storage: + size: 10Gi + +keycloak: + enabled: false + +abac: + enabled: false + +aasDiscovery: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasRegistry: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasEnvironment: + enabled: false + + +dppApi: + enabled: false + + +submodelRegistry: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +submodelRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +cdRepository: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +aasWebGui: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + infrastructureConfig: + infrastructures: + default: main + main: + name: "BaSyx Go {{ .Values.instanceName }}" + components: + aasDiscovery: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasDiscovery }}" + aasRegistry: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasRegistry }}" + submodelRegistry: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.submodelRegistry }}" + aasRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.aasRepository }}" + submodelRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.submodelRepository }}" + conceptDescriptionRepository: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.cdRepository }}" + companyLookup: + baseUrl: "https://{{ .Values.host }}{{ .Values.paths.companyLookup }}" + security: + type: None + config: null + +companyLookup: + enabled: true + ingress: + enabled: false + httpRoute: + enabled: true + + +digitalTwinRegistry: + enabled: false diff --git a/values/values.gatewayapi.example.yaml b/values/values.gatewayapi.example.yaml index af5c2b0..fcdd418 100644 --- a/values/values.gatewayapi.example.yaml +++ b/values/values.gatewayapi.example.yaml @@ -1,5 +1,8 @@ # Example values for a BaSyx Go deployment routed through the Kubernetes -# Gateway API (HTTPRoute) instead of a classic Ingress controller. +# Gateway API (HTTPRoute) instead of a classic Ingress controller, against a +# Gateway you bring yourself (the recommended path - see +# values.gatewayapi-managed.example.yaml for the alternative where the chart +# creates its own Gateway instead). # # Prerequisite: the target cluster must already have the Gateway API CRDs # installed and an existing `Gateway` resource (e.g. named "basyx-gateway" in From 44b2be22e32a87b6f64f6c3fbb0e4b30b4a0901f Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Fri, 21 Aug 2026 09:38:22 +0200 Subject: [PATCH 07/12] fix: add chart labels and derive cert-manager annotations for the self-managed Gateway --- charts/basyx/templates/_helpers.tpl | 33 +++++++++++++++++++++++++++++ charts/basyx/templates/gateway.yaml | 7 ++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/charts/basyx/templates/_helpers.tpl b/charts/basyx/templates/_helpers.tpl index 10bccc3..54846cf 100644 --- a/charts/basyx/templates/_helpers.tpl +++ b/charts/basyx/templates/_helpers.tpl @@ -1169,6 +1169,39 @@ below so both agree on the same name. {{- .Values.gatewayApi.gateway.name | default (printf "%s-gateway" .Release.Name) -}} {{- end }} +{{/* +Cert-manager annotations for the chart-created Gateway's https listener, +derived from the same tls.enabled + ingress.certificateIssuer/issuer/clusterIssuer +config already used for Ingress - so cert-manager's gateway-shim issues the +same certificate (same secretName) the Ingress path would otherwise reuse, +without requiring a second, gateway-specific issuer configuration. Explicit +gatewayApi.gateway.annotations always take precedence over the derived +cert-manager annotation. +*/}} +{{- define "common.gatewayApi.gateway.annotations" -}} +{{- $root := . -}} +{{- $annotations := dict -}} +{{- if and $root.Values.tls.enabled $root.Values.gatewayApi.gateway.tls.enabled -}} +{{- if $root.Values.ingress.certificateIssuer.enabled -}} +{{- $_ := set $annotations "cert-manager.io/issuer" ($root.Values.ingress.certificateIssuer.name | toString) -}} +{{- else if $root.Values.ingress.issuer -}} +{{- $_ := set $annotations "cert-manager.io/issuer" ($root.Values.ingress.issuer | toString) -}} +{{- else if $root.Values.ingress.clusterIssuer -}} +{{- $_ := set $annotations "cert-manager.io/cluster-issuer" ($root.Values.ingress.clusterIssuer | toString) -}} +{{- end -}} +{{- end -}} +{{- range $key, $value := ($root.Values.gatewayApi.gateway.annotations | default dict) -}} +{{- if kindIs "invalid" $value -}} +{{- $_ := unset $annotations $key -}} +{{- else -}} +{{- $_ := set $annotations $key (tpl (print $value) $root) -}} +{{- end -}} +{{- end -}} +{{- if $annotations -}} +{{- toYaml $annotations -}} +{{- end -}} +{{- end }} + {{/* Render parentRefs for a Gateway API HTTPRoute. Falls back, in order, to: the chart-wide default parentRefs (.Values.gatewayApi.parentRefs), then - if the diff --git a/charts/basyx/templates/gateway.yaml b/charts/basyx/templates/gateway.yaml index e55c1e0..af7ba40 100644 --- a/charts/basyx/templates/gateway.yaml +++ b/charts/basyx/templates/gateway.yaml @@ -7,9 +7,12 @@ kind: Gateway metadata: name: {{ include "common.gatewayApi.gatewayName" . }} namespace: {{ .Release.Namespace }} - {{- with .Values.gatewayApi.gateway.annotations }} + labels: + {{- include "basyx.labels" . | nindent 4 }} + {{- $gatewayAnnotations := include "common.gatewayApi.gateway.annotations" . }} + {{- if $gatewayAnnotations }} annotations: - {{- toYaml . | nindent 4 }} + {{- $gatewayAnnotations | nindent 4 }} {{- end }} spec: gatewayClassName: {{ tpl (print .Values.gatewayApi.gateway.className) . | quote }} From a8993ac021be31ebd46f74c3b228a4ee951656f9 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Fri, 21 Aug 2026 09:38:26 +0200 Subject: [PATCH 08/12] test: cover Gateway labels and cert-manager annotation derivation --- charts/basyx/tests/gateway_test.yaml | 83 ++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/charts/basyx/tests/gateway_test.yaml b/charts/basyx/tests/gateway_test.yaml index e9a1fdc..aa93ac5 100644 --- a/charts/basyx/tests/gateway_test.yaml +++ b/charts/basyx/tests/gateway_test.yaml @@ -53,6 +53,15 @@ tests: - equal: path: spec.listeners[1].tls.certificateRefs[0].name value: basyx-tls-secret + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: basyx + - equal: + path: metadata.labels["app.kubernetes.io/instance"] + value: basyx + - equal: + path: metadata.labels["app.kubernetes.io/managed-by"] + value: Helm - it: uses a custom Gateway name when set template: templates/gateway.yaml @@ -102,6 +111,80 @@ tests: path: metadata.annotations["example.com/foo"] value: bar + - it: derives a cert-manager issuer annotation from the same ingress.issuer used by Ingress + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.enabled: true + ingress.issuer: internal-issuer + asserts: + - equal: + path: metadata.annotations["cert-manager.io/issuer"] + value: internal-issuer + + - it: derives a cert-manager cluster-issuer annotation from ingress.clusterIssuer + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.enabled: true + ingress.clusterIssuer: letsencrypt-prod + asserts: + - equal: + path: metadata.annotations["cert-manager.io/cluster-issuer"] + value: letsencrypt-prod + + - it: derives a cert-manager issuer annotation from ingress.certificateIssuer when enabled + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.enabled: true + ingress.certificateIssuer.enabled: true + ingress.certificateIssuer.name: ingress-issuer + asserts: + - equal: + path: metadata.annotations["cert-manager.io/issuer"] + value: ingress-issuer + + - it: lets explicit gatewayApi.gateway.annotations override the derived cert-manager issuer + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.enabled: true + ingress.issuer: internal-issuer + gatewayApi.gateway.annotations: + cert-manager.io/issuer: custom-issuer + asserts: + - equal: + path: metadata.annotations["cert-manager.io/issuer"] + value: custom-issuer + + - it: does not derive a cert-manager annotation when the Gateway https listener is disabled + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + gatewayApi.gateway.tls.enabled: false + tls.enabled: true + ingress.issuer: internal-issuer + asserts: + - notExists: + path: metadata.annotations + + - it: does not derive a cert-manager annotation when tls is globally disabled + template: templates/gateway.yaml + set: + gatewayApi.gateway.enabled: true + gatewayApi.gateway.className: envoy-gateway + tls.enabled: false + ingress.issuer: internal-issuer + asserts: + - notExists: + path: metadata.annotations + - it: derives HTTPRoute parentRefs from the self-created Gateway when none are set template: templates/keycloak/httproute.yaml set: From 0018463130871819997e36a3e09363df957dfc19 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Fri, 21 Aug 2026 09:38:30 +0200 Subject: [PATCH 09/12] docs: document automatic cert-manager annotation derivation for the self-managed Gateway --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3417fb3..b64de03 100644 --- a/README.md +++ b/README.md @@ -470,7 +470,9 @@ keycloak: enabled: true ``` -This renders one `Gateway` (named `-gateway` unless `gatewayApi.gateway.name` is set) with a fixed `http` listener and, unless `gatewayApi.gateway.tls.enabled: false`, a fixed `https` listener terminating TLS with the same certificate already used by the Ingress path (`tls.secretName` / `-tls-secret`). Both listeners are scoped to `host`, matching how the Ingress path is host-scoped too - required for cert-manager's [gateway-shim](https://cert-manager.io/docs/usage/gateway/) to know which hostname to request a certificate for if you annotate the Gateway with `cert-manager.io/issuer`/`cert-manager.io/cluster-issuer` to issue it its own certificate (rather than reusing the Ingress-issued one). If you need more control over listeners than these opinionated defaults offer, use the bring-your-own path (`gatewayApi.parentRefs`) against a `Gateway` you manage yourself instead. +This renders one `Gateway` (named `-gateway` unless `gatewayApi.gateway.name` is set, carrying the same `app.kubernetes.io/*`/`helm.sh/chart` labels as every other chart resource) with a fixed `http` listener and, unless `gatewayApi.gateway.tls.enabled: false`, a fixed `https` listener terminating TLS with the same certificate already used by the Ingress path (`tls.secretName` / `-tls-secret`). Both listeners are scoped to `host`, matching how the Ingress path is host-scoped too - required for cert-manager's [gateway-shim](https://cert-manager.io/docs/usage/gateway/) to know which hostname to request a certificate for. + +To have cert-manager actually issue that certificate, the chart automatically annotates the Gateway with `cert-manager.io/issuer`/`cert-manager.io/cluster-issuer`, derived from the exact same `tls.enabled` + `ingress.issuer`/`ingress.clusterIssuer`/`ingress.certificateIssuer` configuration already used for Ingress - no separate issuer setup needed, and it keeps working even if every `.ingress.enabled` is `false` and no Ingress is ever rendered to trigger cert-manager's ingress-shim instead. Set `gatewayApi.gateway.annotations["cert-manager.io/issuer"]` (or `-cluster-issuer`) explicitly to issue the Gateway a different certificate than the Ingress path uses; explicit `gatewayApi.gateway.annotations` always take precedence over the derived one. If you need more control over listeners than these opinionated defaults offer, use the bring-your-own path (`gatewayApi.parentRefs`) against a `Gateway` you manage yourself instead. The derived `parentRefs` (see step 3 above) always target the `https` listener while `gatewayApi.gateway.tls.enabled` is `true` (the default). The `http` listener is still created and its port is still exposed, but no `HTTPRoute` attaches to it automatically, and the chart does not configure an HTTP-to-HTTPS redirect. To route plain HTTP traffic for a service instead, set that service's own `.httpRoute.parentRefs` with `sectionName: http` explicitly. From b7a0ca397253177ef4e7f89a3606848651496569 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Fri, 21 Aug 2026 13:43:00 +0200 Subject: [PATCH 10/12] fix: decouple gatewayApi.gateway cert-manager issuer from ingress values --- README.md | 8 +++--- charts/basyx/templates/_helpers.tpl | 26 +++++++++---------- charts/basyx/tests/gateway_test.yaml | 23 ++++++++-------- charts/basyx/values.schema.json | 2 ++ charts/basyx/values.yaml | 13 ++++++++++ values/values.gatewayapi-managed.example.yaml | 7 +++-- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b64de03..0b078b6 100644 --- a/README.md +++ b/README.md @@ -470,9 +470,9 @@ keycloak: enabled: true ``` -This renders one `Gateway` (named `-gateway` unless `gatewayApi.gateway.name` is set, carrying the same `app.kubernetes.io/*`/`helm.sh/chart` labels as every other chart resource) with a fixed `http` listener and, unless `gatewayApi.gateway.tls.enabled: false`, a fixed `https` listener terminating TLS with the same certificate already used by the Ingress path (`tls.secretName` / `-tls-secret`). Both listeners are scoped to `host`, matching how the Ingress path is host-scoped too - required for cert-manager's [gateway-shim](https://cert-manager.io/docs/usage/gateway/) to know which hostname to request a certificate for. +This renders one `Gateway` (named `-gateway` unless `gatewayApi.gateway.name` is set, carrying the same `app.kubernetes.io/*`/`helm.sh/chart` labels as every other chart resource) with a fixed `http` listener and, unless `gatewayApi.gateway.tls.enabled: false`, a fixed `https` listener terminating TLS with the certificate named by `gatewayApi.gateway.tls.secretName` (defaults to `tls.secretName` / `-tls-secret` - the same secret name the Ingress path uses, so the two can share a certificate if you point both at the same issuer). Both listeners are scoped to `host`, matching how the Ingress path is host-scoped too - required for cert-manager's [gateway-shim](https://cert-manager.io/docs/usage/gateway/) to know which hostname to request a certificate for. -To have cert-manager actually issue that certificate, the chart automatically annotates the Gateway with `cert-manager.io/issuer`/`cert-manager.io/cluster-issuer`, derived from the exact same `tls.enabled` + `ingress.issuer`/`ingress.clusterIssuer`/`ingress.certificateIssuer` configuration already used for Ingress - no separate issuer setup needed, and it keeps working even if every `.ingress.enabled` is `false` and no Ingress is ever rendered to trigger cert-manager's ingress-shim instead. Set `gatewayApi.gateway.annotations["cert-manager.io/issuer"]` (or `-cluster-issuer`) explicitly to issue the Gateway a different certificate than the Ingress path uses; explicit `gatewayApi.gateway.annotations` always take precedence over the derived one. If you need more control over listeners than these opinionated defaults offer, use the bring-your-own path (`gatewayApi.parentRefs`) against a `Gateway` you manage yourself instead. +To have cert-manager actually issue that certificate, set `gatewayApi.gateway.issuer` or `gatewayApi.gateway.clusterIssuer`; the chart then annotates the Gateway with `cert-manager.io/issuer`/`cert-manager.io/cluster-issuer` accordingly, so gateway-shim issues the certificate independently of whether any Ingress is ever rendered. This is deliberately its own, separate setting rather than a reuse of `ingress.issuer`/`ingress.clusterIssuer`/`ingress.certificateIssuer` - Gateway API usage should not implicitly depend on the Ingress-shaped values. Point it at the same Issuer name as the Ingress path (e.g. `internal-issuer` when using the internal CA, see `internal.certificateIssuer`) to reuse that certificate, or at a different one to issue the Gateway its own. `gatewayApi.gateway.annotations` always takes precedence over the derived annotation, for full manual control. If you need more control over listeners than these opinionated defaults offer, use the bring-your-own path (`gatewayApi.parentRefs`) against a `Gateway` you manage yourself instead. The derived `parentRefs` (see step 3 above) always target the `https` listener while `gatewayApi.gateway.tls.enabled` is `true` (the default). The `http` listener is still created and its port is still exposed, but no `HTTPRoute` attaches to it automatically, and the chart does not configure an HTTP-to-HTTPS redirect. To route plain HTTP traffic for a service instead, set that service's own `.httpRoute.parentRefs` with `sectionName: http` explicitly. @@ -481,7 +481,9 @@ The derived `parentRefs` (see step 3 above) always target the `https` listener w | `gatewayApi.gateway.enabled` | Lets the chart create its own `Gateway` instead of requiring one to already exist. Disabled by default. | | `gatewayApi.gateway.name` | Name of the chart-created `Gateway`. Defaults to `-gateway`. | | `gatewayApi.gateway.className` | `GatewayClass` name. Required when enabled. | -| `gatewayApi.gateway.annotations` | Annotations added to the `Gateway` metadata. | +| `gatewayApi.gateway.annotations` | Annotations added to the `Gateway` metadata. Takes precedence over the derived cert-manager annotation below. | +| `gatewayApi.gateway.issuer` | Namespaced cert-manager `Issuer` name, annotated as `cert-manager.io/issuer`. Independent of `ingress.issuer`. | +| `gatewayApi.gateway.clusterIssuer` | Cert-manager `ClusterIssuer` name, annotated as `cert-manager.io/cluster-issuer`. Independent of `ingress.clusterIssuer`. Ignored when `issuer` is also set. | | `gatewayApi.gateway.port` | Port for the fixed `http` listener. Defaults to `80`. | | `gatewayApi.gateway.tls.enabled` | Whether to also render the fixed `https` listener. Defaults to `true`. | | `gatewayApi.gateway.tls.port` | Port for the `https` listener. Defaults to `443`. | diff --git a/charts/basyx/templates/_helpers.tpl b/charts/basyx/templates/_helpers.tpl index 54846cf..1114470 100644 --- a/charts/basyx/templates/_helpers.tpl +++ b/charts/basyx/templates/_helpers.tpl @@ -1171,26 +1171,24 @@ below so both agree on the same name. {{/* Cert-manager annotations for the chart-created Gateway's https listener, -derived from the same tls.enabled + ingress.certificateIssuer/issuer/clusterIssuer -config already used for Ingress - so cert-manager's gateway-shim issues the -same certificate (same secretName) the Ingress path would otherwise reuse, -without requiring a second, gateway-specific issuer configuration. Explicit -gatewayApi.gateway.annotations always take precedence over the derived -cert-manager annotation. +derived from gatewayApi.gateway.issuer/clusterIssuer - deliberately its own, +independent config rather than reusing ingress.issuer/clusterIssuer, so +Gateway API usage never implicitly depends on the Ingress-shaped values. +Explicit gatewayApi.gateway.annotations always take precedence over the +derived cert-manager annotation. */}} {{- define "common.gatewayApi.gateway.annotations" -}} {{- $root := . -}} +{{- $gateway := $root.Values.gatewayApi.gateway -}} {{- $annotations := dict -}} -{{- if and $root.Values.tls.enabled $root.Values.gatewayApi.gateway.tls.enabled -}} -{{- if $root.Values.ingress.certificateIssuer.enabled -}} -{{- $_ := set $annotations "cert-manager.io/issuer" ($root.Values.ingress.certificateIssuer.name | toString) -}} -{{- else if $root.Values.ingress.issuer -}} -{{- $_ := set $annotations "cert-manager.io/issuer" ($root.Values.ingress.issuer | toString) -}} -{{- else if $root.Values.ingress.clusterIssuer -}} -{{- $_ := set $annotations "cert-manager.io/cluster-issuer" ($root.Values.ingress.clusterIssuer | toString) -}} +{{- if and $root.Values.tls.enabled $gateway.tls.enabled -}} +{{- if $gateway.issuer -}} +{{- $_ := set $annotations "cert-manager.io/issuer" ($gateway.issuer | toString) -}} +{{- else if $gateway.clusterIssuer -}} +{{- $_ := set $annotations "cert-manager.io/cluster-issuer" ($gateway.clusterIssuer | toString) -}} {{- end -}} {{- end -}} -{{- range $key, $value := ($root.Values.gatewayApi.gateway.annotations | default dict) -}} +{{- range $key, $value := ($gateway.annotations | default dict) -}} {{- if kindIs "invalid" $value -}} {{- $_ := unset $annotations $key -}} {{- else -}} diff --git a/charts/basyx/tests/gateway_test.yaml b/charts/basyx/tests/gateway_test.yaml index aa93ac5..f034cb2 100644 --- a/charts/basyx/tests/gateway_test.yaml +++ b/charts/basyx/tests/gateway_test.yaml @@ -111,42 +111,43 @@ tests: path: metadata.annotations["example.com/foo"] value: bar - - it: derives a cert-manager issuer annotation from the same ingress.issuer used by Ingress + - it: derives a cert-manager issuer annotation from gatewayApi.gateway.issuer template: templates/gateway.yaml set: gatewayApi.gateway.enabled: true gatewayApi.gateway.className: envoy-gateway tls.enabled: true - ingress.issuer: internal-issuer + gatewayApi.gateway.issuer: internal-issuer asserts: - equal: path: metadata.annotations["cert-manager.io/issuer"] value: internal-issuer - - it: derives a cert-manager cluster-issuer annotation from ingress.clusterIssuer + - it: derives a cert-manager cluster-issuer annotation from gatewayApi.gateway.clusterIssuer template: templates/gateway.yaml set: gatewayApi.gateway.enabled: true gatewayApi.gateway.className: envoy-gateway tls.enabled: true - ingress.clusterIssuer: letsencrypt-prod + gatewayApi.gateway.clusterIssuer: letsencrypt-prod asserts: - equal: path: metadata.annotations["cert-manager.io/cluster-issuer"] value: letsencrypt-prod - - it: derives a cert-manager issuer annotation from ingress.certificateIssuer when enabled + - it: does not derive a cert-manager annotation from ingress.issuer or ingress.clusterIssuer template: templates/gateway.yaml set: gatewayApi.gateway.enabled: true gatewayApi.gateway.className: envoy-gateway tls.enabled: true + ingress.issuer: internal-issuer + ingress.clusterIssuer: letsencrypt-prod ingress.certificateIssuer.enabled: true ingress.certificateIssuer.name: ingress-issuer asserts: - - equal: - path: metadata.annotations["cert-manager.io/issuer"] - value: ingress-issuer + - notExists: + path: metadata.annotations - it: lets explicit gatewayApi.gateway.annotations override the derived cert-manager issuer template: templates/gateway.yaml @@ -154,7 +155,7 @@ tests: gatewayApi.gateway.enabled: true gatewayApi.gateway.className: envoy-gateway tls.enabled: true - ingress.issuer: internal-issuer + gatewayApi.gateway.issuer: internal-issuer gatewayApi.gateway.annotations: cert-manager.io/issuer: custom-issuer asserts: @@ -169,7 +170,7 @@ tests: gatewayApi.gateway.className: envoy-gateway gatewayApi.gateway.tls.enabled: false tls.enabled: true - ingress.issuer: internal-issuer + gatewayApi.gateway.issuer: internal-issuer asserts: - notExists: path: metadata.annotations @@ -180,7 +181,7 @@ tests: gatewayApi.gateway.enabled: true gatewayApi.gateway.className: envoy-gateway tls.enabled: false - ingress.issuer: internal-issuer + gatewayApi.gateway.issuer: internal-issuer asserts: - notExists: path: metadata.annotations diff --git a/charts/basyx/values.schema.json b/charts/basyx/values.schema.json index 44bf531..afb0e7c 100644 --- a/charts/basyx/values.schema.json +++ b/charts/basyx/values.schema.json @@ -1361,6 +1361,8 @@ "name": { "type": "string" }, "className": { "type": "string" }, "annotations": { "type": "object" }, + "issuer": { "type": "string" }, + "clusterIssuer": { "type": "string" }, "port": { "type": "integer" }, "tls": { "type": "object", diff --git a/charts/basyx/values.yaml b/charts/basyx/values.yaml index 06f5f0e..48b3f8d 100644 --- a/charts/basyx/values.yaml +++ b/charts/basyx/values.yaml @@ -96,6 +96,19 @@ gatewayApi: # ingress.className, there is no cluster-portable default. className: "" annotations: {} + # Namespaced cert-manager Issuer used to annotate the Gateway with + # cert-manager.io/issuer, so cert-manager's gateway-shim issues a + # certificate for the https listener below. Deliberately independent of + # ingress.issuer - set this to the same Issuer name if you want the + # Gateway to reuse the certificate the Ingress path would otherwise use, + # e.g. "internal-issuer" when using the internal CA (see + # internal.certificateIssuer) or the name of an Issuer created via + # ingress.certificateIssuer. + issuer: "" + # cert-manager ClusterIssuer name, annotated as + # cert-manager.io/cluster-issuer. Only one of issuer/clusterIssuer should + # be set; issuer takes precedence when both are. + clusterIssuer: "" port: 80 tls: enabled: true diff --git a/values/values.gatewayapi-managed.example.yaml b/values/values.gatewayapi-managed.example.yaml index efc0aa0..7e4888e 100644 --- a/values/values.gatewayapi-managed.example.yaml +++ b/values/values.gatewayapi-managed.example.yaml @@ -50,14 +50,17 @@ ingress: # The chart creates its own Gateway (named "basyx-gateway" here, defaults to # "-gateway" if `name` is left empty) with a fixed `http` -# listener and an `https` listener reusing the same TLS certificate the -# Ingress path would otherwise use. No `gatewayApi.parentRefs` needed - it is +# listener and an `https` listener. No `gatewayApi.parentRefs` needed - it is # derived automatically to point at this Gateway. gatewayApi: gateway: enabled: true name: basyx-gateway className: envoy-gateway # GatewayClass name - matches your installed Gateway API controller + # Independent of ingress.issuer - point at the same Issuer name ("internal-issuer" + # here, matching internal.certificateIssuer below) to reuse that certificate, + # or at a different Issuer/ClusterIssuer to issue the Gateway its own. + issuer: internal-issuer database: clusterName: basyx-database From 183a333f4d970f45c2ee5edac13e8e99e7fa9135 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Mon, 24 Aug 2026 09:18:48 +0200 Subject: [PATCH 11/12] fix: use required instead of fail for gatewayApi.gateway.className validation --- charts/basyx/templates/gateway.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/charts/basyx/templates/gateway.yaml b/charts/basyx/templates/gateway.yaml index af7ba40..4fb71c0 100644 --- a/charts/basyx/templates/gateway.yaml +++ b/charts/basyx/templates/gateway.yaml @@ -1,7 +1,4 @@ {{- if .Values.gatewayApi.gateway.enabled }} -{{- if not .Values.gatewayApi.gateway.className }} -{{- fail "gatewayApi.gateway.enabled is true but gatewayApi.gateway.className is not set" }} -{{- end }} apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: @@ -15,7 +12,7 @@ metadata: {{- $gatewayAnnotations | nindent 4 }} {{- end }} spec: - gatewayClassName: {{ tpl (print .Values.gatewayApi.gateway.className) . | quote }} + gatewayClassName: {{ required "gatewayApi.gateway.className is required when gatewayApi.gateway.enabled is true" (tpl (print .Values.gatewayApi.gateway.className) .) | quote }} listeners: - name: http hostname: {{ tpl (print .Values.host) . | quote }} From 65c551a8274516361a9e13c62ddae03289d7edd3 Mon Sep 17 00:00:00 2001 From: Adrian Vondendriesch Date: Thu, 17 Sep 2026 15:46:02 +0200 Subject: [PATCH 12/12] chore: bump chart version to 3.14.0 --- charts/basyx/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/basyx/Chart.yaml b/charts/basyx/Chart.yaml index 2403c23..fdef00b 100644 --- a/charts/basyx/Chart.yaml +++ b/charts/basyx/Chart.yaml @@ -5,7 +5,7 @@ description: > including AAS registries, repositories, discovery service, and optional Keycloak authentication with ABAC authorization. type: application -version: 3.13.0 +version: 3.14.0 appVersion: "1.0.12" home: https://github.com/eclipse-basyx/charts sources: