...

Source file src/k8s.io/kubernetes/pkg/apis/apiserverinternal/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/apiserverinternal/validation

     1  /*
     2  Copyright 2020 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 validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    24  	"k8s.io/apimachinery/pkg/util/sets"
    25  	utilvalidation "k8s.io/apimachinery/pkg/util/validation"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	"k8s.io/kubernetes/pkg/apis/apiserverinternal"
    28  	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    29  )
    30  
    31  // ValidateStorageVersion validate the storage version object.
    32  func ValidateStorageVersion(sv *apiserverinternal.StorageVersion) field.ErrorList {
    33  	var allErrs field.ErrorList
    34  	allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&sv.ObjectMeta, false, ValidateStorageVersionName, field.NewPath("metadata"))...)
    35  	allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...)
    36  	return allErrs
    37  }
    38  
    39  // ValidateStorageVersionName is a ValidateNameFunc for storage version names
    40  func ValidateStorageVersionName(name string, prefix bool) []string {
    41  	var allErrs []string
    42  	idx := strings.LastIndex(name, ".")
    43  	if idx < 0 {
    44  		allErrs = append(allErrs, "name must be in the form of <group>.<resource>")
    45  	} else {
    46  		for _, msg := range utilvalidation.IsDNS1123Subdomain(name[:idx]) {
    47  			allErrs = append(allErrs, "the group segment "+msg)
    48  		}
    49  		for _, msg := range utilvalidation.IsDNS1035Label(name[idx+1:]) {
    50  			allErrs = append(allErrs, "the resource segment "+msg)
    51  		}
    52  	}
    53  	return allErrs
    54  }
    55  
    56  // ValidateStorageVersionUpdate tests if an update to a StorageVersion is valid.
    57  func ValidateStorageVersionUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList {
    58  	// no error since StorageVersionSpec is an empty spec
    59  	return field.ErrorList{}
    60  }
    61  
    62  // ValidateStorageVersionStatusUpdate tests if an update to a StorageVersionStatus is valid.
    63  func ValidateStorageVersionStatusUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList {
    64  	var allErrs field.ErrorList
    65  	allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...)
    66  	return allErrs
    67  }
    68  
    69  func validateStorageVersionStatus(ss apiserverinternal.StorageVersionStatus, fldPath *field.Path) field.ErrorList {
    70  	var allErrs field.ErrorList
    71  	allAPIServerIDs := sets.New[string]()
    72  	for i, ssv := range ss.StorageVersions {
    73  		if allAPIServerIDs.Has(ssv.APIServerID) {
    74  			allErrs = append(allErrs, field.Duplicate(fldPath.Child("storageVersions").Index(i).Child("apiServerID"), ssv.APIServerID))
    75  		} else {
    76  			allAPIServerIDs.Insert(ssv.APIServerID)
    77  		}
    78  		allErrs = append(allErrs, validateServerStorageVersion(ssv, fldPath.Child("storageVersions").Index(i))...)
    79  	}
    80  	if err := validateCommonVersion(ss, fldPath); err != nil {
    81  		allErrs = append(allErrs, err)
    82  	}
    83  	allErrs = append(allErrs, validateStorageVersionCondition(ss.Conditions, fldPath)...)
    84  	return allErrs
    85  }
    86  
    87  func validateServerStorageVersion(ssv apiserverinternal.ServerStorageVersion, fldPath *field.Path) field.ErrorList {
    88  	allErrs := field.ErrorList{}
    89  	for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(ssv.APIServerID, false) {
    90  		allErrs = append(allErrs, field.Invalid(fldPath.Child("apiServerID"), ssv.APIServerID, msg))
    91  	}
    92  	if errs := isValidAPIVersion(ssv.EncodingVersion); len(errs) > 0 {
    93  		allErrs = append(allErrs, field.Invalid(fldPath.Child("encodingVersion"), ssv.EncodingVersion, strings.Join(errs, ",")))
    94  	}
    95  
    96  	found := false
    97  	for i, dv := range ssv.DecodableVersions {
    98  		if errs := isValidAPIVersion(dv); len(errs) > 0 {
    99  			allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions").Index(i), dv, strings.Join(errs, ",")))
   100  		}
   101  		if dv == ssv.EncodingVersion {
   102  			found = true
   103  		}
   104  	}
   105  	if !found {
   106  		allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions"), ssv.DecodableVersions, fmt.Sprintf("decodableVersions must include encodingVersion %s", ssv.EncodingVersion)))
   107  	}
   108  
   109  	for i, sv := range ssv.ServedVersions {
   110  		if errs := isValidAPIVersion(sv); len(errs) > 0 {
   111  			allErrs = append(allErrs, field.Invalid(fldPath.Child("servedVersions").Index(i), sv, strings.Join(errs, ",")))
   112  		}
   113  		foundDecodableVersion := false
   114  		for _, dv := range ssv.DecodableVersions {
   115  			if sv == dv {
   116  				foundDecodableVersion = true
   117  				break
   118  			}
   119  		}
   120  		if !foundDecodableVersion {
   121  			allErrs = append(allErrs, field.Invalid(fldPath.Child("servedVersions").Index(i), sv, fmt.Sprintf("individual served version : %s must be included in decodableVersions : %s", sv, ssv.DecodableVersions)))
   122  		}
   123  	}
   124  	return allErrs
   125  }
   126  
   127  func commonVersion(ssv []apiserverinternal.ServerStorageVersion) *string {
   128  	if len(ssv) == 0 {
   129  		return nil
   130  	}
   131  	commonVersion := ssv[0].EncodingVersion
   132  	for _, v := range ssv[1:] {
   133  		if v.EncodingVersion != commonVersion {
   134  			return nil
   135  		}
   136  	}
   137  	return &commonVersion
   138  }
   139  
   140  func validateCommonVersion(svs apiserverinternal.StorageVersionStatus, fldPath *field.Path) *field.Error {
   141  	actualCommonVersion := commonVersion(svs.StorageVersions)
   142  	if actualCommonVersion == nil && svs.CommonEncodingVersion == nil {
   143  		return nil
   144  	}
   145  	if actualCommonVersion == nil && svs.CommonEncodingVersion != nil {
   146  		return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet")
   147  	}
   148  	if actualCommonVersion != nil && svs.CommonEncodingVersion == nil {
   149  		return field.Invalid(fldPath.Child("commonEncodingVersion"), svs.CommonEncodingVersion, fmt.Sprintf("the common encoding version is %s", *actualCommonVersion))
   150  	}
   151  	if actualCommonVersion != nil && svs.CommonEncodingVersion != nil && *actualCommonVersion != *svs.CommonEncodingVersion {
   152  		return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, fmt.Sprintf("the actual common encoding version is %s", *actualCommonVersion))
   153  	}
   154  	return nil
   155  }
   156  
   157  func validateStorageVersionCondition(conditions []apiserverinternal.StorageVersionCondition, fldPath *field.Path) field.ErrorList {
   158  	allErrs := field.ErrorList{}
   159  	// We do not verify that the condition type or the condition status is
   160  	// a predefined one because we might add more type or status later.
   161  	seenType := make(map[apiserverinternal.StorageVersionConditionType]int)
   162  	for i, condition := range conditions {
   163  		if ii, ok := seenType[condition.Type]; ok {
   164  			allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), string(condition.Type),
   165  				fmt.Sprintf("the type of the condition is not unique, it also appears in conditions[%d]", ii)))
   166  		}
   167  		seenType[condition.Type] = i
   168  		allErrs = append(allErrs, apivalidation.ValidateQualifiedName(string(condition.Type), fldPath.Index(i).Child("type"))...)
   169  		allErrs = append(allErrs, apivalidation.ValidateQualifiedName(string(condition.Status), fldPath.Index(i).Child("status"))...)
   170  		if condition.Reason == "" {
   171  			allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("reason"), "reason cannot be empty"))
   172  		}
   173  		if condition.Message == "" {
   174  			allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("message"), "message cannot be empty"))
   175  		}
   176  	}
   177  	return allErrs
   178  }
   179  
   180  const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
   181  const dns1035LabelErrMsg string = "a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
   182  
   183  // isValidAPIVersion tests whether the value passed is a valid apiVersion. A
   184  // valid apiVersion contains a version string that matches DNS_LABEL format,
   185  // with an optional group/ prefix, where the group string matches DNS_SUBDOMAIN
   186  // format. If the value is not valid, a list of error strings is returned.
   187  // Otherwise an empty list (or nil) is returned.
   188  func isValidAPIVersion(apiVersion string) []string {
   189  	var errs []string
   190  	parts := strings.Split(apiVersion, "/")
   191  	var version string
   192  	switch len(parts) {
   193  	case 1:
   194  		version = parts[0]
   195  	case 2:
   196  		var group string
   197  		group, version = parts[0], parts[1]
   198  		if len(group) == 0 {
   199  			errs = append(errs, "group part: "+utilvalidation.EmptyError())
   200  		} else if msgs := utilvalidation.IsDNS1123Subdomain(group); len(msgs) != 0 {
   201  			errs = append(errs, prefixEach(msgs, "group part: ")...)
   202  		}
   203  	default:
   204  		return append(errs, "an apiVersion is "+utilvalidation.RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123")+
   205  			" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')")
   206  	}
   207  
   208  	if len(version) == 0 {
   209  		errs = append(errs, "version part: "+utilvalidation.EmptyError())
   210  	} else if msgs := utilvalidation.IsDNS1035Label(version); len(msgs) != 0 {
   211  		errs = append(errs, prefixEach(msgs, "version part: ")...)
   212  	}
   213  	return errs
   214  }
   215  
   216  func prefixEach(msgs []string, prefix string) []string {
   217  	for i := range msgs {
   218  		msgs[i] = prefix + msgs[i]
   219  	}
   220  	return msgs
   221  }
   222  

View as plain text