From 98ba3665709c89bd07c486af4fead264176c78ed Mon Sep 17 00:00:00 2001 From: Gregor Pogacnik Date: Tue, 14 Nov 2023 11:10:48 +0100 Subject: [PATCH] copy annotations too --- replicate/common/common.go | 25 ++++- replicate/common/consts.go | 25 +++-- replicate/configmap/configmaps.go | 4 + replicate/role/roles.go | 3 + replicate/rolebinding/rolebindings.go | 4 +- replicate/secret/secrets.go | 3 + replicate/secret/secrets_test.go | 102 ++++++++++++++++++++ replicate/serviceaccount/serviceaccounts.go | 2 + 8 files changed, 156 insertions(+), 12 deletions(-) diff --git a/replicate/common/common.go b/replicate/common/common.go index 33dd1933..f7db1b24 100644 --- a/replicate/common/common.go +++ b/replicate/common/common.go @@ -1,9 +1,10 @@ package common import ( + "strings" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strings" ) type Replicator interface { @@ -42,3 +43,25 @@ func BuildStrictRegex(regex string) string { func JSONPatchPathEscape(annotation string) string { return strings.ReplaceAll(annotation, "/", "~1") } + +type Annotatable interface { + GetAnnotations() map[string]string + SetAnnotations(map[string]string) +} + +func CopyAnnotations[I, O Annotatable](input I, output O) { + val := input.GetAnnotations() + copy := make(map[string]string, len(val)) + + strip, ok := val[StripAnnotations] + if !ok && strip != "true" { + for k, v := range val { + if strings.HasPrefix(k, Prefix) { + continue + } + copy[k] = v + } + + output.SetAnnotations(copy) + } +} diff --git a/replicate/common/consts.go b/replicate/common/consts.go index db18cb38..67f6a845 100644 --- a/replicate/common/consts.go +++ b/replicate/common/consts.go @@ -2,14 +2,19 @@ package common // Annotations that are used to control this Controller's behaviour const ( - ReplicateFromAnnotation = "replicator.v1.mittwald.de/replicate-from" - ReplicatedAtAnnotation = "replicator.v1.mittwald.de/replicated-at" - ReplicatedFromVersionAnnotation = "replicator.v1.mittwald.de/replicated-from-version" - ReplicatedKeysAnnotation = "replicator.v1.mittwald.de/replicated-keys" - ReplicationAllowed = "replicator.v1.mittwald.de/replication-allowed" - ReplicationAllowedNamespaces = "replicator.v1.mittwald.de/replication-allowed-namespaces" - ReplicateTo = "replicator.v1.mittwald.de/replicate-to" - ReplicateToMatching = "replicator.v1.mittwald.de/replicate-to-matching" - KeepOwnerReferences = "replicator.v1.mittwald.de/keep-owner-references" - StripLabels = "replicator.v1.mittwald.de/strip-labels" + Prefix = "replicator.v1.mittwald.de" +) + +var ( + ReplicateFromAnnotation = Prefix + "/replicate-from" + ReplicatedAtAnnotation = Prefix + "/replicated-at" + ReplicatedFromVersionAnnotation = Prefix + "/replicated-from-version" + ReplicatedKeysAnnotation = Prefix + "/replicated-keys" + ReplicationAllowed = Prefix + "/replication-allowed" + ReplicationAllowedNamespaces = Prefix + "/replication-allowed-namespaces" + ReplicateTo = Prefix + "/replicate-to" + ReplicateToMatching = Prefix + "/replicate-to-matching" + KeepOwnerReferences = Prefix + "/keep-owner-references" + StripLabels = Prefix + "/strip-labels" + StripAnnotations = Prefix + "/strip-annotations" ) diff --git a/replicate/configmap/configmaps.go b/replicate/configmap/configmaps.go index 3302d9d3..c1090481 100644 --- a/replicate/configmap/configmaps.go +++ b/replicate/configmap/configmaps.go @@ -71,6 +71,7 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac } targetCopy := target.DeepCopy() + if targetCopy.Data == nil { targetCopy.Data = make(map[string]string) } @@ -107,6 +108,8 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac logger.Infof("updating config map %s/%s", target.Namespace, target.Name) + common.CopyAnnotations(source, targetCopy) + targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion targetCopy.Annotations[common.ReplicatedKeysAnnotation] = strings.Join(replicatedKeys, ",") @@ -207,6 +210,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa sort.Strings(replicatedKeys) resourceCopy.Name = source.Name resourceCopy.Labels = labelsCopy + common.CopyAnnotations(source, resourceCopy) resourceCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) resourceCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion resourceCopy.Annotations[common.ReplicatedKeysAnnotation] = strings.Join(replicatedKeys, ",") diff --git a/replicate/role/roles.go b/replicate/role/roles.go index ec28f9e1..72889b8a 100644 --- a/replicate/role/roles.go +++ b/replicate/role/roles.go @@ -77,6 +77,8 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac logger.Infof("updating target %s/%s", target.Namespace, target.Name) + common.CopyAnnotations(source, targetCopy) + targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion @@ -148,6 +150,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa targetCopy.Name = source.Name targetCopy.Labels = labelsCopy targetCopy.Rules = source.Rules + common.CopyAnnotations(source, targetCopy) targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion diff --git a/replicate/rolebinding/rolebindings.go b/replicate/rolebinding/rolebindings.go index f895863d..f66ba9ea 100644 --- a/replicate/rolebinding/rolebindings.go +++ b/replicate/rolebinding/rolebindings.go @@ -79,6 +79,7 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac log.Infof("updating target %s/%s", target.Namespace, target.Name) + common.CopyAnnotations(source, targetCopy) targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion @@ -149,6 +150,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa targetCopy.Labels = labelsCopy targetCopy.Subjects = source.Subjects targetCopy.RoleRef = source.RoleRef + common.CopyAnnotations(source, targetCopy) targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion @@ -178,7 +180,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa return nil } -//Checks if Role required for RoleBinding exists. Retries a few times before returning error to allow replication to catch up +// Checks if Role required for RoleBinding exists. Retries a few times before returning error to allow replication to catch up func (r *Replicator) canReplicate(targetNameSpace string, roleRef string) (err error) { for i := 0; i < 5; i++ { _, err = r.Client.RbacV1().Roles(targetNameSpace).Get(context.TODO(), roleRef, metav1.GetOptions{}) diff --git a/replicate/secret/secrets.go b/replicate/secret/secrets.go index b8d1ec0f..dca16d60 100644 --- a/replicate/secret/secrets.go +++ b/replicate/secret/secrets.go @@ -102,6 +102,8 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac logger.Infof("updating target %s", common.MustGetKey(target)) + common.CopyAnnotations(source, targetCopy) + targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion targetCopy.Annotations[common.ReplicatedKeysAnnotation] = strings.Join(replicatedKeys, ",") @@ -179,6 +181,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa resourceCopy.Name = source.Name resourceCopy.Labels = labelsCopy resourceCopy.Type = targetResourceType + common.CopyAnnotations(source, resourceCopy) resourceCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) resourceCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion resourceCopy.Annotations[common.ReplicatedKeysAnnotation] = strings.Join(replicatedKeys, ",") diff --git a/replicate/secret/secrets_test.go b/replicate/secret/secrets_test.go index 81ed90ea..7dd5c374 100644 --- a/replicate/secret/secrets_test.go +++ b/replicate/secret/secrets_test.go @@ -1275,6 +1275,108 @@ func TestSecretReplicator(t *testing.T) { }) + t.Run("replication copies annotations", func(t *testing.T) { + sourceLabels := map[string]string{ + "foo": "bar", + "hello": "world", + } + source := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "annotation-push", + Namespace: ns.Name, + Annotations: map[string]string{ + common.ReplicateTo: prefix + "test2", + "test-annotation": "bar", + }, + Labels: sourceLabels, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "foo": []byte("Hello Foo"), + "bar": []byte("Hello Bar"), + }, + } + + wg, stop := waitForSecrets(client, 2, EventHandlerFuncs{ + AddFunc: func(wg *sync.WaitGroup, obj interface{}) { + secret := obj.(*corev1.Secret) + if secret.Namespace == source.Namespace && secret.Name == source.Name { + log.Debugf("AddFunc %+v", obj) + wg.Done() + } else if secret.Namespace == prefix+"test2" && secret.Name == source.Name { + log.Debugf("AddFunc %+v", obj) + wg.Done() + } + }, + }) + _, err := secrets.Create(context.TODO(), &source, metav1.CreateOptions{}) + require.NoError(t, err) + + waitWithTimeout(wg, MaxWaitTime) + close(stop) + + secrets2 := client.CoreV1().Secrets(prefix + "test2") + updTarget, err := secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) + + require.NoError(t, err) + require.Equal(t, []byte("Hello Foo"), updTarget.Data["foo"]) + require.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) + + require.Equal(t, "bar", updTarget.Annotations["test-annotation"]) + }) + + t.Run("replication copies annotations but honors strip-annotations", func(t *testing.T) { + sourceLabels := map[string]string{ + "foo": "bar", + "hello": "world", + } + source := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "annotation-push-strip", + Namespace: ns.Name, + Annotations: map[string]string{ + common.ReplicateTo: prefix + "test2", + common.StripAnnotations: "true", + "test-annotation": "bar", + }, + Labels: sourceLabels, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "foo": []byte("Hello Foo"), + "bar": []byte("Hello Bar"), + }, + } + + wg, stop := waitForSecrets(client, 2, EventHandlerFuncs{ + AddFunc: func(wg *sync.WaitGroup, obj interface{}) { + secret := obj.(*corev1.Secret) + if secret.Namespace == source.Namespace && secret.Name == source.Name { + log.Debugf("AddFunc %+v", obj) + wg.Done() + } else if secret.Namespace == prefix+"test2" && secret.Name == source.Name { + log.Debugf("AddFunc %+v", obj) + wg.Done() + } + }, + }) + _, err := secrets.Create(context.TODO(), &source, metav1.CreateOptions{}) + require.NoError(t, err) + + waitWithTimeout(wg, MaxWaitTime) + close(stop) + + secrets2 := client.CoreV1().Secrets(prefix + "test2") + updTarget, err := secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) + + require.NoError(t, err) + require.Equal(t, []byte("Hello Foo"), updTarget.Data["foo"]) + require.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) + + _, exists := updTarget.Annotations["test-annotation"] + require.False(t, exists) + }) + } func waitForNamespaces(client *kubernetes.Clientset, count int, eventHandlers EventHandlerFuncs) (wg *sync.WaitGroup, stop chan struct{}) { diff --git a/replicate/serviceaccount/serviceaccounts.go b/replicate/serviceaccount/serviceaccounts.go index ebe736de..13e083eb 100644 --- a/replicate/serviceaccount/serviceaccounts.go +++ b/replicate/serviceaccount/serviceaccounts.go @@ -76,6 +76,7 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac targetCopy.ImagePullSecrets = source.ImagePullSecrets log.Infof("updating target %s/%s", target.Namespace, target.Name) + common.CopyAnnotations(source, target) targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion @@ -146,6 +147,7 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa targetCopy.Name = source.Name targetCopy.Labels = labelsCopy targetCopy.ImagePullSecrets = source.ImagePullSecrets + common.CopyAnnotations(source, targetCopy) targetCopy.Annotations[common.ReplicatedAtAnnotation] = time.Now().Format(time.RFC3339) targetCopy.Annotations[common.ReplicatedFromVersionAnnotation] = source.ResourceVersion