235 lines
7.4 KiB
Go
235 lines
7.4 KiB
Go
/*
|
|
Copyright 2019 The Crossplane Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package v1alpha1
|
|
|
|
import (
|
|
"sort"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
// A ConditionType represents a condition a resource could be in.
|
|
type ConditionType string
|
|
|
|
// Condition types.
|
|
const (
|
|
// TypeReady managed resources are believed to be ready to handle work.
|
|
TypeReady ConditionType = "Ready"
|
|
|
|
// TypeSynced managed resources are believed to be in sync with the
|
|
// Kubernetes resources that manage their lifecycle.
|
|
TypeSynced ConditionType = "Synced"
|
|
)
|
|
|
|
// A ConditionReason represents the reason a resource is in a condition.
|
|
type ConditionReason string
|
|
|
|
// Reasons a resource is or is not ready.
|
|
const (
|
|
ReasonAvailable ConditionReason = "Managed resource is available for use"
|
|
ReasonUnavailable ConditionReason = "Managed resource is not available for use"
|
|
ReasonCreating ConditionReason = "Managed resource is being created"
|
|
ReasonDeleting ConditionReason = "Managed resource is being deleted"
|
|
)
|
|
|
|
// Reasons a resource is or is not synced.
|
|
const (
|
|
ReasonReconcileSuccess ConditionReason = "Successfully reconciled managed resource"
|
|
ReasonReconcileError ConditionReason = "Encountered an error during managed resource reconciliation"
|
|
)
|
|
|
|
// A Condition that may apply to a managed resource.
|
|
type Condition struct {
|
|
// Type of this condition. At most one of each condition type may apply to
|
|
// a resource at any point in time.
|
|
Type ConditionType `json:"type"`
|
|
|
|
// Status of this condition; is it currently True, False, or Unknown?
|
|
Status corev1.ConditionStatus `json:"status"`
|
|
|
|
// LastTransitionTime is the last time this condition transitioned from one
|
|
// status to another.
|
|
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
|
|
|
|
// A Reason for this condition's last transition from one status to another.
|
|
Reason ConditionReason `json:"reason"`
|
|
|
|
// A Message containing details about this condition's last transition from
|
|
// one status to another, if any.
|
|
// +optional
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
// Equal returns true if the condition is identical to the supplied condition,
|
|
// ignoring the LastTransitionTime.
|
|
func (c Condition) Equal(other Condition) bool {
|
|
return c.Type == other.Type &&
|
|
c.Status == other.Status &&
|
|
c.Reason == other.Reason &&
|
|
c.Message == other.Message
|
|
}
|
|
|
|
// NOTE(negz): Conditions are implemented as a slice rather than a map to comply
|
|
// with Kubernetes API conventions. Ideally we'd comply by using a map that
|
|
// marshalled to a JSON array, but doing so confuses the CRD schema generator.
|
|
// https://github.com/kubernetes/community/blob/9bf8cd/contributors/devel/sig-architecture/api-conventions.md#lists-of-named-subobjects-preferred-over-maps
|
|
|
|
// NOTE(negz): Do not manipulate Conditions directly. Use the Set method.
|
|
|
|
// A ConditionedStatus reflects the observed status of a managed resource. Only
|
|
// one condition of each type may exist.
|
|
type ConditionedStatus struct {
|
|
// Conditions of the resource.
|
|
// +optional
|
|
Conditions []Condition `json:"conditions,omitempty"`
|
|
}
|
|
|
|
// NewConditionedStatus returns a stat with the supplied conditions set.
|
|
func NewConditionedStatus(c ...Condition) *ConditionedStatus {
|
|
s := &ConditionedStatus{}
|
|
s.SetConditions(c...)
|
|
return s
|
|
}
|
|
|
|
// SetConditions sets the supplied conditions, replacing any existing conditions
|
|
// of the same type. This is a no-op if all supplied conditions are identical,
|
|
// ignoring the last transition time, to those already set.
|
|
func (s *ConditionedStatus) SetConditions(c ...Condition) {
|
|
for _, new := range c {
|
|
exists := false
|
|
for i, existing := range s.Conditions {
|
|
if existing.Type != new.Type {
|
|
continue
|
|
}
|
|
|
|
if existing.Equal(new) {
|
|
exists = true
|
|
continue
|
|
}
|
|
|
|
s.Conditions[i] = new
|
|
exists = true
|
|
}
|
|
if !exists {
|
|
s.Conditions = append(s.Conditions, new)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Equal returns true if the status is identical to the supplied status,
|
|
// ignoring the LastTransitionTimes and order of statuses.
|
|
func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool {
|
|
if s == nil || other == nil {
|
|
return s == nil && other == nil
|
|
}
|
|
|
|
if len(other.Conditions) != len(s.Conditions) {
|
|
return false
|
|
}
|
|
|
|
sc := make([]Condition, len(s.Conditions))
|
|
copy(sc, s.Conditions)
|
|
|
|
oc := make([]Condition, len(other.Conditions))
|
|
copy(oc, other.Conditions)
|
|
|
|
// We should not have more than one condition of each type.
|
|
sort.Slice(sc, func(i, j int) bool { return sc[i].Type < sc[j].Type })
|
|
sort.Slice(oc, func(i, j int) bool { return oc[i].Type < oc[j].Type })
|
|
|
|
for i := range sc {
|
|
if !sc[i].Equal(oc[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Creating returns a condition that indicates the managed resource is currently
|
|
// being created.
|
|
func Creating() Condition {
|
|
return Condition{
|
|
Type: TypeReady,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonCreating,
|
|
}
|
|
}
|
|
|
|
// Deleting returns a condition that indicates the managed resource is currently
|
|
// being deleted.
|
|
func Deleting() Condition {
|
|
return Condition{
|
|
Type: TypeReady,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonDeleting,
|
|
}
|
|
}
|
|
|
|
// Available returns a condition that indicates the managed resource is
|
|
// currently observed to be available for use.
|
|
func Available() Condition {
|
|
return Condition{
|
|
Type: TypeReady,
|
|
Status: corev1.ConditionTrue,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonAvailable,
|
|
}
|
|
}
|
|
|
|
// Unavailable returns a condition that indicates the managed resource is not
|
|
// currently available for use. Unavailable should be set only when Crossplane
|
|
// expects the managed resource to be available but knows it is not, for example
|
|
// because its API reports it is unhealthy.
|
|
func Unavailable() Condition {
|
|
return Condition{
|
|
Type: TypeReady,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonUnavailable,
|
|
}
|
|
}
|
|
|
|
// ReconcileSuccess returns a condition indicating that Crossplane successfully
|
|
// completed the most recent reconciliation of the managed resource.
|
|
func ReconcileSuccess() Condition {
|
|
return Condition{
|
|
Type: TypeSynced,
|
|
Status: corev1.ConditionTrue,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonReconcileSuccess,
|
|
}
|
|
}
|
|
|
|
// ReconcileError returns a condition indicating that Crossplane encountered an
|
|
// error while reconciling the managed resource. This could mean Crossplane was
|
|
// unable to update the managed resource to reflect its desired state, or that
|
|
// Crossplane was unable to determine the current actual state of the managed
|
|
// resource.
|
|
func ReconcileError(err error) Condition {
|
|
return Condition{
|
|
Type: TypeSynced,
|
|
Status: corev1.ConditionFalse,
|
|
LastTransitionTime: metav1.Now(),
|
|
Reason: ReasonReconcileError,
|
|
Message: err.Error(),
|
|
}
|
|
}
|