Split package upstreamwatchers into four packages

This commit is contained in:
Ryan Richard 2021-05-12 14:00:39 -07:00
parent 22092e9aed
commit 1ae3c6a1ad
7 changed files with 126 additions and 95 deletions

View File

@ -32,7 +32,8 @@ import (
"go.pinniped.dev/internal/config/supervisor"
"go.pinniped.dev/internal/controller/supervisorconfig"
"go.pinniped.dev/internal/controller/supervisorconfig/generator"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorconfig/ldapupstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorconfig/oidcupstreamwatcher"
"go.pinniped.dev/internal/controller/supervisorstorage"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/deploymentref"
@ -233,7 +234,7 @@ func startControllers(
singletonWorker,
).
WithController(
upstreamwatcher.NewOIDCUpstreamWatcherController(
oidcupstreamwatcher.New(
dynamicUpstreamIDPProvider,
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
@ -243,7 +244,7 @@ func startControllers(
),
singletonWorker).
WithController(
upstreamwatcher.NewLDAPUpstreamWatcherController(
ldapupstreamwatcher.New(
dynamicUpstreamIDPProvider,
pinnipedClient,
pinnipedInformers.IDP().V1alpha1().LDAPIdentityProviders(),

View File

@ -0,0 +1,68 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package conditionsutil
import (
"sort"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/equality"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
)
// Merge merges conditions into conditionsToUpdate. If returns true if it merged any error conditions.
func Merge(conditions []*v1alpha1.Condition, observedGeneration int64, conditionsToUpdate *[]v1alpha1.Condition, log logr.Logger) bool {
hadErrorCondition := false
for i := range conditions {
cond := conditions[i].DeepCopy()
cond.LastTransitionTime = v1.Now()
cond.ObservedGeneration = observedGeneration
if mergeCondition(conditionsToUpdate, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)
}
if cond.Status == v1alpha1.ConditionFalse {
hadErrorCondition = true
}
}
sort.SliceStable(*conditionsToUpdate, func(i, j int) bool {
return (*conditionsToUpdate)[i].Type < (*conditionsToUpdate)[j].Type
})
return hadErrorCondition
}
// mergeCondition merges a new v1alpha1.Condition into a slice of existing conditions. It returns true
// if the condition has meaningfully changed.
func mergeCondition(existing *[]v1alpha1.Condition, new *v1alpha1.Condition) bool {
// Find any existing condition with a matching type.
var old *v1alpha1.Condition
for i := range *existing {
if (*existing)[i].Type == new.Type {
old = &(*existing)[i]
continue
}
}
// If there is no existing condition of this type, append this one and we're done.
if old == nil {
*existing = append(*existing, *new)
return true
}
// Set the LastTransitionTime depending on whether the status has changed.
new = new.DeepCopy()
if old.Status == new.Status {
new.LastTransitionTime = old.LastTransitionTime
}
// If anything has actually changed, update the entry and return true.
if !equality.Semantic.DeepEqual(old, new) {
*old = *new
return true
}
// Otherwise the entry is already up to date.
return false
}

View File

@ -1,7 +1,8 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatcher
// Package ldapupstreamwatcher implements a controller which watches LDAPIdentityProviders.
package ldapupstreamwatcher
import (
"context"
@ -22,6 +23,8 @@ import (
pinnipedclientset "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned"
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatchers"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/upstreamldap"
@ -58,8 +61,8 @@ type ldapWatcherController struct {
secretInformer corev1informers.SecretInformer
}
// NewLDAPUpstreamWatcherController instantiates a new controllerlib.Controller which will populate the provided UpstreamLDAPIdentityProviderICache.
func NewLDAPUpstreamWatcherController(
// New instantiates a new controllerlib.Controller which will populate the provided UpstreamLDAPIdentityProviderICache.
func New(
idpCache UpstreamLDAPIdentityProviderICache,
client pinnipedclientset.Interface,
ldapIdentityProviderInformer idpinformers.LDAPIdentityProviderInformer,
@ -178,7 +181,7 @@ func (c *ldapWatcherController) validateTLSConfig(upstream *v1alpha1.LDAPIdentit
ca := x509.NewCertPool()
ok := ca.AppendCertsFromPEM(bundle)
if !ok {
return c.invalidTLSCondition(fmt.Sprintf("certificateAuthorityData is invalid: %s", errNoCertificates))
return c.invalidTLSCondition(fmt.Sprintf("certificateAuthorityData is invalid: %s", upstreamwatchers.ErrNoCertificates))
}
config.CABundle = bundle
@ -219,7 +222,7 @@ func (c *ldapWatcherController) testConnection(
return &v1alpha1.Condition{
Type: typeLDAPConnectionValid,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: fmt.Sprintf(`successfully able to connect to "%s" and bind as user "%s" [validated with Secret "%s" at version "%s"]`,
config.Host, config.BindUsername, upstream.Spec.Bind.SecretName, currentSecretVersion),
}
@ -248,7 +251,7 @@ func (c *ldapWatcherController) validTLSCondition(message string) *v1alpha1.Cond
return &v1alpha1.Condition{
Type: typeTLSConfigurationValid,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: message,
}
}
@ -257,7 +260,7 @@ func (c *ldapWatcherController) invalidTLSCondition(message string) *v1alpha1.Co
return &v1alpha1.Condition{
Type: typeTLSConfigurationValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonInvalidTLSConfig,
Reason: upstreamwatchers.ReasonInvalidTLSConfig,
Message: message,
}
}
@ -270,7 +273,7 @@ func (c *ldapWatcherController) validateSecret(upstream *v1alpha1.LDAPIdentityPr
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonNotFound,
Reason: upstreamwatchers.ReasonNotFound,
Message: err.Error(),
}, ""
}
@ -279,7 +282,7 @@ func (c *ldapWatcherController) validateSecret(upstream *v1alpha1.LDAPIdentityPr
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonWrongType,
Reason: upstreamwatchers.ReasonWrongType,
Message: fmt.Sprintf("referenced Secret %q has wrong type %q (should be %q)",
secretName, secret.Type, corev1.SecretTypeBasicAuth),
}, secret.ResourceVersion
@ -291,7 +294,7 @@ func (c *ldapWatcherController) validateSecret(upstream *v1alpha1.LDAPIdentityPr
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonMissingKeys,
Reason: upstreamwatchers.ReasonMissingKeys,
Message: fmt.Sprintf("referenced Secret %q is missing required keys %q",
secretName, []string{corev1.BasicAuthUsernameKey, corev1.BasicAuthPasswordKey}),
}, secret.ResourceVersion
@ -300,7 +303,7 @@ func (c *ldapWatcherController) validateSecret(upstream *v1alpha1.LDAPIdentityPr
return &v1alpha1.Condition{
Type: typeBindSecretValid,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: "loaded bind secret",
}, secret.ResourceVersion
}
@ -309,7 +312,7 @@ func (c *ldapWatcherController) updateStatus(ctx context.Context, upstream *v1al
log := klogr.New().WithValues("namespace", upstream.Namespace, "name", upstream.Name)
updated := upstream.DeepCopy()
hadErrorCondition := mergeConditions(conditions, upstream.Generation, &updated.Status.Conditions, log)
hadErrorCondition := conditionsutil.Merge(conditions, upstream.Generation, &updated.Status.Conditions, log)
updated.Status.Phase = v1alpha1.LDAPPhaseReady
if hadErrorCondition {

View File

@ -1,7 +1,7 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatcher
package ldapupstreamwatcher
import (
"context"
@ -78,7 +78,7 @@ func TestLDAPUpstreamWatcherControllerFilterSecrets(t *testing.T) {
secretInformer := kubeInformers.Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
NewLDAPUpstreamWatcherController(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
New(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(secretInformer)
@ -123,7 +123,7 @@ func TestLDAPUpstreamWatcherControllerFilterLDAPIdentityProviders(t *testing.T)
secretInformer := kubeInformers.Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
NewLDAPUpstreamWatcherController(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
New(nil, nil, ldapIDPInformer, secretInformer, withInformer.WithInformer)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(ldapIDPInformer)

View File

@ -1,8 +1,8 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package upstreamwatcher implements controllers that watch the idp.supervisor.pinniped.dev API group's objects.
package upstreamwatcher
// Package oidcupstreamwatcher implements a controller which watches OIDCIdentityProviders.
package oidcupstreamwatcher
import (
"context"
@ -31,6 +31,8 @@ import (
idpinformers "go.pinniped.dev/generated/latest/client/supervisor/informers/externalversions/idp/v1alpha1"
"go.pinniped.dev/internal/constable"
pinnipedcontroller "go.pinniped.dev/internal/controller"
"go.pinniped.dev/internal/controller/conditionsutil"
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatchers"
"go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/internal/upstreamoidc"
@ -52,17 +54,12 @@ const (
// Constants related to conditions.
typeClientCredentialsValid = "ClientCredentialsValid"
typeOIDCDiscoverySucceeded = "OIDCDiscoverySucceeded"
reasonNotFound = "SecretNotFound"
reasonWrongType = "SecretWrongType"
reasonMissingKeys = "SecretMissingKeys"
reasonSuccess = "Success"
reasonUnreachable = "Unreachable"
reasonInvalidTLSConfig = "InvalidTLSConfig"
reasonInvalidResponse = "InvalidResponse"
reasonUnreachable = "Unreachable"
reasonInvalidResponse = "InvalidResponse"
// Errors that are generated by our reconcile process.
errOIDCFailureStatus = constable.Error("OIDCIdentityProvider has a failing condition")
errNoCertificates = constable.Error("no certificates found")
)
// UpstreamOIDCIdentityProviderICache is a thread safe cache that holds a list of validated upstream OIDC IDP configurations.
@ -111,8 +108,8 @@ type oidcWatcherController struct {
}
}
// NewOIDCUpstreamWatcherController instantiates a new controllerlib.Controller which will populate the provided UpstreamOIDCIdentityProviderICache.
func NewOIDCUpstreamWatcherController(
// New instantiates a new controllerlib.Controller which will populate the provided UpstreamOIDCIdentityProviderICache.
func New(
idpCache UpstreamOIDCIdentityProviderICache,
client pinnipedclientset.Interface,
oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer,
@ -212,7 +209,7 @@ func (c *oidcWatcherController) validateSecret(upstream *v1alpha1.OIDCIdentityPr
return &v1alpha1.Condition{
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonNotFound,
Reason: upstreamwatchers.ReasonNotFound,
Message: err.Error(),
}
}
@ -222,7 +219,7 @@ func (c *oidcWatcherController) validateSecret(upstream *v1alpha1.OIDCIdentityPr
return &v1alpha1.Condition{
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonWrongType,
Reason: upstreamwatchers.ReasonWrongType,
Message: fmt.Sprintf("referenced Secret %q has wrong type %q (should be %q)", secretName, secret.Type, oidcClientSecretType),
}
}
@ -234,7 +231,7 @@ func (c *oidcWatcherController) validateSecret(upstream *v1alpha1.OIDCIdentityPr
return &v1alpha1.Condition{
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionFalse,
Reason: reasonMissingKeys,
Reason: upstreamwatchers.ReasonMissingKeys,
Message: fmt.Sprintf("referenced Secret %q is missing required keys %q", secretName, []string{clientIDDataKey, clientSecretDataKey}),
}
}
@ -245,7 +242,7 @@ func (c *oidcWatcherController) validateSecret(upstream *v1alpha1.OIDCIdentityPr
return &v1alpha1.Condition{
Type: typeClientCredentialsValid,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: "loaded client credentials",
}
}
@ -262,7 +259,7 @@ func (c *oidcWatcherController) validateIssuer(ctx context.Context, upstream *v1
return &v1alpha1.Condition{
Type: typeOIDCDiscoverySucceeded,
Status: v1alpha1.ConditionFalse,
Reason: reasonInvalidTLSConfig,
Reason: upstreamwatchers.ReasonInvalidTLSConfig,
Message: err.Error(),
}
}
@ -314,7 +311,7 @@ func (c *oidcWatcherController) validateIssuer(ctx context.Context, upstream *v1
return &v1alpha1.Condition{
Type: typeOIDCDiscoverySucceeded,
Status: v1alpha1.ConditionTrue,
Reason: reasonSuccess,
Reason: upstreamwatchers.ReasonSuccess,
Message: "discovered issuer configuration",
}
}
@ -335,7 +332,7 @@ func (*oidcWatcherController) getTLSConfig(upstream *v1alpha1.OIDCIdentityProvid
result.RootCAs = x509.NewCertPool()
if !result.RootCAs.AppendCertsFromPEM(bundle) {
return nil, fmt.Errorf("spec.certificateAuthorityData is invalid: %w", errNoCertificates)
return nil, fmt.Errorf("spec.certificateAuthorityData is invalid: %w", upstreamwatchers.ErrNoCertificates)
}
return &result, nil
@ -345,7 +342,7 @@ func (c *oidcWatcherController) updateStatus(ctx context.Context, upstream *v1al
log := c.log.WithValues("namespace", upstream.Namespace, "name", upstream.Name)
updated := upstream.DeepCopy()
hadErrorCondition := mergeConditions(conditions, upstream.Generation, &updated.Status.Conditions, log)
hadErrorCondition := conditionsutil.Merge(conditions, upstream.Generation, &updated.Status.Conditions, log)
updated.Status.Phase = v1alpha1.PhaseReady
if hadErrorCondition {
@ -365,60 +362,6 @@ func (c *oidcWatcherController) updateStatus(ctx context.Context, upstream *v1al
}
}
// mergeConditions merges conditions into conditionsToUpdate. If returns true if it merged any error conditions.
func mergeConditions(conditions []*v1alpha1.Condition, observedGeneration int64, conditionsToUpdate *[]v1alpha1.Condition, log logr.Logger) bool {
hadErrorCondition := false
for i := range conditions {
cond := conditions[i].DeepCopy()
cond.LastTransitionTime = metav1.Now()
cond.ObservedGeneration = observedGeneration
if mergeCondition(conditionsToUpdate, cond) {
log.Info("updated condition", "type", cond.Type, "status", cond.Status, "reason", cond.Reason, "message", cond.Message)
}
if cond.Status == v1alpha1.ConditionFalse {
hadErrorCondition = true
}
}
sort.SliceStable(*conditionsToUpdate, func(i, j int) bool {
return (*conditionsToUpdate)[i].Type < (*conditionsToUpdate)[j].Type
})
return hadErrorCondition
}
// mergeCondition merges a new v1alpha1.Condition into a slice of existing conditions. It returns true
// if the condition has meaningfully changed.
func mergeCondition(existing *[]v1alpha1.Condition, new *v1alpha1.Condition) bool {
// Find any existing condition with a matching type.
var old *v1alpha1.Condition
for i := range *existing {
if (*existing)[i].Type == new.Type {
old = &(*existing)[i]
continue
}
}
// If there is no existing condition of this type, append this one and we're done.
if old == nil {
*existing = append(*existing, *new)
return true
}
// Set the LastTransitionTime depending on whether the status has changed.
new = new.DeepCopy()
if old.Status == new.Status {
new.LastTransitionTime = old.LastTransitionTime
}
// If anything has actually changed, update the entry and return true.
if !equality.Semantic.DeepEqual(old, new) {
*old = *new
return true
}
// Otherwise the entry is already up to date.
return false
}
func (*oidcWatcherController) computeScopes(additionalScopes []string) []string {
// First compute the unique set of scopes, including "openid" (de-duplicate).
set := make(map[string]bool, len(additionalScopes)+1)

View File

@ -1,7 +1,7 @@
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatcher
package oidcupstreamwatcher
import (
"context"
@ -82,7 +82,7 @@ func TestOIDCUpstreamWatcherControllerFilterSecret(t *testing.T) {
secretInformer := kubeInformers.Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
NewOIDCUpstreamWatcherController(
New(
cache,
nil,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
@ -762,7 +762,7 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
&upstreamoidc.ProviderConfig{Name: "initial-entry"},
})
controller := NewOIDCUpstreamWatcherController(
controller := New(
cache,
fakePinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),

View File

@ -0,0 +1,16 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package upstreamwatchers
import "go.pinniped.dev/internal/constable"
const (
ReasonNotFound = "SecretNotFound"
ReasonWrongType = "SecretWrongType"
ReasonMissingKeys = "SecretMissingKeys"
ReasonSuccess = "Success"
ReasonInvalidTLSConfig = "InvalidTLSConfig"
ErrNoCertificates = constable.Error("no certificates found")
)