135 lines
5.2 KiB
Go
135 lines
5.2 KiB
Go
package multiclusterservice
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
|
|
admissionv1 "k8s.io/api/admission/v1"
|
|
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
validation "k8s.io/apimachinery/pkg/util/validation"
|
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
|
|
|
clustervalidation "github.com/karmada-io/karmada/pkg/apis/cluster/validation"
|
|
networkingv1alpha1 "github.com/karmada-io/karmada/pkg/apis/networking/v1alpha1"
|
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
|
)
|
|
|
|
// ValidatingAdmission validates MultiClusterService object when creating/updating.
|
|
type ValidatingAdmission struct {
|
|
decoder *admission.Decoder
|
|
}
|
|
|
|
// Check if our ValidatingAdmission implements necessary interface
|
|
var _ admission.Handler = &ValidatingAdmission{}
|
|
var _ admission.DecoderInjector = &ValidatingAdmission{}
|
|
|
|
// Handle implements admission.Handler interface.
|
|
// It yields a response to an AdmissionRequest.
|
|
func (v *ValidatingAdmission) Handle(ctx context.Context, req admission.Request) admission.Response {
|
|
mcs := &networkingv1alpha1.MultiClusterService{}
|
|
err := v.decoder.Decode(req, mcs)
|
|
if err != nil {
|
|
return admission.Errored(http.StatusBadRequest, err)
|
|
}
|
|
klog.Infof("Validating MultiClusterService(%s/%s) for request: %s", mcs.Namespace, mcs.Name, req.Operation)
|
|
|
|
if req.Operation == admissionv1.Update {
|
|
oldMcs := &networkingv1alpha1.MultiClusterService{}
|
|
err = v.decoder.DecodeRaw(req.OldObject, oldMcs)
|
|
if err != nil {
|
|
return admission.Errored(http.StatusBadRequest, err)
|
|
}
|
|
if errs := v.validateMCSUpdate(oldMcs, mcs); len(errs) != 0 {
|
|
klog.Errorf("Validating MultiClusterServiceUpdate failed: %v", errs)
|
|
return admission.Denied(errs.ToAggregate().Error())
|
|
}
|
|
} else {
|
|
if errs := v.validateMCS(mcs); len(errs) != 0 {
|
|
klog.Errorf("Validating MultiClusterService failed: %v", errs)
|
|
return admission.Denied(errs.ToAggregate().Error())
|
|
}
|
|
}
|
|
return admission.Allowed("")
|
|
}
|
|
|
|
// InjectDecoder implements admission.DecoderInjector interface.
|
|
// A decoder will be automatically injected.
|
|
func (v *ValidatingAdmission) InjectDecoder(d *admission.Decoder) error {
|
|
v.decoder = d
|
|
return nil
|
|
}
|
|
|
|
func (v *ValidatingAdmission) validateMCSUpdate(oldMcs, newMcs *networkingv1alpha1.MultiClusterService) field.ErrorList {
|
|
allErrs := apimachineryvalidation.ValidateObjectMetaUpdate(&newMcs.ObjectMeta, &oldMcs.ObjectMeta, field.NewPath("metadata"))
|
|
allErrs = append(allErrs, v.validateMCS(newMcs)...)
|
|
allErrs = append(allErrs, lifted.ValidateLoadBalancerStatus(&newMcs.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...)
|
|
return allErrs
|
|
}
|
|
|
|
func (v *ValidatingAdmission) validateMCS(mcs *networkingv1alpha1.MultiClusterService) field.ErrorList {
|
|
allErrs := apimachineryvalidation.ValidateObjectMeta(&mcs.ObjectMeta, true,
|
|
apimachineryvalidation.NameIsDNS1035Label, field.NewPath("metadata"))
|
|
allErrs = append(allErrs, v.validateMultiClusterServiceSpec(mcs)...)
|
|
return allErrs
|
|
}
|
|
|
|
// validateMultiClusterServiceSpec validates MultiClusterService spec.
|
|
func (v *ValidatingAdmission) validateMultiClusterServiceSpec(mcs *networkingv1alpha1.MultiClusterService) field.ErrorList {
|
|
allErrs := field.ErrorList{}
|
|
specPath := field.NewPath("spec")
|
|
allPortNames := sets.New[string]()
|
|
|
|
portsPath := specPath.Child("ports")
|
|
for i := range mcs.Spec.Ports {
|
|
portPath := portsPath.Index(i)
|
|
port := mcs.Spec.Ports[i]
|
|
allErrs = append(allErrs, v.validateExposurePort(&port, allPortNames, portPath)...)
|
|
}
|
|
typesPath := specPath.Child("types")
|
|
for i := range mcs.Spec.Ports {
|
|
typePath := typesPath.Index(i)
|
|
exposureType := mcs.Spec.Types[i]
|
|
allErrs = append(allErrs, v.validateExposureType(&exposureType, typePath)...)
|
|
}
|
|
clusterNamesPath := specPath.Child("range").Child("clusterNames")
|
|
for i := range mcs.Spec.Range.ClusterNames {
|
|
clusterNamePath := clusterNamesPath.Index(i)
|
|
clusterName := mcs.Spec.Range.ClusterNames[i]
|
|
if errMegs := clustervalidation.ValidateClusterName(clusterName); len(errMegs) > 0 {
|
|
allErrs = append(allErrs, field.Invalid(clusterNamePath, clusterName, strings.Join(errMegs, ",")))
|
|
}
|
|
}
|
|
return allErrs
|
|
}
|
|
|
|
// validateExposurePort validates MultiClusterService ExposurePort.
|
|
func (v *ValidatingAdmission) validateExposurePort(ep *networkingv1alpha1.ExposurePort, allNames sets.Set[string], fldPath *field.Path) field.ErrorList {
|
|
allErrs := field.ErrorList{}
|
|
if len(ep.Name) != 0 {
|
|
allErrs = append(allErrs, lifted.ValidateDNS1123Label(ep.Name, fldPath.Child("name"))...)
|
|
if allNames.Has(ep.Name) {
|
|
allErrs = append(allErrs, field.Duplicate(fldPath.Child("name"), ep.Name))
|
|
} else {
|
|
allNames.Insert(ep.Name)
|
|
}
|
|
}
|
|
for _, msg := range validation.IsValidPortNum(int(ep.Port)) {
|
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), ep.Port, msg))
|
|
}
|
|
return allErrs
|
|
}
|
|
|
|
// validateExposureType validates MultiClusterService ExposureType.
|
|
func (v *ValidatingAdmission) validateExposureType(et *networkingv1alpha1.ExposureType, fldPath *field.Path) field.ErrorList {
|
|
allErrs := field.ErrorList{}
|
|
if *et != networkingv1alpha1.ExposureTypeCrossCluster && *et != networkingv1alpha1.ExposureTypeLoadBalancer {
|
|
msg := "ExposureType Error"
|
|
allErrs = append(allErrs, field.Invalid(fldPath, *et, msg))
|
|
}
|
|
return allErrs
|
|
}
|