From cd08f05d37cfe1b05725e48f64da60f06ccfd54d Mon Sep 17 00:00:00 2001 From: airycanon Date: Wed, 6 Sep 2023 18:14:40 +0800 Subject: [PATCH] add secret mutator --- secret/mutate.go | 62 ++++++++++++++++++++++++ secret/mutate_test.go | 110 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 secret/mutate.go create mode 100644 secret/mutate_test.go diff --git a/secret/mutate.go b/secret/mutate.go new file mode 100644 index 00000000..39494f0d --- /dev/null +++ b/secret/mutate.go @@ -0,0 +1,62 @@ +package secret + +import ( + "context" + + corev1 "k8s.io/api/core/v1" +) + +// Mutator interface defines a method for modifying Secret objects. +type Mutator interface { + Mutate(context.Context, *corev1.Secret) error +} + +// MutateFunc is a function type that implements the Mutator interface. +type MutateFunc func(context.Context, *corev1.Secret) error + +func (m MutateFunc) Mutate(ctx context.Context, secret *corev1.Secret) error { + return m(ctx, secret) +} + +// MutatorList is a slice of Mutators. +type MutatorList []Mutator + +// Mutate iterates over the list of mutators and applies them to the provided Secret object. +func (m MutatorList) Mutate(ctx context.Context, secret *corev1.Secret) error { + for _, mutator := range m { + if err := mutator.Mutate(ctx, secret); err != nil { + return err + } + } + return nil +} + +// DefaultMutator is a no-operation implementation of the Mutator interface. +// It is used as a default option when there is no Mutator associated with the context. +var DefaultMutator = &NoOpMutator{} + +// NoOpMutator struct implements the Mutator interface but performs no actions. +type NoOpMutator struct { +} + +// Mutate is the NoOpMutator's implementation that simply returns the passed-in Secret object without any modifications. +func (a *NoOpMutator) Mutate(_ context.Context, secret *corev1.Secret) error { + return nil +} + +// mutatorKey is a key used for storing a Mutator in a context. +var mutatorKey = struct{}{} + +// MutatorFromCtx retrieves a Mutator from the given context. +// If no Mutator is found, it returns the noOpMutator. +func MutatorFromCtx(ctx context.Context) Mutator { + if mutation, ok := ctx.Value(mutatorKey).(Mutator); ok { + return mutation + } + return DefaultMutator +} + +// WithMutator attaches a Mutator to a context and returns the new context. +func WithMutator(ctx context.Context, mutator Mutator) context.Context { + return context.WithValue(ctx, mutatorKey, mutator) +} diff --git a/secret/mutate_test.go b/secret/mutate_test.go new file mode 100644 index 00000000..86665700 --- /dev/null +++ b/secret/mutate_test.go @@ -0,0 +1,110 @@ +package secret + +import ( + "context" + "errors" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" +) + +// MockMutator is a mock implementation of the Mutator interface for testing. +type MockMutator struct { + Modified bool +} + +// Mutate modifies the Secret object by appending "-modified" to its Name field. +func (m *MockMutator) Mutate(_ context.Context, secret *corev1.Secret) error { + secret.Name += "-modified" + m.Modified = true + return nil +} + +// TestMutatorList tests the Mutate function for a list of Mutators. +func TestMutatorList(t *testing.T) { + g := NewGomegaWithT(t) + + mockMutator := &MockMutator{} + list := MutatorList{DefaultMutator, mockMutator} + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + } + err := list.Mutate(context.Background(), secret) + + g.Expect(err).To(BeNil()) + g.Expect(secret.Name).To(Equal("test-modified")) + g.Expect(mockMutator.Modified).To(BeTrue()) +} + +// TestMutatorList tests the Mutate function for a list of Mutators. +func TestMutatorListError(t *testing.T) { + g := NewGomegaWithT(t) + + m1 := MutateFunc(func(ctx context.Context, secret *corev1.Secret) error { + return errors.New("test") + }) + + m2 := MutateFunc(func(ctx context.Context, secret *corev1.Secret) error { + return nil + }) + list := MutatorList{m1, m2} + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + } + err := list.Mutate(context.Background(), secret) + + g.Expect(err).NotTo(BeNil()) +} + +// TestNoOpMutator tests the no-operation Mutator. +func TestNoOpMutator(t *testing.T) { + g := NewGomegaWithT(t) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + } + err := DefaultMutator.Mutate(context.Background(), secret) + + g.Expect(err).To(BeNil()) + g.Expect(secret.Name).To(Equal("test")) +} + +// TestNoOpMutator tests the no-operation Mutator. +func TestMutatorFunc(t *testing.T) { + g := NewGomegaWithT(t) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + } + mutator := MutateFunc(func(ctx context.Context, secret *corev1.Secret) error { + secret.Name += "-modified" + return nil + }) + err := mutator.Mutate(context.Background(), secret) + + g.Expect(err).To(BeNil()) + g.Expect(secret.Name).To(Equal("test-modified")) +} + +// TestMutatorFromCtx tests the retrieval of a Mutator from a context. +func TestMutatorFromCtx(t *testing.T) { + g := NewGomegaWithT(t) + + ctx := context.Background() + g.Expect(MutatorFromCtx(ctx)).To(Equal(DefaultMutator)) + + mockMutator := &MockMutator{} + ctx = WithMutator(ctx, mockMutator) + g.Expect(MutatorFromCtx(ctx)).To(Equal(mockMutator)) +}