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  }