This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch feat/add_resource_verify in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit d50dea80f26700e7b1afe484116445cdeae37bad Author: Ashing Zheng <[email protected]> AuthorDate: Fri Sep 26 15:31:41 2025 +0800 fix: checker Signed-off-by: Ashing Zheng <[email protected]> --- internal/webhook/v1/gatewayproxy_webhook.go | 78 +++++++------------- internal/webhook/v1/reference/checker.go | 107 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 52 deletions(-) diff --git a/internal/webhook/v1/gatewayproxy_webhook.go b/internal/webhook/v1/gatewayproxy_webhook.go index 0abd5e34..aaa8a1de 100644 --- a/internal/webhook/v1/gatewayproxy_webhook.go +++ b/internal/webhook/v1/gatewayproxy_webhook.go @@ -19,9 +19,8 @@ import ( "context" "fmt" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -29,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1" + "github.com/apache/apisix-ingress-controller/internal/webhook/v1/reference" ) var gatewayProxyLog = logf.Log.WithName("gatewayproxy-resource") @@ -73,61 +73,35 @@ func (v *GatewayProxyCustomValidator) ValidateDelete(context.Context, runtime.Ob } func (v *GatewayProxyCustomValidator) collectWarnings(ctx context.Context, gp *v1alpha1.GatewayProxy) admission.Warnings { - var warnings admission.Warnings - - warnings = append(warnings, v.warnIfProviderServiceMissing(ctx, gp)...) - warnings = append(warnings, v.warnIfAdminKeySecretMissing(ctx, gp)...) - - return warnings -} + checker := reference.NewChecker(v.Client, gatewayProxyLog) -func (v *GatewayProxyCustomValidator) warnIfProviderServiceMissing(ctx context.Context, gp *v1alpha1.GatewayProxy) admission.Warnings { - if gp.Spec.Provider == nil || gp.Spec.Provider.ControlPlane == nil || gp.Spec.Provider.ControlPlane.Service == nil { - return nil - } + var warnings admission.Warnings - svcRef := gp.Spec.Provider.ControlPlane.Service - key := client.ObjectKey{Namespace: gp.GetNamespace(), Name: svcRef.Name} - var svc corev1.Service - if err := v.Client.Get(ctx, key, &svc); err != nil { - if k8serrors.IsNotFound(err) { - msg := fmt.Sprintf("Referenced Service '%s/%s' not found at spec.provider.controlPlane.service", key.Namespace, key.Name) - gatewayProxyLog.Info("GatewayProxy references missing Service", "gatewayproxy", gp.GetName(), "namespace", key.Namespace, "service", key.Name) - return admission.Warnings{msg} + if gp.Spec.Provider != nil && gp.Spec.Provider.ControlPlane != nil { + if svc := gp.Spec.Provider.ControlPlane.Service; svc != nil { + warnings = append(warnings, checker.Service(ctx, reference.ServiceRef{ + Object: gp, + NamespacedName: types.NamespacedName{ + Namespace: gp.GetNamespace(), + Name: svc.Name, + }, + })...) } - gatewayProxyLog.Error(err, "failed to resolve Service for GatewayProxy", "gatewayproxy", gp.GetName(), "namespace", key.Namespace, "service", key.Name) - } - return nil -} - -func (v *GatewayProxyCustomValidator) warnIfAdminKeySecretMissing(ctx context.Context, gp *v1alpha1.GatewayProxy) admission.Warnings { - if gp.Spec.Provider == nil || gp.Spec.Provider.ControlPlane == nil { - return nil - } - - auth := gp.Spec.Provider.ControlPlane.Auth - if auth.Type != v1alpha1.AuthTypeAdminKey || auth.AdminKey == nil || auth.AdminKey.ValueFrom == nil || auth.AdminKey.ValueFrom.SecretKeyRef == nil { - return nil - } - ref := auth.AdminKey.ValueFrom.SecretKeyRef - key := client.ObjectKey{Namespace: gp.GetNamespace(), Name: ref.Name} - var secret corev1.Secret - if err := v.Client.Get(ctx, key, &secret); err != nil { - if k8serrors.IsNotFound(err) { - msg := fmt.Sprintf("Referenced Secret '%s/%s' not found at spec.provider.controlPlane.auth.adminKey.valueFrom.secretKeyRef", key.Namespace, key.Name) - gatewayProxyLog.Info("GatewayProxy references missing Secret", "gatewayproxy", gp.GetName(), "namespace", key.Namespace, "secret", key.Name) - return admission.Warnings{msg} + auth := gp.Spec.Provider.ControlPlane.Auth + if auth.Type == v1alpha1.AuthTypeAdminKey && auth.AdminKey != nil && auth.AdminKey.ValueFrom != nil && auth.AdminKey.ValueFrom.SecretKeyRef != nil { + secretRef := auth.AdminKey.ValueFrom.SecretKeyRef + key := secretRef.Key + warnings = append(warnings, checker.Secret(ctx, reference.SecretRef{ + Object: gp, + NamespacedName: types.NamespacedName{ + Namespace: gp.GetNamespace(), + Name: secretRef.Name, + }, + Key: &key, + })...) } - gatewayProxyLog.Error(err, "failed to resolve Secret for GatewayProxy", "gatewayproxy", gp.GetName(), "namespace", key.Namespace, "secret", key.Name) - return nil - } - - if _, ok := secret.Data[ref.Key]; !ok { - msg := fmt.Sprintf("Secret key '%s' not found in Secret '%s/%s' at spec.provider.controlPlane.auth.adminKey.valueFrom.secretKeyRef", ref.Key, key.Namespace, key.Name) - gatewayProxyLog.Info("GatewayProxy references Secret without required key", "gatewayproxy", gp.GetName(), "namespace", key.Namespace, "secret", key.Name, "key", ref.Key) - return admission.Warnings{msg} } - return nil + return warnings } diff --git a/internal/webhook/v1/reference/checker.go b/internal/webhook/v1/reference/checker.go new file mode 100644 index 00000000..7d80c83b --- /dev/null +++ b/internal/webhook/v1/reference/checker.go @@ -0,0 +1,107 @@ +// 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 reference + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// ServiceRef captures the information needed to validate a Service reference. +type ServiceRef struct { + Object client.Object + NamespacedName types.NamespacedName +} + +// SecretRef captures the information needed to validate a Secret reference. +type SecretRef struct { + Object client.Object + NamespacedName types.NamespacedName + Key *string +} + +// Checker performs reference lookups and returns admission warnings on failure. +type Checker struct { + client client.Client + log logr.Logger +} + +// NewChecker constructs a Checker instance. +func NewChecker(c client.Client, log logr.Logger) Checker { + return Checker{client: c, log: log} +} + +// Service ensures the referenced Service exists and returns warnings when it does not. +func (c Checker) Service(ctx context.Context, ref ServiceRef) admission.Warnings { + if ref.NamespacedName.Name == "" || ref.NamespacedName.Namespace == "" { + return nil + } + + var svc corev1.Service + if err := c.client.Get(ctx, ref.NamespacedName, &svc); err != nil { + if k8serrors.IsNotFound(err) { + msg := fmt.Sprintf("Referenced Service '%s/%s' not found", ref.NamespacedName.Namespace, ref.NamespacedName.Name) + return admission.Warnings{msg} + } + c.log.Error(err, "Failed to get Service", + "ownerKind", ref.Object.GetObjectKind().GroupVersionKind().Kind, + "ownerNamespace", ref.Object.GetNamespace(), + "ownerName", ref.Object.GetName(), + "serviceNamespace", ref.NamespacedName.Namespace, + "serviceName", ref.NamespacedName.Name, + ) + } + return nil +} + +// Secret ensures the referenced Secret (and optional key) exists and returns warnings when missing. +func (c Checker) Secret(ctx context.Context, ref SecretRef) admission.Warnings { + if ref.NamespacedName.Name == "" || ref.NamespacedName.Namespace == "" { + return nil + } + + var secret corev1.Secret + if err := c.client.Get(ctx, ref.NamespacedName, &secret); err != nil { + if k8serrors.IsNotFound(err) { + msg := fmt.Sprintf("Referenced Secret '%s/%s' not found", ref.NamespacedName.Namespace, ref.NamespacedName.Name) + return admission.Warnings{msg} + } + c.log.Error(err, "Failed to get Secret", + "ownerKind", ref.Object.GetObjectKind().GroupVersionKind().Kind, + "ownerNamespace", ref.Object.GetNamespace(), + "ownerName", ref.Object.GetName(), + "secretNamespace", ref.NamespacedName.Namespace, + "secretName", ref.NamespacedName.Name, + ) + return nil + } + + if ref.Key != nil { + if _, ok := secret.Data[*ref.Key]; !ok { + msg := fmt.Sprintf("Secret key '%s' not found in Secret '%s/%s'", *ref.Key, ref.NamespacedName.Namespace, ref.NamespacedName.Name) + return admission.Warnings{msg} + } + } + + return nil +}
