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,