github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/apis/apps/v1alpha1/clusterversion_webhook.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v1alpha1 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apimachinery/pkg/types" 28 "k8s.io/apimachinery/pkg/util/validation/field" 29 ctrl "sigs.k8s.io/controller-runtime" 30 logf "sigs.k8s.io/controller-runtime/pkg/log" 31 "sigs.k8s.io/controller-runtime/pkg/webhook" 32 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 33 ) 34 35 // log is for logging in this package. 36 var clusterversionlog = logf.Log.WithName("clusterversion-resource") 37 38 func (r *ClusterVersion) SetupWebhookWithManager(mgr ctrl.Manager) error { 39 return ctrl.NewWebhookManagedBy(mgr). 40 For(r). 41 Complete() 42 } 43 44 // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. 45 // +kubebuilder:webhook:path=/validate-apps-kubeblocks-io-v1alpha1-clusterversion,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps.kubeblocks.io,resources=clusterversions,verbs=create;update,versions=v1alpha1,name=vclusterversion.kb.io,admissionReviewVersions=v1 46 47 var _ webhook.Validator = &ClusterVersion{} 48 49 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type 50 func (r *ClusterVersion) ValidateCreate() (admission.Warnings, error) { 51 clusterversionlog.Info("validate create", "name", r.Name) 52 return nil, r.validate() 53 } 54 55 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type 56 func (r *ClusterVersion) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { 57 clusterversionlog.Info("validate update", "name", r.Name) 58 // determine whether r.spec content is modified 59 lastClusterVersion := old.(*ClusterVersion) 60 if !reflect.DeepEqual(lastClusterVersion.Spec, r.Spec) { 61 return nil, newInvalidError(ClusterVersionKind, r.Name, "", "ClusterVersion.spec is immutable, you can not update it.") 62 } 63 return nil, nil 64 } 65 66 // ValidateDelete implements webhook.Validator so a webhook will be registered for the type 67 func (r *ClusterVersion) ValidateDelete() (admission.Warnings, error) { 68 clusterversionlog.Info("validate delete", "name", r.Name) 69 return nil, nil 70 } 71 72 // Validate ClusterVersion.spec is legal 73 func (r *ClusterVersion) validate() error { 74 var ( 75 allErrs field.ErrorList 76 ctx = context.Background() 77 clusterDef = &ClusterDefinition{} 78 ) 79 if webhookMgr == nil { 80 return nil 81 } 82 err := webhookMgr.client.Get(ctx, types.NamespacedName{ 83 Namespace: r.Namespace, 84 Name: r.Spec.ClusterDefinitionRef, 85 }, clusterDef) 86 87 if err != nil { 88 allErrs = append(allErrs, field.Invalid(field.NewPath("spec.clusterDefinitionRef"), 89 r.Spec.ClusterDefinitionRef, err.Error())) 90 } else { 91 notFoundComponentDefNames, noContainersComponents := r.GetInconsistentComponentsInfo(clusterDef) 92 93 if len(notFoundComponentDefNames) > 0 { 94 allErrs = append(allErrs, field.NotFound(field.NewPath("spec.components[*].type"), 95 getComponentDefNotFoundMsg(notFoundComponentDefNames, r.Spec.ClusterDefinitionRef))) 96 } 97 98 if len(noContainersComponents) > 0 { 99 allErrs = append(allErrs, field.NotFound(field.NewPath("spec.components[*].type"), 100 fmt.Sprintf("containers are not defined in ClusterDefinition.spec.components[*]: %v", noContainersComponents))) 101 } 102 } 103 104 if err := r.validateConfigTemplate(); err != nil { 105 allErrs = append(allErrs, field.Duplicate(field.NewPath("spec.components[*].configTemplateRefs"), err)) 106 } 107 108 if len(allErrs) > 0 { 109 return apierrors.NewInvalid( 110 schema.GroupKind{Group: APIVersion, Kind: ClusterVersionKind}, 111 r.Name, allErrs) 112 } 113 return nil 114 } 115 116 func (r *ClusterVersion) validateConfigTemplate() error { 117 for _, c := range r.Spec.ComponentVersions { 118 if len(c.ConfigSpecs) > 1 { 119 return validateConfigTemplateList(c.ConfigSpecs) 120 } 121 } 122 return nil 123 } 124 125 // GetInconsistentComponentsInfo get clusterVersion invalid component name and no containers component compared with clusterDefinitionDef 126 func (r *ClusterVersion) GetInconsistentComponentsInfo(clusterDef *ClusterDefinition) ([]string, []string) { 127 128 var ( 129 // clusterDefinition components to map. the value of map represents whether there is a default containers 130 componentMap = map[string]bool{} 131 notFoundComponentDefNames = make([]string, 0) 132 noContainersComponent = make([]string, 0) 133 ) 134 135 for _, v := range clusterDef.Spec.ComponentDefs { 136 if v.PodSpec == nil || v.PodSpec.Containers == nil || len(v.PodSpec.Containers) == 0 { 137 componentMap[v.Name] = false 138 } else { 139 componentMap[v.Name] = true 140 } 141 } 142 // get not found component name in clusterDefinition 143 for _, v := range r.Spec.ComponentVersions { 144 if _, ok := componentMap[v.ComponentDefRef]; !ok { 145 notFoundComponentDefNames = append(notFoundComponentDefNames, v.ComponentDefRef) 146 } else if (len(v.VersionsCtx.Containers) > 0) || 147 (len(v.VersionsCtx.InitContainers) > 0) { 148 componentMap[v.ComponentDefRef] = true 149 } 150 } 151 // get no containers components in clusterDefinition and clusterVersion 152 for k, v := range componentMap { 153 if !v { 154 noContainersComponent = append(noContainersComponent, k) 155 } 156 } 157 return notFoundComponentDefNames, noContainersComponent 158 } 159 160 func getComponentDefNotFoundMsg(invalidComponentDefNames []string, clusterDefName string) string { 161 return fmt.Sprintf(" %v is not found in ClusterDefinition.spec.componentDefs[*].name of %s", 162 invalidComponentDefNames, clusterDefName) 163 } 164 165 // NewInvalidError create an invalid api error 166 func newInvalidError(kind, resourceName, path, reason string) error { 167 return apierrors.NewInvalid(schema.GroupKind{Group: APIVersion, Kind: kind}, 168 resourceName, field.ErrorList{field.InternalError(field.NewPath(path), 169 fmt.Errorf(reason))}) 170 }