sigs.k8s.io/cluster-api@v1.7.1/util/conditions/setter.go (about)

     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 conditions
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"time"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  )
    29  
    30  // Setter interface defines methods that a Cluster API object should implement in order to
    31  // use the conditions package for setting conditions.
    32  type Setter interface {
    33  	Getter
    34  	SetConditions(clusterv1.Conditions)
    35  }
    36  
    37  // Set sets the given condition.
    38  //
    39  // NOTE: If a condition already exists, the LastTransitionTime is updated only if a change is detected
    40  // in any of the following fields: Status, Reason, Severity and Message.
    41  func Set(to Setter, condition *clusterv1.Condition) {
    42  	if to == nil || condition == nil {
    43  		return
    44  	}
    45  
    46  	// Check if the new conditions already exists, and change it only if there is a status
    47  	// transition (otherwise we should preserve the current last transition time)-
    48  	conditions := to.GetConditions()
    49  	exists := false
    50  	for i := range conditions {
    51  		existingCondition := conditions[i]
    52  		if existingCondition.Type == condition.Type {
    53  			exists = true
    54  			if !hasSameState(&existingCondition, condition) {
    55  				condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
    56  				conditions[i] = *condition
    57  				break
    58  			}
    59  			condition.LastTransitionTime = existingCondition.LastTransitionTime
    60  			break
    61  		}
    62  	}
    63  
    64  	// If the condition does not exist, add it, setting the transition time only if not already set
    65  	if !exists {
    66  		if condition.LastTransitionTime.IsZero() {
    67  			condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second))
    68  		}
    69  		conditions = append(conditions, *condition)
    70  	}
    71  
    72  	// Sorts conditions for convenience of the consumer, i.e. kubectl.
    73  	sort.Slice(conditions, func(i, j int) bool {
    74  		return lexicographicLess(&conditions[i], &conditions[j])
    75  	})
    76  
    77  	to.SetConditions(conditions)
    78  }
    79  
    80  // TrueCondition returns a condition with Status=True and the given type.
    81  func TrueCondition(t clusterv1.ConditionType) *clusterv1.Condition {
    82  	return &clusterv1.Condition{
    83  		Type:   t,
    84  		Status: corev1.ConditionTrue,
    85  	}
    86  }
    87  
    88  // FalseCondition returns a condition with Status=False and the given type.
    89  func FalseCondition(t clusterv1.ConditionType, reason string, severity clusterv1.ConditionSeverity, messageFormat string, messageArgs ...interface{}) *clusterv1.Condition {
    90  	return &clusterv1.Condition{
    91  		Type:     t,
    92  		Status:   corev1.ConditionFalse,
    93  		Reason:   reason,
    94  		Severity: severity,
    95  		Message:  fmt.Sprintf(messageFormat, messageArgs...),
    96  	}
    97  }
    98  
    99  // UnknownCondition returns a condition with Status=Unknown and the given type.
   100  func UnknownCondition(t clusterv1.ConditionType, reason string, messageFormat string, messageArgs ...interface{}) *clusterv1.Condition {
   101  	return &clusterv1.Condition{
   102  		Type:    t,
   103  		Status:  corev1.ConditionUnknown,
   104  		Reason:  reason,
   105  		Message: fmt.Sprintf(messageFormat, messageArgs...),
   106  	}
   107  }
   108  
   109  // MarkTrue sets Status=True for the condition with the given type.
   110  func MarkTrue(to Setter, t clusterv1.ConditionType) {
   111  	Set(to, TrueCondition(t))
   112  }
   113  
   114  // MarkUnknown sets Status=Unknown for the condition with the given type.
   115  func MarkUnknown(to Setter, t clusterv1.ConditionType, reason, messageFormat string, messageArgs ...interface{}) {
   116  	Set(to, UnknownCondition(t, reason, messageFormat, messageArgs...))
   117  }
   118  
   119  // MarkFalse sets Status=False for the condition with the given type.
   120  func MarkFalse(to Setter, t clusterv1.ConditionType, reason string, severity clusterv1.ConditionSeverity, messageFormat string, messageArgs ...interface{}) {
   121  	Set(to, FalseCondition(t, reason, severity, messageFormat, messageArgs...))
   122  }
   123  
   124  // SetSummary sets a Ready condition with the summary of all the conditions existing
   125  // on an object. If the object does not have other conditions, no summary condition is generated.
   126  func SetSummary(to Setter, options ...MergeOption) {
   127  	Set(to, summary(to, options...))
   128  }
   129  
   130  // SetMirror creates a new condition by mirroring the Ready condition from a dependent object;
   131  // if the Ready condition does not exists in the source object, no target conditions is generated.
   132  func SetMirror(to Setter, targetCondition clusterv1.ConditionType, from Getter, options ...MirrorOptions) {
   133  	Set(to, mirror(from, targetCondition, options...))
   134  }
   135  
   136  // SetAggregate creates a new condition with the aggregation of all the Ready condition
   137  // from a list of dependent objects; if the Ready condition does not exists in one of the source object,
   138  // the object is excluded from the aggregation; if none of the source object have ready condition,
   139  // no target conditions is generated.
   140  func SetAggregate(to Setter, targetCondition clusterv1.ConditionType, from []Getter, options ...MergeOption) {
   141  	Set(to, aggregate(from, targetCondition, options...))
   142  }
   143  
   144  // Delete deletes the condition with the given type.
   145  func Delete(to Setter, t clusterv1.ConditionType) {
   146  	if to == nil {
   147  		return
   148  	}
   149  
   150  	conditions := to.GetConditions()
   151  	newConditions := make(clusterv1.Conditions, 0, len(conditions))
   152  	for _, condition := range conditions {
   153  		if condition.Type != t {
   154  			newConditions = append(newConditions, condition)
   155  		}
   156  	}
   157  	to.SetConditions(newConditions)
   158  }
   159  
   160  // lexicographicLess returns true if a condition is less than another with regards to the
   161  // to order of conditions designed for convenience of the consumer, i.e. kubectl.
   162  // According to this order the Ready condition always goes first, followed by all the other
   163  // conditions sorted by Type.
   164  func lexicographicLess(i, j *clusterv1.Condition) bool {
   165  	return (i.Type == clusterv1.ReadyCondition || i.Type < j.Type) && j.Type != clusterv1.ReadyCondition
   166  }
   167  
   168  // hasSameState returns true if a condition has the same state of another; state is defined
   169  // by the union of following fields: Type, Status, Reason, Severity and Message (it excludes LastTransitionTime).
   170  func hasSameState(i, j *clusterv1.Condition) bool {
   171  	return i.Type == j.Type &&
   172  		i.Status == j.Status &&
   173  		i.Reason == j.Reason &&
   174  		i.Severity == j.Severity &&
   175  		i.Message == j.Message
   176  }