consul/agent/structs/config_entry_status.go
hashicorp-copywrite[bot] 5fb9df1640
[COMPLIANCE] License changes (#18443)
* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

* Update copyright file headers to BUSL-1.1

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 09:12:13 -04:00

239 lines
6.5 KiB
Go

// 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
}