297 lines
9.3 KiB
Go
297 lines
9.3 KiB
Go
|
// Copyright 2023 the Pinniped contributors. All Rights Reserved.
|
||
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
||
|
package secrets
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
corev1 "k8s.io/api/core/v1"
|
||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
"k8s.io/apimachinery/pkg/runtime"
|
||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||
|
kubeinformers "k8s.io/client-go/informers"
|
||
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||
|
kubetesting "k8s.io/client-go/testing"
|
||
|
|
||
|
"go.pinniped.dev/internal/controllerlib"
|
||
|
"go.pinniped.dev/internal/plog"
|
||
|
"go.pinniped.dev/internal/testutil"
|
||
|
)
|
||
|
|
||
|
func TestNewServiceAccountTokenCleanupController(t *testing.T) {
|
||
|
namespace := "a-namespace"
|
||
|
legacySecretName := "a-secret"
|
||
|
observableWithInformerOption := testutil.NewObservableWithInformerOption()
|
||
|
secretsInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().Secrets()
|
||
|
|
||
|
var log bytes.Buffer
|
||
|
_ = NewServiceAccountTokenCleanupController(
|
||
|
namespace,
|
||
|
legacySecretName,
|
||
|
nil, // not needed for this test
|
||
|
secretsInformer,
|
||
|
observableWithInformerOption.WithInformer,
|
||
|
plog.TestLogger(t, &log),
|
||
|
)
|
||
|
|
||
|
secretsInformerFilter := observableWithInformerOption.GetFilterForInformer(secretsInformer)
|
||
|
|
||
|
legacySecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: legacySecretName, Namespace: namespace}, Type: corev1.SecretTypeServiceAccountToken}
|
||
|
wrongName := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrongName", Namespace: namespace}, Type: corev1.SecretTypeServiceAccountToken}
|
||
|
wrongType := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrongType", Namespace: namespace}, Type: "other-type"}
|
||
|
wrongNamespace := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wrongNamespace", Namespace: "wrong-namespace"}, Type: corev1.SecretTypeServiceAccountToken}
|
||
|
wrongObject := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "config-map", Namespace: namespace}}
|
||
|
|
||
|
require.False(t, secretsInformerFilter.Add(wrongName))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongName, wrongNamespace))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongNamespace, wrongName))
|
||
|
require.False(t, secretsInformerFilter.Delete(wrongName))
|
||
|
|
||
|
require.False(t, secretsInformerFilter.Add(wrongObject))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongObject, wrongNamespace))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongNamespace, wrongObject))
|
||
|
require.False(t, secretsInformerFilter.Delete(wrongObject))
|
||
|
|
||
|
require.False(t, secretsInformerFilter.Add(wrongNamespace))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongNamespace, wrongObject))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongObject, wrongNamespace))
|
||
|
require.False(t, secretsInformerFilter.Delete(wrongNamespace))
|
||
|
|
||
|
require.False(t, secretsInformerFilter.Add(wrongType))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongType, wrongNamespace))
|
||
|
require.False(t, secretsInformerFilter.Update(wrongNamespace, wrongType))
|
||
|
require.False(t, secretsInformerFilter.Delete(wrongType))
|
||
|
|
||
|
require.True(t, secretsInformerFilter.Add(legacySecret))
|
||
|
require.True(t, secretsInformerFilter.Update(legacySecret, wrongNamespace))
|
||
|
require.True(t, secretsInformerFilter.Update(wrongNamespace, legacySecret))
|
||
|
require.True(t, secretsInformerFilter.Delete(legacySecret))
|
||
|
}
|
||
|
|
||
|
func TestSync(t *testing.T) {
|
||
|
kubeAPIClient := kubernetesfake.NewSimpleClientset()
|
||
|
kubeInformerClient := kubernetesfake.NewSimpleClientset()
|
||
|
|
||
|
kubeInformers := kubeinformers.NewSharedInformerFactory(
|
||
|
kubeInformerClient,
|
||
|
0,
|
||
|
)
|
||
|
|
||
|
namespace := "some-namespace"
|
||
|
secretToDelete := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "secret-to-delete",
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
secretWithWrongName := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "wrong-name",
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
secretWithWrongType := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "secret-to-leave-alone",
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
}
|
||
|
secretWithWrongNamespace := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "secret-to-delete",
|
||
|
Namespace: "other",
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretToDelete))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretToDelete))
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongName))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongName))
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongType))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongType))
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongNamespace))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongNamespace))
|
||
|
|
||
|
var log bytes.Buffer
|
||
|
|
||
|
controller := NewServiceAccountTokenCleanupController(
|
||
|
namespace,
|
||
|
"secret-to-delete",
|
||
|
kubeAPIClient,
|
||
|
kubeInformers.Core().V1().Secrets(),
|
||
|
controllerlib.WithInformer,
|
||
|
plog.TestLogger(t, &log),
|
||
|
)
|
||
|
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
defer cancel()
|
||
|
|
||
|
// Must start informers before calling TestRunSynchronously().
|
||
|
kubeInformers.Start(ctx.Done())
|
||
|
|
||
|
controllerlib.TestRunSynchronously(t, controller)
|
||
|
|
||
|
err := controllerlib.TestSync(t, controller, controllerlib.Context{
|
||
|
Context: ctx,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
t.Log(log.String())
|
||
|
|
||
|
expectedActions := []kubetesting.Action{kubetesting.NewDeleteAction(
|
||
|
schema.GroupVersionResource{
|
||
|
Group: "",
|
||
|
Version: "v1",
|
||
|
Resource: "secrets",
|
||
|
},
|
||
|
namespace,
|
||
|
"secret-to-delete",
|
||
|
)}
|
||
|
actualActions := kubeAPIClient.Actions()
|
||
|
require.Equal(t, expectedActions, actualActions)
|
||
|
}
|
||
|
|
||
|
func TestSync_NoSecretToDelete(t *testing.T) {
|
||
|
kubeAPIClient := kubernetesfake.NewSimpleClientset()
|
||
|
kubeInformerClient := kubernetesfake.NewSimpleClientset()
|
||
|
|
||
|
kubeInformers := kubeinformers.NewSharedInformerFactory(
|
||
|
kubeInformerClient,
|
||
|
0,
|
||
|
)
|
||
|
|
||
|
namespace := "some-namespace"
|
||
|
secretWithWrongName := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "wrong-name",
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
secretWithWrongType := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "secret-to-leave-alone",
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
}
|
||
|
secretWithWrongNamespace := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: "secret-to-delete",
|
||
|
Namespace: "other",
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongName))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongName))
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongType))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongType))
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretWithWrongNamespace))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretWithWrongNamespace))
|
||
|
|
||
|
var log bytes.Buffer
|
||
|
|
||
|
controller := NewServiceAccountTokenCleanupController(
|
||
|
namespace,
|
||
|
"secret-to-delete",
|
||
|
kubeAPIClient,
|
||
|
kubeInformers.Core().V1().Secrets(),
|
||
|
controllerlib.WithInformer,
|
||
|
plog.TestLogger(t, &log),
|
||
|
)
|
||
|
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
defer cancel()
|
||
|
|
||
|
// Must start informers before calling TestRunSynchronously().
|
||
|
kubeInformers.Start(ctx.Done())
|
||
|
|
||
|
controllerlib.TestRunSynchronously(t, controller)
|
||
|
|
||
|
err := controllerlib.TestSync(t, controller, controllerlib.Context{
|
||
|
Context: ctx,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
t.Log(log.String())
|
||
|
|
||
|
actualActions := kubeAPIClient.Actions()
|
||
|
require.Empty(t, actualActions)
|
||
|
}
|
||
|
|
||
|
func TestSync_ReturnsAPIErrors(t *testing.T) {
|
||
|
kubeAPIClient := kubernetesfake.NewSimpleClientset()
|
||
|
kubeInformerClient := kubernetesfake.NewSimpleClientset()
|
||
|
|
||
|
kubeInformers := kubeinformers.NewSharedInformerFactory(
|
||
|
kubeInformerClient,
|
||
|
0,
|
||
|
)
|
||
|
|
||
|
errorMessage := "error from API client"
|
||
|
kubeAPIClient.PrependReactor(
|
||
|
"delete",
|
||
|
"secrets",
|
||
|
func(a kubetesting.Action) (bool, runtime.Object, error) {
|
||
|
return true, nil, errors.New(errorMessage)
|
||
|
},
|
||
|
)
|
||
|
namespace := "some-namespace"
|
||
|
|
||
|
var log bytes.Buffer
|
||
|
|
||
|
legacySecretName := "secret-to-delete"
|
||
|
secretToDelete := &corev1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: legacySecretName,
|
||
|
Namespace: namespace,
|
||
|
},
|
||
|
Type: corev1.SecretTypeServiceAccountToken,
|
||
|
}
|
||
|
|
||
|
require.NoError(t, kubeAPIClient.Tracker().Add(secretToDelete))
|
||
|
require.NoError(t, kubeInformerClient.Tracker().Add(secretToDelete))
|
||
|
|
||
|
controller := NewServiceAccountTokenCleanupController(
|
||
|
namespace,
|
||
|
legacySecretName,
|
||
|
kubeAPIClient,
|
||
|
kubeInformers.Core().V1().Secrets(),
|
||
|
controllerlib.WithInformer,
|
||
|
plog.TestLogger(t, &log),
|
||
|
)
|
||
|
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
defer cancel()
|
||
|
|
||
|
// Must start informers before calling TestRunSynchronously().
|
||
|
kubeInformers.Start(ctx.Done())
|
||
|
|
||
|
controllerlib.TestRunSynchronously(t, controller)
|
||
|
|
||
|
err := controllerlib.TestSync(t, controller, controllerlib.Context{
|
||
|
Context: ctx,
|
||
|
})
|
||
|
require.ErrorContains(t, err, fmt.Sprintf("unable to delete secret %s in namespace %s: %s", legacySecretName, namespace, errorMessage))
|
||
|
t.Log(log.String())
|
||
|
|
||
|
expectedActions := []kubetesting.Action{kubetesting.NewDeleteAction(
|
||
|
schema.GroupVersionResource{
|
||
|
Group: "",
|
||
|
Version: "v1",
|
||
|
Resource: "secrets",
|
||
|
},
|
||
|
namespace,
|
||
|
legacySecretName,
|
||
|
)}
|
||
|
actualActions := kubeAPIClient.Actions()
|
||
|
require.Equal(t, expectedActions, actualActions)
|
||
|
}
|