github.com/kubevela/workflow@v0.6.0/api/condition/condition.go (about) 1 /* 2 3 // This file is originally from https://github.com/crossplane/crossplane-runtime/blob/master/apis/common/v1/condition.go 4 // We copy it here to reduce dependency and add more 5 6 Copyright 2022 The KubeVela Authors. 7 Copyright 2019 The Crossplane Authors. 8 9 10 Licensed under the Apache License, Version 2.0 (the "License"); 11 you may not use this file except in compliance with the License. 12 You may obtain a copy of the License at 13 14 http://www.apache.org/licenses/LICENSE-2.0 15 16 Unless required by applicable law or agreed to in writing, software 17 distributed under the License is distributed on an "AS IS" BASIS, 18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 See the License for the specific language governing permissions and 20 limitations under the License. 21 */ 22 23 package condition 24 25 import ( 26 "sort" 27 "time" 28 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 ) 32 33 // A ConditionType represents a condition a resource could be in. 34 // nolint:revive 35 type ConditionType string 36 37 // Condition types. 38 const ( 39 // TypeReady resources are believed to be ready to handle work. 40 TypeReady ConditionType = "Ready" 41 42 // TypeSynced resources are believed to be in sync with the 43 // Kubernetes resources that manage their lifecycle. 44 TypeSynced ConditionType = "Synced" 45 ) 46 47 // A ConditionReason represents the reason a resource is in a condition. 48 // nolint:revive 49 type ConditionReason string 50 51 // Reasons a resource is or is not ready. 52 const ( 53 ReasonAvailable ConditionReason = "Available" 54 ReasonUnavailable ConditionReason = "Unavailable" 55 ReasonCreating ConditionReason = "Creating" 56 ReasonDeleting ConditionReason = "Deleting" 57 ) 58 59 // Reasons a resource is or is not synced. 60 const ( 61 ReasonReconcileSuccess ConditionReason = "ReconcileSuccess" 62 ReasonReconcileError ConditionReason = "ReconcileError" 63 ) 64 65 // A Condition that may apply to a resource. 66 type Condition struct { 67 // Type of this condition. At most one of each condition type may apply to 68 // a resource at any point in time. 69 Type ConditionType `json:"type"` 70 71 // Status of this condition; is it currently True, False, or Unknown? 72 Status corev1.ConditionStatus `json:"status"` 73 74 // LastTransitionTime is the last time this condition transitioned from one 75 // status to another. 76 LastTransitionTime metav1.Time `json:"lastTransitionTime"` 77 78 // A Reason for this condition's last transition from one status to another. 79 Reason ConditionReason `json:"reason"` 80 81 // A Message containing details about this condition's last transition from 82 // one status to another, if any. 83 // +optional 84 Message string `json:"message,omitempty"` 85 } 86 87 // Equal returns true if the condition is identical to the supplied condition, 88 // ignoring the LastTransitionTime. 89 func (c Condition) Equal(other Condition) bool { 90 return c.Type == other.Type && 91 c.Status == other.Status && 92 c.Reason == other.Reason && 93 c.Message == other.Message 94 } 95 96 // WithMessage returns a condition by adding the provided message to existing 97 // condition. 98 func (c Condition) WithMessage(msg string) Condition { 99 c.Message = msg 100 return c 101 } 102 103 // NOTE(negz): Conditions are implemented as a slice rather than a map to comply 104 // with Kubernetes API conventions. Ideally we'd comply by using a map that 105 // marshalled to a JSON array, but doing so confuses the CRD schema generator. 106 // https://github.com/kubernetes/community/blob/9bf8cd/contributors/devel/sig-architecture/api-conventions.md#lists-of-named-subobjects-preferred-over-maps 107 108 // NOTE(negz): Do not manipulate Conditions directly. Use the Set method. 109 110 // A ConditionedStatus reflects the observed status of a resource. Only 111 // one condition of each type may exist. 112 type ConditionedStatus struct { 113 // Conditions of the resource. 114 // +optional 115 Conditions []Condition `json:"conditions,omitempty"` 116 } 117 118 // NewConditionedStatus returns a stat with the supplied conditions set. 119 func NewConditionedStatus(c ...Condition) *ConditionedStatus { 120 s := &ConditionedStatus{} 121 s.SetConditions(c...) 122 return s 123 } 124 125 // GetCondition returns the condition for the given ConditionType if exists, 126 // otherwise returns nil 127 func (s *ConditionedStatus) GetCondition(ct ConditionType) Condition { 128 for _, c := range s.Conditions { 129 if c.Type == ct { 130 return c 131 } 132 } 133 134 return Condition{Type: ct, Status: corev1.ConditionUnknown} 135 } 136 137 // SetConditions sets the supplied conditions, replacing any existing conditions 138 // of the same type. This is a no-op if all supplied conditions are identical, 139 // ignoring the last transition time, to those already set. 140 func (s *ConditionedStatus) SetConditions(c ...Condition) { 141 for _, new := range c { 142 exists := false 143 for i, existing := range s.Conditions { 144 if existing.Type != new.Type { 145 continue 146 } 147 148 if existing.Equal(new) { 149 exists = true 150 continue 151 } 152 153 s.Conditions[i] = new 154 exists = true 155 } 156 if !exists { 157 s.Conditions = append(s.Conditions, new) 158 } 159 } 160 } 161 162 // Equal returns true if the status is identical to the supplied status, 163 // ignoring the LastTransitionTimes and order of statuses. 164 func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool { 165 if s == nil || other == nil { 166 return s == nil && other == nil 167 } 168 169 if len(other.Conditions) != len(s.Conditions) { 170 return false 171 } 172 173 sc := make([]Condition, len(s.Conditions)) 174 copy(sc, s.Conditions) 175 176 oc := make([]Condition, len(other.Conditions)) 177 copy(oc, other.Conditions) 178 179 // We should not have more than one condition of each type. 180 sort.Slice(sc, func(i, j int) bool { return sc[i].Type < sc[j].Type }) 181 sort.Slice(oc, func(i, j int) bool { return oc[i].Type < oc[j].Type }) 182 183 for i := range sc { 184 if !sc[i].Equal(oc[i]) { 185 return false 186 } 187 } 188 189 return true 190 } 191 192 // Creating returns a condition that indicates the resource is currently 193 // being created. 194 func Creating() Condition { 195 return Condition{ 196 Type: TypeReady, 197 Status: corev1.ConditionFalse, 198 LastTransitionTime: metav1.Now(), 199 Reason: ReasonCreating, 200 } 201 } 202 203 // Deleting returns a condition that indicates the resource is currently 204 // being deleted. 205 func Deleting() Condition { 206 return Condition{ 207 Type: TypeReady, 208 Status: corev1.ConditionFalse, 209 LastTransitionTime: metav1.Now(), 210 Reason: ReasonDeleting, 211 } 212 } 213 214 // Available returns a condition that indicates the resource is 215 // currently observed to be available for use. 216 func Available() Condition { 217 return Condition{ 218 Type: TypeReady, 219 Status: corev1.ConditionTrue, 220 LastTransitionTime: metav1.Now(), 221 Reason: ReasonAvailable, 222 } 223 } 224 225 // Unavailable returns a condition that indicates the resource is not 226 // currently available for use. Unavailable should be set only when Crossplane 227 // expects the resource to be available but knows it is not, for example 228 // because its API reports it is unhealthy. 229 func Unavailable() Condition { 230 return Condition{ 231 Type: TypeReady, 232 Status: corev1.ConditionFalse, 233 LastTransitionTime: metav1.Now(), 234 Reason: ReasonUnavailable, 235 } 236 } 237 238 // ReconcileSuccess returns a condition indicating that Crossplane successfully 239 // completed the most recent reconciliation of the resource. 240 func ReconcileSuccess() Condition { 241 return Condition{ 242 Type: TypeSynced, 243 Status: corev1.ConditionTrue, 244 LastTransitionTime: metav1.Now(), 245 Reason: ReasonReconcileSuccess, 246 } 247 } 248 249 // ReconcileError returns a condition indicating that Crossplane encountered an 250 // error while reconciling the resource. This could mean Crossplane was 251 // unable to update the resource to reflect its desired state, or that 252 // Crossplane was unable to determine the current actual state of the resource. 253 func ReconcileError(err error) Condition { 254 return Condition{ 255 Type: TypeSynced, 256 Status: corev1.ConditionFalse, 257 LastTransitionTime: metav1.Now(), 258 Reason: ReasonReconcileError, 259 Message: err.Error(), 260 } 261 } 262 263 // ReadyCondition generate ready condition for conditionType 264 func ReadyCondition(tpy string) Condition { 265 return Condition{ 266 Type: ConditionType(tpy), 267 Status: corev1.ConditionTrue, 268 Reason: ReasonAvailable, 269 LastTransitionTime: metav1.NewTime(time.Now()), 270 } 271 } 272 273 // ErrorCondition generate error condition for conditionType and error 274 func ErrorCondition(tpy string, err error) Condition { 275 return Condition{ 276 Type: ConditionType(tpy), 277 Status: corev1.ConditionFalse, 278 LastTransitionTime: metav1.NewTime(time.Now()), 279 Reason: ReasonReconcileError, 280 Message: err.Error(), 281 } 282 }