mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			228 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2024 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package validation
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation/field"
 | |
| 	"k8s.io/kubernetes/pkg/apis/storagemigration"
 | |
| 
 | |
| 	corev1 "k8s.io/api/core/v1"
 | |
| 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
 | |
| 	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
 | |
| )
 | |
| 
 | |
| func ValidateStorageVersionMigration(svm *storagemigration.StorageVersionMigration) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 	allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&svm.ObjectMeta, false, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...)
 | |
| 
 | |
| 	allErrs = checkAndAppendError(allErrs, field.NewPath("spec", "resource", "resource"), svm.Spec.Resource.Resource, "resource is required")
 | |
| 	allErrs = checkAndAppendError(allErrs, field.NewPath("spec", "resource", "version"), svm.Spec.Resource.Version, "version is required")
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| func ValidateStorageVersionMigrationUpdate(newSVMBundle, oldSVMBundle *storagemigration.StorageVersionMigration) field.ErrorList {
 | |
| 	allErrs := ValidateStorageVersionMigration(newSVMBundle)
 | |
| 	allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&newSVMBundle.ObjectMeta, &oldSVMBundle.ObjectMeta, field.NewPath("metadata"))...)
 | |
| 
 | |
| 	// prevent changes to the group, version and resource
 | |
| 	if newSVMBundle.Spec.Resource.Group != oldSVMBundle.Spec.Resource.Group {
 | |
| 		allErrs = append(allErrs, field.Invalid(field.NewPath("group"), newSVMBundle.Spec.Resource.Group, "field is immutable"))
 | |
| 	}
 | |
| 	if newSVMBundle.Spec.Resource.Version != oldSVMBundle.Spec.Resource.Version {
 | |
| 		allErrs = append(allErrs, field.Invalid(field.NewPath("version"), newSVMBundle.Spec.Resource.Version, "field is immutable"))
 | |
| 	}
 | |
| 	if newSVMBundle.Spec.Resource.Resource != oldSVMBundle.Spec.Resource.Resource {
 | |
| 		allErrs = append(allErrs, field.Invalid(field.NewPath("resource"), newSVMBundle.Spec.Resource.Resource, "field is immutable"))
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| func ValidateStorageVersionMigrationStatusUpdate(newSVMBundle, oldSVMBundle *storagemigration.StorageVersionMigration) field.ErrorList {
 | |
| 	allErrs := apivalidation.ValidateObjectMetaUpdate(&newSVMBundle.ObjectMeta, &oldSVMBundle.ObjectMeta, field.NewPath("metadata"))
 | |
| 
 | |
| 	fldPath := field.NewPath("status")
 | |
| 
 | |
| 	// resource version should be a non-negative integer
 | |
| 	rvInt, err := convertResourceVersionToInt(newSVMBundle.Status.ResourceVersion)
 | |
| 	if err != nil {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newSVMBundle.Status.ResourceVersion, err.Error()))
 | |
| 	}
 | |
| 	allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(rvInt, fldPath.Child("resourceVersion"))...)
 | |
| 
 | |
| 	// TODO: after switching to metav1.Conditions in beta replace this validation with metav1.ValidateConditions
 | |
| 	allErrs = append(allErrs, validateConditions(newSVMBundle.Status.Conditions, fldPath.Child("conditions"))...)
 | |
| 
 | |
| 	// resource version should not change once it has been set
 | |
| 	if len(oldSVMBundle.Status.ResourceVersion) != 0 && oldSVMBundle.Status.ResourceVersion != newSVMBundle.Status.ResourceVersion {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceVersion"), newSVMBundle.Status.ResourceVersion, "resourceVersion cannot be updated"))
 | |
| 	}
 | |
| 
 | |
| 	// at most one of success or failed may be true
 | |
| 	if isSuccessful(newSVMBundle) && isFailed(newSVMBundle) {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Both success and failed conditions cannot be true at the same time"))
 | |
| 	}
 | |
| 
 | |
| 	// running must be false when success is true or failed is true
 | |
| 	if isSuccessful(newSVMBundle) && isRunning(newSVMBundle) {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Running condition cannot be true when success condition is true"))
 | |
| 	}
 | |
| 	if isFailed(newSVMBundle) && isRunning(newSVMBundle) {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Running condition cannot be true when failed condition is true"))
 | |
| 	}
 | |
| 
 | |
| 	// success cannot be set to false once it is true
 | |
| 	isOldSuccessful := isSuccessful(oldSVMBundle)
 | |
| 	if isOldSuccessful && !isSuccessful(newSVMBundle) {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Success condition cannot be set to false once it is true"))
 | |
| 	}
 | |
