Change FederationDomain.Status to use Phase and Conditions

This commit is contained in:
Ryan Richard 2023-06-30 13:43:40 -07:00
parent 022fdb9cfd
commit 0b408f4fc0
51 changed files with 1431 additions and 941 deletions

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -728,9 +728,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-21-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -728,9 +728,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-22-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -728,9 +728,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-23-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -728,9 +728,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-24-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -726,9 +726,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-25-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -726,9 +726,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-26-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -726,9 +726,8 @@ FederationDomainStatus is a struct that describes the actual state of an OIDC Pr
[cols="25a,75a", options="header"]
|===
| Field | Description
| *`status`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-federationdomainstatuscondition[$$FederationDomainStatusCondition$$]__ | Status holds an enum that describes the state of this OIDC Provider. Note that this Status can represent success or failure.
| *`message`* __string__ | Message provides human-readable details about the Status.
| *`lastUpdateTime`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#time-v1-meta[$$Time$$]__ | LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get around some undesirable behavior with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
| *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`conditions`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-condition[$$Condition$$] array__ | Conditions represent the observations of an FederationDomain's current state.
| *`secrets`* __xref:{anchor_prefix}-go-pinniped-dev-generated-1-27-apis-supervisor-config-v1alpha1-federationdomainsecrets[$$FederationDomainSecrets$$]__ | Secrets contains information about this OIDC Provider's secrets.
|===

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -143,9 +143,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer
name: Issuer
type: string
- jsonPath: .status.status
name: Status
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
@ -348,14 +348,80 @@ spec:
status:
description: Status of the OIDC provider.
properties:
lastUpdateTime:
description: LastUpdateTime holds the time at which the Status was
last updated. It is a pointer to get around some undesirable behavior
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811).
conditions:
description: Conditions represent the observations of an FederationDomain's
current state.
items:
description: Condition status of a resource (mirrored from the metav1.Condition
type added in Kubernetes 1.19). In a future API version we can
switch to using the upstream type. See https://github.com/kubernetes/apimachinery/blob/v0.19.0/pkg/apis/meta/v1/types.go#L1353-L1413.
properties:
lastTransitionTime:
description: lastTransitionTime is the last time the condition
transitioned from one status to another. This should be when
the underlying condition changed. If that is not known, then
using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Message provides human-readable details about the Status.
description: message is a human readable message indicating
details about the transition. This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: observedGeneration represents the .metadata.generation
that the condition was set based upon. For instance, if .metadata.generation
is currently 12, but the .status.conditions[x].observedGeneration
is 9, the condition is out of date with respect to the current
state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: reason contains a programmatic identifier indicating
the reason for the condition's last transition. Producers
of specific condition types may define expected values and
meanings for this field, and whether the values are considered
a guaranteed API. The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
--- Many .condition.type values are consistent across resources
like Available, but because arbitrary conditions can be useful
(see .node.status.conditions), the ability to deconflict is
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
phase:
default: Pending
description: Phase summarizes the overall status of the FederationDomain.
enum:
- Pending
- Ready
- Error
type: string
secrets:
description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string
type: object
type: object
status:
description: Status holds an enum that describes the state of this
OIDC Provider. Note that this Status can represent success or failure.
enum:
- Success
- Duplicate
- Invalid
- SameIssuerHostMustUseSameSecret
type: string
type: object
required:
- spec

View File

