sigs.k8s.io/cluster-api@v1.7.1/util/conditions/merge_strategies.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  	"strings"
    22  
    23  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    24  )
    25  
    26  // mergeOptions allows to set strategies for merging a set of conditions into a single condition,
    27  // and more specifically for computing the target Reason and the target Message.
    28  type mergeOptions struct {
    29  	conditionTypes                     []clusterv1.ConditionType
    30  	addSourceRef                       bool
    31  	addStepCounter                     bool
    32  	addStepCounterIfOnlyConditionTypes []clusterv1.ConditionType
    33  	stepCounter                        int
    34  }
    35  
    36  // MergeOption defines an option for computing a summary of conditions.
    37  type MergeOption func(*mergeOptions)
    38  
    39  // WithConditions instructs merge about the condition types to consider when doing a merge operation;
    40  // if this option is not specified, all the conditions (excepts Ready) will be considered. This is required
    41  // so we can provide some guarantees about the semantic of the target condition without worrying about
    42  // side effects if someone or something adds custom conditions to the objects.
    43  //
    44  // NOTE: The order of conditions types defines the priority for determining the Reason and Message for the
    45  // target condition.
    46  // IMPORTANT: This options works only while generating the Summary condition.
    47  func WithConditions(t ...clusterv1.ConditionType) MergeOption {
    48  	return func(c *mergeOptions) {
    49  		c.conditionTypes = t
    50  	}
    51  }
    52  
    53  // WithStepCounter instructs merge to add a "x of y completed" string to the message,
    54  // where x is the number of conditions with Status=true and y is the number of conditions in scope.
    55  func WithStepCounter() MergeOption {
    56  	return func(c *mergeOptions) {
    57  		c.addStepCounter = true
    58  	}
    59  }
    60  
    61  // WithStepCounterIf adds a step counter if the value is true.
    62  // This can be used e.g. to add a step counter only if the object is not being deleted.
    63  //
    64  // IMPORTANT: This options works only while generating the Summary condition.
    65  func WithStepCounterIf(value bool) MergeOption {
    66  	return func(c *mergeOptions) {
    67  		c.addStepCounter = value
    68  	}
    69  }
    70  
    71  // WithStepCounterIfOnly ensure a step counter is show only if a subset of condition exists.
    72  // This applies for example on Machines, where we want to use
    73  // the step counter notation while provisioning the machine, but then we want to move away from this notation
    74  // as soon as the machine is provisioned and e.g. a Machine health check condition is generated
    75  //
    76  // IMPORTANT: This options requires WithStepCounter or WithStepCounterIf to be set.
    77  // IMPORTANT: This options works only while generating the Summary condition.
    78  func WithStepCounterIfOnly(t ...clusterv1.ConditionType) MergeOption {
    79  	return func(c *mergeOptions) {
    80  		c.addStepCounterIfOnlyConditionTypes = t
    81  	}
    82  }
    83  
    84  // AddSourceRef instructs merge to add info about the originating object to the target Reason.
    85  func AddSourceRef() MergeOption {
    86  	return func(c *mergeOptions) {
    87  		c.addSourceRef = true
    88  	}
    89  }
    90  
    91  // getReason returns the reason to be applied to the condition resulting by merging a set of condition groups.
    92  // The reason is computed according to the given mergeOptions.
    93  func getReason(groups conditionGroups, options *mergeOptions) string {
    94  	return getFirstReason(groups, options.conditionTypes, options.addSourceRef)
    95  }
    96  
    97  // getFirstReason returns the first reason from the ordered list of conditions in the top group.
    98  // If required, the reason gets localized with the source object reference.
    99  func getFirstReason(g conditionGroups, order []clusterv1.ConditionType, addSourceRef bool) string {
   100  	if condition := getFirstCondition(g, order); condition != nil {
   101  		reason := condition.Reason
   102  		if addSourceRef {
   103  			return localizeReason(reason, condition.Getter)
   104  		}
   105  		return reason
   106  	}
   107  	return ""
   108  }
   109  
   110  // localizeReason adds info about the originating object to the target Reason.
   111  func localizeReason(reason string, from Getter) string {
   112  	if strings.Contains(reason, "@") {
   113  		return reason
   114  	}
   115  	return fmt.Sprintf("%s @ %s/%s", reason, from.GetObjectKind().GroupVersionKind().Kind, from.GetName())
   116  }
   117  
   118  // getMessage returns the message to be applied to the condition resulting by merging a set of condition groups.
   119  // The message is computed according to the given mergeOptions, but in case of errors or warning a
   120  // summary of existing errors is automatically added.
   121  func getMessage(groups conditionGroups, options *mergeOptions) string {
   122  	if options.addStepCounter {
   123  		return getStepCounterMessage(groups, options.stepCounter)
   124  	}
   125  
   126  	return getFirstMessage(groups, options.conditionTypes)
   127  }
   128  
   129  // getStepCounterMessage returns a message "x of y completed", where x is the number of conditions
   130  // with Status=true and y is the number passed to this method.
   131  func getStepCounterMessage(groups conditionGroups, to int) string {
   132  	ct := 0
   133  	if trueGroup := groups.TrueGroup(); trueGroup != nil {
   134  		ct = len(trueGroup.conditions)
   135  	}
   136  	return fmt.Sprintf("%d of %d completed", ct, to)
   137  }
   138  
   139  // getFirstMessage returns the message from the ordered list of conditions in the top group.
   140  func getFirstMessage(groups conditionGroups, order []clusterv1.ConditionType) string {
   141  	if condition := getFirstCondition(groups, order); condition != nil {
   142  		return condition.Message
   143  	}
   144  	return ""
   145  }
   146  
   147  // getFirstCondition returns a first condition from the ordered list of conditions in the top group.
   148  func getFirstCondition(g conditionGroups, priority []clusterv1.ConditionType) *localizedCondition {
   149  	topGroup := g.TopGroup()
   150  	if topGroup == nil {
   151  		return nil
   152  	}
   153  
   154  	switch len(topGroup.conditions) {
   155  	case 0:
   156  		return nil
   157  	case 1:
   158  		return &topGroup.conditions[0]
   159  	default:
   160  		for _, p := range priority {
   161  			for _, c := range topGroup.conditions {
   162  				if c.Type == p {
   163  					return &c
   164  				}
   165  			}
   166  		}
   167  		return &topGroup.conditions[0]
   168  	}
   169  }