| 	isOldFailed := isFailed(oldSVMBundle)
 | |
| 	if isOldFailed && !isFailed(newSVMBundle) {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), newSVMBundle.Status.Conditions, "Failed condition cannot be set to false once it is true"))
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| func isSuccessful(svm *storagemigration.StorageVersionMigration) bool {
 | |
| 	successCondition := getCondition(svm, storagemigration.MigrationSucceeded)
 | |
| 	if successCondition != nil && successCondition.Status == corev1.ConditionTrue {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func isFailed(svm *storagemigration.StorageVersionMigration) bool {
 | |
| 	failedCondition := getCondition(svm, storagemigration.MigrationFailed)
 | |
| 	if failedCondition != nil && failedCondition.Status == corev1.ConditionTrue {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func isRunning(svm *storagemigration.StorageVersionMigration) bool {
 | |
| 	runningCondition := getCondition(svm, storagemigration.MigrationRunning)
 | |
| 	if runningCondition != nil && runningCondition.Status == corev1.ConditionTrue {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func getCondition(svm *storagemigration.StorageVersionMigration, conditionType storagemigration.MigrationConditionType) *storagemigration.MigrationCondition {
 | |
| 	for _, c := range svm.Status.Conditions {
 | |
| 		if c.Type == conditionType {
 | |
| 			return &c
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func validateConditions(conditions []storagemigration.MigrationCondition, fldPath *field.Path) field.ErrorList {
 | |
| 	var allErrs field.ErrorList
 | |
| 
 | |
| 	conditionTypeToFirstIndex := map[string]int{}
 | |
| 	for i, condition := range conditions {
 | |
| 		if _, ok := conditionTypeToFirstIndex[string(condition.Type)]; ok {
 | |
| 			allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type))
 | |
| 		} else {
 | |
| 			conditionTypeToFirstIndex[string(condition.Type)] = i
 | |
| 		}
 | |
| 
 | |
| 		allErrs = append(allErrs, validateCondition(condition, fldPath.Index(i))...)
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| func validateCondition(condition storagemigration.MigrationCondition, fldPath *field.Path) field.ErrorList {
 | |
| 	var allErrs field.ErrorList
 | |
| 	var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown))
 | |
| 
 | |
| 	// type is set and is a valid format
 | |
| 	allErrs = append(allErrs, metav1validation.ValidateLabelName(string(condition.Type), fldPath.Child("type"))...)
 | |
| 
 | |
| 	// status is set and is an accepted value
 | |
| 	if !validConditionStatuses.Has(string(condition.Status)) {
 | |
| 		allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List()))
 | |
| 	}
 | |
| 
 | |
| 	if condition.LastUpdateTime.IsZero() {
 | |
| 		allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set"))
 | |
| 	}
 | |
| 
 | |
| 	if len(condition.Reason) == 0 {
 | |
| 		allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set"))
 | |
| 	} else {
 | |
| 		for _, currErr := range isValidConditionReason(condition.Reason) {
 | |
| 			allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr))
 | |
| 		}
 | |
| 
 | |
| 		const maxReasonLen int = 1 * 1024 // 1024
 | |
| 		if len(condition.Reason) > maxReasonLen {
 | |
| 			allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), "" /*unused*/, maxReasonLen))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	const maxMessageLen int = 32 * 1024 // 32768
 | |
| 	if len(condition.Message) > maxMessageLen {
 | |
| 		allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), "" /*unused*/, maxMessageLen))
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| func isValidConditionReason(value string) []string {
 | |
| 	const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?"
 | |
| 	const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'"
 | |
| 	var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$")
 | |
| 
 | |
| 	if !conditionReasonRegexp.MatchString(value) {
 | |
| 		return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func checkAndAppendError(allErrs field.ErrorList, fieldPath *field.Path, value string, message string) field.ErrorList {
 | |
| 	if len(value) == 0 {
 | |
| 		allErrs = append(allErrs, field.Required(fieldPath, message))
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| func convertResourceVersionToInt(rv string) (int64, error) {
 | |
| 	// initial value of RV is expected to be empty, which means the resource version is not set
 | |
| 	if len(rv) == 0 {
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 
 | |
| 	resourceVersion, err := strconv.ParseInt(rv, 10, 64)
 | |
| 	if err != nil {
 | |
| 		return 0, fmt.Errorf("failed to parse resource version %q: %w", rv, err)
 | |
| 	}
 | |
| 
 | |
| 	return resourceVersion, nil
 | |
| }
 | 