@ -8,14 +8,17 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret
type FederationDomainStatusCondition string
type FederationDomainPhase string
const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success")
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate")
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid")
// FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
FederationDomainPhasePending FederationDomainPhase = "Pending"
// FederationDomainPhaseReady is the phase for an FederationDomain resource in a healthy state.
FederationDomainPhaseReady FederationDomainPhase = "Ready"
// FederationDomainPhaseError is the phase for an FederationDomain in an unhealthy state.
FederationDomainPhaseError FederationDomainPhase = "Error"
)
// FederationDomainTLSSpec is a struct that describes the TLS configuration for an OIDC Provider.
@ -263,20 +266,17 @@ type FederationDomainSecrets struct {
// FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can
// represent success or failure.
// +optional
Status FederationDomainStatusCondition `json:"status,omitempty"`
// Phase summarizes the overall status of the FederationDomain.
// +kubebuilder:default=Pending
// +kubebuilder:validation:Enum=Pending;Ready;Error
Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status.
// +optional
Message string `json:"message,omitempty"`
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get
// around some undesirable behavior with respect to the empty metav1.Time value (see
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Conditions represent the observations of an FederationDomain's current state.
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// Secrets contains information about this OIDC Provider's secrets.
// +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer`
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status
type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string
const (
// PhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending"
// OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready"
// OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error"
// OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
OIDCClientPhaseError OIDCClientPhase = "Error"
)
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/`

View File

@ -142,9 +142,12 @@ func (in *FederationDomainSpec) DeepCopy() *FederationDomainSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in
if in.LastUpdateTime != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime
*out = (*in).DeepCopy()
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.Secrets = in.Secrets
return

View File

@ -67,11 +67,11 @@ func mergeIDPCondition(existing *[]v1.Condition, new *v1.Condition) bool {
}
// MergeConfigConditions merges conditions into conditionsToUpdate. If returns true if it merged any error conditions.
func MergeConfigConditions(conditions []*v1.Condition, observedGeneration int64, conditionsToUpdate *[]v1.Condition, log plog.MinLogger) bool {
func MergeConfigConditions(conditions []*v1.Condition, observedGeneration int64, conditionsToUpdate *[]v1.Condition, log plog.MinLogger, now v1.Time) bool {
hadErrorCondition := false
for i := range conditions {
cond := conditions[i].DeepCopy()
cond.LastTransitionTime = v1.Now()
cond.LastTransitionTime = now
cond.ObservedGeneration = observedGeneration
if mergeConfigCondition(conditionsToUpdate, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)

View File

@ -10,12 +10,11 @@ import (
"strings"
"time"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
"k8s.io/utils/clock"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
@ -24,12 +23,29 @@ import (
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
"go.pinniped.dev/internal/celtransformer"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/federationdomain/federationdomainproviders"
"go.pinniped.dev/internal/idtransform"
"go.pinniped.dev/internal/plog"
)
const (
typeReady = "Ready"
typeIssuerURLValid = "IssuerURLValid"
typeOneTLSSecretPerIssuerHostname = "OneTLSSecretPerIssuerHostname"
typeIssuerIsUnique = "IssuerIsUnique"
reasonSuccess = "Success"
reasonNotReady = "NotReady"
reasonUnableToValidate = "UnableToValidate"
reasonInvalidIssuerURL = "InvalidIssuerURL"
reasonDuplicateIssuer = "DuplicateIssuer"
reasonDifferentSecretRefsFound = "DifferentSecretRefsFound"
celTransformerMaxExpressionRuntime = 5 * time.Second
)
// FederationDomainsSetter can be notified of all known valid providers with its SetIssuer function.
// If there are no longer any valid issuers, then it can be called with no arguments.
// Implementations of this type should be thread-safe to support calls from multiple goroutines.
@ -109,75 +125,14 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
return err
}
// Make a map of issuer strings -> count of how many times we saw that issuer string.
// This will help us complain when there are duplicate issuer strings.
// Also make a helper function for forming keys into this map.
issuerCounts := make(map[string]int)
issuerURLToIssuerKey := func(issuerURL *url.URL) string {
return fmt.Sprintf("%s://%s%s", issuerURL.Scheme, strings.ToLower(issuerURL.Host), issuerURL.Path)
}
// Make a map of issuer hostnames -> set of unique secret names. This will help us complain when
// multiple FederationDomains have the same issuer hostname (excluding port) but specify
// different TLS serving Secrets. Doesn't make sense to have the one address use more than one
// TLS cert. Ignore ports because SNI information on the incoming requests is not going to include
// port numbers. Also make a helper function for forming keys into this map.
uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool)
issuerURLToHostnameKey := lowercaseHostWithoutPort
for _, federationDomain := range federationDomains {
issuerURL, err := url.Parse(federationDomain.Spec.Issuer)
if err != nil {
continue // Skip url parse errors because they will be validated again below.
}
issuerCounts[issuerURLToIssuerKey(issuerURL)]++
setOfSecretNames := uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]
if setOfSecretNames == nil {
setOfSecretNames = make(map[string]bool)
uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)] = setOfSecretNames
}
if federationDomain.Spec.TLS != nil {
setOfSecretNames[federationDomain.Spec.TLS.SecretName] = true
}
}
var errs []error
federationDomainIssuers := make([]*federationdomainproviders.FederationDomainIssuer, 0)
crossDomainConfigValidator := newCrossFederationDomainConfigValidator(federationDomains)
for _, federationDomain := range federationDomains {
issuerURL, urlParseErr := url.Parse(federationDomain.Spec.Issuer)
conditions := make([]*configv1alpha1.Condition, 0, 4)
// Skip url parse errors because they will be validated below.
if urlParseErr == nil {
if issuerCount := issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 {
if err := c.updateStatus(
ctx.Context,
federationDomain.Namespace,
federationDomain.Name,
configv1alpha1.DuplicateFederationDomainStatusCondition,
"Duplicate issuer: "+federationDomain.Spec.Issuer,
); err != nil {
errs = append(errs, fmt.Errorf("could not update status: %w", err))
}
continue
}
}
// Skip url parse errors because they will be validated below.
if urlParseErr == nil && len(uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]) > 1 {
if err := c.updateStatus(
ctx.Context,
federationDomain.Namespace,
federationDomain.Name,
configv1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition,
"Issuers with the same DNS hostname (address not including port) must use the same secretName: "+issuerURLToHostnameKey(issuerURL),
); err != nil {
errs = append(errs, fmt.Errorf("could not update status: %w", err))
}
continue
}
conditions = crossDomainConfigValidator.Validate(federationDomain, conditions)
// TODO: Move all this identity provider stuff into helper functions. This is just a sketch of how the code would
// work in the sense that this is not doing error handling, is not validating everything that it should, and
@ -232,7 +187,7 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
}
// If there is an explicit list of IDPs on the FederationDomain, then process the list.
celTransformer, _ := celtransformer.NewCELTransformer(time.Second) // TODO: what is a good duration limit here?
celTransformer, _ := celtransformer.NewCELTransformer(celTransformerMaxExpressionRuntime) // TODO: what is a good duration limit here?
// TODO: handle err
for _, idp := range federationDomain.Spec.IdentityProviders {
var idpResourceUID types.UID
@ -375,7 +330,7 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
}
if !stringSlicesEqual(e.Expects.Groups, result.Groups) {
// TODO: Do we need to make this insensitive to ordering, or should the transformations evaluator be changed to always return sorted group names at the end of the pipeline?
// TODO: What happens if the user did not write any group expectation? Treat it like expecting any empty list of groups?
// TODO: What happens if the user did not write any group expectation? Treat it like expecting an empty list of groups?
// TODO: handle this failed example
plog.Warning("FederationDomain identity provider transformations example failed: expected a different transformed groups list",
"federationDomain", federationDomain.Name,
@ -402,7 +357,6 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
// Now that we have the list of IDPs for this FederationDomain, create the issuer.
var federationDomainIssuer *federationdomainproviders.FederationDomainIssuer
err = nil
if defaultFederationDomainIdentityProvider != nil {
// This is the constructor for the backwards compatibility mode.
federationDomainIssuer, err = federationdomainproviders.NewFederationDomainIssuerWithDefaultIDP(federationDomain.Spec.Issuer, defaultFederationDomainIdentityProvider)
@ -411,38 +365,193 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
federationDomainIssuer, err = federationdomainproviders.NewFederationDomainIssuer(federationDomain.Spec.Issuer, federationDomainIdentityProviders)
}
if err != nil {
// Note that the FederationDomainIssuer constructors validate the Issuer URL.
if err := c.updateStatus(
ctx.Context,
federationDomain.Namespace,
federationDomain.Name,
configv1alpha1.InvalidFederationDomainStatusCondition,
"Invalid: "+err.Error(),
); err != nil {
errs = append(errs, fmt.Errorf("could not update status: %w", err))
}
continue
// Note that the FederationDomainIssuer constructors only validate the Issuer URL,
// so these are always issuer URL validation errors.
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeIssuerURLValid,
Status: configv1alpha1.ConditionFalse,
Reason: reasonInvalidIssuerURL,
Message: err.Error(),
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeIssuerURLValid,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "spec.issuer is a valid URL",
})
}
if err := c.updateStatus(
ctx.Context,
federationDomain.Namespace,
federationDomain.Name,
configv1alpha1.SuccessFederationDomainStatusCondition,
"Provider successfully created",
); err != nil {
if err = c.updateStatus(ctx.Context, federationDomain, conditions); err != nil {
errs = append(errs, fmt.Errorf("could not update status: %w", err))
continue
}
if !hadErrorCondition(conditions) {
// Successfully validated the FederationDomain, so allow it to be loaded.
federationDomainIssuers = append(federationDomainIssuers, federationDomainIssuer)
}
}
c.federationDomainsSetter.SetFederationDomains(federationDomainIssuers...)
return errors.NewAggregate(errs)
}
func (c *federationDomainWatcherController) updateStatus(
ctx context.Context,
federationDomain *configv1alpha1.FederationDomain,
conditions []*configv1alpha1.Condition,
) error {
updated := federationDomain.DeepCopy()
if hadErrorCondition(conditions) {
updated.Status.Phase = configv1alpha1.FederationDomainPhaseError
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeReady,
Status: configv1alpha1.ConditionFalse,
Reason: reasonNotReady,
Message: "the FederationDomain is not ready: see other conditions for details",
})
} else {
updated.Status.Phase = configv1alpha1.FederationDomainPhaseReady
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeReady,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: fmt.Sprintf("the FederationDomain is ready and its endpoints are available: "+
"the discovery endpoint is %s/.well-known/openid-configuration", federationDomain.Spec.Issuer),
})
}
_ = conditionsutil.MergeConfigConditions(conditions,
federationDomain.Generation, &updated.Status.Conditions, plog.New(), metav1.NewTime(c.clock.Now()))
if equality.Semantic.DeepEqual(federationDomain, updated) {
return nil
}
_, err := c.client.
ConfigV1alpha1().
FederationDomains(federationDomain.Namespace).
UpdateStatus(ctx, updated, metav1.UpdateOptions{})
return err
}
type crossFederationDomainConfigValidator struct {
issuerCounts map[string]int
uniqueSecretNamesPerIssuerAddress map[string]map[string]bool
}
func issuerURLToHostnameKey(issuerURL *url.URL) string {
return lowercaseHostWithoutPort(issuerURL)
}
func issuerURLToIssuerKey(issuerURL *url.URL) string {
return fmt.Sprintf("%s://%s%s", issuerURL.Scheme, strings.ToLower(issuerURL.Host), issuerURL.Path)
}
func (v *crossFederationDomainConfigValidator) Validate(federationDomain *configv1alpha1.FederationDomain, conditions []*configv1alpha1.Condition) []*configv1alpha1.Condition {
issuerURL, urlParseErr := url.Parse(federationDomain.Spec.Issuer)
if urlParseErr != nil {
// Don't write a condition about the issuer URL being invalid because that is added elsewhere in the controller.
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeIssuerIsUnique,
Status: configv1alpha1.ConditionUnknown,
Reason: reasonUnableToValidate,
Message: "unable to check if spec.issuer is unique among all FederationDomains because URL cannot be parsed",
})
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeOneTLSSecretPerIssuerHostname,
Status: configv1alpha1.ConditionUnknown,
Reason: reasonUnableToValidate,
Message: "unable to check if all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL because URL cannot be parsed",
})
return conditions
}
if issuerCount := v.issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeIssuerIsUnique,
Status: configv1alpha1.ConditionFalse,
Reason: reasonDuplicateIssuer,
Message: "multiple FederationDomains have the same spec.issuer URL: these URLs must be unique (can use different hosts or paths)",
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeIssuerIsUnique,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "spec.issuer is unique among all FederationDomains",
})
}
if len(v.uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]) > 1 {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeOneTLSSecretPerIssuerHostname,
Status: configv1alpha1.ConditionFalse,
Reason: reasonDifferentSecretRefsFound,
Message: "when different FederationDomains are using the same hostname in the spec.issuer URL then they must also use the same TLS secretRef: different secretRefs found",
})
} else {
conditions = append(conditions, &configv1alpha1.Condition{
Type: typeOneTLSSecretPerIssuerHostname,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL",
})
}
return conditions
}
func newCrossFederationDomainConfigValidator(federationDomains []*configv1alpha1.FederationDomain) *crossFederationDomainConfigValidator {
// Make a map of issuer strings -> count of how many times we saw that issuer string.
// This will help us complain when there are duplicate issuer strings.
// Also make a helper function for forming keys into this map.
issuerCounts := make(map[string]int)
// Make a map of issuer hostnames -> set of unique secret names. This will help us complain when
// multiple FederationDomains have the same issuer hostname (excluding port) but specify
// different TLS serving Secrets. Doesn't make sense to have the one address use more than one
// TLS cert. Ignore ports because SNI information on the incoming requests is not going to include
// port numbers. Also make a helper function for forming keys into this map.
uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool)
for _, federationDomain := range federationDomains {
issuerURL, err := url.Parse(federationDomain.Spec.Issuer)
if err != nil {
continue // Skip url parse errors because they will be handled in the Validate function.
}
issuerCounts[issuerURLToIssuerKey(issuerURL)]++
setOfSecretNames := uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]
if setOfSecretNames == nil {
setOfSecretNames = make(map[string]bool)
uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)] = setOfSecretNames
}
if federationDomain.Spec.TLS != nil {
setOfSecretNames[federationDomain.Spec.TLS.SecretName] = true
}
}
return &crossFederationDomainConfigValidator{
issuerCounts: issuerCounts,
uniqueSecretNamesPerIssuerAddress: uniqueSecretNamesPerIssuerAddress,
}
}
func hadErrorCondition(conditions []*configv1alpha1.Condition) bool {
for _, c := range conditions {
if c.Status != configv1alpha1.ConditionTrue {
return true
}
}
return false
}
func stringSlicesEqual(a []string, b []string) bool {
if len(a) != len(b) {
return false
@ -454,38 +563,3 @@ func stringSlicesEqual(a []string, b []string) bool {
}
return true
}
func (c *federationDomainWatcherController) updateStatus(
ctx context.Context,
namespace, name string,
status configv1alpha1.FederationDomainStatusCondition,
message string,
) error {
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
federationDomain, err := c.client.ConfigV1alpha1().FederationDomains(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("get failed: %w", err)
}
if federationDomain.Status.Status == status && federationDomain.Status.Message == message {
return nil
}
plog.Debug(
"attempting status update",
"federationdomain",
klog.KRef(namespace, name),
"status",
status,
"message",
message,
)
federationDomain.Status.Status = status
federationDomain.Status.Message = message
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(c.clock.Now()))
_, err = c.client.ConfigV1alpha1().FederationDomains(namespace).UpdateStatus(ctx, federationDomain, metav1.UpdateOptions{})
return err
})
}
func timePtr(t metav1.Time) *metav1.Time { return &t }

View File

@ -16,7 +16,6 @@ import (
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/stretchr/testify/require"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -28,7 +27,6 @@ import (
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/federationdomain/federationdomainproviders"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil"
)
@ -113,8 +111,21 @@ func TestSync(t *testing.T) {
var cancelContextCancelFunc context.CancelFunc
var syncContext *controllerlib.Context
var frozenNow time.Time
var frozenMetav1Now metav1.Time
var federationDomainsSetter *fakeFederationDomainsSetter
var federationDomainGVR schema.GroupVersionResource
var allHappyConditions func(issuer string, time metav1.Time, observedGeneration int64) []v1alpha1.Condition
var happyReadyCondition func(issuer string, time metav1.Time, observedGeneration int64) v1alpha1.Condition
var happyIssuerIsUniqueCondition,
unknownIssuerIsUniqueCondition,
sadIssuerIsUniqueCondition,
happyOneTLSSecretPerIssuerHostnameCondition,
unknownOneTLSSecretPerIssuerHostnameCondition,
sadOneTLSSecretPerIssuerHostnameCondition,
happyIssuerURLValidCondition,
sadIssuerURLValidConditionCannotHaveQuery,
sadIssuerURLValidConditionCannotParse,
sadReadyCondition func(time metav1.Time, observedGeneration int64) v1alpha1.Condition
// Defer starting the informers until the last possible moment so that the
// nested Before's can keep adding things to the informer caches.
@ -163,6 +174,139 @@ func TestSync(t *testing.T) {
Version: v1alpha1.SchemeGroupVersion.Version,
Resource: "federationdomains",
}
frozenMetav1Now = metav1.NewTime(frozenNow)
happyReadyCondition = func(issuer string, time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "Ready",
Status: "True",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "Success",
Message: fmt.Sprintf("the FederationDomain is ready and its endpoints are available: "+
"the discovery endpoint is %s/.well-known/openid-configuration", issuer),
}
}
sadReadyCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "Ready",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "NotReady",
Message: "the FederationDomain is not ready: see other conditions for details",
}
}
happyIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerIsUnique",
Status: "True",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "Success",
Message: "spec.issuer is unique among all FederationDomains",
}
}
unknownIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerIsUnique",
Status: "Unknown",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "UnableToValidate",
Message: "unable to check if spec.issuer is unique among all FederationDomains because URL cannot be parsed",
}
}
sadIssuerIsUniqueCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerIsUnique",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "DuplicateIssuer",
Message: "multiple FederationDomains have the same spec.issuer URL: these URLs must be unique (can use different hosts or paths)",
}
}
happyOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "OneTLSSecretPerIssuerHostname",
Status: "True",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "Success",
Message: "all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL",
}
}
unknownOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "OneTLSSecretPerIssuerHostname",
Status: "Unknown",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "UnableToValidate",
Message: "unable to check if all FederationDomains are using the same TLS secret when using the same hostname in the spec.issuer URL because URL cannot be parsed",
}
}
sadOneTLSSecretPerIssuerHostnameCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "OneTLSSecretPerIssuerHostname",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "DifferentSecretRefsFound",
Message: "when different FederationDomains are using the same hostname in the spec.issuer URL then they must also use the same TLS secretRef: different secretRefs found",
}
}
happyIssuerURLValidCondition = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerURLValid",
Status: "True",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "Success",
Message: "spec.issuer is a valid URL",
}
}
sadIssuerURLValidConditionCannotHaveQuery = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerURLValid",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "InvalidIssuerURL",
Message: "issuer must not have query",
}
}
sadIssuerURLValidConditionCannotParse = func(time metav1.Time, observedGeneration int64) v1alpha1.Condition {
return v1alpha1.Condition{
Type: "IssuerURLValid",
Status: "False",
ObservedGeneration: observedGeneration,
LastTransitionTime: time,
Reason: "InvalidIssuerURL",
Message: `could not parse issuer as URL: parse ":/host//path": missing protocol scheme`,
}
}
allHappyConditions = func(issuer string, time metav1.Time, observedGeneration int64) []v1alpha1.Condition {
return []v1alpha1.Condition{
happyIssuerIsUniqueCondition(time, observedGeneration),
happyIssuerURLValidCondition(time, observedGeneration),
happyOneTLSSecretPerIssuerHostnameCondition(time, observedGeneration),
happyReadyCondition(issuer, time, observedGeneration),
}
}
})
it.After(func() {
@ -177,14 +321,14 @@ func TestSync(t *testing.T) {
it.Before(func() {
federationDomain1 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "config1", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain1))
federationDomain2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2))
@ -212,36 +356,24 @@ func TestSync(t *testing.T) {
)
})
it("updates the status to success in the FederationDomains", func() {
it("updates the status to ready in the FederationDomains", func() {
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain1.Status.Message = "Provider successfully created"
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain2.Status.Message = "Provider successfully created"
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomain1.Namespace,
federationDomain1,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -254,9 +386,8 @@ func TestSync(t *testing.T) {
when("one FederationDomain is already up to date", func() {
it.Before(func() {
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain1.Status.Message = "Provider successfully created"
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
r.NoError(pinnipedInformerClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
@ -267,21 +398,10 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain2.Status.Message = "Provider successfully created"
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -314,7 +434,7 @@ func TestSync(t *testing.T) {
})
})
when("updating only one FederationDomain fails for a reason other than conflict", func() {
when("updating only one FederationDomain fails", func() {
it.Before(func() {
once := sync.Once{}
pinnipedAPIClient.PrependReactor(
@ -354,31 +474,19 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error")
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain1.Status.Message = "Provider successfully created"
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain2.Status.Message = "Provider successfully created"
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomain1.Namespace,
federationDomain1,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -398,67 +506,14 @@ func TestSync(t *testing.T) {
it.Before(func() {
federationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "config", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain))
})
when("there is a conflict while updating an FederationDomain", func() {
it.Before(func() {
once := sync.Once{}
pinnipedAPIClient.PrependReactor(
"update",
"federationdomains",
func(_ coretesting.Action) (bool, runtime.Object, error) {
var err error
once.Do(func() {
err = k8serrors.NewConflict(schema.GroupResource{}, "", nil)
})
return true, nil, err
},
)
})
it("retries updating the FederationDomain", func() {
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain.Status.Message = "Provider successfully created"
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomain.Namespace,
federationDomain,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomain.Namespace,
federationDomain,
),
}
r.Equal(expectedActions, pinnipedAPIClient.Actions())
})
})
when("updating the FederationDomain fails for a reason other than conflict", func() {
when("updating the FederationDomain fails", func() {
it.Before(func() {
pinnipedAPIClient.PrependReactor(
"update",
@ -474,16 +529,10 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error")
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain.Status.Message = "Provider successfully created"
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123)
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -491,38 +540,7 @@ func TestSync(t *testing.T) {
federationDomain,
),
}
r.Equal(expectedActions, pinnipedAPIClient.Actions())
})
})
when("there is an error when getting the FederationDomain", func() {
it.Before(func() {
pinnipedAPIClient.PrependReactor(
"get",
"federationdomains",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get error")
},
)
})
it("returns the get error", func() {
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: get failed: some get error")
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain.Status.Message = "Provider successfully created"
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
}
r.Equal(expectedActions, pinnipedAPIClient.Actions())
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
})
})
@ -535,14 +553,14 @@ func TestSync(t *testing.T) {
it.Before(func() {
validFederationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "valid-config", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "valid-config", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://valid-issuer.com"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain))
r.NoError(pinnipedInformerClient.Tracker().Add(validFederationDomain))
invalidFederationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(invalidFederationDomain))
@ -566,36 +584,29 @@ func TestSync(t *testing.T) {
)
})
it("updates the status to success/invalid in the FederationDomains", func() {
it("updates the status in each FederationDomain", func() {
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
validFederationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
validFederationDomain.Status.Message = "Provider successfully created"
validFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123)
invalidFederationDomain.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition
invalidFederationDomain.Status.Message = "Invalid: issuer must not have query"
invalidFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError
invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{
happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
invalidFederationDomain.Namespace,
invalidFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
invalidFederationDomain.Namespace,
invalidFederationDomain,
),
coretesting.NewGetAction(
federationDomainGVR,
validFederationDomain.Namespace,
validFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -606,7 +617,7 @@ func TestSync(t *testing.T) {
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
when("updating only the invalid FederationDomain fails for a reason other than conflict", func() {
when("updating only the invalid FederationDomain fails", func() {
it.Before(func() {
pinnipedAPIClient.PrependReactor(
"update",
@ -645,31 +656,24 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error")
validFederationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
validFederationDomain.Status.Message = "Provider successfully created"
validFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123)
invalidFederationDomain.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition
invalidFederationDomain.Status.Message = "Invalid: issuer must not have query"
invalidFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError
invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{
happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
invalidFederationDomain.Namespace,
invalidFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
invalidFederationDomain.Namespace,
invalidFederationDomain,
),
coretesting.NewGetAction(
federationDomainGVR,
validFederationDomain.Namespace,
validFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -693,20 +697,20 @@ func TestSync(t *testing.T) {
// Hostnames are case-insensitive, so consider them to be duplicates if they only differ by case.
// Paths are case-sensitive, so having a path that differs only by case makes a new issuer.
federationDomainDuplicate1 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "duplicate1", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "duplicate1", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate1))
federationDomainDuplicate2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"},
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate2))
federationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path
}
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
@ -735,46 +739,38 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain.Status.Message = "Provider successfully created"
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123)
federationDomainDuplicate1.Status.Status = v1alpha1.DuplicateFederationDomainStatusCondition
federationDomainDuplicate1.Status.Message = "Duplicate issuer: https://iSSueR-duPlicAte.cOm/a"
federationDomainDuplicate1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainDuplicate1.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainDuplicate1.Status.Conditions = []v1alpha1.Condition{
sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainDuplicate2.Status.Status = v1alpha1.DuplicateFederationDomainStatusCondition
federationDomainDuplicate2.Status.Message = "Duplicate issuer: https://issuer-duplicate.com/a"
federationDomainDuplicate2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainDuplicate2.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainDuplicate2.Status.Conditions = []v1alpha1.Condition{
sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate1.Namespace,
federationDomainDuplicate1.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomainDuplicate1.Namespace,
federationDomainDuplicate1,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate2.Namespace,
federationDomainDuplicate2.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomainDuplicate2.Namespace,
federationDomainDuplicate2,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -784,50 +780,6 @@ func TestSync(t *testing.T) {
}
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
when("we cannot talk to the API", func() {
var count int
it.Before(func() {
pinnipedAPIClient.PrependReactor(
"get",
"federationdomains",
func(_ coretesting.Action) (bool, runtime.Object, error) {
count++
return true, nil, fmt.Errorf("some get error %d", count)
},
)
})
it("returns the get errors", func() {
expectedError := here.Doc(`[could not update status: get failed: some get error 1, could not update status: get failed: some get error 2, could not update status: get failed: some get error 3]`)
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, expectedError)
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomain.Status.Message = "Provider successfully created"
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate1.Namespace,
federationDomainDuplicate1.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate2.Namespace,
federationDomainDuplicate2.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
}
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
})
})
when("there are FederationDomains with the same issuer DNS hostname using different secretNames", func() {
@ -840,7 +792,7 @@ func TestSync(t *testing.T) {
it.Before(func() {
federationDomainSameIssuerAddress1 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "fd1", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "fd1", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{
Issuer: "https://iSSueR-duPlicAte-adDress.cOm/path1",
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"},
@ -849,7 +801,7 @@ func TestSync(t *testing.T) {
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress1))
federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "fd2", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "fd2", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{
// Validation treats these as the same DNS hostname even though they have different port numbers,
// because SNI information on the incoming requests is not going to include port numbers.
@ -861,7 +813,7 @@ func TestSync(t *testing.T) {
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress2))
federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressFederationDomain", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressFederationDomain", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{
Issuer: "https://issuer-not-duplicate.com",
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"},
@ -876,7 +828,7 @@ func TestSync(t *testing.T) {
_, err := url.Parse(invalidIssuerURL) //nolint:staticcheck // Yes, this URL is intentionally invalid.
r.Error(err)
federationDomainWithInvalidIssuerURL = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLFederationDomain", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLFederationDomain", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{
Issuer: invalidIssuerURL,
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"},
@ -908,27 +860,39 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err)
federationDomainDifferentIssuerAddress.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomainDifferentIssuerAddress.Status.Message = "Provider successfully created"
federationDomainDifferentIssuerAddress.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainDifferentIssuerAddress.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomainDifferentIssuerAddress.Status.Conditions = allHappyConditions(federationDomainDifferentIssuerAddress.Spec.Issuer, frozenMetav1Now, 123)
federationDomainSameIssuerAddress1.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition
federationDomainSameIssuerAddress1.Status.Message = "Issuers with the same DNS hostname (address not including port) must use the same secretName: issuer-duplicate-address.com"
federationDomainSameIssuerAddress1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainSameIssuerAddress1.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainSameIssuerAddress1.Status.Conditions = []v1alpha1.Condition{
happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainSameIssuerAddress2.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition
federationDomainSameIssuerAddress2.Status.Message = "Issuers with the same DNS hostname (address not including port) must use the same secretName: issuer-duplicate-address.com"
federationDomainSameIssuerAddress2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainSameIssuerAddress2.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainSameIssuerAddress2.Status.Conditions = []v1alpha1.Condition{
happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainWithInvalidIssuerURL.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition
federationDomainWithInvalidIssuerURL.Status.Message = `Invalid: could not parse issuer as URL: parse ":/host//path": missing protocol scheme`
federationDomainWithInvalidIssuerURL.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainWithInvalidIssuerURL.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainWithInvalidIssuerURL.Status.Conditions = []v1alpha1.Condition{
unknownIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotParse(frozenMetav1Now, 123),
unknownOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
federationDomainSameIssuerAddress1.Namespace,
federationDomainSameIssuerAddress1.Name,
"status",
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
@ -936,33 +900,12 @@ func TestSync(t *testing.T) {
federationDomainSameIssuerAddress1.Namespace,
federationDomainSameIssuerAddress1,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainSameIssuerAddress2.Namespace,
federationDomainSameIssuerAddress2.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomainSameIssuerAddress2.Namespace,
federationDomainSameIssuerAddress2,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainWithInvalidIssuerURL.Namespace,
federationDomainWithInvalidIssuerURL.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
@ -972,55 +915,6 @@ func TestSync(t *testing.T) {
}
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
when("we cannot talk to the API", func() {
var count int
it.Before(func() {
pinnipedAPIClient.PrependReactor(
"get",
"federationdomains",
func(_ coretesting.Action) (bool, runtime.Object, error) {
count++
return true, nil, fmt.Errorf("some get error %d", count)
},
)
})
it("returns the get errors", func() {
expectedError := here.Doc(`[could not update status: get failed: some get error 1, could not update status: get failed: some get error 2, could not update status: get failed: some get error 3, could not update status: get failed: some get error 4]`)
startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, expectedError)
federationDomainDifferentIssuerAddress.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition
federationDomainDifferentIssuerAddress.Status.Message = "Provider successfully created"
federationDomainDifferentIssuerAddress.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomainSameIssuerAddress1.Namespace,
federationDomainSameIssuerAddress1.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainSameIssuerAddress2.Namespace,
federationDomainSameIssuerAddress2.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainWithInvalidIssuerURL.Namespace,
federationDomainWithInvalidIssuerURL.Name,
),
}
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions())
})
})
})
when("there are no FederationDomains in the informer", func() {

View File

@ -133,11 +133,12 @@ func (c *oidcClientWatcherController) updateStatus(
) error {
updated := upstream.DeepCopy()
hadErrorCondition := conditionsutil.MergeConfigConditions(conditions, upstream.Generation, &updated.Status.Conditions, plog.New())
hadErrorCondition := conditionsutil.MergeConfigConditions(conditions,
upstream.Generation, &updated.Status.Conditions, plog.New(), metav1.Now())
updated.Status.Phase = v1alpha1.PhaseReady
updated.Status.Phase = v1alpha1.OIDCClientPhaseReady
if hadErrorCondition {
updated.Status.Phase = v1alpha1.PhaseError
updated.Status.Phase = v1alpha1.OIDCClientPhaseError
}
updated.Status.TotalClientSecrets = int32(totalClientSecrets)

View File

@ -103,7 +103,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
downstream := testlib.CreateTestFederationDomain(topSetupCtx, t,
issuerURL.String(),
certSecret.Name,
configv1alpha1.SuccessFederationDomainStatusCondition,
configv1alpha1.FederationDomainPhaseReady,
)
// Create a JWTAuthenticator that will validate the tokens from the downstream issuer.

View File

@ -441,7 +441,7 @@ func TestGetAPIResourceList(t *testing.T) { //nolint:gocyclo // each t.Run is pr
// over time, make a rudimentary assertion that this test exercised the whole tree of all fields of all
// Pinniped API resources. Without this, the test could accidentally skip parts of the tree if the
// format has changed.
require.Equal(t, 254, foundFieldNames,
require.Equal(t, 259, foundFieldNames,
"Expected to find all known fields of all Pinniped API resources. "+
"You may will need to update this expectation if you added new fields to the API types.",
)

View File

@ -125,14 +125,24 @@ func TestSupervisorOIDCDiscovery_Disruptive(t *testing.T) {
// When the same issuer is added twice, both issuers are marked as duplicates, and neither provider is serving.
config6Duplicate1, _ := requireCreatingFederationDomainCausesDiscoveryEndpointsToAppear(ctx, t, scheme, addr, caBundle, issuer6, client)
config6Duplicate2 := testlib.CreateTestFederationDomain(ctx, t, issuer6, "", "")
requireStatus(t, client, ns, config6Duplicate1.Name, v1alpha1.DuplicateFederationDomainStatusCondition)
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.DuplicateFederationDomainStatusCondition)
requireStatus(t, client, ns, config6Duplicate1.Name, v1alpha1.FederationDomainPhaseError, map[string]v1alpha1.ConditionStatus{
"Ready": v1alpha1.ConditionFalse,
"IssuerIsUnique": v1alpha1.ConditionFalse,
"OneTLSSecretPerIssuerHostname": v1alpha1.ConditionTrue,
"IssuerURLValid": v1alpha1.ConditionTrue,
})
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.FederationDomainPhaseError, map[string]v1alpha1.ConditionStatus{
"Ready": v1alpha1.ConditionFalse,
"IssuerIsUnique": v1alpha1.ConditionFalse,
"OneTLSSecretPerIssuerHostname": v1alpha1.ConditionTrue,
"IssuerURLValid": v1alpha1.ConditionTrue,
})
requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, issuer6)
// If we delete the first duplicate issuer, the second duplicate issuer starts serving.
requireDelete(t, client, ns, config6Duplicate1.Name)
requireWellKnownEndpointIsWorking(t, scheme, addr, caBundle, issuer6, nil)
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, client, ns, config6Duplicate2.Name)
// When we finally delete all issuers, the endpoint should be down.
requireDeletingFederationDomainCausesDiscoveryEndpointsToDisappear(t, config6Duplicate2, client, ns, scheme, addr, caBundle, issuer6)
@ -144,7 +154,12 @@ func TestSupervisorOIDCDiscovery_Disruptive(t *testing.T) {
// When we create a provider with an invalid issuer, the status is set to invalid.
badConfig := testlib.CreateTestFederationDomain(ctx, t, badIssuer, "", "")
requireStatus(t, client, ns, badConfig.Name, v1alpha1.InvalidFederationDomainStatusCondition)
requireStatus(t, client, ns, badConfig.Name, v1alpha1.FederationDomainPhaseError, map[string]v1alpha1.ConditionStatus{
"Ready": v1alpha1.ConditionFalse,
"IssuerIsUnique": v1alpha1.ConditionTrue,
"OneTLSSecretPerIssuerHostname": v1alpha1.ConditionTrue,
"IssuerURLValid": v1alpha1.ConditionFalse,
})
requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, badIssuer)
requireDeletingFederationDomainCausesDiscoveryEndpointsToDisappear(t, badConfig, client, ns, scheme, addr, caBundle, badIssuer)
})
@ -172,7 +187,7 @@ func TestSupervisorTLSTerminationWithSNI_Disruptive(t *testing.T) {
// Create an FederationDomain with a spec.tls.secretName.
federationDomain1 := testlib.CreateTestFederationDomain(ctx, t, issuer1, certSecretName1, "")
requireStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name)
// The spec.tls.secretName Secret does not exist, so the endpoints should fail with TLS errors.
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
@ -212,7 +227,7 @@ func TestSupervisorTLSTerminationWithSNI_Disruptive(t *testing.T) {
// Create an FederationDomain with a spec.tls.secretName.
federationDomain2 := testlib.CreateTestFederationDomain(ctx, t, issuer2, certSecretName2, "")
requireStatus(t, pinnipedClient, federationDomain2.Namespace, federationDomain2.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, pinnipedClient, federationDomain2.Namespace, federationDomain2.Name)
// Create the Secret.
ca2 := createTLSCertificateSecret(ctx, t, ns, hostname2, nil, certSecretName2, kubeClient)
@ -256,7 +271,7 @@ func TestSupervisorTLSTerminationWithDefaultCerts_Disruptive(t *testing.T) {
// Create an FederationDomain without a spec.tls.secretName.
federationDomain1 := testlib.CreateTestFederationDomain(ctx, t, issuerUsingIPAddress, "", "")
requireStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, pinnipedClient, federationDomain1.Namespace, federationDomain1.Name)
// There is no default TLS cert and the spec.tls.secretName was not set, so the endpoints should fail with TLS errors.
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuerUsingIPAddress)
@ -270,7 +285,7 @@ func TestSupervisorTLSTerminationWithDefaultCerts_Disruptive(t *testing.T) {
// Create an FederationDomain with a spec.tls.secretName.
certSecretName := "integration-test-cert-1"
federationDomain2 := testlib.CreateTestFederationDomain(ctx, t, issuerUsingHostname, certSecretName, "")
requireStatus(t, pinnipedClient, federationDomain2.Namespace, federationDomain2.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, pinnipedClient, federationDomain2.Namespace, federationDomain2.Name)
// Create the Secret.
certCA := createTLSCertificateSecret(ctx, t, ns, hostname, nil, certSecretName, kubeClient)
@ -458,7 +473,7 @@ func requireCreatingFederationDomainCausesDiscoveryEndpointsToAppear(
t.Helper()
newFederationDomain := testlib.CreateTestFederationDomain(ctx, t, issuerName, "", "")
jwksResult := requireDiscoveryEndpointsAreWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName, nil)
requireStatus(t, client, newFederationDomain.Namespace, newFederationDomain.Name, v1alpha1.SuccessFederationDomainStatusCondition)
requireFullySuccessfulStatus(t, client, newFederationDomain.Namespace, newFederationDomain.Name)
return newFederationDomain, jwksResult
}
@ -626,7 +641,16 @@ func requireDelete(t *testing.T, client pinnipedclientset.Interface, ns, name st
require.NoError(t, err)
}
func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name string, status v1alpha1.FederationDomainStatusCondition) {
func requireFullySuccessfulStatus(t *testing.T, client pinnipedclientset.Interface, ns, name string) {
requireStatus(t, client, ns, name, v1alpha1.FederationDomainPhaseReady, map[string]v1alpha1.ConditionStatus{
"Ready": v1alpha1.ConditionTrue,
"IssuerIsUnique": v1alpha1.ConditionTrue,
"OneTLSSecretPerIssuerHostname": v1alpha1.ConditionTrue,
"IssuerURLValid": v1alpha1.ConditionTrue,
})
}
func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name string, phase v1alpha1.FederationDomainPhase, conditionTypeToStatus map[string]v1alpha1.ConditionStatus) {
t.Helper()
testlib.RequireEventually(t, func(requireEventually *require.Assertions) {
@ -636,8 +660,14 @@ func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name st
federationDomain, err := client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
requireEventually.NoError(err)
t.Logf("found FederationDomain %s/%s with status %s", ns, name, federationDomain.Status.Status)
requireEventually.Equalf(status, federationDomain.Status.Status, "unexpected status (message = '%s')", federationDomain.Status.Message)
t.Logf("found FederationDomain %s/%s with phase %s", ns, name, federationDomain.Status.Phase)
requireEventually.Equalf(phase, federationDomain.Status.Phase, "unexpected phase (conditions = '%#v')", federationDomain.Status.Conditions)
actualConditionTypeToStatus := map[string]v1alpha1.ConditionStatus{}
for _, c := range federationDomain.Status.Conditions {
actualConditionTypeToStatus[c.Type] = c.Status
}
requireEventually.Equal(conditionTypeToStatus, actualConditionTypeToStatus, "unexpected statuses for conditions by type")
}, 5*time.Minute, 200*time.Millisecond)
}

View File

@ -1469,7 +1469,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC,
// the ID token Subject should include the upstream user ID after the upstream issuer name
@ -1502,7 +1502,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC,
// the ID token Subject should include the upstream user ID after the upstream issuer name
@ -1526,7 +1526,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1558,7 +1558,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange grant type not allowed
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "username", "groups"}, // a validation requires that we also disallow the pinniped:request-audience scope
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1592,7 +1592,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1626,7 +1626,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange not allowed (required to exclude groups scope)
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "groups"}, // username not allowed
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1652,7 +1652,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange not allowed (required to exclude groups scope)
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "username"}, // groups not allowed
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1678,7 +1678,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1711,7 +1711,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1750,7 +1750,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access"}, // validations require that when username/groups are excluded, then token exchange must also not be allowed
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
@ -1789,7 +1789,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
},
requestAuthorization: func(t *testing.T, downstreamIssuer, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, httpClient *http.Client) {
requestAuthorizationUsingBrowserAuthcodeFlowLDAP(t,
@ -1825,7 +1825,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
}, configv1alpha1.OIDCClientPhaseReady)
return clientID, "wrong-client-secret"
},
testUser: func(t *testing.T) (string, string) {
@ -2080,7 +2080,7 @@ func testSupervisorLogin(
downstream := testlib.CreateTestFederationDomain(ctx, t,
issuerURL.String(),
certSecret.Name,
configv1alpha1.SuccessFederationDomainStatusCondition,
configv1alpha1.FederationDomainPhaseReady, // TODO: expect another phase because this is a legacy FederationDomain and there is no IDP yet, so it is not safe to try to do logins until the IDP exists and the controller has a chance to run again to set the default IDP
)
// Ensure the the JWKS data is created and ready for the new FederationDomain by waiting for
@ -2104,6 +2104,9 @@ func testSupervisorLogin(
// Create upstream IDP and wait for it to become ready.
idpName := createIDP(t)
// Now that both the FederationDomain and the IDP are created, the FederationDomain should be ready.
testlib.WaitForTestFederationDomainStatus(ctx, t, downstream.Name, configv1alpha1.FederationDomainPhaseReady)
// Start a callback server on localhost.
localCallbackServer := startLocalCallbackServer(t)

View File

@ -85,7 +85,7 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
downstream := testlib.CreateTestFederationDomain(ctx, t,
issuerURL.String(),
certSecret.Name,
configv1alpha1.SuccessFederationDomainStatusCondition,
configv1alpha1.FederationDomainPhaseReady,
)
// Create a JWTAuthenticator that will validate the tokens from the downstream issuer.

View File

@ -272,7 +272,13 @@ func CreateTestJWTAuthenticator(ctx context.Context, t *testing.T, spec auth1alp
// current test's lifetime.
// If the provided issuer is not the empty string, then it will be used for the
// FederationDomain.Spec.Issuer field. Else, a random issuer will be generated.
func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string, certSecretName string, expectStatus configv1alpha1.FederationDomainStatusCondition) *configv1alpha1.FederationDomain {
func CreateTestFederationDomain(
ctx context.Context,
t *testing.T,
issuer string,
certSecretName string,
expectStatus configv1alpha1.FederationDomainPhase,
) *configv1alpha1.FederationDomain {
t.Helper()
testEnv := IntegrationEnv(t)
@ -283,8 +289,8 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
issuer = fmt.Sprintf("http://test-issuer-%s.pinniped.dev", RandHex(t, 8))
}
federationDomains := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
federationDomain, err := federationDomains.Create(createContext, &configv1alpha1.FederationDomain{
federationDomainsClient := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
federationDomain, err := federationDomainsClient.Create(createContext, &configv1alpha1.FederationDomain{
ObjectMeta: testObjectMeta(t, "oidc-provider"),
Spec: configv1alpha1.FederationDomainSpec{
Issuer: issuer,
@ -299,7 +305,7 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
t.Logf("cleaning up test FederationDomain %s/%s", federationDomain.Namespace, federationDomain.Name)
deleteCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
err := federationDomains.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{})
err := federationDomainsClient.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{})
notFound := k8serrors.IsNotFound(err)
// It's okay if it is not found, because it might have been deleted by another part of this test.
if !notFound {
@ -313,22 +319,31 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
}
// Wait for the FederationDomain to enter the expected phase (or time out).
WaitForTestFederationDomainStatus(ctx, t, federationDomain.Name, expectStatus)
return federationDomain
}
func WaitForTestFederationDomainStatus(ctx context.Context, t *testing.T, federationDomainName string, expectStatus configv1alpha1.FederationDomainPhase) {
t.Helper()
testEnv := IntegrationEnv(t)
federationDomainsClient := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
var result *configv1alpha1.FederationDomain
RequireEventuallyf(t, func(requireEventually *require.Assertions) {
var err error
result, err = federationDomains.Get(ctx, federationDomain.Name, metav1.GetOptions{})
result, err = federationDomainsClient.Get(ctx, federationDomainName, metav1.GetOptions{})
requireEventually.NoError(err)
requireEventually.Equal(expectStatus, result.Status.Status)
requireEventually.Equal(expectStatus, result.Status.Phase)
// If the FederationDomain was successfully created, ensure all secrets are present before continuing
if expectStatus == configv1alpha1.SuccessFederationDomainStatusCondition {
if expectStatus == configv1alpha1.FederationDomainPhaseReady {
requireEventually.NotEmpty(result.Status.Secrets.JWKS.Name, "expected status.secrets.jwks.name not to be empty")
requireEventually.NotEmpty(result.Status.Secrets.TokenSigningKey.Name, "expected status.secrets.tokenSigningKey.name not to be empty")
requireEventually.NotEmpty(result.Status.Secrets.StateSigningKey.Name, "expected status.secrets.stateSigningKey.name not to be empty")
requireEventually.NotEmpty(result.Status.Secrets.StateEncryptionKey.Name, "expected status.secrets.stateEncryptionKey.name not to be empty")
}
}, 60*time.Second, 1*time.Second, "expected the FederationDomain to have status %q", expectStatus)
return federationDomain
}
func RandBytes(t *testing.T, numBytes int) []byte {