istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/kstatus/helper.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kstatus
    16  
    17  import (
    18  	"reflect"
    19  
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  
    22  	"istio.io/istio/pkg/config"
    23  	"istio.io/istio/pkg/slices"
    24  )
    25  
    26  const (
    27  	StatusTrue  = "True"
    28  	StatusFalse = "False"
    29  )
    30  
    31  // InvertStatus returns the opposite of the provided status. If an invalid status is passed in, False is returned
    32  func InvertStatus(status metav1.ConditionStatus) metav1.ConditionStatus {
    33  	switch status {
    34  	case StatusFalse:
    35  		return StatusTrue
    36  	default:
    37  		return StatusFalse
    38  	}
    39  }
    40  
    41  // WrappedStatus provides a wrapper around a status message that keeps track of whether or not any
    42  // changes have been made. This allows users to declarative write status, without worrying about
    43  // tracking changes. When read to commit (typically to Kubernetes), any messages with Dirty=false can
    44  // be discarded.
    45  type WrappedStatus struct {
    46  	// Status is the object that is wrapped.
    47  	config.Status
    48  	// Dirty indicates if this object has been modified at all.
    49  	// Note: only changes wrapped in Mutate are tracked.
    50  	Dirty bool
    51  }
    52  
    53  func Wrap(s config.Status) *WrappedStatus {
    54  	return &WrappedStatus{config.DeepCopy(s), false}
    55  }
    56  
    57  func (w *WrappedStatus) Mutate(f func(s config.Status) config.Status) {
    58  	if w.Status == nil {
    59  		return
    60  	}
    61  	old := config.DeepCopy(w.Status)
    62  	w.Status = f(w.Status)
    63  	// TODO: change this to be more efficient. Likely we allow modifications via WrappedStatus that
    64  	// modify specific things (ie conditions).
    65  	if !reflect.DeepEqual(old, w.Status) {
    66  		w.Dirty = true
    67  	}
    68  }
    69  
    70  func (w *WrappedStatus) Unwrap() config.Status {
    71  	return w.Status
    72  }
    73  
    74  var EmptyCondition = metav1.Condition{}
    75  
    76  func GetCondition(conditions []metav1.Condition, condition string) metav1.Condition {
    77  	for _, cond := range conditions {
    78  		if cond.Type == condition {
    79  			return cond
    80  		}
    81  	}
    82  	return EmptyCondition
    83  }
    84  
    85  // UpdateConditionIfChanged updates a condition if it has been changed.
    86  func UpdateConditionIfChanged(conditions []metav1.Condition, condition metav1.Condition) []metav1.Condition {
    87  	ret := slices.Clone(conditions)
    88  	existing := slices.FindFunc(ret, func(cond metav1.Condition) bool {
    89  		return cond.Type == condition.Type
    90  	})
    91  	if existing == nil {
    92  		ret = append(ret, condition)
    93  		return ret
    94  	}
    95  
    96  	if existing.Status == condition.Status {
    97  		if existing.Message == condition.Message &&
    98  			existing.ObservedGeneration == condition.ObservedGeneration {
    99  			// Skip update, no changes
   100  			return conditions
   101  		}
   102  		// retain LastTransitionTime if status is not changed
   103  		condition.LastTransitionTime = existing.LastTransitionTime
   104  	}
   105  	*existing = condition
   106  
   107  	return ret
   108  }
   109  
   110  // CreateCondition sets a condition only if it has not already been set
   111  func CreateCondition(conditions []metav1.Condition, condition metav1.Condition, unsetReason string) []metav1.Condition {
   112  	ret := append([]metav1.Condition(nil), conditions...)
   113  	idx := -1
   114  	for i, cond := range ret {
   115  		if cond.Type == condition.Type {
   116  			idx = i
   117  			if cond.Reason == unsetReason {
   118  				// Condition is set, but its for unsetReason. This is needed because some conditions have defaults
   119  				ret[idx] = condition
   120  				return ret
   121  			}
   122  			break
   123  		}
   124  	}
   125  
   126  	if idx == -1 {
   127  		// Not found! We should set it
   128  		ret = append(ret, condition)
   129  	}
   130  	return ret
   131  }