mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
6.5 KiB
238 lines
6.5 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package structs |
|
|
|
import ( |
|
"fmt" |
|
"sort" |
|
"time" |
|
|
|
"github.com/hashicorp/consul/acl" |
|
"github.com/hashicorp/consul/api" |
|
) |
|
|
|
// ResourceReference is a reference to a ConfigEntry |
|
// with an optional reference to a subsection of that ConfigEntry |
|
// that can be specified as SectionName |
|
type ResourceReference struct { |
|
// Kind is the kind of ConfigEntry that this resource refers to. |
|
Kind string |
|
// Name is the identifier for the ConfigEntry this resource refers to. |
|
Name string |
|
// SectionName is a generic subresource identifier that specifies |
|
// a subset of the ConfigEntry to which this reference applies. Usage |
|
// of this field should be up to the controller that leverages it. If |
|
// unused, this should be blank. |
|
SectionName string |
|
|
|
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` |
|
} |
|
|
|
func (r *ResourceReference) String() string { |
|
return fmt.Sprintf("%s:%s/%s/%s/%s", r.Kind, r.PartitionOrDefault(), r.NamespaceOrDefault(), r.Name, r.SectionName) |
|
} |
|
|
|
func (r *ResourceReference) IsSame(other *ResourceReference) bool { |
|
if r == nil && other == nil { |
|
return true |
|
} |
|
if r == nil || other == nil { |
|
return false |
|
} |
|
return r.Kind == other.Kind && |
|
r.Name == other.Name && |
|
r.SectionName == other.SectionName && |
|
r.EnterpriseMeta.IsSame(&other.EnterpriseMeta) |
|
} |
|
|
|
// Status is used for propagating back asynchronously calculated |
|
// messages from control loops to a user |
|
type Status struct { |
|
// Conditions is the set of condition objects associated with |
|
// a ConfigEntry status. |
|
Conditions []Condition |
|
} |
|
|
|
func (s *Status) MatchesConditionStatus(condition Condition) bool { |
|
for _, c := range s.Conditions { |
|
if c.IsCondition(&condition) && |
|
c.Status == condition.Status { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func (s Status) SameConditions(other Status) bool { |
|
if len(s.Conditions) != len(other.Conditions) { |
|
return false |
|
} |
|
sortConditions := func(conditions []Condition) []Condition { |
|
sort.SliceStable(conditions, func(i, j int) bool { |
|
if conditions[i].Type < conditions[j].Type { |
|
return true |
|
} |
|
if conditions[i].Type > conditions[j].Type { |
|
return false |
|
} |
|
return lessResource(conditions[i].Resource, conditions[j].Resource) |
|
}) |
|
return conditions |
|
} |
|
oneConditions := sortConditions(s.Conditions) |
|
twoConditions := sortConditions(other.Conditions) |
|
for i, condition := range oneConditions { |
|
other := twoConditions[i] |
|
if !condition.IsSame(&other) { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
func lessResource(one, two *ResourceReference) bool { |
|
if one == nil && two == nil { |
|
return false |
|
} |
|
if one == nil { |
|
return true |
|
} |
|
if two == nil { |
|
return false |
|
} |
|
if one.Kind < two.Kind { |
|
return true |
|
} |
|
if one.Kind > two.Kind { |
|
return false |
|
} |
|
if one.Name < two.Name { |
|
return true |
|
} |
|
if one.Name > two.Name { |
|
return false |
|
} |
|
return one.SectionName < two.SectionName |
|
} |
|
|
|
// Condition is used for a single message and state associated |
|
// with an object. For example, a ConfigEntry that references |
|
// multiple other resources may have different statuses with |
|
// respect to each of those resources. |
|
type Condition struct { |
|
// Type is a value from a bounded set of types that an object might have |
|
Type string |
|
// Status is a value from a bounded set of statuses that an object might have |
|
Status string |
|
// Reason is a value from a bounded set of reasons for a given status |
|
Reason string |
|
// Message is a message that gives more detailed information about |
|
// why a Condition has a given status and reason |
|
Message string |
|
// Resource is an optional reference to a resource for which this |
|
// condition applies |
|
Resource *ResourceReference |
|
// LastTransitionTime is the time at which this Condition was created |
|
LastTransitionTime *time.Time |
|
} |
|
|
|
func (c *Condition) IsCondition(other *Condition) bool { |
|
return c.Type == other.Type && c.Resource.IsSame(other.Resource) |
|
} |
|
|
|
func (c *Condition) IsSame(other *Condition) bool { |
|
return c.IsCondition(other) && |
|
c.Status == other.Status && |
|
c.Reason == other.Reason && |
|
c.Message == other.Message |
|
} |
|
|
|
type StatusUpdater struct { |
|
entry ControlledConfigEntry |
|
status Status |
|
} |
|
|
|
func NewStatusUpdater(entry ControlledConfigEntry) *StatusUpdater { |
|
status := entry.GetStatus() |
|
return &StatusUpdater{ |
|
entry: entry, |
|
status: *status.DeepCopy(), |
|
} |
|
} |
|
|
|
func (u *StatusUpdater) SetCondition(condition Condition) { |
|
for i, c := range u.status.Conditions { |
|
if c.IsCondition(&condition) { |
|
if !c.IsSame(&condition) { |
|
// the conditions aren't identical, merge this one in |
|
u.status.Conditions[i] = condition |
|
} |
|
// we either set the condition or it was already set, so |
|
// just return |
|
return |
|
} |
|
} |
|
u.status.Conditions = append(u.status.Conditions, condition) |
|
} |
|
|
|
func (u *StatusUpdater) ClearConditions() { |
|
u.status.Conditions = []Condition{} |
|
} |
|
|
|
func (u *StatusUpdater) RemoveCondition(condition Condition) { |
|
filtered := []Condition{} |
|
for _, c := range u.status.Conditions { |
|
if !c.IsCondition(&condition) { |
|
filtered = append(filtered, c) |
|
} |
|
} |
|
u.status.Conditions = filtered |
|
} |
|
|
|
func (u *StatusUpdater) UpdateEntry() (ControlledConfigEntry, bool) { |
|
if u.status.SameConditions(u.entry.GetStatus()) { |
|
return nil, false |
|
} |
|
u.entry.SetStatus(u.status) |
|
return u.entry, true |
|
} |
|
|
|
func NewGatewayCondition(name api.GatewayConditionType, status api.ConditionStatus, reason api.GatewayConditionReason, message string, resource ResourceReference) Condition { |
|
if err := api.ValidateGatewayConditionReason(name, status, reason); err != nil { |
|
// note we panic here because an invalid combination is a programmer error |
|
// this should never actually be hit |
|
panic(err) |
|
} |
|
|
|
return Condition{ |
|
Type: string(name), |
|
Status: string(status), |
|
Reason: string(reason), |
|
Message: message, |
|
Resource: ptrTo(resource), |
|
LastTransitionTime: ptrTo(time.Now().UTC()), |
|
} |
|
} |
|
|
|
// NewRouteCondition is a helper to build allowable Conditions for a Route config entry |
|
func NewRouteCondition(name api.RouteConditionType, status api.ConditionStatus, reason api.RouteConditionReason, message string, ref ResourceReference) Condition { |
|
if err := api.ValidateRouteConditionReason(name, status, reason); err != nil { |
|
// note we panic here because an invalid combination is a programmer error |
|
// this should never actually be hit |
|
panic(err) |
|
} |
|
|
|
return Condition{ |
|
Type: string(name), |
|
Status: string(status), |
|
Reason: string(reason), |
|
Message: message, |
|
Resource: ptrTo(ref), |
|
LastTransitionTime: ptrTo(time.Now().UTC()), |
|
} |
|
} |
|
|
|
func ptrTo[T any](val T) *T { |
|
return &val |
|
}
|
|
|