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

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

View File

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

View File

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

View File

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