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  }