diff --git a/docs/libs/resource.md b/docs/libs/resource.md index c613a72..6fbe387 100644 --- a/docs/libs/resource.md +++ b/docs/libs/resource.md @@ -9,12 +9,15 @@ Create or update a `ConfigMap`, a `ServiceAccount` and a `Deployment` using the ```go type myDeploymentMutator struct { + meta MetadataMutator } var _ resource.Mutator[*appsv1.Deployment] = &myDeploymentMutator{} -func newDeploymentMutator() resources.Mutator[*appsv1.Deployment] { - return &MyDeploymentMutator{} +func newDeploymentMutator() resource.Mutator[*appsv1.Deployment] { + return &MyDeploymentMutator{ + meta: NewMetadataMutator() + } } func (m *MyDeploymentMutator) String() string { @@ -38,17 +41,22 @@ func (m *MyDeploymentMutator) Mutate(deployment *appsv1.Deployment) error { Image: "test-image:latest", }, } - return nil + return m.meta.Mutate(deployment) +} + +func (m *MyDeploymentMutator) MetadataMutator() resource.MetadataMutator { + return m.meta } func ReconcileResources(ctx context.Context, client client.Client) error { - configMapResource := resource.NewConfigMap("my-configmap", "my-namespace", map[string]string{ + configMapResource := resource.NewConfigMapMutator("my-configmap", "my-namespace") + configMapResource.MetadataMutator().WithLabels(map[string]string{ "label1": "value1", "label2": "value2", - }, nil) + }) - serviceAccountResource := resource.NewServiceAccount("my-serviceaccount", "my-namespace", nil, nil) + serviceAccountResource := resource.NewServiceAccountMutator("my-serviceaccount", "my-namespace") myDeploymentMutator := newDeploymentMutator() diff --git a/pkg/crds/crds.go b/pkg/crds/crds.go index 0372e97..cc7224d 100644 --- a/pkg/crds/crds.go +++ b/pkg/crds/crds.go @@ -57,7 +57,9 @@ func (m *CRDManager) CreateOrUpdateCRDs(ctx context.Context, log *logging.Logger if log != nil { log.Info("creating/updating CRD", "name", crd.Name, "cluster", c.ID()) } - err = resources.CreateOrUpdateResource(ctx, c.Client(), resources.NewCRDMutator(crd, crd.Labels, crd.Annotations)) + m := resources.NewCRDMutator(crd) + m.MetadataMutator().WithLabels(crd.Labels).WithAnnotations(crd.Annotations) + err = resources.CreateOrUpdateResource(ctx, c.Client(), m) errs = errors.Join(errs, err) } diff --git a/pkg/resources/clusterrole.go b/pkg/resources/clusterrole.go index d67dbbf..3d55951 100644 --- a/pkg/resources/clusterrole.go +++ b/pkg/resources/clusterrole.go @@ -3,8 +3,6 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - v1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -12,16 +10,16 @@ import ( type ClusterRoleMutator struct { Name string Rules []v1.PolicyRule - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*v1.ClusterRole] = &ClusterRoleMutator{} -func NewClusterRoleMutator(name string, rules []v1.PolicyRule, labels map[string]string, annotations map[string]string) Mutator[*v1.ClusterRole] { +func NewClusterRoleMutator(name string, rules []v1.PolicyRule) Mutator[*v1.ClusterRole] { return &ClusterRoleMutator{ Name: name, Rules: rules, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -45,3 +43,7 @@ func (m *ClusterRoleMutator) Mutate(r *v1.ClusterRole) error { r.Rules = m.Rules return m.meta.Mutate(r) } + +func (m *ClusterRoleMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/clusterrole_test.go b/pkg/resources/clusterrole_test.go index 9e4efcf..d467e5a 100644 --- a/pkg/resources/clusterrole_test.go +++ b/pkg/resources/clusterrole_test.go @@ -48,7 +48,8 @@ var _ = Describe("ClusterRoleMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a cluster role mutator - mutator = resources.NewClusterRoleMutator("test-clusterrole", rules, labels, annotations) + mutator = resources.NewClusterRoleMutator("test-clusterrole", rules) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty cluster role with correct metadata", func() { diff --git a/pkg/resources/clusterrolebinding.go b/pkg/resources/clusterrolebinding.go index 36daf2b..7cda001 100644 --- a/pkg/resources/clusterrolebinding.go +++ b/pkg/resources/clusterrolebinding.go @@ -3,8 +3,6 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - v1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -13,17 +11,17 @@ type ClusterRoleBindingMutator struct { ClusterRoleBindingName string RoleRef v1.RoleRef Subjects []v1.Subject - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*v1.ClusterRoleBinding] = &ClusterRoleBindingMutator{} -func NewClusterRoleBindingMutator(clusterRoleBindingName string, subjects []v1.Subject, roleRef v1.RoleRef, labels map[string]string, annotations map[string]string) Mutator[*v1.ClusterRoleBinding] { +func NewClusterRoleBindingMutator(clusterRoleBindingName string, subjects []v1.Subject, roleRef v1.RoleRef) Mutator[*v1.ClusterRoleBinding] { return &ClusterRoleBindingMutator{ ClusterRoleBindingName: clusterRoleBindingName, RoleRef: roleRef, Subjects: subjects, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -48,3 +46,7 @@ func (m *ClusterRoleBindingMutator) Mutate(r *v1.ClusterRoleBinding) error { r.Subjects = m.Subjects return m.meta.Mutate(r) } + +func (m *ClusterRoleBindingMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/clusterrolebinding_test.go b/pkg/resources/clusterrolebinding_test.go index 9f6b041..b639983 100644 --- a/pkg/resources/clusterrolebinding_test.go +++ b/pkg/resources/clusterrolebinding_test.go @@ -50,7 +50,8 @@ var _ = Describe("ClusterRoleBindingMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a cluster role binding mutator - mutator = resources.NewClusterRoleBindingMutator("test-clusterrolebinding", subjects, roleRef, labels, annotations) + mutator = resources.NewClusterRoleBindingMutator("test-clusterrolebinding", subjects, roleRef) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty cluster role binding with correct metadata", func() { diff --git a/pkg/resources/configmap.go b/pkg/resources/configmap.go index baea795..a531ee5 100644 --- a/pkg/resources/configmap.go +++ b/pkg/resources/configmap.go @@ -5,24 +5,23 @@ import ( core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) type ConfigMapMutator struct { Name string Namespace string Data map[string]string - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*core.ConfigMap] = &ConfigMapMutator{} -func NewConfigMapMutator(name, namespace string, data map[string]string, labels map[string]string, annotations map[string]string) Mutator[*core.ConfigMap] { +func NewConfigMapMutator(name, namespace string, data map[string]string) Mutator[*core.ConfigMap] { return &ConfigMapMutator{ Name: name, Namespace: namespace, Data: data, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -53,3 +52,7 @@ func (m *ConfigMapMutator) Mutate(cm *core.ConfigMap) error { } return m.meta.Mutate(cm) } + +func (m *ConfigMapMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/configmap_test.go b/pkg/resources/configmap_test.go index 06ed8a3..04c49a8 100644 --- a/pkg/resources/configmap_test.go +++ b/pkg/resources/configmap_test.go @@ -42,7 +42,8 @@ var _ = Describe("ConfigMapMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a ConfigMap mutator - mutator = resources.NewConfigMapMutator("test-configmap", "test-namespace", data, labels, annotations) + mutator = resources.NewConfigMapMutator("test-configmap", "test-namespace", data) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty ConfigMap with correct metadata", func() { diff --git a/pkg/resources/crd.go b/pkg/resources/crd.go index 5fcabd2..84503cc 100644 --- a/pkg/resources/crd.go +++ b/pkg/resources/crd.go @@ -3,21 +3,19 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type crdMutator struct { crd *apiextv1.CustomResourceDefinition - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*apiextv1.CustomResourceDefinition] = &crdMutator{} -func NewCRDMutator(crd *apiextv1.CustomResourceDefinition, labels map[string]string, annotations map[string]string) Mutator[*apiextv1.CustomResourceDefinition] { - return &crdMutator{crd: crd, meta: NewMetadataMutator(labels, annotations)} +func NewCRDMutator(crd *apiextv1.CustomResourceDefinition) Mutator[*apiextv1.CustomResourceDefinition] { + return &crdMutator{crd: crd, meta: NewMetadataMutator()} } func (m *crdMutator) String() string { @@ -40,3 +38,7 @@ func (m *crdMutator) Mutate(r *apiextv1.CustomResourceDefinition) error { m.crd.Spec.DeepCopyInto(&r.Spec) return m.meta.Mutate(r) } + +func (m *crdMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/crd_test.go b/pkg/resources/crd_test.go index 7f86959..c69675f 100644 --- a/pkg/resources/crd_test.go +++ b/pkg/resources/crd_test.go @@ -61,7 +61,8 @@ var _ = Describe("CRDMutator", func() { } // Create a CRD mutator - mutator = resources.NewCRDMutator(crd, labels, annotations) + mutator = resources.NewCRDMutator(crd) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty CRD with correct metadata", func() { diff --git a/pkg/resources/metadata.go b/pkg/resources/metadata.go index 2c6c7d5..3209494 100644 --- a/pkg/resources/metadata.go +++ b/pkg/resources/metadata.go @@ -1,21 +1,52 @@ package resources import ( + "maps" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) type metadataMutator struct { - Labels map[string]string - Annotations map[string]string + Labels map[string]string + Annotations map[string]string + OwnerReferences []metav1.OwnerReference + Finalizers []string +} + +type MetadataMutator interface { + Mutator[client.Object] + WithOwnerReferences(ownerReferences []metav1.OwnerReference) MetadataMutator + WithFinalizers(finalizers []string) MetadataMutator + WithLabels(labels map[string]string) MetadataMutator + WithAnnotations(annotations map[string]string) MetadataMutator } var _ Mutator[client.Object] = &metadataMutator{} +var _ MetadataMutator = &metadataMutator{} -func NewMetadataMutator(labels map[string]string, annotations map[string]string) Mutator[client.Object] { - return &metadataMutator{ - Labels: labels, - Annotations: annotations, - } +func NewMetadataMutator() MetadataMutator { + return &metadataMutator{} +} + +func (m *metadataMutator) WithLabels(labels map[string]string) MetadataMutator { + m.Labels = labels + return m +} + +func (m *metadataMutator) WithAnnotations(annotations map[string]string) MetadataMutator { + m.Annotations = annotations + return m +} + +func (m *metadataMutator) WithOwnerReferences(ownerReferences []metav1.OwnerReference) MetadataMutator { + m.OwnerReferences = ownerReferences + return m +} + +func (m *metadataMutator) WithFinalizers(finalizers []string) MetadataMutator { + m.Finalizers = finalizers + return m } func (m *metadataMutator) String() string { @@ -31,19 +62,62 @@ func (m *metadataMutator) Mutate(res client.Object) error { if res.GetLabels() == nil { res.SetLabels(make(map[string]string)) } - for k, v := range m.Labels { - res.GetLabels()[k] = v - } + maps.Copy(res.GetLabels(), m.Labels) } if m.Annotations != nil { if res.GetAnnotations() == nil { res.SetAnnotations(make(map[string]string)) } + maps.Copy(res.GetAnnotations(), m.Annotations) + } + + if m.OwnerReferences != nil { + // ensure that all owner references in m are also in res + if len(res.GetOwnerReferences()) == 0 { + res.SetOwnerReferences(make([]metav1.OwnerReference, len(m.OwnerReferences))) + for i, ownerRef := range m.OwnerReferences { + res.GetOwnerReferences()[i] = *ownerRef.DeepCopy() + } + } else { + for _, ownerRef := range m.OwnerReferences { + found := false + for _, existingRef := range res.GetOwnerReferences() { + if ownerRef.UID == existingRef.UID { + found = true + break + } + } + if !found { + res.SetOwnerReferences(append(res.GetOwnerReferences(), *ownerRef.DeepCopy())) + } + } + } + } - for k, v := range m.Annotations { - res.GetAnnotations()[k] = v + if m.Finalizers != nil { + if len(res.GetFinalizers()) == 0 { + res.SetFinalizers(make([]string, len(m.Finalizers))) + copy(res.GetFinalizers(), m.Finalizers) + } else { + for _, fin := range m.Finalizers { + found := false + for _, existingFin := range res.GetFinalizers() { + if fin == existingFin { + found = true + break + } + } + if !found { + res.SetFinalizers(append(res.GetFinalizers(), fin)) + } + } } } + return nil } + +func (m *metadataMutator) MetadataMutator() MetadataMutator { + return m +} diff --git a/pkg/resources/metadata_test.go b/pkg/resources/metadata_test.go new file mode 100644 index 0000000..3e06273 --- /dev/null +++ b/pkg/resources/metadata_test.go @@ -0,0 +1,80 @@ +package resources_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openmcp-project/controller-utils/pkg/resources" +) + +var _ = Describe("Metadata Mutator", func() { + + It("should correctly add labels, annotations, owner references and finalizers", func() { + ns := &corev1.Namespace{} + ns.ObjectMeta = metav1.ObjectMeta{ + Name: "test-namespace", + Labels: map[string]string{ + "oldLabel": "old", + }, + Annotations: map[string]string{ + "oldAnnotation": "old", + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Owner", + Name: "owner", + UID: "12345", + }, + }, + Finalizers: []string{ + "oldFinalizer", + }, + } + + m := resources.NewMetadataMutator(). + WithLabels(map[string]string{ + "newLabel": "new", + }). + WithAnnotations(map[string]string{ + "newAnnotation": "new", + }). + WithOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "NewOwner", + Name: "newOwner", + UID: "67890", + }, + }). + WithFinalizers([]string{"newFinalizer"}) + Expect(m.Mutate(ns)).To(Succeed()) + Expect(ns.Labels).To(HaveKeyWithValue("oldLabel", "old")) + Expect(ns.Labels).To(HaveKeyWithValue("newLabel", "new")) + Expect(ns.Annotations).To(HaveKeyWithValue("oldAnnotation", "old")) + Expect(ns.Annotations).To(HaveKeyWithValue("newAnnotation", "new")) + Expect(ns.OwnerReferences).To(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "APIVersion": Equal("v1"), + "Kind": Equal("Owner"), + "Name": Equal("owner"), + "UID": BeEquivalentTo("12345"), + }), + MatchFields(IgnoreExtras, Fields{ + "APIVersion": Equal("v1"), + "Kind": Equal("NewOwner"), + "Name": Equal("newOwner"), + "UID": BeEquivalentTo("67890"), + }), + )) + Expect(ns.Finalizers).To(ConsistOf( + "oldFinalizer", + "newFinalizer", + )) + }) + +}) diff --git a/pkg/resources/mutator.go b/pkg/resources/mutator.go index 7aeb3ce..c6bee99 100644 --- a/pkg/resources/mutator.go +++ b/pkg/resources/mutator.go @@ -12,6 +12,7 @@ type Mutator[K client.Object] interface { Empty() K Mutate(res K) error String() string + MetadataMutator() MetadataMutator } func GetResource[K client.Object](ctx context.Context, clt client.Client, m Mutator[K]) (K, error) { diff --git a/pkg/resources/mutator_test.go b/pkg/resources/mutator_test.go index 1d496d5..77a9691 100644 --- a/pkg/resources/mutator_test.go +++ b/pkg/resources/mutator_test.go @@ -42,7 +42,8 @@ var _ = Describe("Resource Functions", func() { data = map[string]string{"key1": "value1", "key2": "value2"} // Create a ConfigMap mutator - mutator = resources.NewConfigMapMutator("test-configmap", "test-namespace", data, labels, annotations) + mutator = resources.NewConfigMapMutator("test-configmap", "test-namespace", data) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should get, create or update, and delete a resource", func() { diff --git a/pkg/resources/namespace.go b/pkg/resources/namespace.go index 23a0c24..eedc66c 100644 --- a/pkg/resources/namespace.go +++ b/pkg/resources/namespace.go @@ -3,21 +3,19 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type namespaceMutator struct { name string - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*v1.Namespace] = &namespaceMutator{} -func NewNamespaceMutator(name string, labels map[string]string, annotations map[string]string) Mutator[*v1.Namespace] { - return &namespaceMutator{name: name, meta: NewMetadataMutator(labels, annotations)} +func NewNamespaceMutator(name string) Mutator[*v1.Namespace] { + return &namespaceMutator{name: name, meta: NewMetadataMutator()} } func (m *namespaceMutator) String() string { @@ -39,3 +37,7 @@ func (m *namespaceMutator) Empty() *v1.Namespace { func (m *namespaceMutator) Mutate(r *v1.Namespace) error { return m.meta.Mutate(r) } + +func (m *namespaceMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/namespace_test.go b/pkg/resources/namespace_test.go index 26a28bc..5515b95 100644 --- a/pkg/resources/namespace_test.go +++ b/pkg/resources/namespace_test.go @@ -40,7 +40,8 @@ var _ = Describe("NamespaceMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a namespace mutator - mutator = resources.NewNamespaceMutator("test-namespace", labels, annotations) + mutator = resources.NewNamespaceMutator("test-namespace") + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty namespace with correct metadata", func() { diff --git a/pkg/resources/role.go b/pkg/resources/role.go index 30ae583..abe81b9 100644 --- a/pkg/resources/role.go +++ b/pkg/resources/role.go @@ -3,8 +3,6 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - v1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -13,17 +11,17 @@ type RoleMutator struct { Name string Namespace string Rules []v1.PolicyRule - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*v1.Role] = &RoleMutator{} -func NewRoleMutator(name, namespace string, rules []v1.PolicyRule, labels map[string]string, annotations map[string]string) Mutator[*v1.Role] { +func NewRoleMutator(name, namespace string, rules []v1.PolicyRule) Mutator[*v1.Role] { return &RoleMutator{ Name: name, Namespace: namespace, Rules: rules, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -48,3 +46,7 @@ func (m *RoleMutator) Mutate(r *v1.Role) error { r.Rules = m.Rules return m.meta.Mutate(r) } + +func (m *RoleMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/role_test.go b/pkg/resources/role_test.go index 5b2f17e..7ee3b60 100644 --- a/pkg/resources/role_test.go +++ b/pkg/resources/role_test.go @@ -48,7 +48,8 @@ var _ = Describe("RoleMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a role mutator - mutator = resources.NewRoleMutator("test-role", "test-namespace", rules, labels, annotations) + mutator = resources.NewRoleMutator("test-role", "test-namespace", rules) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty role with correct metadata", func() { diff --git a/pkg/resources/rolebinding.go b/pkg/resources/rolebinding.go index a6b4f86..e97b67f 100644 --- a/pkg/resources/rolebinding.go +++ b/pkg/resources/rolebinding.go @@ -3,8 +3,6 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - v1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -14,18 +12,18 @@ type RoleBindingMutator struct { Namespace string Subjects []v1.Subject RoleRef v1.RoleRef - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*v1.RoleBinding] = &RoleBindingMutator{} -func NewRoleBindingMutator(name, namespace string, subjects []v1.Subject, roleRef v1.RoleRef, labels map[string]string, annotations map[string]string) Mutator[*v1.RoleBinding] { +func NewRoleBindingMutator(name, namespace string, subjects []v1.Subject, roleRef v1.RoleRef) Mutator[*v1.RoleBinding] { return &RoleBindingMutator{ Name: name, Namespace: namespace, Subjects: subjects, RoleRef: roleRef, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -51,3 +49,7 @@ func (m *RoleBindingMutator) Mutate(rb *v1.RoleBinding) error { rb.RoleRef = m.RoleRef return m.meta.Mutate(rb) } + +func (m *RoleBindingMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/rolebinding_test.go b/pkg/resources/rolebinding_test.go index 6c633c6..933dc8f 100644 --- a/pkg/resources/rolebinding_test.go +++ b/pkg/resources/rolebinding_test.go @@ -50,7 +50,8 @@ var _ = Describe("RoleBindingMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a role binding mutator - mutator = resources.NewRoleBindingMutator("test-rolebinding", "test-namespace", subjects, roleRef, labels, annotations) + mutator = resources.NewRoleBindingMutator("test-rolebinding", "test-namespace", subjects, roleRef) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty role binding with correct metadata", func() { diff --git a/pkg/resources/secret.go b/pkg/resources/secret.go index b6866c2..1a2c6fd 100644 --- a/pkg/resources/secret.go +++ b/pkg/resources/secret.go @@ -3,28 +3,40 @@ package resources import ( "fmt" + "maps" + core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) type SecretMutator struct { - Name string - Namespace string - Data map[string][]byte - Type core.SecretType - meta Mutator[client.Object] + Name string + Namespace string + Data map[string][]byte + StringData map[string]string + Type core.SecretType + meta MetadataMutator } var _ Mutator[*core.Secret] = &SecretMutator{} -func NewSecretMutator(name, namespace string, data map[string][]byte, secretType core.SecretType, labels map[string]string, annotations map[string]string) Mutator[*core.Secret] { +func NewSecretMutator(name, namespace string, data map[string][]byte, secretType core.SecretType) Mutator[*core.Secret] { return &SecretMutator{ Name: name, Namespace: namespace, Data: data, Type: secretType, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), + } +} + +func NewSecretMutatorWithStringData(name, namespace string, stringData map[string]string, secretType core.SecretType) Mutator[*core.Secret] { + return &SecretMutator{ + Name: name, + Namespace: namespace, + StringData: stringData, + Type: secretType, + meta: NewMetadataMutator(), } } @@ -33,7 +45,7 @@ func (m *SecretMutator) String() string { } func (m *SecretMutator) Empty() *core.Secret { - return &core.Secret{ + s := &core.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", Kind: "Secret", @@ -42,17 +54,31 @@ func (m *SecretMutator) Empty() *core.Secret { Name: m.Name, Namespace: m.Namespace, }, - Data: m.Data, Type: m.Type, } + if m.Data != nil { + s.Data = make(map[string][]byte, len(m.Data)) + maps.Copy(s.Data, m.Data) + } + if m.StringData != nil { + s.StringData = make(map[string]string, len(m.StringData)) + maps.Copy(s.StringData, m.StringData) + } + return s } func (m *SecretMutator) Mutate(s *core.Secret) error { - if s.Data == nil { - s.Data = make(map[string][]byte) + if m.Data != nil { + s.Data = make(map[string][]byte, len(m.Data)) + maps.Copy(s.Data, m.Data) } - for key, value := range m.Data { - s.Data[key] = value + if m.StringData != nil { + s.StringData = make(map[string]string, len(m.StringData)) + maps.Copy(s.StringData, m.StringData) } return m.meta.Mutate(s) } + +func (m *SecretMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/secret_test.go b/pkg/resources/secret_test.go index b351132..9bfae4f 100644 --- a/pkg/resources/secret_test.go +++ b/pkg/resources/secret_test.go @@ -19,6 +19,7 @@ var _ = Describe("SecretMutator", func() { fakeClient client.WithWatch scheme *runtime.Scheme data map[string][]byte + stringData map[string]string labels map[string]string annotations map[string]string secretType core.SecretType @@ -39,15 +40,15 @@ var _ = Describe("SecretMutator", func() { // Define data, labels, annotations, and secret type data = map[string][]byte{"key1": []byte("value1"), "key2": []byte("value2")} + stringData = map[string]string{"key3": "value3", "key4": "value4"} labels = map[string]string{"label1": "value1"} annotations = map[string]string{"annotation1": "value1"} secretType = core.SecretTypeOpaque - - // Create a Secret mutator - mutator = resources.NewSecretMutator("test-secret", "test-namespace", data, secretType, labels, annotations) }) It("should create an empty Secret with correct metadata", func() { + mutator = resources.NewSecretMutator("test-secret", "test-namespace", data, secretType) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) secret := mutator.Empty() Expect(secret.Name).To(Equal("test-secret")) @@ -56,9 +57,26 @@ var _ = Describe("SecretMutator", func() { Expect(secret.Kind).To(Equal("Secret")) Expect(secret.Type).To(Equal(secretType)) Expect(secret.Data).To(Equal(data)) + Expect(secret.StringData).To(BeEmpty()) + }) + + It("should create an empty Secret with correct metadata (string data)", func() { + mutator = resources.NewSecretMutatorWithStringData("test-secret", "test-namespace", stringData, secretType) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) + secret := mutator.Empty() + + Expect(secret.Name).To(Equal("test-secret")) + Expect(secret.Namespace).To(Equal("test-namespace")) + Expect(secret.APIVersion).To(Equal("v1")) + Expect(secret.Kind).To(Equal("Secret")) + Expect(secret.Type).To(Equal(secretType)) + Expect(secret.Data).To(BeEmpty()) + Expect(secret.StringData).To(Equal(stringData)) }) It("should apply data, labels, and annotations using Mutate", func() { + mutator = resources.NewSecretMutator("test-secret", "test-namespace", data, secretType) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) secret := mutator.Empty() // Apply the mutator's Mutate method @@ -66,11 +84,29 @@ var _ = Describe("SecretMutator", func() { // Verify that the data, labels, and annotations are applied Expect(secret.Data).To(Equal(data)) + Expect(secret.StringData).To(BeEmpty()) + Expect(secret.Labels).To(Equal(labels)) + Expect(secret.Annotations).To(Equal(annotations)) + }) + + It("should apply data, labels, and annotations using Mutate (string data)", func() { + mutator = resources.NewSecretMutatorWithStringData("test-secret", "test-namespace", stringData, secretType) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) + secret := mutator.Empty() + + // Apply the mutator's Mutate method + Expect(mutator.Mutate(secret)).To(Succeed()) + + // Verify that the data, labels, and annotations are applied + Expect(secret.Data).To(BeEmpty()) + Expect(secret.StringData).To(Equal(stringData)) Expect(secret.Labels).To(Equal(labels)) Expect(secret.Annotations).To(Equal(annotations)) }) It("should create and retrieve the Secret using the fake client", func() { + mutator = resources.NewSecretMutator("test-secret", "test-namespace", data, secretType) + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) secret := mutator.Empty() Expect(mutator.Mutate(secret)).To(Succeed()) diff --git a/pkg/resources/serviceaccount.go b/pkg/resources/serviceaccount.go index be14eb8..457d5c3 100644 --- a/pkg/resources/serviceaccount.go +++ b/pkg/resources/serviceaccount.go @@ -3,8 +3,6 @@ package resources import ( "fmt" - "sigs.k8s.io/controller-runtime/pkg/client" - core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -12,16 +10,16 @@ import ( type ServiceAccountMutator struct { Name string Namespace string - meta Mutator[client.Object] + meta MetadataMutator } var _ Mutator[*core.ServiceAccount] = &ServiceAccountMutator{} -func NewServiceAccountMutator(name, namespace string, labels map[string]string, annotations map[string]string) Mutator[*core.ServiceAccount] { +func NewServiceAccountMutator(name, namespace string) Mutator[*core.ServiceAccount] { return &ServiceAccountMutator{ Name: name, Namespace: namespace, - meta: NewMetadataMutator(labels, annotations), + meta: NewMetadataMutator(), } } @@ -45,3 +43,7 @@ func (m *ServiceAccountMutator) Empty() *core.ServiceAccount { func (m *ServiceAccountMutator) Mutate(s *core.ServiceAccount) error { return m.meta.Mutate(s) } + +func (m *ServiceAccountMutator) MetadataMutator() MetadataMutator { + return m.meta +} diff --git a/pkg/resources/serviceaccount_test.go b/pkg/resources/serviceaccount_test.go index 4d5ebc1..6199b25 100644 --- a/pkg/resources/serviceaccount_test.go +++ b/pkg/resources/serviceaccount_test.go @@ -40,7 +40,8 @@ var _ = Describe("ServiceAccountMutator", func() { annotations = map[string]string{"annotation1": "value1"} // Create a ServiceAccount mutator - mutator = resources.NewServiceAccountMutator("test-serviceaccount", "test-namespace", labels, annotations) + mutator = resources.NewServiceAccountMutator("test-serviceaccount", "test-namespace") + mutator.MetadataMutator().WithLabels(labels).WithAnnotations(annotations) }) It("should create an empty ServiceAccount with correct metadata", func() {