...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go

Documentation: k8s.io/apiextensions-apiserver/pkg/registry/customresource

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     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 customresource
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math"
    23  	"strings"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	"k8s.io/apimachinery/pkg/api/validation"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	apimachineryvalidation "k8s.io/apimachinery/pkg/util/validation"
    33  	"k8s.io/apimachinery/pkg/util/validation/field"
    34  
    35  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    36  	apiextensionsvalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
    37  )
    38  
    39  type customResourceValidator struct {
    40  	namespaceScoped       bool
    41  	kind                  schema.GroupVersionKind
    42  	schemaValidator       apiextensionsvalidation.SchemaValidator
    43  	statusSchemaValidator apiextensionsvalidation.SchemaValidator
    44  }
    45  
    46  func (a customResourceValidator) Validate(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
    47  	if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
    48  		return errs
    49  	}
    50  
    51  	var allErrs field.ErrorList
    52  
    53  	allErrs = append(allErrs, validation.ValidateObjectMetaAccessor(obj, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
    54  	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResource(nil, obj.UnstructuredContent(), a.schemaValidator)...)
    55  	allErrs = append(allErrs, a.ValidateScaleSpec(ctx, obj, scale)...)
    56  	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
    57  
    58  	return allErrs
    59  }
    60  
    61  func (a customResourceValidator) ValidateUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale, options ...apiextensionsvalidation.ValidationOption) field.ErrorList {
    62  	if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
    63  		return errs
    64  	}
    65  
    66  	var allErrs field.ErrorList
    67  
    68  	allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
    69  	allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, obj.UnstructuredContent(), old.UnstructuredContent(), a.schemaValidator, options...)...)
    70  	allErrs = append(allErrs, a.ValidateScaleSpec(ctx, obj, scale)...)
    71  	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
    72  
    73  	return allErrs
    74  }
    75  
    76  var standardFinalizers = sets.NewString(
    77  	metav1.FinalizerOrphanDependents,
    78  	metav1.FinalizerDeleteDependents,
    79  	string(corev1.FinalizerKubernetes),
    80  )
    81  
    82  func validateKubeFinalizerName(stringValue string, fldPath *field.Path) []string {
    83  	var allWarnings []string
    84  	for _, msg := range apimachineryvalidation.IsQualifiedName(stringValue) {
    85  		allWarnings = append(allWarnings, fmt.Sprintf("%s: %q: %s", fldPath.String(), stringValue, msg))
    86  	}
    87  	if len(strings.Split(stringValue, "/")) == 1 {
    88  		if !standardFinalizers.Has(stringValue) {
    89  			allWarnings = append(allWarnings, fmt.Sprintf("%s: %q: prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers", fldPath.String(), stringValue))
    90  		}
    91  	}
    92  	return allWarnings
    93  }
    94  
    95  func (a customResourceValidator) ValidateStatusUpdate(ctx context.Context, obj, old *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
    96  	if errs := a.ValidateTypeMeta(ctx, obj); len(errs) > 0 {
    97  		return errs
    98  	}
    99  
   100  	var allErrs field.ErrorList
   101  
   102  	allErrs = append(allErrs, validation.ValidateObjectMetaAccessorUpdate(obj, old, field.NewPath("metadata"))...)
   103  	if status, hasStatus := obj.UnstructuredContent()["status"]; hasStatus {
   104  		allErrs = append(allErrs, apiextensionsvalidation.ValidateCustomResourceUpdate(nil, status, old.UnstructuredContent()["status"], a.statusSchemaValidator)...)
   105  	}
   106  	allErrs = append(allErrs, a.ValidateScaleStatus(ctx, obj, scale)...)
   107  
   108  	return allErrs
   109  }
   110  
   111  func (a customResourceValidator) ValidateTypeMeta(ctx context.Context, obj *unstructured.Unstructured) field.ErrorList {
   112  	typeAccessor, err := meta.TypeAccessor(obj)
   113  	if err != nil {
   114  		return field.ErrorList{field.Invalid(field.NewPath("kind"), nil, err.Error())}
   115  	}
   116  
   117  	var allErrs field.ErrorList
   118  	if typeAccessor.GetKind() != a.kind.Kind {
   119  		allErrs = append(allErrs, field.Invalid(field.NewPath("kind"), typeAccessor.GetKind(), fmt.Sprintf("must be %v", a.kind.Kind)))
   120  	}
   121  	if typeAccessor.GetAPIVersion() != a.kind.Group+"/"+a.kind.Version {
   122  		allErrs = append(allErrs, field.Invalid(field.NewPath("apiVersion"), typeAccessor.GetAPIVersion(), fmt.Sprintf("must be %v", a.kind.Group+"/"+a.kind.Version)))
   123  	}
   124  	return allErrs
   125  }
   126  
   127  func (a customResourceValidator) ValidateScaleSpec(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
   128  	if scale == nil {
   129  		return nil
   130  	}
   131  
   132  	var allErrs field.ErrorList
   133  
   134  	// validate specReplicas
   135  	specReplicasPath := strings.TrimPrefix(scale.SpecReplicasPath, ".") // ignore leading period
   136  	specReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(specReplicasPath, ".")...)
   137  	if err != nil {
   138  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, err.Error()))
   139  	} else if specReplicas < 0 {
   140  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, "should be a non-negative integer"))
   141  	} else if specReplicas > math.MaxInt32 {
   142  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.SpecReplicasPath), specReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
   143  	}
   144  
   145  	return allErrs
   146  }
   147  
   148  func (a customResourceValidator) ValidateScaleStatus(ctx context.Context, obj *unstructured.Unstructured, scale *apiextensions.CustomResourceSubresourceScale) field.ErrorList {
   149  	if scale == nil {
   150  		return nil
   151  	}
   152  
   153  	var allErrs field.ErrorList
   154  
   155  	// validate statusReplicas
   156  	statusReplicasPath := strings.TrimPrefix(scale.StatusReplicasPath, ".") // ignore leading period
   157  	statusReplicas, _, err := unstructured.NestedInt64(obj.UnstructuredContent(), strings.Split(statusReplicasPath, ".")...)
   158  	if err != nil {
   159  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, err.Error()))
   160  	} else if statusReplicas < 0 {
   161  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, "should be a non-negative integer"))
   162  	} else if statusReplicas > math.MaxInt32 {
   163  		allErrs = append(allErrs, field.Invalid(field.NewPath(scale.StatusReplicasPath), statusReplicas, fmt.Sprintf("should be less than or equal to %v", math.MaxInt32)))
   164  	}
   165  
   166  	// validate labelSelector
   167  	if scale.LabelSelectorPath != nil {
   168  		labelSelectorPath := strings.TrimPrefix(*scale.LabelSelectorPath, ".") // ignore leading period
   169  		labelSelector, _, err := unstructured.NestedString(obj.UnstructuredContent(), strings.Split(labelSelectorPath, ".")...)
   170  		if err != nil {
   171  			allErrs = append(allErrs, field.Invalid(field.NewPath(*scale.LabelSelectorPath), labelSelector, err.Error()))
   172  		}
   173  	}
   174  
   175  	return allErrs
   176  }
   177  

View as plain text