Test filters in SupervisorSecretsController

This commit is contained in:
aram price 2020-12-14 16:08:48 -08:00
parent 9c79adcb26
commit 6e8d564013
3 changed files with 192 additions and 45 deletions

View File

@ -159,6 +159,7 @@ func startControllers(
plog.Debug("setting csrf cookie secret") plog.Debug("setting csrf cookie secret")
secretCache.SetCSRFCookieEncoderHashKey(secret) secretCache.SetCSRFCookieEncoderHashKey(secret)
}, },
controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
). ).

View File

@ -29,34 +29,37 @@ var generateKey = generateSymmetricKey
type supervisorSecretsController struct { type supervisorSecretsController struct {
owner *appsv1.Deployment owner *appsv1.Deployment
labels map[string]string labels map[string]string
client kubernetes.Interface kubeClient kubernetes.Interface
secrets corev1informers.SecretInformer secretInformer corev1informers.SecretInformer
setCache func(secret []byte) setCacheFunc func(secret []byte)
} }
// NewSupervisorSecretsController instantiates a new controllerlib.Controller which will ensure existence of a generated secret. // NewSupervisorSecretsController instantiates a new controllerlib.Controller which will ensure existence of a generated secret.
func NewSupervisorSecretsController( func NewSupervisorSecretsController(
// TODO: generate the name for the secret and label the secret with the UID of the owner? So that we don't have naming conflicts if the user has already created a Secret with that name. // TODO: generate the name for the secret and label the secret with the UID of the owner? So that we don't have naming conflicts if the user has already created a Secret with that name.
// TODO: add tests for the filter like we do in the JWKSWriterController?
owner *appsv1.Deployment, owner *appsv1.Deployment,
labels map[string]string, labels map[string]string,
client kubernetes.Interface, kubeClient kubernetes.Interface,
secrets corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
setCache func(secret []byte), setCacheFunc func(secret []byte),
withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller { ) controllerlib.Controller {
c := supervisorSecretsController{ c := supervisorSecretsController{
owner: owner, owner: owner,
labels: labels, labels: labels,
client: client, kubeClient: kubeClient,
secrets: secrets, secretInformer: secretInformer,
setCache: setCache, setCacheFunc: setCacheFunc,
} }
filter := pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool {
return metav1.IsControlledBy(obj, owner)
}, nil)
return controllerlib.New( return controllerlib.New(
controllerlib.Config{Name: owner.Name + "-secret-generator", Syncer: &c}, controllerlib.Config{Name: owner.Name + "-secret-generator", Syncer: &c},
controllerlib.WithInformer(secrets, filter, controllerlib.InformerOption{}), withInformer(
secretInformer,
pinnipedcontroller.SimpleFilter(func(obj metav1.Object) bool {
return metav1.IsControlledBy(obj, owner)
}, nil),
controllerlib.InformerOption{},
),
controllerlib.WithInitialEvent(controllerlib.Key{ controllerlib.WithInitialEvent(controllerlib.Key{
Namespace: owner.Namespace, Namespace: owner.Namespace,
Name: owner.Name + "-key", Name: owner.Name + "-key",
@ -66,7 +69,7 @@ func NewSupervisorSecretsController(
// Sync implements controllerlib.Syncer.Sync(). // Sync implements controllerlib.Syncer.Sync().
func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error { func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error {
secret, err := c.secrets.Lister().Secrets(ctx.Key.Namespace).Get(ctx.Key.Name) secret, err := c.secretInformer.Lister().Secrets(ctx.Key.Namespace).Get(ctx.Key.Name)
isNotFound := k8serrors.IsNotFound(err) isNotFound := k8serrors.IsNotFound(err)
if !isNotFound && err != nil { if !isNotFound && err != nil {
return fmt.Errorf("failed to list secret %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err) return fmt.Errorf("failed to list secret %s/%s: %w", ctx.Key.Namespace, ctx.Key.Name, err)
@ -75,7 +78,7 @@ func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error {
secretNeedsUpdate := isNotFound || !isValid(secret) secretNeedsUpdate := isNotFound || !isValid(secret)
if !secretNeedsUpdate { if !secretNeedsUpdate {
plog.Debug("secret is up to date", "secret", klog.KObj(secret)) plog.Debug("secret is up to date", "secret", klog.KObj(secret))
c.setCache(secret.Data[symmetricKeySecretDataKey]) c.setCacheFunc(secret.Data[symmetricKeySecretDataKey])
return nil return nil
} }
@ -93,18 +96,18 @@ func (c *supervisorSecretsController) Sync(ctx controllerlib.Context) error {
return fmt.Errorf("failed to create/update secret %s/%s: %w", newSecret.Namespace, newSecret.Name, err) return fmt.Errorf("failed to create/update secret %s/%s: %w", newSecret.Namespace, newSecret.Name, err)
} }
c.setCache(newSecret.Data[symmetricKeySecretDataKey]) c.setCacheFunc(newSecret.Data[symmetricKeySecretDataKey])
return nil return nil
} }
func (c *supervisorSecretsController) createSecret(ctx context.Context, newSecret *corev1.Secret) error { func (c *supervisorSecretsController) createSecret(ctx context.Context, newSecret *corev1.Secret) error {
_, err := c.client.CoreV1().Secrets(newSecret.Namespace).Create(ctx, newSecret, metav1.CreateOptions{}) _, err := c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Create(ctx, newSecret, metav1.CreateOptions{})
return err return err
} }
func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecret **corev1.Secret, secretName string) error { func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecret **corev1.Secret, secretName string) error {
secrets := c.client.CoreV1().Secrets((*newSecret).Namespace) secrets := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
return retry.RetryOnConflict(retry.DefaultBackoff, func() error { return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
currentSecret, err := secrets.Get(ctx, secretName, metav1.GetOptions{}) currentSecret, err := secrets.Get(ctx, secretName, metav1.GetOptions{})
isNotFound := k8serrors.IsNotFound(err) isNotFound := k8serrors.IsNotFound(err)

View File

@ -21,10 +21,163 @@ import (
kubernetesfake "k8s.io/client-go/kubernetes/fake" kubernetesfake "k8s.io/client-go/kubernetes/fake"
kubetesting "k8s.io/client-go/testing" kubetesting "k8s.io/client-go/testing"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
"go.pinniped.dev/internal/controllerlib" "go.pinniped.dev/internal/controllerlib"
"go.pinniped.dev/internal/testutil"
) )
func TestController(t *testing.T) { var (
owner = &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "some-owner-name",
UID: "some-owner-uid",
},
}
ownerGVK = schema.GroupVersionKind{
Group: appsv1.SchemeGroupVersion.Group,
Version: appsv1.SchemeGroupVersion.Version,
Kind: "Deployment",
}
labels = map[string]string{
"some-label-key-1": "some-label-value-1",
"some-label-key-2": "some-label-value-2",
}
)
func TestSupervisorSecretsControllerFilterSecret(t *testing.T) {
t.Parallel()
tests := []struct {
name string
secret corev1.Secret
wantAdd bool
wantUpdate bool
wantDelete bool
}{
{
name: "no owner reference",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{},
},
},
{
name: "owner reference without controller set to true",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Name: "some-name",
Kind: ownerGVK.Kind,
UID: owner.GetUID(),
},
},
},
},
},
{
name: "owner reference without correct APIVersion",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Name: "some-name",
Kind: ownerGVK.Kind,
Controller: boolPtr(true),
UID: owner.GetUID(),
}},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "owner reference without correct Kind",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
Name: "some-name",
Kind: "IncorrectKind",
Controller: boolPtr(true),
UID: owner.GetUID(),
},
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "correct owner reference",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(owner, ownerGVK),
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
{
name: "multiple owner references",
secret: corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "some-namespace",
OwnerReferences: []metav1.OwnerReference{
{
Kind: "UnrelatedKind",
},
*metav1.NewControllerRef(owner, ownerGVK),
},
},
},
wantAdd: true,
wantUpdate: true,
wantDelete: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
secretInformer := kubeinformers.NewSharedInformerFactory(
kubernetesfake.NewSimpleClientset(),
0,
).Core().V1().Secrets()
withInformer := testutil.NewObservableWithInformerOption()
_ = NewSupervisorSecretsController(
owner,
labels,
nil, // kubeClient, not needed
secretInformer,
nil, // setCache, not needed
withInformer.WithInformer,
)
unrelated := corev1.Secret{}
filter := withInformer.GetFilterForInformer(secretInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.secret))
require.Equal(t, test.wantUpdate, filter.Update(&test.secret, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.secret))
})
}
}
func TestSupervisorSecretsControllerSync(t *testing.T) {
const ( const (
generatedSecretNamespace = "some-namespace" generatedSecretNamespace = "some-namespace"
generatedSecretName = "some-name-abc123" generatedSecretName = "some-name-abc123"
@ -37,26 +190,9 @@ func TestController(t *testing.T) {
Resource: "secrets", Resource: "secrets",
} }
owner = &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "some-owner-name",
UID: "some-owner-uid",
},
}
ownerGVK = schema.GroupVersionKind{
Group: appsv1.SchemeGroupVersion.Group,
Version: appsv1.SchemeGroupVersion.Version,
Kind: "Deployment",
}
generatedSymmetricKey = []byte("some-neato-32-byte-generated-key") generatedSymmetricKey = []byte("some-neato-32-byte-generated-key")
otherGeneratedSymmetricKey = []byte("some-funio-32-byte-generated-key") otherGeneratedSymmetricKey = []byte("some-funio-32-byte-generated-key")
labels = map[string]string{
"some-label-key-1": "some-label-value-1",
"some-label-key-2": "some-label-value-2",
}
generatedSecret = &corev1.Secret{ generatedSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: generatedSecretName, Name: generatedSecretName,
@ -313,10 +449,17 @@ func TestController(t *testing.T) {
secrets := informers.Core().V1().Secrets() secrets := informers.Core().V1().Secrets()
var callbackSecret []byte var callbackSecret []byte
c := NewSupervisorSecretsController(owner, labels, apiClient, secrets, func(secret []byte) { c := NewSupervisorSecretsController(
owner,
labels,
apiClient,
secrets,
func(secret []byte) {
require.Nil(t, callbackSecret, "callback was called twice") require.Nil(t, callbackSecret, "callback was called twice")
callbackSecret = secret callbackSecret = secret
}) },
testutil.NewObservableWithInformerOption().WithInformer,
)
// Must start informers before calling TestRunSynchronously(). // Must start informers before calling TestRunSynchronously().
informers.Start(ctx.Done()) informers.Start(ctx.Done())