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

ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 3b3bb2ca feat: add webhook for ingressclass and gateway (#2572)
3b3bb2ca is described below

commit 3b3bb2ca6f678a0cbe9e3c2dc16b3f152a0a2fbd
Author: Ashing Zheng <[email protected]>
AuthorDate: Wed Sep 24 15:34:29 2025 +0800

    feat: add webhook for ingressclass and gateway (#2572)
    
    Signed-off-by: Ashing Zheng <[email protected]>
---
 .github/workflows/apisix-e2e-test.yml       |   7 +-
 PROJECT                                     |  17 ++++
 config/webhook/manifests.yaml               |  40 +++++++++
 internal/manager/webhooks.go                |   6 ++
 internal/webhook/v1/gateway_webhook.go      | 130 ++++++++++++++++++++++++++++
 internal/webhook/v1/ingressclass_webhook.go | 126 +++++++++++++++++++++++++++
 test/e2e/framework/manifests/webhook.yaml   |  52 +++++++++--
 test/e2e/gatewayapi/webhook.go              |  94 ++++++++++++++++++++
 test/e2e/ingress/ingressclass_webhook.go    |  83 ++++++++++++++++++
 test/e2e/ingress/webhook.go                 |   2 +-
 10 files changed, 550 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/apisix-e2e-test.yml 
b/.github/workflows/apisix-e2e-test.yml
index 7db3c8c5..45e0b070 100644
--- a/.github/workflows/apisix-e2e-test.yml
+++ b/.github/workflows/apisix-e2e-test.yml
@@ -42,6 +42,7 @@ jobs:
         cases_subset:
         - apisix.apache.org
         - networking.k8s.io
+        - webhook
       fail-fast: false
     runs-on: ubuntu-latest
     steps:
@@ -110,4 +111,8 @@ jobs:
           TEST_LABEL: ${{ matrix.cases_subset }}
           TEST_ENV: CI
         run: |
-          make ginkgo-e2e-test
+          if [[ "${{ matrix.cases_subset }}" == "webhook" ]]; then
+            E2E_NODES=1 make ginkgo-e2e-test
+          else
+            make ginkgo-e2e-test
+          fi
diff --git a/PROJECT b/PROJECT
index a9b4a155..cb21ee36 100644
--- a/PROJECT
+++ b/PROJECT
@@ -78,4 +78,21 @@ resources:
   webhooks:
     validation: true
     webhookVersion: v1
+- core: true
+  domain: k8s.io
+  group: networking
+  kind: IngressClass
+  path: k8s.io/api/networking/v1
+  version: v1
+  webhooks:
+    validation: true
+    webhookVersion: v1
+- external: true
+  group: gateway.networking.k8s.io
+  kind: Gateway
+  path: sigs.k8s.io/gateway-api/apis/v1
+  version: v1
+  webhooks:
+    validation: true
+    webhookVersion: v1
 version: "3"
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index 8632054a..efcc2fca 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -4,6 +4,26 @@ kind: ValidatingWebhookConfiguration
 metadata:
   name: validating-webhook-configuration
 webhooks:
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: system
+      path: /validate-gateway-networking-k8s-io-v1-gateway
+  failurePolicy: Fail
+  name: vgateway-v1.kb.io
+  rules:
+  - apiGroups:
+    - gateway.networking.k8s.io
+    apiVersions:
+    - v1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - gateways
+  sideEffects: None
 - admissionReviewVersions:
   - v1
   clientConfig:
@@ -24,3 +44,23 @@ webhooks:
     resources:
     - ingresses
   sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: system
+      path: /validate-networking-k8s-io-v1-ingressclass
+  failurePolicy: Fail
+  name: vingressclass-v1.kb.io
+  rules:
+  - apiGroups:
+    - networking.k8s.io
+    apiVersions:
+    - v1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - ingressclasses
+  sideEffects: None
diff --git a/internal/manager/webhooks.go b/internal/manager/webhooks.go
index 0a67f07a..07b6f381 100644
--- a/internal/manager/webhooks.go
+++ b/internal/manager/webhooks.go
@@ -29,5 +29,11 @@ func setupWebhooks(_ context.Context, mgr manager.Manager) 
error {
        if err := webhookv1.SetupIngressWebhookWithManager(mgr); err != nil {
                return err
        }
+       if err := webhookv1.SetupIngressClassWebhookWithManager(mgr); err != 
nil {
+               return err
+       }
+       if err := webhookv1.SetupGatewayWebhookWithManager(mgr); err != nil {
+               return err
+       }
        return nil
 }
diff --git a/internal/webhook/v1/gateway_webhook.go 
b/internal/webhook/v1/gateway_webhook.go
new file mode 100644
index 00000000..e2c11ff4
--- /dev/null
+++ b/internal/webhook/v1/gateway_webhook.go
@@ -0,0 +1,130 @@
+// 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.
+
+package v1
+
+import (
+       "context"
+       "fmt"
+
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       logf "sigs.k8s.io/controller-runtime/pkg/log"
+       "sigs.k8s.io/controller-runtime/pkg/webhook"
+       "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+       gatewaynetworkingk8siov1 "sigs.k8s.io/gateway-api/apis/v1"
+
+       v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       "github.com/apache/apisix-ingress-controller/internal/controller/config"
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
+)
+
+// nolint:unused
+// log is for logging in this package.
+var gatewaylog = logf.Log.WithName("gateway-resource")
+
+// SetupGatewayWebhookWithManager registers the webhook for Gateway in the 
manager.
+func SetupGatewayWebhookWithManager(mgr ctrl.Manager) error {
+       return 
ctrl.NewWebhookManagedBy(mgr).For(&gatewaynetworkingk8siov1.Gateway{}).
+               WithValidator(&GatewayCustomValidator{Client: mgr.GetClient()}).
+               Complete()
+}
+
+// NOTE: The 'path' attribute must follow a specific pattern and should not be 
modified directly here.
+// Modifying the path for an invalid path can cause API server errors; failing 
to locate the webhook.
+// 
+kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-gateway,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=gateways,verbs=create;update,versions=v1,name=vgateway-v1.kb.io,admissionReviewVersions=v1
+
+// GatewayCustomValidator struct is responsible for validating the Gateway 
resource
+// when it is created, updated, or deleted.
+//
+// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen 
from generating DeepCopy methods,
+// as this struct is used only for temporary operations and does not need to 
be deeply copied.
+type GatewayCustomValidator struct {
+       Client client.Client
+}
+
+var _ webhook.CustomValidator = &GatewayCustomValidator{}
+
+// ValidateCreate implements webhook.CustomValidator so a webhook will be 
registered for the type Gateway.
+func (v *GatewayCustomValidator) ValidateCreate(ctx context.Context, obj 
runtime.Object) (admission.Warnings, error) {
+       gateway, ok := obj.(*gatewaynetworkingk8siov1.Gateway)
+       if !ok {
+               return nil, fmt.Errorf("expected a Gateway object but got %T", 
obj)
+       }
+       gatewaylog.Info("Validation for Gateway upon creation", "name", 
gateway.GetName())
+
+       warnings := v.warnIfMissingGatewayProxyForGateway(ctx, gateway)
+
+       return warnings, nil
+}
+
+// ValidateUpdate implements webhook.CustomValidator so a webhook will be 
registered for the type Gateway.
+func (v *GatewayCustomValidator) ValidateUpdate(ctx context.Context, oldObj, 
newObj runtime.Object) (admission.Warnings, error) {
+       gateway, ok := newObj.(*gatewaynetworkingk8siov1.Gateway)
+       if !ok {
+               return nil, fmt.Errorf("expected a Gateway object for the 
newObj but got %T", newObj)
+       }
+       gatewaylog.Info("Validation for Gateway upon update", "name", 
gateway.GetName())
+
+       warnings := v.warnIfMissingGatewayProxyForGateway(ctx, gateway)
+
+       return warnings, nil
+}
+
+// ValidateDelete implements webhook.CustomValidator so a webhook will be 
registered for the type Gateway.
+func (v *GatewayCustomValidator) ValidateDelete(_ context.Context, obj 
runtime.Object) (admission.Warnings, error) {
+       return nil, nil
+}
+
+func (v *GatewayCustomValidator) warnIfMissingGatewayProxyForGateway(ctx 
context.Context, gateway *gatewaynetworkingk8siov1.Gateway) admission.Warnings {
+       var warnings admission.Warnings
+
+       // get gateway class
+       gatewayClass := &gatewaynetworkingk8siov1.GatewayClass{}
+       if err := v.Client.Get(ctx, client.ObjectKey{Name: 
string(gateway.Spec.GatewayClassName)}, gatewayClass); err != nil {
+               gatewaylog.Error(err, "failed to get gateway class", "gateway", 
gateway.GetName(), "gatewayclass", gateway.Spec.GatewayClassName)
+               return nil
+       }
+       // match controller
+       if string(gatewayClass.Spec.ControllerName) != 
config.ControllerConfig.ControllerName {
+               return nil
+       }
+
+       infra := gateway.Spec.Infrastructure
+       if infra == nil || infra.ParametersRef == nil {
+               return nil
+       }
+       ref := infra.ParametersRef
+       if string(ref.Group) != v1alpha1.GroupVersion.Group || string(ref.Kind) 
!= internaltypes.KindGatewayProxy {
+               return nil
+       }
+
+       ns := gateway.GetNamespace()
+       name := ref.Name
+
+       var gp v1alpha1.GatewayProxy
+       if err := v.Client.Get(ctx, client.ObjectKey{Namespace: ns, Name: 
name}, &gp); err != nil {
+               if k8serrors.IsNotFound(err) {
+                       msg := fmt.Sprintf("Referenced GatewayProxy '%s/%s' not 
found.", ns, name)
+                       warnings = append(warnings, msg)
+                       gatewaylog.Info("Gateway references missing 
GatewayProxy", "gateway", gateway.GetName(), "namespace", ns, "gatewayproxy", 
name)
+               } else {
+                       gatewaylog.Error(err, "failed to resolve GatewayProxy 
for Gateway", "gateway", gateway.GetName(), "namespace", ns, "gatewayproxy", 
name)
+               }
+       }
+       return warnings
+}
diff --git a/internal/webhook/v1/ingressclass_webhook.go 
b/internal/webhook/v1/ingressclass_webhook.go
new file mode 100644
index 00000000..06ae18e9
--- /dev/null
+++ b/internal/webhook/v1/ingressclass_webhook.go
@@ -0,0 +1,126 @@
+// 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.
+
+package v1
+
+import (
+       "context"
+       "fmt"
+
+       networkingv1 "k8s.io/api/networking/v1"
+       k8serrors "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/runtime"
+       ctrl "sigs.k8s.io/controller-runtime"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+       logf "sigs.k8s.io/controller-runtime/pkg/log"
+       "sigs.k8s.io/controller-runtime/pkg/webhook"
+       "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+       v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       "github.com/apache/apisix-ingress-controller/internal/controller/config"
+       internaltypes 
"github.com/apache/apisix-ingress-controller/internal/types"
+)
+
+// nolint:unused
+// log is for logging in this package.
+var ingressclasslog = logf.Log.WithName("ingressclass-resource")
+
+// SetupIngressClassWebhookWithManager registers the webhook for IngressClass 
in the manager.
+func SetupIngressClassWebhookWithManager(mgr ctrl.Manager) error {
+       return ctrl.NewWebhookManagedBy(mgr).For(&networkingv1.IngressClass{}).
+               WithValidator(&IngressClassCustomValidator{Client: 
mgr.GetClient()}).
+               Complete()
+}
+
+// NOTE: The 'path' attribute must follow a specific pattern and should not be 
modified directly here.
+// Modifying the path for an invalid path can cause API server errors; failing 
to locate the webhook.
+// 
+kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingressclass,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingressclasses,verbs=create;update,versions=v1,name=vingressclass-v1.kb.io,admissionReviewVersions=v1
+
+// IngressClassCustomValidator struct is responsible for validating the 
IngressClass resource
+// when it is created, updated, or deleted.
+//
+// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen 
from generating DeepCopy methods,
+// as this struct is used only for temporary operations and does not need to 
be deeply copied.
+type IngressClassCustomValidator struct {
+       Client client.Client
+}
+
+var _ webhook.CustomValidator = &IngressClassCustomValidator{}
+
+// ValidateCreate implements webhook.CustomValidator so a webhook will be 
registered for the type IngressClass.
+func (v *IngressClassCustomValidator) ValidateCreate(ctx context.Context, obj 
runtime.Object) (admission.Warnings, error) {
+       ingressclass, ok := obj.(*networkingv1.IngressClass)
+       if !ok {
+               return nil, fmt.Errorf("expected a IngressClass object but got 
%T", obj)
+       }
+       ingressclasslog.Info("Validation for IngressClass upon creation", 
"name", ingressclass.GetName())
+
+       warnings := v.warnIfMissingGatewayProxyForIngressClass(ctx, 
ingressclass)
+
+       return warnings, nil
+}
+
+// ValidateUpdate implements webhook.CustomValidator so a webhook will be 
registered for the type IngressClass.
+func (v *IngressClassCustomValidator) ValidateUpdate(ctx context.Context, 
oldObj, newObj runtime.Object) (admission.Warnings, error) {
+       ingressclass, ok := newObj.(*networkingv1.IngressClass)
+       if !ok {
+               return nil, fmt.Errorf("expected a IngressClass object for the 
newObj but got %T", newObj)
+       }
+       ingressclasslog.Info("Validation for IngressClass upon update", "name", 
ingressclass.GetName())
+
+       warnings := v.warnIfMissingGatewayProxyForIngressClass(ctx, 
ingressclass)
+
+       return warnings, nil
+}
+
+// ValidateDelete implements webhook.CustomValidator so a webhook will be 
registered for the type IngressClass.
+func (v *IngressClassCustomValidator) ValidateDelete(_ context.Context, obj 
runtime.Object) (admission.Warnings, error) {
+       return nil, nil
+}
+
+func (v *IngressClassCustomValidator) 
warnIfMissingGatewayProxyForIngressClass(ctx context.Context, ingressClass 
*networkingv1.IngressClass) admission.Warnings {
+       var warnings admission.Warnings
+
+       // match controller
+       if ingressClass.Spec.Controller != 
config.ControllerConfig.ControllerName {
+               return nil
+       }
+
+       params := ingressClass.Spec.Parameters
+       if params == nil || params.APIGroup == nil {
+               return nil
+       }
+       if *params.APIGroup != v1alpha1.GroupVersion.Group || params.Kind != 
internaltypes.KindGatewayProxy {
+               return nil
+       }
+
+       ns := ingressClass.GetNamespace()
+       if params.Namespace != nil && *params.Namespace != "" {
+               ns = *params.Namespace
+       }
+       name := params.Name
+
+       var gp v1alpha1.GatewayProxy
+       if err := v.Client.Get(ctx, client.ObjectKey{Namespace: ns, Name: 
name}, &gp); err != nil {
+               if k8serrors.IsNotFound(err) {
+                       msg := fmt.Sprintf("Referenced GatewayProxy '%s/%s' not 
found.", ns, name)
+                       warnings = append(warnings, msg)
+                       ingressclasslog.Info("IngressClass references missing 
GatewayProxy", "ingressclass", ingressClass.GetName(), "namespace", ns, 
"gatewayproxy", name)
+               } else {
+                       ingressclasslog.Error(err, "failed to resolve 
GatewayProxy for IngressClass", "ingressclass", ingressClass.GetName(), 
"namespace", ns, "gatewayproxy", name)
+               }
+       }
+       return warnings
+}
diff --git a/test/e2e/framework/manifests/webhook.yaml 
b/test/e2e/framework/manifests/webhook.yaml
index 4d96e454..432873c9 100644
--- a/test/e2e/framework/manifests/webhook.yaml
+++ b/test/e2e/framework/manifests/webhook.yaml
@@ -27,17 +27,59 @@ webhooks:
       namespace: {{ .Namespace }}
       path: /validate-networking-k8s-io-v1-ingress
     caBundle: {{ .CABundle }}
-  admissionReviewVersions: 
+  admissionReviewVersions:
     - v1
   rules:
-  - operations: 
+  - operations:
       - CREATE
       - UPDATE
-    apiGroups: 
+    apiGroups:
       - networking.k8s.io
-    apiVersions: 
+    apiVersions:
       - v1
-    resources: 
+    resources:
       - ingresses
   failurePolicy: Fail
   sideEffects: None
+- name: vingressclass-v1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-networking-k8s-io-v1-ingressclass
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - ingressclasses
+  failurePolicy: Fail
+  sideEffects: None
+- name: vgateway-v1.kb.io
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: {{ .Namespace }}
+      path: /validate-gateway-networking-k8s-io-v1-gateway
+    caBundle: {{ .CABundle }}
+  admissionReviewVersions:
+    - v1
+  rules:
+    - operations:
+        - CREATE
+        - UPDATE
+      apiGroups:
+        - gateway.networking.k8s.io
+      apiVersions:
+        - v1
+      resources:
+        - gateways
+  failurePolicy: Fail
+  sideEffects: None
diff --git a/test/e2e/gatewayapi/webhook.go b/test/e2e/gatewayapi/webhook.go
new file mode 100644
index 00000000..92f65f93
--- /dev/null
+++ b/test/e2e/gatewayapi/webhook.go
@@ -0,0 +1,94 @@
+// 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.
+
+package gatewayapi
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test Gateway Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "gateway-webhook-test",
+               EnableWebhook: true,
+       })
+
+       It("should warn when referenced GatewayProxy does not exist on create 
and update", func() {
+               By("creating GatewayClass with controller name")
+               err := s.CreateResourceFromString(s.GetGatewayClassYaml())
+               Expect(err).ShouldNot(HaveOccurred())
+
+               time.Sleep(2 * time.Second)
+
+               By("creating Gateway referencing a missing GatewayProxy")
+               missingName := "missing-proxy"
+               gwYAML := `
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: %s
+spec:
+  gatewayClassName: %s
+  listeners:
+  - name: http1
+    protocol: HTTP
+    port: 80
+  infrastructure:
+    parametersRef:
+      group: apisix.apache.org
+      kind: GatewayProxy
+      name: %s
+`
+
+               output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gwYAML, s.Namespace(), 
s.Namespace(), missingName))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced GatewayProxy '%s/%s' not found.", s.Namespace(), missingName)))
+
+               time.Sleep(2 * time.Second)
+
+               By("updating Gateway to reference another missing GatewayProxy")
+               missingName2 := "missing-proxy-2"
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gwYAML, s.Namespace(), 
s.Namespace(), missingName2))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: 
Referenced GatewayProxy '%s/%s' not found.", s.Namespace(), missingName2)))
+
+               By("create GatewayProxy")
+               err = s.CreateResourceFromString(s.GetGatewayProxySpec())
+               Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
+               time.Sleep(5 * time.Second)
+
+               By("updating Gateway to reference an existing GatewayProxy")
+               existingName := "apisix-proxy-config"
+               output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(gwYAML, s.Namespace(), 
s.Namespace(), existingName))
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: 
Referenced GatewayProxy '%s/%s' not found.", s.Namespace(), existingName)))
+
+               By("delete Gateway")
+               err = s.DeleteResource("Gateway", s.Namespace())
+               Expect(err).ShouldNot(HaveOccurred())
+
+               By("delete GatewayClass")
+               err = s.DeleteResource("GatewayClass", s.Namespace())
+               Expect(err).ShouldNot(HaveOccurred())
+       })
+})
diff --git a/test/e2e/ingress/ingressclass_webhook.go 
b/test/e2e/ingress/ingressclass_webhook.go
new file mode 100644
index 00000000..9c1f3174
--- /dev/null
+++ b/test/e2e/ingress/ingressclass_webhook.go
@@ -0,0 +1,83 @@
+// 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.
+
+package ingress
+
+import (
+       "fmt"
+       "time"
+
+       . "github.com/onsi/ginkgo/v2"
+       . "github.com/onsi/gomega"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = Describe("Test IngressClass Webhook", Label("webhook"), func() {
+       s := scaffold.NewScaffold(scaffold.Options{
+               Name:          "ingressclass-webhook-test",
+               EnableWebhook: true,
+       })
+
+       Context("IngressClass Validation", func() {
+               It("should warn when referenced GatewayProxy does not exist on 
create and update", func() {
+                       By("creating IngressClass referencing a missing 
GatewayProxy")
+                       missingName := "missing-proxy"
+                       icYAML := `
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  name: apisix-with-missing
+spec:
+  controller: "%s"
+  parameters:
+    apiGroup: "apisix.apache.org"
+    kind: "GatewayProxy"
+    name: "%s"
+    namespace: "%s"
+    scope: "Namespace"
+`
+
+                       output, err := 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(icYAML, 
s.GetControllerName(), missingName, s.Namespace()))
+                       Expect(err).ShouldNot(HaveOccurred())
+                       
Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: Referenced 
GatewayProxy '%s/%s' not found.", s.Namespace(), missingName)))
+
+                       time.Sleep(2 * time.Second)
+
+                       By("updating IngressClass to reference another missing 
GatewayProxy")
+                       missingName2 := "missing-proxy-2"
+                       output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(icYAML, 
s.GetControllerName(), missingName2, s.Namespace()))
+                       Expect(err).ShouldNot(HaveOccurred())
+                       
Expect(output).To(ContainSubstring(fmt.Sprintf("Warning: Referenced 
GatewayProxy '%s/%s' not found.", s.Namespace(), missingName2)))
+
+                       By("create GatewayProxy")
+                       err = 
s.CreateResourceFromString(s.GetGatewayProxySpec())
+                       Expect(err).NotTo(HaveOccurred(), "creating 
GatewayProxy")
+                       time.Sleep(5 * time.Second)
+
+                       By("updating IngressClass to reference an existing 
GatewayProxy")
+                       existingName := "apisix-proxy-config"
+                       output, err = 
s.CreateResourceFromStringAndGetOutput(fmt.Sprintf(icYAML, 
s.GetControllerName(), existingName, s.Namespace()))
+                       Expect(err).ShouldNot(HaveOccurred())
+                       
Expect(output).NotTo(ContainSubstring(fmt.Sprintf("Warning: Referenced 
GatewayProxy '%s/%s' not found.", s.Namespace(), existingName)))
+
+                       By("deleting IngressClass")
+                       err = s.DeleteResource("IngressClass", 
"apisix-with-missing")
+                       Expect(err).ShouldNot(HaveOccurred())
+               })
+       })
+})
diff --git a/test/e2e/ingress/webhook.go b/test/e2e/ingress/webhook.go
index 2f8c9eff..310b6629 100644
--- a/test/e2e/ingress/webhook.go
+++ b/test/e2e/ingress/webhook.go
@@ -28,7 +28,7 @@ import (
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
 )
 
-var _ = Describe("Test Ingress Webhook", Label("networking.k8s.io", 
"ingress"), func() {
+var _ = Describe("Test Ingress Webhook", Label("webhook"), func() {
        s := scaffold.NewScaffold(scaffold.Options{
                Name:          "webhook-test",
                EnableWebhook: true,

Reply via email to