This is an automated email from the ASF dual-hosted git repository.

Miretpl pushed a commit to branch chart/v1-2x-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/chart/v1-2x-test by this push:
     new a5caeec970c Add Webserver HTTPRoute support to the Helm chart 1.2x 
line (#70425)
a5caeec970c is described below

commit a5caeec970c6cfc53ca2cca4383c034fb001ca88
Author: Aaron Chen <[email protected]>
AuthorDate: Sun Aug 2 05:28:43 2026 +0800

    Add Webserver HTTPRoute support to the Helm chart 1.2x line (#70425)
    
    * Add Webserver HTTPRoute support to the Helm chart 1.2x line
    
    * Update helm-tests/tests/helm_tests/webserver/test_httproute_webserver.py
    
    Co-authored-by: Przemysław Mirowski 
<[email protected]>
    
    * Update chart/values.yaml
    
    Co-authored-by: Przemysław Mirowski 
<[email protected]>
    
    * Update chart/docs/production-guide.rst
    
    Co-authored-by: Przemysław Mirowski 
<[email protected]>
    
    * Keep web Ingress label test and add Webserver HTTPRoute case
---
 chart/docs/production-guide.rst                    |  31 +-
 chart/templates/webserver/webserver-httproute.yaml |  70 ++++
 chart/templates/webserver/webserver-ingress.yaml   |   3 +
 chart/values.schema.json                           | 291 ++++++---------
 chart/values.yaml                                  |  46 +++
 .../airflow_aux/test_basic_helm_chart.py           |  42 ++-
 .../webserver/test_httproute_webserver.py          | 407 +++++++++++++++++++++
 7 files changed, 692 insertions(+), 198 deletions(-)

diff --git a/chart/docs/production-guide.rst b/chart/docs/production-guide.rst
index 22f2f88923c..49d12a99946 100644
--- a/chart/docs/production-guide.rst
+++ b/chart/docs/production-guide.rst
@@ -485,7 +485,7 @@ Gateway API (HTTPRoute)
 ^^^^^^^^^^^^^^^^^^^^^^^
 
 As an alternative to ``Ingress``, the chart can create
-`Kubernetes Gateway API <https://gateway-api.sigs.k8s.io/>`_ ``HTTPRoute`` 
resources for the API server and Flower.
+`Kubernetes Gateway API <https://gateway-api.sigs.k8s.io/>`_ ``HTTPRoute`` 
resources for the API server, Flower, and the Webserver.
 This requires the Gateway API CRDs to be installed in the cluster and a 
``Gateway`` to already exist —
 the chart only creates the ``HTTPRoute`` resources and attaches them to the 
Gateway via ``parentRefs``.
 
@@ -515,13 +515,34 @@ the chart only creates the ``HTTPRoute`` resources and 
attaches them to the Gate
        hostnames:
          - flower.example.com
 
+The Webserver exists only on Airflow 2 (the API server replaces it on Airflow 
3), so its ``HTTPRoute``
+is configured in an Airflow 2 deployment:
+
+.. code-block:: yaml
+   :caption: values.yaml (Airflow 2)
+
+   defaultAirflowTag: "2.11.0"
+   airflowVersion: "2.11.0"
+
+   webserver:
+     httpRoute:
+       enabled: true
+       parentRefs:
+         - name: main-gateway
+           namespace: gateway-system
+           sectionName: https
+       hostnames:
+         - airflow.example.com
+
 Flower HTTPRoute resources are only created when Flower itself is created, so 
``flower.enabled`` must be
 ``true`` and the executor must include ``CeleryExecutor`` or 
``CeleryKubernetesExecutor``.
 
-For fine-grained routing, supply ``apiServer.httpRoute.rules`` or 
``flower.httpRoute.rules`` directly —
-the entry mirrors the upstream ``HTTPRouteRule`` schema and overrides the 
default rule generated from
-the corresponding ``apiServer.httpRoute.path`` and 
``apiServer.httpRoute.pathType``, or
-``flower.httpRoute.path`` and ``flower.httpRoute.pathType`` values.
+For fine-grained routing, supply ``apiServer.httpRoute.rules``, 
``flower.httpRoute.rules``, or
+``webserver.httpRoute.rules`` directly — the entry mirrors the upstream 
``HTTPRouteRule`` schema and
+overrides the default rule generated from the corresponding
+``apiServer.httpRoute.path`` and ``apiServer.httpRoute.pathType``,
+``flower.httpRoute.path`` and ``flower.httpRoute.pathType``, or
+``webserver.httpRoute.path`` and ``webserver.httpRoute.pathType`` values.
 
 .. note::
 
diff --git a/chart/templates/webserver/webserver-httproute.yaml 
b/chart/templates/webserver/webserver-httproute.yaml
new file mode 100644
index 00000000000..3b3d38fc65e
--- /dev/null
+++ b/chart/templates/webserver/webserver-httproute.yaml
@@ -0,0 +1,70 @@
+{{/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/}}
+
+##############################
+## Airflow Webserver HTTPRoute
+##############################
+{{- if and .Values.webserver.enabled .Values.webserver.httpRoute.enabled 
(semverCompare "<3.0.0" .Values.airflowVersion) }}
+{{- if or .Values.ingress.web.enabled .Values.ingress.enabled }}
+{{- fail "`webserver.httpRoute.enabled` and 
`ingress.web.enabled`/`ingress.enabled` are both enabled. HTTPRoute (Gateway 
API) is an alternative to the Webserver Ingress; enable only one of them." }}
+{{- end }}
+{{- if not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1") }}
+{{- fail "`webserver.httpRoute.enabled` is `true` but the Gateway API 
HTTPRoute CRD (`gateway.networking.k8s.io/v1`) is not installed in the cluster. 
Install the Gateway API CRDs 
(https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api) before 
enabling `webserver.httpRoute`." }}
+{{- end }}
+{{- if not .Values.webserver.httpRoute.parentRefs }}
+{{- fail "`webserver.httpRoute.enabled` is `true` but 
`webserver.httpRoute.parentRefs` is empty. An HTTPRoute must reference at least 
one parent Gateway; set `webserver.httpRoute.parentRefs`." }}
+{{- end }}
+{{- $fullname := include "airflow.fullname" . }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: {{ $fullname }}-webserver-httproute
+  labels:
+    tier: airflow
+    component: webserver-httproute
+    release: {{ .Release.Name }}
+    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
+    heritage: {{ .Release.Service }}
+    {{- if or .Values.labels .Values.webserver.labels 
.Values.webserver.httpRoute.labels }}
+      {{- mustMerge .Values.webserver.httpRoute.labels 
.Values.webserver.labels .Values.labels | toYaml | nindent 4 }}
+    {{- end }}
+  {{- with .Values.webserver.httpRoute.annotations }}
+  annotations: {{- toYaml . | nindent 4 }}
+  {{- end }}
+spec:
+  parentRefs: {{- toYaml .Values.webserver.httpRoute.parentRefs | nindent 4 }}
+  {{- with .Values.webserver.httpRoute.hostnames }}
+  hostnames:
+    {{- range . }}
+    - {{ tpl . $ | quote }}
+    {{- end }}
+  {{- end }}
+  rules:
+    {{- if .Values.webserver.httpRoute.rules }}
+    {{- toYaml .Values.webserver.httpRoute.rules | nindent 4 }}
+    {{- else }}
+    - matches:
+        - path:
+            type: {{ .Values.webserver.httpRoute.pathType }}
+            value: {{ .Values.webserver.httpRoute.path | quote }}
+      backendRefs:
+        - name: {{ $fullname }}-webserver
+          port: {{ .Values.ports.airflowUI }}
+    {{- end }}
+{{- end }}
diff --git a/chart/templates/webserver/webserver-ingress.yaml 
b/chart/templates/webserver/webserver-ingress.yaml
index 1b9f8d41b4a..7363c6140fc 100644
--- a/chart/templates/webserver/webserver-ingress.yaml
+++ b/chart/templates/webserver/webserver-ingress.yaml
@@ -21,6 +21,9 @@
 ## Airflow Webserver Ingress
 #################################
 {{- if and .Values.webserver.enabled (or .Values.ingress.web.enabled 
.Values.ingress.enabled) (semverCompare "<3.0.0" .Values.airflowVersion) }}
+{{- if .Values.webserver.httpRoute.enabled }}
+{{- fail "`ingress.web.enabled`/`ingress.enabled` and 
`webserver.httpRoute.enabled` are both enabled. The Webserver Ingress is an 
alternative to HTTPRoute (Gateway API); enable only one of them." }}
+{{- end }}
 {{- $fullname := (include "airflow.fullname" .) }}
 apiVersion: networking.k8s.io/v1
 kind: Ingress
diff --git a/chart/values.schema.json b/chart/values.schema.json
index 7bb9cc90d36..092e5abc2d1 100644
--- a/chart/values.schema.json
+++ b/chart/values.schema.json
@@ -7079,100 +7079,8 @@
                     "default": true
                 },
                 "httpRoute": {
-                    "description": "Kubernetes Gateway API (HTTPRoute) 
configuration for the API server.",
-                    "type": "object",
-                    "additionalProperties": false,
-                    "properties": {
-                        "enabled": {
-                            "description": "Enable API server HTTPRoute 
resource.",
-                            "type": "boolean",
-                            "default": false
-                        },
-                        "labels": {
-                            "description": "Extra labels for the API server 
HTTPRoute.",
-                            "type": "object",
-                            "default": {},
-                            "additionalProperties": {
-                                "type": "string"
-                            }
-                        },
-                        "annotations": {
-                            "description": "Annotations for the API server 
HTTPRoute.",
-                            "type": "object",
-                            "default": {},
-                            "additionalProperties": {
-                                "type": "string"
-                            }
-                        },
-                        "parentRefs": {
-                            "description": "List of parent Gateway references 
this HTTPRoute attaches to. Required when enabled.",
-                            "type": [
-                                "array",
-                                "null"
-                            ],
-                            "default": null,
-                            "minItems": 1,
-                            "items": {
-                                "type": "object",
-                                "properties": {
-                                    "name": {
-                                        "description": "Name of the referenced 
Gateway.",
-                                        "type": "string"
-                                    },
-                                    "namespace": {
-                                        "description": "Namespace of the 
referenced Gateway. Defaults to the local namespace.",
-                                        "type": "string"
-                                    },
-                                    "sectionName": {
-                                        "description": "Name of the Gateway 
listener section this route attaches to.",
-                                        "type": "string"
-                                    },
-                                    "port": {
-                                        "description": "Port of the Gateway 
listener this route attaches to.",
-                                        "type": "integer"
-                                    },
-                                    "kind": {
-                                        "description": "Kind of the referenced 
resource. Defaults to `Gateway`.",
-                                        "type": "string"
-                                    },
-                                    "group": {
-                                        "description": "API group of the 
referenced resource. Defaults to `gateway.networking.k8s.io`.",
-                                        "type": "string"
-                                    }
-                                },
-                                "required": [
-                                    "name"
-                                ]
-                            }
-                        },
-                        "hostnames": {
-                            "description": "Hostnames this HTTPRoute should 
match (templated). Empty matches all.",
-                            "type": "array",
-                            "default": [],
-                            "items": {
-                                "type": "string"
-                            }
-                        },
-                        "path": {
-                            "description": "Default routing rule path (used 
only when `apiServer.httpRoute.rules` is empty).",
-                            "type": "string",
-                            "default": "/"
-                        },
-                        "pathType": {
-                            "description": "The pathType for the default path: 
PathPrefix, Exact, or RegularExpression.",
-                            "type": "string",
-                            "default": "PathPrefix"
-                        },
-                        "rules": {
-                            "description": "Custom routing rules. When set, 
overrides the default rule generated from `path` + `pathType`. Each entry 
follows the upstream Gateway API `HTTPRouteRule` schema.",
-                            "type": "array",
-                            "default": [],
-                            "items": {
-                                "type": "object",
-                                "additionalProperties": {}
-                            }
-                        }
-                    }
+                    "$ref": "#/definitions/httpRoute",
+                    "description": "Kubernetes Gateway API (HTTPRoute) 
configuration for the API server."
                 },
                 "configMapAnnotations": {
                     "description": "Extra annotations to apply to the API 
server configmap.",
@@ -7948,6 +7856,10 @@
                     "type": "boolean",
                     "default": true
                 },
+                "httpRoute": {
+                    "$ref": "#/definitions/httpRoute",
+                    "description": "Kubernetes Gateway API (HTTPRoute) 
configuration for the Webserver."
+                },
                 "configMapAnnotations": {
                     "description": "Extra annotations to apply to the 
webserver configmap.",
                     "type": "object",
@@ -8768,100 +8680,8 @@
                     "default": false
                 },
                 "httpRoute": {
-                    "description": "Kubernetes Gateway API (HTTPRoute) 
configuration for Flower.",
-                    "type": "object",
-                    "additionalProperties": false,
-                    "properties": {
-                        "enabled": {
-                            "description": "Enable Flower HTTPRoute resource.",
-                            "type": "boolean",
-                            "default": false
-                        },
-                        "labels": {
-                            "description": "Extra labels for the Flower 
HTTPRoute.",
-                            "type": "object",
-                            "default": {},
-                            "additionalProperties": {
-                                "type": "string"
-                            }
-                        },
-                        "annotations": {
-                            "description": "Annotations for the Flower 
HTTPRoute.",
-                            "type": "object",
-                            "default": {},
-                            "additionalProperties": {
-                                "type": "string"
-                            }
-                        },
-                        "parentRefs": {
-                            "description": "List of parent Gateway references 
this HTTPRoute attaches to. Required when enabled.",
-                            "type": [
-                                "array",
-                                "null"
-                            ],
-                            "default": null,
-                            "minItems": 1,
-                            "items": {
-                                "type": "object",
-                                "properties": {
-                                    "name": {
-                                        "description": "Name of the referenced 
Gateway.",
-                                        "type": "string"
-                                    },
-                                    "namespace": {
-                                        "description": "Namespace of the 
referenced Gateway. Defaults to the local namespace.",
-                                        "type": "string"
-                                    },
-                                    "sectionName": {
-                                        "description": "Name of the Gateway 
listener section this route attaches to.",
-                                        "type": "string"
-                                    },
-                                    "port": {
-                                        "description": "Port of the Gateway 
listener this route attaches to.",
-                                        "type": "integer"
-                                    },
-                                    "kind": {
-                                        "description": "Kind of the referenced 
resource. Defaults to `Gateway`.",
-                                        "type": "string"
-                                    },
-                                    "group": {
-                                        "description": "API group of the 
referenced resource. Defaults to `gateway.networking.k8s.io`.",
-                                        "type": "string"
-                                    }
-                                },
-                                "required": [
-                                    "name"
-                                ]
-                            }
-                        },
-                        "hostnames": {
-                            "description": "Hostnames this HTTPRoute should 
match (templated). Empty matches all.",
-                            "type": "array",
-                            "default": [],
-                            "items": {
-                                "type": "string"
-                            }
-                        },
-                        "path": {
-                            "description": "Default routing rule path (used 
only when `flower.httpRoute.rules` is empty).",
-                            "type": "string",
-                            "default": "/"
-                        },
-                        "pathType": {
-                            "description": "The pathType for the default path: 
PathPrefix, Exact, or RegularExpression.",
-                            "type": "string",
-                            "default": "PathPrefix"
-                        },
-                        "rules": {
-                            "description": "Custom routing rules. When set, 
overrides the default rule generated from `path` + `pathType`. Each entry 
follows the upstream Gateway API `HTTPRouteRule` schema.",
-                            "type": "array",
-                            "default": [],
-                            "items": {
-                                "type": "object",
-                                "additionalProperties": {}
-                            }
-                        }
-                    }
+                    "$ref": "#/definitions/httpRoute",
+                    "description": "Kubernetes Gateway API (HTTPRoute) 
configuration for Flower."
                 },
                 "livenessProbe": {
                     "description": "Liveness probe configuration.",
@@ -12544,6 +12364,101 @@
         }
     },
     "definitions": {
+        "httpRoute": {
+            "type": "object",
+            "additionalProperties": false,
+            "properties": {
+                "enabled": {
+                    "description": "Enable the HTTPRoute resource.",
+                    "type": "boolean",
+                    "default": false
+                },
+                "labels": {
+                    "description": "Extra labels for the HTTPRoute.",
+                    "type": "object",
+                    "default": {},
+                    "additionalProperties": {
+                        "type": "string"
+                    }
+                },
+                "annotations": {
+                    "description": "Annotations for the HTTPRoute.",
+                    "type": "object",
+                    "default": {},
+                    "additionalProperties": {
+                        "type": "string"
+                    }
+                },
+                "parentRefs": {
+                    "description": "List of parent Gateway references this 
HTTPRoute attaches to. Required when enabled.",
+                    "type": [
+                        "array",
+                        "null"
+                    ],
+                    "default": null,
+                    "minItems": 1,
+                    "items": {
+                        "type": "object",
+                        "properties": {
+                            "name": {
+                                "description": "Name of the referenced 
Gateway.",
+                                "type": "string"
+                            },
+                            "namespace": {
+                                "description": "Namespace of the referenced 
Gateway. Defaults to the local namespace.",
+                                "type": "string"
+                            },
+                            "sectionName": {
+                                "description": "Name of the Gateway listener 
section this route attaches to.",
+                                "type": "string"
+                            },
+                            "port": {
+                                "description": "Port of the Gateway listener 
this route attaches to.",
+                                "type": "integer"
+                            },
+                            "kind": {
+                                "description": "Kind of the referenced 
resource. Defaults to `Gateway`.",
+                                "type": "string"
+                            },
+                            "group": {
+                                "description": "API group of the referenced 
resource. Defaults to `gateway.networking.k8s.io`.",
+                                "type": "string"
+                            }
+                        },
+                        "required": [
+                            "name"
+                        ]
+                    }
+                },
+                "hostnames": {
+                    "description": "Hostnames this HTTPRoute should match 
(templated). Empty matches all.",
+                    "type": "array",
+                    "default": [],
+                    "items": {
+                        "type": "string"
+                    }
+                },
+                "path": {
+                    "description": "Default routing rule path (used only when 
`rules` is empty).",
+                    "type": "string",
+                    "default": "/"
+                },
+                "pathType": {
+                    "description": "The pathType for the default path: 
PathPrefix, Exact, or RegularExpression.",
+                    "type": "string",
+                    "default": "PathPrefix"
+                },
+                "rules": {
+                    "description": "Custom routing rules. When set, overrides 
the default rule generated from `path` + `pathType`. Each entry follows the 
upstream Gateway API `HTTPRouteRule` schema.",
+                    "type": "array",
+                    "default": [],
+                    "items": {
+                        "type": "object",
+                        "additionalProperties": {}
+                    }
+                }
+            }
+        },
         "io.k8s.api.apps.v1.DeploymentStrategy": {
             "description": "DeploymentStrategy describes how to replace 
existing pods with new ones.",
             "properties": {
diff --git a/chart/values.yaml b/chart/values.yaml
index 7d848823458..274a8d79b4a 100644
--- a/chart/values.yaml
+++ b/chart/values.yaml
@@ -202,6 +202,7 @@ ingress:
   # Configs for the Ingress of the web Service (Airflow <3.0.0)
   web:
     # Enable web ingress resource
+    # Mutually exclusive with `webserver.httpRoute.enabled` (Gateway API); 
enable only one of them.
     enabled: false
 
     # Annotations for the web Ingress
@@ -2540,6 +2541,51 @@ apiServer:
 webserver:
   enabled: true
 
+  # Kubernetes Gateway API (HTTPRoute) configuration for the Webserver.
+  # Requires the Gateway API CRDs to be installed in the cluster
+  # (https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api).
+  # The HTTPRoute references an externally managed Gateway via `parentRefs`.
+  httpRoute:
+    # Enable Webserver HTTPRoute resource.
+    # Mutually exclusive with `ingress.web.enabled` and the legacy 
`ingress.enabled`;
+    # enable only one routing mechanism.
+    enabled: false
+
+    # Extra labels for the Webserver HTTPRoute
+    labels: {}
+
+    # Annotations for the Webserver HTTPRoute
+    annotations: {}
+
+    # List of parent Gateway references this HTTPRoute attaches to. Required 
when enabled.
+    # See 
https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference
+    parentRefs: ~
+    # - name: my-gateway
+    #   namespace: gateway-system
+    #   sectionName: https
+
+    # Hostnames this HTTPRoute should match (templated). Empty matches all.
+    hostnames: []
+    # - "airflow.example.com"
+
+    # Default routing rule path (used only when `webserver.httpRoute.rules` is 
empty)
+    path: "/"
+
+    # The pathType for the default path. PathPrefix | Exact | RegularExpression
+    pathType: PathPrefix
+
+    # Custom routing rules. When set, overrides the default rule generated 
from `path` + `pathType`.
+    # Use this for non-standard Webserver service port layouts.
+    # See 
https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule
+    rules: []
+    # - matches:
+    #     - path:
+    #         type: PathPrefix
+    #         value: /
+    #   backendRefs:
+    #     - name: my-webserver-service
+    #       port: 8080
+
   # Add custom annotations to the webserver ConfigMap
   configMapAnnotations: {}
 
diff --git a/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py 
b/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
index 22120bc2805..228b2ed0979 100644
--- a/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
+++ b/helm-tests/tests/helm_tests/airflow_aux/test_basic_helm_chart.py
@@ -372,7 +372,7 @@ class TestBaseChartTest:
         ],
     )
     @pytest.mark.parametrize(
-        ("flower_routing_values", "flower_routing_resource", "api_versions"),
+        ("routing_values", "flower_routing_resource", 
"webserver_routing_resource", "api_versions"),
         [
             pytest.param(
                 {
@@ -384,8 +384,9 @@ class TestBaseChartTest:
                     "flower": {"enabled": True},
                 },
                 ("flower-ingress", "Ingress", "flower-ingress"),
+                ("ingress", "Ingress", "airflow-ingress"),
                 [],
-                id="flower-ingress",
+                id="ingress",
             ),
             pytest.param(
                 {
@@ -402,17 +403,41 @@ class TestBaseChartTest:
                     },
                 },
                 ("flower-httproute", "HTTPRoute", "flower-httproute"),
+                ("ingress", "Ingress", "airflow-ingress"),
                 ["gateway.networking.k8s.io/v1"],
                 id="flower-httproute",
             ),
+            pytest.param(
+                {
+                    "ingress": {"apiServer": {"enabled": True}},
+                    "flower": {
+                        "enabled": True,
+                        "httpRoute": {
+                            "enabled": True,
+                            "parentRefs": [{"name": "main-gateway"}],
+                        },
+                    },
+                    "webserver": {
+                        "httpRoute": {
+                            "enabled": True,
+                            "parentRefs": [{"name": "main-gateway"}],
+                        },
+                    },
+                },
+                ("flower-httproute", "HTTPRoute", "flower-httproute"),
+                ("webserver-httproute", "HTTPRoute", "webserver-httproute"),
+                ["gateway.networking.k8s.io/v1"],
+                id="httproute",
+            ),
         ],
     )
     def test_labels_are_valid(
         self,
         airflow_version,
         executor,
-        flower_routing_values,
+        routing_values,
         flower_routing_resource,
+        webserver_routing_resource,
         api_versions,
     ):
         """Test labels are correctly applied on all objects created by this 
chart."""
@@ -445,7 +470,7 @@ class TestBaseChartTest:
                 {"name": "class1", "value": 10000},
             ],
         }
-        values.update(flower_routing_values)
+        values.update(routing_values)
 
         if airflow_version != "default":
             values["airflowVersion"] = airflow_version
@@ -457,6 +482,9 @@ class TestBaseChartTest:
         }
 
         flower_routing_name, flower_routing_kind, flower_routing_component = 
flower_routing_resource
+        webserver_routing_name, webserver_routing_kind, 
webserver_routing_component = (
+            webserver_routing_resource
+        )
 
         kind_names_tuples = [
             (f"{release_name}-airflow-cleanup", "ServiceAccount", 
"airflow-cleanup-pods"),
@@ -535,7 +563,11 @@ class TestBaseChartTest:
                 (f"{release_name}-webserver", "Service", "webserver"),
                 (f"{release_name}-webserver-secret-key", "Secret", 
"webserver"),
                 (f"{release_name}-webserver-policy", "NetworkPolicy", 
"airflow-webserver-policy"),
-                (f"{release_name}-ingress", "Ingress", "airflow-ingress"),
+                (
+                    f"{release_name}-{webserver_routing_name}",
+                    webserver_routing_kind,
+                    webserver_routing_component,
+                ),
             ]
 
         cleanup_kubernetes_executor_only_objects = {
diff --git a/helm-tests/tests/helm_tests/webserver/test_httproute_webserver.py 
b/helm-tests/tests/helm_tests/webserver/test_httproute_webserver.py
new file mode 100644
index 00000000000..597bc05ef4a
--- /dev/null
+++ b/helm-tests/tests/helm_tests/webserver/test_httproute_webserver.py
@@ -0,0 +1,407 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import jmespath
+import pytest
+from chart_utils.helm_template_generator import HelmFailedError, render_chart
+
+SHOW_ONLY = ["templates/webserver/webserver-httproute.yaml"]
+WEBSERVER_INGRESS_SHOW_ONLY = ["templates/webserver/webserver-ingress.yaml"]
+
+GATEWAY_API_VERSIONS = ["gateway.networking.k8s.io/v1"]
+MINIMAL_PARENT_REFS = [{"name": "main-gateway"}]
+# The Webserver only exists on Airflow 2 (`airflowVersion` < 3.0.0); the API 
server replaces it on 3.
+AIRFLOW_2 = "2.11.0"
+
+
+class TestHTTPRouteWebserver:
+    """Tests HTTPRoute Webserver (Kubernetes Gateway API)."""
+
+    def test_should_pass_validation_with_minimal_config(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert len(docs) == 1
+        assert jmespath.search("kind", docs[0]) == "HTTPRoute"
+        assert jmespath.search("metadata.name", docs[0]) == 
"release-name-webserver-httproute"
+
+    def test_should_set_api_version_and_kind(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("apiVersion", docs[0]) == 
"gateway.networking.k8s.io/v1"
+        assert jmespath.search("kind", docs[0]) == "HTTPRoute"
+
+    def test_should_allow_more_than_one_annotation(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "annotations": {"aa": "bb", "cc": "dd"},
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("metadata.annotations", docs[0]) == {"aa": 
"bb", "cc": "dd"}
+
+    def test_should_add_extra_labels(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "labels": {"custom": "value"},
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search('metadata.labels."custom"', docs[0]) == "value"
+
+    def test_should_pass_parent_refs_through(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": [
+                            {"name": "main-gateway", "namespace": 
"gateway-system"},
+                            {"name": "main-gateway", "namespace": 
"gateway-system", "sectionName": "https"},
+                        ],
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.parentRefs", docs[0]) == [
+            {"name": "main-gateway", "namespace": "gateway-system"},
+            {"name": "main-gateway", "namespace": "gateway-system", 
"sectionName": "https"},
+        ]
+
+    def test_should_set_hostnames(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "hostnames": ["airflow.example.com", 
"airflow2.example.com"],
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.hostnames", docs[0]) == [
+            "airflow.example.com",
+            "airflow2.example.com",
+        ]
+
+    def test_hostnames_should_be_templated(self):
+        docs = render_chart(
+            name="airflow",
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "hostnames": ["{{ .Release.Name }}.example.com"],
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.hostnames", docs[0]) == 
["airflow.example.com"]
+
+    def test_should_default_to_path_prefix_and_webserver_backend(self):
+        docs = render_chart(
+            name="my-release",
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.rules", docs[0]) == [
+            {
+                "matches": [{"path": {"type": "PathPrefix", "value": "/"}}],
+                "backendRefs": [{"name": "my-release-webserver", "port": 
8080}],
+            },
+        ]
+
+    def test_custom_path_and_path_type_should_apply(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "path": "/web",
+                        "pathType": "Exact",
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.rules[0].matches[0].path.type", docs[0]) 
== "Exact"
+        assert jmespath.search("spec.rules[0].matches[0].path.value", docs[0]) 
== "/web"
+
+    def test_custom_rules_override_default_rule(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "rules": [
+                            {
+                                "matches": [{"path": {"type": "PathPrefix", 
"value": "/custom-web"}}],
+                                "backendRefs": [{"name": "external-webserver", 
"port": 8443}],
+                            },
+                        ],
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.rules", docs[0]) == [
+            {
+                "matches": [{"path": {"type": "PathPrefix", "value": 
"/custom-web"}}],
+                "backendRefs": [{"name": "external-webserver", "port": 8443}],
+            },
+        ]
+
+    def test_should_use_airflow_ui_port_for_default_backend(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "ports": {"airflowUI": 9000},
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search("spec.rules[0].backendRefs[0].port", docs[0]) 
== 9000
+
+    def test_httproute_not_created_when_unset(self):
+        docs = render_chart(
+            values={"airflowVersion": AIRFLOW_2, "webserver": {"enabled": 
True, "httpRoute": {}}},
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert docs == []
+
+    def test_httproute_not_created_when_disabled(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {"enabled": True, "httpRoute": {"enabled": 
False}},
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert docs == []
+
+    def test_should_not_render_when_webserver_disabled(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "webserver": {
+                    "enabled": False,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert docs == []
+
+    def test_should_not_render_on_airflow_3(self):
+        docs = render_chart(
+            values={
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert docs == []
+
+    def test_should_fail_when_gateway_api_crd_missing(self):
+        with pytest.raises(HelmFailedError) as exc_info:
+            render_chart(
+                values={
+                    "airflowVersion": AIRFLOW_2,
+                    "webserver": {
+                        "enabled": True,
+                        "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                    },
+                },
+                show_only=SHOW_ONLY,
+            )
+        assert "Gateway API HTTPRoute CRD" in exc_info.value.stderr.decode()
+
+    def test_should_fail_when_parent_refs_empty(self):
+        with pytest.raises(HelmFailedError) as exc_info:
+            render_chart(
+                values={
+                    "airflowVersion": AIRFLOW_2,
+                    "webserver": {"enabled": True, "httpRoute": {"enabled": 
True}},
+                },
+                show_only=SHOW_ONLY,
+                api_versions=GATEWAY_API_VERSIONS,
+            )
+        assert "parentRefs" in exc_info.value.stderr.decode()
+
+    def test_should_fail_schema_when_parent_refs_explicitly_empty(self):
+        with pytest.raises(HelmFailedError) as exc_info:
+            render_chart(
+                values={
+                    "airflowVersion": AIRFLOW_2,
+                    "webserver": {"enabled": True, "httpRoute": {"enabled": 
True, "parentRefs": []}},
+                },
+                show_only=SHOW_ONLY,
+                api_versions=GATEWAY_API_VERSIONS,
+            )
+        assert "parentRefs" in exc_info.value.stderr.decode()
+
+    @pytest.mark.parametrize(
+        "ingress_values",
+        [{"web": {"enabled": True}}, {"enabled": True}],
+        ids=["web-ingress", "legacy-ingress"],
+    )
+    def test_should_fail_when_ingress_and_httproute_both_enabled(self, 
ingress_values):
+        with pytest.raises(HelmFailedError) as exc_info:
+            render_chart(
+                values={
+                    "airflowVersion": AIRFLOW_2,
+                    "ingress": ingress_values,
+                    "webserver": {"enabled": True, "httpRoute": {"enabled": 
True}},
+                },
+                show_only=SHOW_ONLY,
+                api_versions=GATEWAY_API_VERSIONS,
+            )
+        assert "enable only one of them" in exc_info.value.stderr.decode()
+
+    @pytest.mark.parametrize(
+        "ingress_values",
+        [{"web": {"enabled": True}}, {"enabled": True}],
+        ids=["web-ingress", "legacy-ingress"],
+    )
+    def 
test_should_fail_when_ingress_template_sees_ingress_and_httproute_both_enabled(self,
 ingress_values):
+        with pytest.raises(HelmFailedError) as exc_info:
+            render_chart(
+                values={
+                    "airflowVersion": AIRFLOW_2,
+                    "ingress": ingress_values,
+                    "webserver": {"enabled": True, "httpRoute": {"enabled": 
True}},
+                },
+                show_only=WEBSERVER_INGRESS_SHOW_ONLY,
+                api_versions=GATEWAY_API_VERSIONS,
+            )
+        assert "enable only one of them" in exc_info.value.stderr.decode()
+
+    def test_backend_service_name_with_fullname_override(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "fullnameOverride": "airflow-fullname-override",
+                "useStandardNaming": True,
+                "webserver": {
+                    "enabled": True,
+                    "httpRoute": {"enabled": True, "parentRefs": 
MINIMAL_PARENT_REFS},
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert (
+            jmespath.search("spec.rules[0].backendRefs[0].name", docs[0])
+            == "airflow-fullname-override-webserver"
+        )
+
+    def test_should_add_component_specific_labels(self):
+        docs = render_chart(
+            values={
+                "airflowVersion": AIRFLOW_2,
+                "labels": {"label1": "value1", "label2": "value2"},
+                "webserver": {
+                    "enabled": True,
+                    "labels": {"test_label": "test_label_value"},
+                    "httpRoute": {
+                        "enabled": True,
+                        "parentRefs": MINIMAL_PARENT_REFS,
+                        "labels": {"route_label": "route_value"},
+                    },
+                },
+            },
+            show_only=SHOW_ONLY,
+            api_versions=GATEWAY_API_VERSIONS,
+        )
+        assert jmespath.search('metadata.labels."label1"', docs[0]) == "value1"
+        assert jmespath.search('metadata.labels."test_label"', docs[0]) == 
"test_label_value"
+        assert jmespath.search('metadata.labels."route_label"', docs[0]) == 
"route_value"

Reply via email to