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 }