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

View File

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

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - 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"] [cols="25a,75a", options="header"]
|=== |===
| Field | Description | 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. | *`phase`* __FederationDomainPhase__ | Phase summarizes the overall status of the FederationDomain.
| *`message`* __string__ | Message provides human-readable details about the Status. | *`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.
| *`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).
| *`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. | *`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" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// +kubebuilder:validation:Enum=Success;Duplicate;Invalid;SameIssuerHostMustUseSameSecret type FederationDomainPhase string
type FederationDomainStatusCondition string
const ( const (
SuccessFederationDomainStatusCondition = FederationDomainStatusCondition("Success") // FederationDomainPhasePending is the default phase for newly-created FederationDomain resources.
DuplicateFederationDomainStatusCondition = FederationDomainStatusCondition("Duplicate") FederationDomainPhasePending FederationDomainPhase = "Pending"
SameIssuerHostMustUseSameSecretFederationDomainStatusCondition = FederationDomainStatusCondition("SameIssuerHostMustUseSameSecret")
InvalidFederationDomainStatusCondition = FederationDomainStatusCondition("Invalid") // 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. // 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. // FederationDomainStatus is a struct that describes the actual state of an OIDC Provider.
type FederationDomainStatus struct { type FederationDomainStatus struct {
// Status holds an enum that describes the state of this OIDC Provider. Note that this Status can // Phase summarizes the overall status of the FederationDomain.
// represent success or failure. // +kubebuilder:default=Pending
// +optional // +kubebuilder:validation:Enum=Pending;Ready;Error
Status FederationDomainStatusCondition `json:"status,omitempty"` Phase FederationDomainPhase `json:"phase,omitempty"`
// Message provides human-readable details about the Status. // Conditions represent the observations of an FederationDomain's current state.
// +optional // +patchMergeKey=type
Message string `json:"message,omitempty"` // +patchStrategy=merge
// +listType=map
// LastUpdateTime holds the time at which the Status was last updated. It is a pointer to get // +listMapKey=type
// around some undesirable behavior with respect to the empty metav1.Time value (see Conditions []Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
// https://github.com/kubernetes/kubernetes/issues/86811).
// +optional
LastUpdateTime *metav1.Time `json:"lastUpdateTime,omitempty"`
// Secrets contains information about this OIDC Provider's secrets. // Secrets contains information about this OIDC Provider's secrets.
// +optional // +optional
@ -288,7 +288,7 @@ type FederationDomainStatus struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:categories=pinniped // +kubebuilder:resource:categories=pinniped
// +kubebuilder:printcolumn:name="Issuer",type=string,JSONPath=`.spec.issuer` // +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:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:subresource:status // +kubebuilder:subresource:status
type FederationDomain struct { type FederationDomain struct {

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return return

View File

@ -21,8 +21,8 @@ spec:
- jsonPath: .spec.issuer - jsonPath: .spec.issuer
name: Issuer name: Issuer
type: string type: string
- jsonPath: .status.status - jsonPath: .status.phase
name: Status name: Phase
type: string type: string
- jsonPath: .metadata.creationTimestamp - jsonPath: .metadata.creationTimestamp
name: Age name: Age
@ -348,14 +348,80 @@ spec:
status: status:
description: Status of the OIDC provider. description: Status of the OIDC provider.
properties: properties:
lastUpdateTime: conditions:
description: LastUpdateTime holds the time at which the Status was description: Conditions represent the observations of an FederationDomain's
last updated. It is a pointer to get around some undesirable behavior current state.
with respect to the empty metav1.Time value (see https://github.com/kubernetes/kubernetes/issues/86811). 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 format: date-time
type: string type: string
message: 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 type: string
secrets: secrets:
description: Secrets contains information about this OIDC Provider's description: Secrets contains information about this OIDC Provider's
@ -402,15 +468,6 @@ spec:
type: string type: string
type: object type: object
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 type: object
required: required:
- spec - spec

View File

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

View File

@ -8,14 +8,14 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type OIDCClientPhase string type OIDCClientPhase string
const ( const (
// PhasePending is the default phase for newly-created OIDCClient resources. // OIDCClientPhasePending is the default phase for newly-created OIDCClient resources.
PhasePending OIDCClientPhase = "Pending" OIDCClientPhasePending OIDCClientPhase = "Pending"
// PhaseReady is the phase for an OIDCClient resource in a healthy state. // OIDCClientPhaseReady is the phase for an OIDCClient resource in a healthy state.
PhaseReady OIDCClientPhase = "Ready" OIDCClientPhaseReady OIDCClientPhase = "Ready"
// PhaseError is the phase for an OIDCClient in an unhealthy state. // OIDCClientPhaseError is the phase for an OIDCClient in an unhealthy state.
PhaseError OIDCClientPhase = "Error" OIDCClientPhaseError OIDCClientPhase = "Error"
) )
// +kubebuilder:validation:Pattern=`^https://.+|^http://(127\.0\.0\.1|\[::1\])(:\d+)?/` // +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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) { func (in *FederationDomainStatus) DeepCopyInto(out *FederationDomainStatus) {
*out = *in *out = *in
if in.LastUpdateTime != nil { if in.Conditions != nil {
in, out := &in.LastUpdateTime, &out.LastUpdateTime in, out := &in.Conditions, &out.Conditions
*out = (*in).DeepCopy() *out = make([]Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
} }
out.Secrets = in.Secrets out.Secrets = in.Secrets
return 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. // 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 hadErrorCondition := false
for i := range conditions { for i := range conditions {
cond := conditions[i].DeepCopy() cond := conditions[i].DeepCopy()
cond.LastTransitionTime = v1.Now() cond.LastTransitionTime = now
cond.ObservedGeneration = observedGeneration cond.ObservedGeneration = observedGeneration
if mergeConfigCondition(conditionsToUpdate, cond) { if mergeConfigCondition(conditionsToUpdate, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message) log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)

View File

@ -10,12 +10,11 @@ import (
"strings" "strings"
"time" "time"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
"k8s.io/utils/clock" "k8s.io/utils/clock"
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1" 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" idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
"go.pinniped.dev/internal/celtransformer" "go.pinniped.dev/internal/celtransformer"
pinnipedcontroller "go.pinniped.dev/internal/controller" pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/federationdomain/federationdomainproviders" "go.pinniped.dev/internal/federationdomain/federationdomainproviders"
"go.pinniped.dev/internal/idtransform" "go.pinniped.dev/internal/idtransform"
"go.pinniped.dev/internal/plog" "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. // 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. // 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. // 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 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 var errs []error
federationDomainIssuers := make([]*federationdomainproviders.FederationDomainIssuer, 0) federationDomainIssuers := make([]*federationdomainproviders.FederationDomainIssuer, 0)
crossDomainConfigValidator := newCrossFederationDomainConfigValidator(federationDomains)
for _, federationDomain := range 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. conditions = crossDomainConfigValidator.Validate(federationDomain, conditions)
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
}
// TODO: Move all this identity provider stuff into helper functions. This is just a sketch of how the code would // 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 // 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. // 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 // TODO: handle err
for _, idp := range federationDomain.Spec.IdentityProviders { for _, idp := range federationDomain.Spec.IdentityProviders {
var idpResourceUID types.UID var idpResourceUID types.UID
@ -375,7 +330,7 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
} }
if !stringSlicesEqual(e.Expects.Groups, result.Groups) { 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: 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 // TODO: handle this failed example
plog.Warning("FederationDomain identity provider transformations example failed: expected a different transformed groups list", plog.Warning("FederationDomain identity provider transformations example failed: expected a different transformed groups list",
"federationDomain", federationDomain.Name, "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. // Now that we have the list of IDPs for this FederationDomain, create the issuer.
var federationDomainIssuer *federationdomainproviders.FederationDomainIssuer var federationDomainIssuer *federationdomainproviders.FederationDomainIssuer
err = nil
if defaultFederationDomainIdentityProvider != nil { if defaultFederationDomainIdentityProvider != nil {
// This is the constructor for the backwards compatibility mode. // This is the constructor for the backwards compatibility mode.
federationDomainIssuer, err = federationdomainproviders.NewFederationDomainIssuerWithDefaultIDP(federationDomain.Spec.Issuer, defaultFederationDomainIdentityProvider) 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) federationDomainIssuer, err = federationdomainproviders.NewFederationDomainIssuer(federationDomain.Spec.Issuer, federationDomainIdentityProviders)
} }
if err != nil { if err != nil {
// Note that the FederationDomainIssuer constructors validate the Issuer URL. // Note that the FederationDomainIssuer constructors only validate the Issuer URL,
if err := c.updateStatus( // so these are always issuer URL validation errors.
ctx.Context, conditions = append(conditions, &configv1alpha1.Condition{
federationDomain.Namespace, Type: typeIssuerURLValid,
federationDomain.Name, Status: configv1alpha1.ConditionFalse,
configv1alpha1.InvalidFederationDomainStatusCondition, Reason: reasonInvalidIssuerURL,
"Invalid: "+err.Error(), Message: err.Error(),
); err != nil { })
errs = append(errs, fmt.Errorf("could not update status: %w", err)) } else {
} conditions = append(conditions, &configv1alpha1.Condition{
continue Type: typeIssuerURLValid,
Status: configv1alpha1.ConditionTrue,
Reason: reasonSuccess,
Message: "spec.issuer is a valid URL",
})
} }
if err := c.updateStatus( if err = c.updateStatus(ctx.Context, federationDomain, conditions); err != nil {
ctx.Context,
federationDomain.Namespace,
federationDomain.Name,
configv1alpha1.SuccessFederationDomainStatusCondition,
"Provider successfully created",
); err != nil {
errs = append(errs, fmt.Errorf("could not update status: %w", err)) errs = append(errs, fmt.Errorf("could not update status: %w", err))
continue continue
} }
if !hadErrorCondition(conditions) {
// Successfully validated the FederationDomain, so allow it to be loaded.
federationDomainIssuers = append(federationDomainIssuers, federationDomainIssuer) federationDomainIssuers = append(federationDomainIssuers, federationDomainIssuer)
} }
}
c.federationDomainsSetter.SetFederationDomains(federationDomainIssuers...) c.federationDomainsSetter.SetFederationDomains(federationDomainIssuers...)
return errors.NewAggregate(errs) 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 { func stringSlicesEqual(a []string, b []string) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
@ -454,38 +563,3 @@ func stringSlicesEqual(a []string, b []string) bool {
} }
return true 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"
"github.com/sclevine/spec/report" "github.com/sclevine/spec/report"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -28,7 +27,6 @@ import (
pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions" pinnipedinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions"
"go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/federationdomain/federationdomainproviders" "go.pinniped.dev/internal/federationdomain/federationdomainproviders"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
) )
@ -113,8 +111,21 @@ func TestSync(t *testing.T) {
var cancelContextCancelFunc context.CancelFunc var cancelContextCancelFunc context.CancelFunc
var syncContext *controllerlib.Context var syncContext *controllerlib.Context
var frozenNow time.Time var frozenNow time.Time
var frozenMetav1Now metav1.Time
var federationDomainsSetter *fakeFederationDomainsSetter var federationDomainsSetter *fakeFederationDomainsSetter
var federationDomainGVR schema.GroupVersionResource 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 // Defer starting the informers until the last possible moment so that the
// nested Before's can keep adding things to the informer caches. // nested Before's can keep adding things to the informer caches.
@ -163,6 +174,139 @@ func TestSync(t *testing.T) {
Version: v1alpha1.SchemeGroupVersion.Version, Version: v1alpha1.SchemeGroupVersion.Version,
Resource: "federationdomains", 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() { it.After(func() {
@ -177,14 +321,14 @@ func TestSync(t *testing.T) {
it.Before(func() { it.Before(func() {
federationDomain1 = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain1))
federationDomain2 = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2)) 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() startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Message = "Provider successfully created" federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Message = "Provider successfully created" federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
federationDomain1.Namespace, federationDomain1.Namespace,
federationDomain1, federationDomain1,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -254,9 +386,8 @@ func TestSync(t *testing.T) {
when("one FederationDomain is already up to date", func() { when("one FederationDomain is already up to date", func() {
it.Before(func() { it.Before(func() {
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Message = "Provider successfully created" federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace)) r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
r.NoError(pinnipedInformerClient.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) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Message = "Provider successfully created" federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "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() { it.Before(func() {
once := sync.Once{} once := sync.Once{}
pinnipedAPIClient.PrependReactor( pinnipedAPIClient.PrependReactor(
@ -354,31 +474,19 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error") r.EqualError(err, "could not update status: some update error")
federationDomain1.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain1.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain1.Status.Message = "Provider successfully created" federationDomain1.Status.Conditions = allHappyConditions(federationDomain1.Spec.Issuer, frozenMetav1Now, 123)
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomain2.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain2.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain2.Status.Message = "Provider successfully created" federationDomain2.Status.Conditions = allHappyConditions(federationDomain2.Spec.Issuer, frozenMetav1Now, 123)
federationDomain2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain1.Namespace,
federationDomain1.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
federationDomain1.Namespace, federationDomain1.Namespace,
federationDomain1, federationDomain1,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain2.Namespace,
federationDomain2.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -398,67 +506,14 @@ func TestSync(t *testing.T) {
it.Before(func() { it.Before(func() {
federationDomain = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomain))
}) })
when("there is a conflict while updating an FederationDomain", func() { when("updating the FederationDomain fails", 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() {
it.Before(func() { it.Before(func() {
pinnipedAPIClient.PrependReactor( pinnipedAPIClient.PrependReactor(
"update", "update",
@ -474,16 +529,10 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error") r.EqualError(err, "could not update status: some update error")
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain.Status.Message = "Provider successfully created" federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123)
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -491,38 +540,7 @@ func TestSync(t *testing.T) {
federationDomain, federationDomain,
), ),
} }
r.Equal(expectedActions, pinnipedAPIClient.Actions()) r.ElementsMatch(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())
}) })
}) })
}) })
@ -535,14 +553,14 @@ func TestSync(t *testing.T) {
it.Before(func() { it.Before(func() {
validFederationDomain = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://valid-issuer.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain)) r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain))
r.NoError(pinnipedInformerClient.Tracker().Add(validFederationDomain)) r.NoError(pinnipedInformerClient.Tracker().Add(validFederationDomain))
invalidFederationDomain = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(invalidFederationDomain)) 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() startInformersAndController()
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
validFederationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
validFederationDomain.Status.Message = "Provider successfully created" validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123)
validFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
invalidFederationDomain.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError
invalidFederationDomain.Status.Message = "Invalid: issuer must not have query" invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{
invalidFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
invalidFederationDomain.Namespace,
invalidFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
invalidFederationDomain.Namespace, invalidFederationDomain.Namespace,
invalidFederationDomain, invalidFederationDomain,
), ),
coretesting.NewGetAction(
federationDomainGVR,
validFederationDomain.Namespace,
validFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -606,7 +617,7 @@ func TestSync(t *testing.T) {
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) 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() { it.Before(func() {
pinnipedAPIClient.PrependReactor( pinnipedAPIClient.PrependReactor(
"update", "update",
@ -645,31 +656,24 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "could not update status: some update error") r.EqualError(err, "could not update status: some update error")
validFederationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition validFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
validFederationDomain.Status.Message = "Provider successfully created" validFederationDomain.Status.Conditions = allHappyConditions(validFederationDomain.Spec.Issuer, frozenMetav1Now, 123)
validFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
invalidFederationDomain.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition invalidFederationDomain.Status.Phase = v1alpha1.FederationDomainPhaseError
invalidFederationDomain.Status.Message = "Invalid: issuer must not have query" invalidFederationDomain.Status.Conditions = []v1alpha1.Condition{
invalidFederationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotHaveQuery(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
invalidFederationDomain.Namespace,
invalidFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
invalidFederationDomain.Namespace, invalidFederationDomain.Namespace,
invalidFederationDomain, invalidFederationDomain,
), ),
coretesting.NewGetAction(
federationDomainGVR,
validFederationDomain.Namespace,
validFederationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "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. // 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. // Paths are case-sensitive, so having a path that differs only by case makes a new issuer.
federationDomainDuplicate1 = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate1))
federationDomainDuplicate2 = &v1alpha1.FederationDomain{ 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate2)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainDuplicate2))
federationDomain = &v1alpha1.FederationDomain{ 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 Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
@ -735,46 +739,38 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
federationDomain.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomain.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomain.Status.Message = "Provider successfully created" federationDomain.Status.Conditions = allHappyConditions(federationDomain.Spec.Issuer, frozenMetav1Now, 123)
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainDuplicate1.Status.Status = v1alpha1.DuplicateFederationDomainStatusCondition federationDomainDuplicate1.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainDuplicate1.Status.Message = "Duplicate issuer: https://iSSueR-duPlicAte.cOm/a" federationDomainDuplicate1.Status.Conditions = []v1alpha1.Condition{
federationDomainDuplicate1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainDuplicate2.Status.Status = v1alpha1.DuplicateFederationDomainStatusCondition federationDomainDuplicate2.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainDuplicate2.Status.Message = "Duplicate issuer: https://issuer-duplicate.com/a" federationDomainDuplicate2.Status.Conditions = []v1alpha1.Condition{
federationDomainDuplicate2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) sadIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
happyOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate1.Namespace,
federationDomainDuplicate1.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
federationDomainDuplicate1.Namespace, federationDomainDuplicate1.Namespace,
federationDomainDuplicate1, federationDomainDuplicate1,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDuplicate2.Namespace,
federationDomainDuplicate2.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
federationDomainDuplicate2.Namespace, federationDomainDuplicate2.Namespace,
federationDomainDuplicate2, federationDomainDuplicate2,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomain.Namespace,
federationDomain.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -784,50 +780,6 @@ func TestSync(t *testing.T) {
} }
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) 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() { 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() { it.Before(func() {
federationDomainSameIssuerAddress1 = &v1alpha1.FederationDomain{ federationDomainSameIssuerAddress1 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "fd1", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "fd1", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{ Spec: v1alpha1.FederationDomainSpec{
Issuer: "https://iSSueR-duPlicAte-adDress.cOm/path1", Issuer: "https://iSSueR-duPlicAte-adDress.cOm/path1",
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"},
@ -849,7 +801,7 @@ func TestSync(t *testing.T) {
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1))
r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress1)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress1))
federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{ federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "fd2", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "fd2", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{ Spec: v1alpha1.FederationDomainSpec{
// Validation treats these as the same DNS hostname even though they have different port numbers, // 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. // 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)) r.NoError(pinnipedInformerClient.Tracker().Add(federationDomainSameIssuerAddress2))
federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{ federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressFederationDomain", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressFederationDomain", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{ Spec: v1alpha1.FederationDomainSpec{
Issuer: "https://issuer-not-duplicate.com", Issuer: "https://issuer-not-duplicate.com",
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, 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. _, err := url.Parse(invalidIssuerURL) //nolint:staticcheck // Yes, this URL is intentionally invalid.
r.Error(err) r.Error(err)
federationDomainWithInvalidIssuerURL = &v1alpha1.FederationDomain{ federationDomainWithInvalidIssuerURL = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLFederationDomain", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "invalidIssuerURLFederationDomain", Namespace: namespace, Generation: 123},
Spec: v1alpha1.FederationDomainSpec{ Spec: v1alpha1.FederationDomainSpec{
Issuer: invalidIssuerURL, Issuer: invalidIssuerURL,
TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"}, TLS: &v1alpha1.FederationDomainTLSSpec{SecretName: "secret1"},
@ -908,27 +860,39 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
federationDomainDifferentIssuerAddress.Status.Status = v1alpha1.SuccessFederationDomainStatusCondition federationDomainDifferentIssuerAddress.Status.Phase = v1alpha1.FederationDomainPhaseReady
federationDomainDifferentIssuerAddress.Status.Message = "Provider successfully created" federationDomainDifferentIssuerAddress.Status.Conditions = allHappyConditions(federationDomainDifferentIssuerAddress.Spec.Issuer, frozenMetav1Now, 123)
federationDomainDifferentIssuerAddress.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
federationDomainSameIssuerAddress1.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition federationDomainSameIssuerAddress1.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainSameIssuerAddress1.Status.Message = "Issuers with the same DNS hostname (address not including port) must use the same secretName: issuer-duplicate-address.com" federationDomainSameIssuerAddress1.Status.Conditions = []v1alpha1.Condition{
federationDomainSameIssuerAddress1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainSameIssuerAddress2.Status.Status = v1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition federationDomainSameIssuerAddress2.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainSameIssuerAddress2.Status.Message = "Issuers with the same DNS hostname (address not including port) must use the same secretName: issuer-duplicate-address.com" federationDomainSameIssuerAddress2.Status.Conditions = []v1alpha1.Condition{
federationDomainSameIssuerAddress2.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) happyIssuerIsUniqueCondition(frozenMetav1Now, 123),
happyIssuerURLValidCondition(frozenMetav1Now, 123),
sadOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
federationDomainWithInvalidIssuerURL.Status.Status = v1alpha1.InvalidFederationDomainStatusCondition federationDomainWithInvalidIssuerURL.Status.Phase = v1alpha1.FederationDomainPhaseError
federationDomainWithInvalidIssuerURL.Status.Message = `Invalid: could not parse issuer as URL: parse ":/host//path": missing protocol scheme` federationDomainWithInvalidIssuerURL.Status.Conditions = []v1alpha1.Condition{
federationDomainWithInvalidIssuerURL.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) unknownIssuerIsUniqueCondition(frozenMetav1Now, 123),
sadIssuerURLValidConditionCannotParse(frozenMetav1Now, 123),
unknownOneTLSSecretPerIssuerHostnameCondition(frozenMetav1Now, 123),
sadReadyCondition(frozenMetav1Now, 123),
}
expectedActions := []coretesting.Action{ expectedActions := []coretesting.Action{
coretesting.NewGetAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
federationDomainSameIssuerAddress1.Namespace, "status",
federationDomainSameIssuerAddress1.Name, federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress,
), ),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
@ -936,33 +900,12 @@ func TestSync(t *testing.T) {
federationDomainSameIssuerAddress1.Namespace, federationDomainSameIssuerAddress1.Namespace,
federationDomainSameIssuerAddress1, federationDomainSameIssuerAddress1,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainSameIssuerAddress2.Namespace,
federationDomainSameIssuerAddress2.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
federationDomainSameIssuerAddress2.Namespace, federationDomainSameIssuerAddress2.Namespace,
federationDomainSameIssuerAddress2, federationDomainSameIssuerAddress2,
), ),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress.Name,
),
coretesting.NewUpdateSubresourceAction(
federationDomainGVR,
"status",
federationDomainDifferentIssuerAddress.Namespace,
federationDomainDifferentIssuerAddress,
),
coretesting.NewGetAction(
federationDomainGVR,
federationDomainWithInvalidIssuerURL.Namespace,
federationDomainWithInvalidIssuerURL.Name,
),
coretesting.NewUpdateSubresourceAction( coretesting.NewUpdateSubresourceAction(
federationDomainGVR, federationDomainGVR,
"status", "status",
@ -972,55 +915,6 @@ func TestSync(t *testing.T) {
} }
r.ElementsMatch(expectedActions, pinnipedAPIClient.Actions()) 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() { when("there are no FederationDomains in the informer", func() {

View File

@ -133,11 +133,12 @@ func (c *oidcClientWatcherController) updateStatus(
) error { ) error {
updated := upstream.DeepCopy() 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 { if hadErrorCondition {
updated.Status.Phase = v1alpha1.PhaseError updated.Status.Phase = v1alpha1.OIDCClientPhaseError
} }
updated.Status.TotalClientSecrets = int32(totalClientSecrets) updated.Status.TotalClientSecrets = int32(totalClientSecrets)

View File

@ -103,7 +103,7 @@ func TestE2EFullIntegration_Browser(t *testing.T) {
downstream := testlib.CreateTestFederationDomain(topSetupCtx, t, downstream := testlib.CreateTestFederationDomain(topSetupCtx, t,
issuerURL.String(), issuerURL.String(),
certSecret.Name, certSecret.Name,
configv1alpha1.SuccessFederationDomainStatusCondition, configv1alpha1.FederationDomainPhaseReady,
) )
// Create a JWTAuthenticator that will validate the tokens from the downstream issuer. // 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 // 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 // Pinniped API resources. Without this, the test could accidentally skip parts of the tree if the
// format has changed. // format has changed.
require.Equal(t, 254, foundFieldNames, require.Equal(t, 259, foundFieldNames,
"Expected to find all known fields of all Pinniped API resources. "+ "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.", "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. // 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) config6Duplicate1, _ := requireCreatingFederationDomainCausesDiscoveryEndpointsToAppear(ctx, t, scheme, addr, caBundle, issuer6, client)
config6Duplicate2 := testlib.CreateTestFederationDomain(ctx, t, issuer6, "", "") config6Duplicate2 := testlib.CreateTestFederationDomain(ctx, t, issuer6, "", "")
requireStatus(t, client, ns, config6Duplicate1.Name, v1alpha1.DuplicateFederationDomainStatusCondition) requireStatus(t, client, ns, config6Duplicate1.Name, v1alpha1.FederationDomainPhaseError, map[string]v1alpha1.ConditionStatus{
requireStatus(t, client, ns, config6Duplicate2.Name, v1alpha1.DuplicateFederationDomainStatusCondition) "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) requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, issuer6)
// If we delete the first duplicate issuer, the second duplicate issuer starts serving. // If we delete the first duplicate issuer, the second duplicate issuer starts serving.
requireDelete(t, client, ns, config6Duplicate1.Name) requireDelete(t, client, ns, config6Duplicate1.Name)
requireWellKnownEndpointIsWorking(t, scheme, addr, caBundle, issuer6, nil) 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. // When we finally delete all issuers, the endpoint should be down.
requireDeletingFederationDomainCausesDiscoveryEndpointsToDisappear(t, config6Duplicate2, client, ns, scheme, addr, caBundle, issuer6) 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. // When we create a provider with an invalid issuer, the status is set to invalid.
badConfig := testlib.CreateTestFederationDomain(ctx, t, badIssuer, "", "") 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) requireDiscoveryEndpointsAreNotFound(t, scheme, addr, caBundle, badIssuer)
requireDeletingFederationDomainCausesDiscoveryEndpointsToDisappear(t, badConfig, client, ns, 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. // Create an FederationDomain with a spec.tls.secretName.
federationDomain1 := testlib.CreateTestFederationDomain(ctx, t, issuer1, certSecretName1, "") 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. // The spec.tls.secretName Secret does not exist, so the endpoints should fail with TLS errors.
requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuer1) requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuer1)
@ -212,7 +227,7 @@ func TestSupervisorTLSTerminationWithSNI_Disruptive(t *testing.T) {
// Create an FederationDomain with a spec.tls.secretName. // Create an FederationDomain with a spec.tls.secretName.
federationDomain2 := testlib.CreateTestFederationDomain(ctx, t, issuer2, certSecretName2, "") 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. // Create the Secret.
ca2 := createTLSCertificateSecret(ctx, t, ns, hostname2, nil, certSecretName2, kubeClient) 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. // Create an FederationDomain without a spec.tls.secretName.
federationDomain1 := testlib.CreateTestFederationDomain(ctx, t, issuerUsingIPAddress, "", "") 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. // 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) requireEndpointHasBootstrapTLSErrorBecauseCertificatesAreNotReady(t, issuerUsingIPAddress)
@ -270,7 +285,7 @@ func TestSupervisorTLSTerminationWithDefaultCerts_Disruptive(t *testing.T) {
// Create an FederationDomain with a spec.tls.secretName. // Create an FederationDomain with a spec.tls.secretName.
certSecretName := "integration-test-cert-1" certSecretName := "integration-test-cert-1"
federationDomain2 := testlib.CreateTestFederationDomain(ctx, t, issuerUsingHostname, certSecretName, "") 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. // Create the Secret.
certCA := createTLSCertificateSecret(ctx, t, ns, hostname, nil, certSecretName, kubeClient) certCA := createTLSCertificateSecret(ctx, t, ns, hostname, nil, certSecretName, kubeClient)
@ -458,7 +473,7 @@ func requireCreatingFederationDomainCausesDiscoveryEndpointsToAppear(
t.Helper() t.Helper()
newFederationDomain := testlib.CreateTestFederationDomain(ctx, t, issuerName, "", "") newFederationDomain := testlib.CreateTestFederationDomain(ctx, t, issuerName, "", "")
jwksResult := requireDiscoveryEndpointsAreWorking(t, supervisorScheme, supervisorAddress, supervisorCABundle, issuerName, nil) 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 return newFederationDomain, jwksResult
} }
@ -626,7 +641,16 @@ func requireDelete(t *testing.T, client pinnipedclientset.Interface, ns, name st
require.NoError(t, err) 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() t.Helper()
testlib.RequireEventually(t, func(requireEventually *require.Assertions) { 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{}) federationDomain, err := client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
requireEventually.NoError(err) requireEventually.NoError(err)
t.Logf("found FederationDomain %s/%s with status %s", ns, name, federationDomain.Status.Status) t.Logf("found FederationDomain %s/%s with phase %s", ns, name, federationDomain.Status.Phase)
requireEventually.Equalf(status, federationDomain.Status.Status, "unexpected status (message = '%s')", federationDomain.Status.Message) 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) }, 5*time.Minute, 200*time.Millisecond)
} }

View File

@ -1469,7 +1469,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC,
// the ID token Subject should include the upstream user ID after the upstream issuer name // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC,
// the ID token Subject should include the upstream user ID after the upstream issuer name // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange grant type not allowed 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 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) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange not allowed (required to exclude groups scope) 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 AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "groups"}, // username not allowed
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, // token exchange not allowed (required to exclude groups scope) 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 AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "username"}, // groups not allowed
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "refresh_token"}, 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 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) { testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test // 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)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, 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) { requestAuthorization: func(t *testing.T, downstreamIssuer, downstreamAuthorizeURL, downstreamCallbackURL, _, _ string, httpClient *http.Client) {
requestAuthorizationUsingBrowserAuthcodeFlowLDAP(t, requestAuthorizationUsingBrowserAuthcodeFlowLDAP(t,
@ -1825,7 +1825,7 @@ func TestSupervisorLogin_Browser(t *testing.T) {
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)}, AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"}, 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"}, AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady) }, configv1alpha1.OIDCClientPhaseReady)
return clientID, "wrong-client-secret" return clientID, "wrong-client-secret"
}, },
testUser: func(t *testing.T) (string, string) { testUser: func(t *testing.T) (string, string) {
@ -2080,7 +2080,7 @@ func testSupervisorLogin(
downstream := testlib.CreateTestFederationDomain(ctx, t, downstream := testlib.CreateTestFederationDomain(ctx, t,
issuerURL.String(), issuerURL.String(),
certSecret.Name, 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 // 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. // Create upstream IDP and wait for it to become ready.
idpName := createIDP(t) 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. // Start a callback server on localhost.
localCallbackServer := startLocalCallbackServer(t) localCallbackServer := startLocalCallbackServer(t)

View File

@ -85,7 +85,7 @@ func TestSupervisorWarnings_Browser(t *testing.T) {
downstream := testlib.CreateTestFederationDomain(ctx, t, downstream := testlib.CreateTestFederationDomain(ctx, t,
issuerURL.String(), issuerURL.String(),
certSecret.Name, certSecret.Name,
configv1alpha1.SuccessFederationDomainStatusCondition, configv1alpha1.FederationDomainPhaseReady,
) )
// Create a JWTAuthenticator that will validate the tokens from the downstream issuer. // 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. // current test's lifetime.
// If the provided issuer is not the empty string, then it will be used for the // 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. // 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() t.Helper()
testEnv := IntegrationEnv(t) 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)) issuer = fmt.Sprintf("http://test-issuer-%s.pinniped.dev", RandHex(t, 8))
} }
federationDomains := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace) federationDomainsClient := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
federationDomain, err := federationDomains.Create(createContext, &configv1alpha1.FederationDomain{ federationDomain, err := federationDomainsClient.Create(createContext, &configv1alpha1.FederationDomain{
ObjectMeta: testObjectMeta(t, "oidc-provider"), ObjectMeta: testObjectMeta(t, "oidc-provider"),
Spec: configv1alpha1.FederationDomainSpec{ Spec: configv1alpha1.FederationDomainSpec{
Issuer: issuer, 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) t.Logf("cleaning up test FederationDomain %s/%s", federationDomain.Namespace, federationDomain.Name)
deleteCtx, cancel := context.WithTimeout(context.Background(), time.Minute) deleteCtx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel() defer cancel()
err := federationDomains.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{}) err := federationDomainsClient.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{})
notFound := k8serrors.IsNotFound(err) notFound := k8serrors.IsNotFound(err)
// It's okay if it is not found, because it might have been deleted by another part of this test. // It's okay if it is not found, because it might have been deleted by another part of this test.
if !notFound { 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). // 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 var result *configv1alpha1.FederationDomain
RequireEventuallyf(t, func(requireEventually *require.Assertions) { RequireEventuallyf(t, func(requireEventually *require.Assertions) {
var err error var err error
result, err = federationDomains.Get(ctx, federationDomain.Name, metav1.GetOptions{}) result, err = federationDomainsClient.Get(ctx, federationDomainName, metav1.GetOptions{})
requireEventually.NoError(err) 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 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.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.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.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") 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) }, 60*time.Second, 1*time.Second, "expected the FederationDomain to have status %q", expectStatus)
return federationDomain
} }
func RandBytes(t *testing.T, numBytes int) []byte { func RandBytes(t *testing.T, numBytes int) []byte {