sigs.k8s.io/cluster-api-provider-azure@v1.14.3/util/webhook/validator.go (about)

     1  /*
     2  Copyright 2021 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 webhook
    18  
    19  import (
    20  	"reflect"
    21  	"sort"
    22  
    23  	"k8s.io/apimachinery/pkg/util/validation/field"
    24  )
    25  
    26  const (
    27  	unsetMessage     = "field is immutable, unable to set an empty value if it was already set"
    28  	setMessage       = "field is immutable, unable to assign a value if it was already empty"
    29  	immutableMessage = "field is immutable"
    30  )
    31  
    32  // ValidateImmutable validates equality across two values,
    33  // and returns a meaningful error to indicate a changed value, a newly set value, or a newly unset value.
    34  func ValidateImmutable(path *field.Path, oldVal, newVal any) *field.Error {
    35  	if reflect.TypeOf(oldVal) != reflect.TypeOf(newVal) {
    36  		return field.Invalid(path, newVal, "unexpected error")
    37  	}
    38  	if !reflect.ValueOf(oldVal).IsZero() {
    39  		// Prevent modification if it was already set to some value
    40  		if reflect.ValueOf(newVal).IsZero() {
    41  			// unsetting the field is not allowed
    42  			return field.Invalid(path, newVal, unsetMessage)
    43  		}
    44  		if !reflect.DeepEqual(oldVal, newVal) {
    45  			// changing the field is not allowed
    46  			return field.Invalid(path, newVal, immutableMessage)
    47  		}
    48  	} else if !reflect.ValueOf(newVal).IsZero() {
    49  		return field.Invalid(path, newVal, setMessage)
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  // ValidateZeroTransition validates equality across two values, with only exception to allow
    56  // the value to transition of a zero value.
    57  func ValidateZeroTransition(path *field.Path, oldVal, newVal any) *field.Error {
    58  	if reflect.ValueOf(newVal).IsZero() {
    59  		// unsetting the field is allowed
    60  		return nil
    61  	}
    62  	return ValidateImmutable(path, oldVal, newVal)
    63  }
    64  
    65  // EnsureStringSlicesAreEquivalent returns if two string slices have equal lengths,
    66  // and that they have the exact same items; it does not enforce strict ordering of items.
    67  func EnsureStringSlicesAreEquivalent(a []string, b []string) bool {
    68  	if len(a) != len(b) {
    69  		return false
    70  	}
    71  
    72  	sort.Strings(a)
    73  	sort.Strings(b)
    74  
    75  	for i := range a {
    76  		if a[i] != b[i] {
    77  			return false
    78  		}
    79  	}
    80  
    81  	return true
    82  }