Add a controller to clean up stale entries in the idpcache.Cache.
Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
parent
acfc5acfb2
commit
75ea0f48d9
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package webhookcachecleaner implements a controller for garbage collectting webhook IDPs from an IDP cache.
|
||||||
|
package webhookcachecleaner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
idpv1alpha1 "github.com/suzerain-io/pinniped/generated/1.19/apis/idp/v1alpha1"
|
||||||
|
idpinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions/idp/v1alpha1"
|
||||||
|
pinnipedcontroller "github.com/suzerain-io/pinniped/internal/controller"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controller/identityprovider/idpcache"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New instantiates a new controllerlib.Controller which will garbage collect webhooks from the provided Cache.
|
||||||
|
func New(cache *idpcache.Cache, webhookIDPs idpinformers.WebhookIdentityProviderInformer, log logr.Logger) controllerlib.Controller {
|
||||||
|
return controllerlib.New(
|
||||||
|
controllerlib.Config{
|
||||||
|
Name: "webhookcachecleaner-controller",
|
||||||
|
Syncer: &controller{
|
||||||
|
cache: cache,
|
||||||
|
webhookIDPs: webhookIDPs,
|
||||||
|
log: log.WithName("webhookcachecleaner-controller"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
controllerlib.WithInformer(
|
||||||
|
webhookIDPs,
|
||||||
|
pinnipedcontroller.NoOpFilter(),
|
||||||
|
controllerlib.InformerOption{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
cache *idpcache.Cache
|
||||||
|
webhookIDPs idpinformers.WebhookIdentityProviderInformer
|
||||||
|
log logr.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync implements controllerlib.Syncer.
|
||||||
|
func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||||
|
webhooks, err := c.webhookIDPs.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list WebhookIdentityProviders: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index the current webhooks by key.
|
||||||
|
webhooksByKey := map[controllerlib.Key]*idpv1alpha1.WebhookIdentityProvider{}
|
||||||
|
for _, webhook := range webhooks {
|
||||||
|
key := controllerlib.Key{Namespace: webhook.Namespace, Name: webhook.Name}
|
||||||
|
webhooksByKey[key] = webhook
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete any entries from the cache which are no longer in the cluster.
|
||||||
|
for _, key := range c.cache.Keys() {
|
||||||
|
if _, exists := webhooksByKey[key]; !exists {
|
||||||
|
c.log.WithValues("idp", klog.KRef(key.Namespace, key.Name)).Info("deleting webhook IDP from cache")
|
||||||
|
c.cache.Delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package webhookcachecleaner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
|
||||||
|
idpv1alpha "github.com/suzerain-io/pinniped/generated/1.19/apis/idp/v1alpha1"
|
||||||
|
pinnipedfake "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned/fake"
|
||||||
|
pinnipedinformers "github.com/suzerain-io/pinniped/generated/1.19/client/informers/externalversions"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controller/identityprovider/idpcache"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/controllerlib"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/testutil/testlogger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestController(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testKey1 := controllerlib.Key{Namespace: "test-namespace", Name: "test-name-one"}
|
||||||
|
testKey2 := controllerlib.Key{Namespace: "test-namespace", Name: "test-name-two"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
syncKey controllerlib.Key
|
||||||
|
webhookIDPs []runtime.Object
|
||||||
|
initialCache map[controllerlib.Key]authenticator.Token
|
||||||
|
wantErr string
|
||||||
|
wantLogs []string
|
||||||
|
wantCacheKeys []controllerlib.Key
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no change",
|
||||||
|
syncKey: testKey1,
|
||||||
|
initialCache: map[controllerlib.Key]authenticator.Token{testKey1: nil},
|
||||||
|
webhookIDPs: []runtime.Object{
|
||||||
|
&idpv1alpha.WebhookIdentityProvider{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: testKey1.Namespace,
|
||||||
|
Name: testKey1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantCacheKeys: []controllerlib.Key{testKey1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IDPs not yet added",
|
||||||
|
syncKey: testKey1,
|
||||||
|
initialCache: nil,
|
||||||
|
webhookIDPs: []runtime.Object{
|
||||||
|
&idpv1alpha.WebhookIdentityProvider{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: testKey1.Namespace,
|
||||||
|
Name: testKey1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&idpv1alpha.WebhookIdentityProvider{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: testKey2.Namespace,
|
||||||
|
Name: testKey2.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantCacheKeys: []controllerlib.Key{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful cleanup",
|
||||||
|
syncKey: testKey1,
|
||||||
|
initialCache: map[controllerlib.Key]authenticator.Token{
|
||||||
|
testKey1: nil,
|
||||||
|
testKey2: nil,
|
||||||
|
},
|
||||||
|
webhookIDPs: []runtime.Object{
|
||||||
|
&idpv1alpha.WebhookIdentityProvider{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: testKey1.Namespace,
|
||||||
|
Name: testKey1.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`webhookcachecleaner-controller "level"=0 "msg"="deleting webhook IDP from cache" "idp"={"name":"test-name-two","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheKeys: []controllerlib.Key{testKey1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fakeClient := pinnipedfake.NewSimpleClientset(tt.webhookIDPs...)
|
||||||
|
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
cache := idpcache.New()
|
||||||
|
for k, v := range tt.initialCache {
|
||||||
|
cache.Store(k, v)
|
||||||
|
}
|
||||||
|
testLog := testlogger.New(t)
|
||||||
|
|
||||||
|
controller := New(cache, informers.IDP().V1alpha1().WebhookIdentityProviders(), testLog)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
informers.Start(ctx.Done())
|
||||||
|
controllerlib.TestRunSynchronously(t, controller)
|
||||||
|
|
||||||
|
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
|
||||||
|
|
||||||
|
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.wantLogs, testLog.Lines())
|
||||||
|
require.ElementsMatch(t, tt.wantCacheKeys, cache.Keys())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user