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.
358 lines
12 KiB
358 lines
12 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: MPL-2.0 |
|
|
|
package api |
|
|
|
import ( |
|
"fmt" |
|
"time" |
|
|
|
"golang.org/x/exp/slices" |
|
) |
|
|
|
// 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 |
|
|
|
// Partition is the partition the config entry is associated with. |
|
// Partitioning is a Consul Enterprise feature. |
|
Partition string `json:",omitempty"` |
|
|
|
// Namespace is the namespace the config entry is associated with. |
|
// Namespacing is a Consul Enterprise feature. |
|
Namespace string `json:",omitempty"` |
|
} |
|
|
|
// ConfigEntryStatus is used for propagating back asynchronously calculated |
|
// messages from control loops to a user |
|
type ConfigEntryStatus struct { |
|
// Conditions is the set of condition objects associated with |
|
// a ConfigEntry status. |
|
Conditions []Condition |
|
} |
|
|
|
// 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 ConditionStatus |
|
// 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 |
|
} |
|
|
|
type ( |
|
ConditionStatus string |
|
) |
|
|
|
const ( |
|
ConditionStatusTrue ConditionStatus = "True" |
|
ConditionStatusFalse ConditionStatus = "False" |
|
ConditionStatusUnknown ConditionStatus = "Unknown" |
|
) |
|
|
|
// GatewayConditionType is a type of condition associated with a |
|
// Gateway. This type should be used with the GatewayStatus.Conditions |
|
// field. |
|
type GatewayConditionType string |
|
|
|
// GatewayConditionReason defines the set of reasons that explain why a |
|
// particular Gateway condition type has been raised. |
|
type GatewayConditionReason string |
|
|
|
// the following are directly from the k8s spec |
|
const ( |
|
// This condition is true when the controller managing the Gateway is |
|
// syntactically and semantically valid enough to produce some configuration |
|
// in the underlying data plane. This does not indicate whether or not the |
|
// configuration has been propagated to the data plane. |
|
// |
|
// Possible reasons for this condition to be True are: |
|
// |
|
// * "Accepted" |
|
// |
|
// Possible reasons for this condition to be False are: |
|
// |
|
// * InvalidCertificates |
|
// |
|
GatewayConditionAccepted GatewayConditionType = "Accepted" |
|
|
|
// This reason is used with the "Accepted" condition when the condition is |
|
// True. |
|
GatewayReasonAccepted GatewayConditionReason = "Accepted" |
|
|
|
// This reason is used with the "Accepted" condition when the gateway has multiple invalid |
|
// certificates and cannot bind to any routes |
|
GatewayReasonInvalidCertificates GatewayConditionReason = "InvalidCertificates" |
|
|
|
// This reason is used with the "Accepted" condition when the gateway has multiple invalid |
|
// JWT providers and cannot bind to any routes |
|
GatewayReasonInvalidJWTProviders GatewayConditionReason = "InvalidJWTProviders" |
|
|
|
// This condition indicates that the gateway was unable to resolve |
|
// conflicting specification requirements for this Listener. If a |
|
// Listener is conflicted, its network port should not be configured |
|
// on any network elements. |
|
// |
|
// Possible reasons for this condition to be true are: |
|
// |
|
// * "RouteConflict" |
|
// |
|
// Possible reasons for this condition to be False are: |
|
// |
|
// * "NoConflict" |
|
// |
|
// Controllers may raise this condition with other reasons, |
|
// but should prefer to use the reasons listed above to improve |
|
// interoperability. |
|
GatewayConditionConflicted GatewayConditionType = "Conflicted" |
|
// This reason is used with the "Conflicted" condition when the condition |
|
// is False. |
|
GatewayReasonNoConflict GatewayConditionReason = "NoConflict" |
|
// This reason is used with the "Conflicted" condition when the route is |
|
// in a conflicted state, such as when a TCPListener attempts to bind to two routes |
|
GatewayReasonRouteConflict GatewayConditionReason = "RouteConflict" |
|
|
|
// This condition indicates whether the controller was able to |
|
// resolve all the object references for the Gateway. When setting this |
|
// condition to False, a ResourceReference to the misconfigured Listener should |
|
// be provided. |
|
// |
|
// Possible reasons for this condition to be true are: |
|
// |
|
// * "ResolvedRefs" |
|
// |
|
// Possible reasons for this condition to be False are: |
|
// |
|
// * "InvalidCertificateRef" |
|
// * "InvalidRouteKinds" |
|
// * "RefNotPermitted" |
|
// |
|
GatewayConditionResolvedRefs GatewayConditionType = "ResolvedRefs" |
|
|
|
// This reason is used with the "ResolvedRefs" condition when the condition |
|
// is true. |
|
GatewayReasonResolvedRefs GatewayConditionReason = "ResolvedRefs" |
|
|
|
// This reason is used with the "ResolvedRefs" condition when a |
|
// Listener has a TLS configuration with at least one TLS CertificateRef |
|
// that is invalid or does not exist. |
|
// A CertificateRef is considered invalid when it refers to a nonexistent |
|
// or unsupported resource or kind, or when the data within that resource |
|
// is malformed. |
|
// This reason must be used only when the reference is allowed, either by |
|
// referencing an object in the same namespace as the Gateway, or when |
|
// a cross-namespace reference has been explicitly allowed by a ReferenceGrant. |
|
// If the reference is not allowed, the reason RefNotPermitted must be used |
|
// instead. |
|
GatewayListenerReasonInvalidCertificateRef GatewayConditionReason = "InvalidCertificateRef" |
|
|
|
// This reason is used with the "ResolvedRefs" condition when a |
|
// Listener has a JWT configuration with at least one JWTProvider |
|
// that is invalid or does not exist. |
|
// A JWTProvider is considered invalid when it refers to a nonexistent |
|
// or unsupported resource or kind, or when the data within that resource |
|
// is malformed. |
|
GatewayListenerReasonInvalidJWTProviderRef GatewayConditionReason = "InvalidJWTProviderRef" |
|
) |
|
|
|
var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[ConditionStatus][]GatewayConditionReason{ |
|
GatewayConditionAccepted: { |
|
ConditionStatusTrue: { |
|
GatewayReasonAccepted, |
|
}, |
|
ConditionStatusFalse: { |
|
GatewayReasonInvalidCertificates, |
|
GatewayReasonInvalidJWTProviders, |
|
}, |
|
ConditionStatusUnknown: {}, |
|
}, |
|
GatewayConditionConflicted: { |
|
ConditionStatusTrue: { |
|
GatewayReasonRouteConflict, |
|
}, |
|
ConditionStatusFalse: { |
|
GatewayReasonNoConflict, |
|
}, |
|
ConditionStatusUnknown: {}, |
|
}, |
|
GatewayConditionResolvedRefs: { |
|
ConditionStatusTrue: { |
|
GatewayReasonResolvedRefs, |
|
}, |
|
ConditionStatusFalse: { |
|
GatewayListenerReasonInvalidCertificateRef, |
|
GatewayListenerReasonInvalidJWTProviderRef, |
|
}, |
|
ConditionStatusUnknown: {}, |
|
}, |
|
} |
|
|
|
func ValidateGatewayConditionReason(name GatewayConditionType, status ConditionStatus, reason GatewayConditionReason) error { |
|
if err := checkConditionStatus(status); err != nil { |
|
return err |
|
} |
|
|
|
reasons, ok := validGatewayConditionReasonsMapping[name] |
|
if !ok { |
|
return fmt.Errorf("unrecognized GatewayConditionType %q", name) |
|
} |
|
|
|
reasonsForStatus, ok := reasons[status] |
|
if !ok { |
|
return fmt.Errorf("unrecognized ConditionStatus %q", status) |
|
} |
|
|
|
if !slices.Contains(reasonsForStatus, reason) { |
|
return fmt.Errorf("gateway condition reason %q not allowed for gateway condition type %q with status %q", reason, name, status) |
|
} |
|
return nil |
|
} |
|
|
|
// RouteConditionType is a type of condition for a route. |
|
type RouteConditionType string |
|
|
|
// RouteConditionReason is a reason for a route condition. |
|
type RouteConditionReason string |
|
|
|
// The following statuses are taken from the K8's Spec |
|
// With the exception of: "RouteReasonInvalidDiscoveryChain" and "NoUpstreamServicesTargeted" |
|
const ( |
|
// This condition indicates whether the route has been accepted or rejected |
|
// by a Gateway, and why. |
|
// |
|
// Possible reasons for this condition to be true are: |
|
// |
|
// * "Accepted" |
|
// |
|
// Possible reasons for this condition to be False are: |
|
// |
|
// * "InvalidDiscoveryChain" |
|
// * "NoUpstreamServicesTargeted" |
|
// |
|
// |
|
// Controllers may raise this condition with other reasons, |
|
// but should prefer to use the reasons listed above to improve |
|
// interoperability. |
|
RouteConditionAccepted RouteConditionType = "Accepted" |
|
|
|
// This reason is used with the "Accepted" condition when the Route has been |
|
// accepted by the Gateway. |
|
RouteReasonAccepted RouteConditionReason = "Accepted" |
|
|
|
// This reason is used with the "Accepted" condition when the route has an |
|
// invalid discovery chain, this includes conditions like the protocol being invalid |
|
// or the discovery chain failing to compile |
|
RouteReasonInvalidDiscoveryChain RouteConditionReason = "InvalidDiscoveryChain" |
|
|
|
// This reason is used with the "Accepted" condition when the route |
|
RouteReasonNoUpstreamServicesTargeted RouteConditionReason = "NoUpstreamServicesTargeted" |
|
) |
|
|
|
// the following statuses are custom to Consul |
|
const ( |
|
// This condition indicates whether the route was able to successfully bind the |
|
// Listener on the gateway |
|
// Possible reasons for this condition to be true are: |
|
// |
|
// * "Bound" |
|
// |
|
// Possible reasons for this condition to be false are: |
|
// |
|
// * "FailedToBind" |
|
// * "GatewayNotFound" |
|
// |
|
RouteConditionBound RouteConditionType = "Bound" |
|
|
|
// This reason is used with the "Bound" condition when the condition |
|
// is true |
|
RouteReasonBound RouteConditionReason = "Bound" |
|
|
|
// This reason is used with the "Bound" condition when the route failed |
|
// to bind to the gateway |
|
RouteReasonFailedToBind RouteConditionReason = "FailedToBind" |
|
|
|
// This reason is used with the "Bound" condition when the route fails |
|
// to find the gateway |
|
RouteReasonGatewayNotFound RouteConditionReason = "GatewayNotFound" |
|
|
|
// This reason is used with the "Accepted" condition when the route references non-existent |
|
// JWTProviders |
|
RouteReasonJWTProvidersNotFound RouteConditionReason = "JWTProvidersNotFound" |
|
) |
|
|
|
var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStatus][]RouteConditionReason{ |
|
RouteConditionAccepted: { |
|
ConditionStatusTrue: { |
|
RouteReasonAccepted, |
|
}, |
|
ConditionStatusFalse: { |
|
RouteReasonInvalidDiscoveryChain, |
|
RouteReasonNoUpstreamServicesTargeted, |
|
}, |
|
ConditionStatusUnknown: {}, |
|
}, |
|
RouteConditionBound: { |
|
ConditionStatusTrue: { |
|
RouteReasonBound, |
|
}, |
|
ConditionStatusFalse: { |
|
RouteReasonGatewayNotFound, |
|
RouteReasonFailedToBind, |
|
RouteReasonJWTProvidersNotFound, |
|
}, |
|
ConditionStatusUnknown: {}, |
|
}, |
|
} |
|
|
|
func ValidateRouteConditionReason(name RouteConditionType, status ConditionStatus, reason RouteConditionReason) error { |
|
if err := checkConditionStatus(status); err != nil { |
|
return err |
|
} |
|
|
|
reasons, ok := validRouteConditionReasonsMapping[name] |
|
if !ok { |
|
return fmt.Errorf("unrecognized RouteConditionType %s", name) |
|
} |
|
|
|
reasonsForStatus, ok := reasons[status] |
|
if !ok { |
|
return fmt.Errorf("unrecognized ConditionStatus %s", name) |
|
} |
|
|
|
if !slices.Contains(reasonsForStatus, reason) { |
|
return fmt.Errorf("route condition reason %s not allowed for route condition type %s with status %s", reason, name, status) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func checkConditionStatus(status ConditionStatus) error { |
|
switch status { |
|
case ConditionStatusTrue, ConditionStatusFalse, ConditionStatusUnknown: |
|
return nil |
|
default: |
|
return fmt.Errorf("unrecognized condition status: %q", status) |
|
} |
|
}
|
|
|