From 6cf126c84d1d23dba2069ce1a256fbc39612c833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 18 Nov 2021 09:32:39 +0100 Subject: [PATCH 1/5] Add replicator.v1.mittwald.de/strip-labels --- README.md | 19 ++++ replicate/common/consts.go | 1 + replicate/configmap/configmaps.go | 9 ++ replicate/role/roles.go | 15 ++- replicate/rolebinding/rolebindings.go | 16 ++- replicate/secret/secrets.go | 15 ++- replicate/secret/secrets_test.go | 152 ++++++++++++++++++++++++++ 7 files changed, 218 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index be8b520c..badd818c 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,25 @@ data: .dockerconfigjson: e30K ``` +#### Special case: Strip labels while replicate the resources. + +Operators like [/~https://github.com/strimzi/strimzi-kafka-operator](strimzi-kafka-operator) implement an own garbage collection based on specific labels defined on resources. If mittwald replicator replicate secrets to different namespace, the strimzi-kafka-operator will remove the replicated secrets because from operators point of view the secret is a left-over. To mitigate the issue, set the annotation `replicator.v1.mittwald.de/strip-labels=true` to remove all labels on the replicated resource. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/managed-by: "strimzi-kafka-operator" + name: cluster-ca-certs + annotations: + replicator.v1.mittwald.de/strip-labels: "true" +type: kubernetes.io/tls +data: + tls.key: "" + tls.crt: "" +``` + #### Special case: Resource with .metadata.ownerReferences Sometimes, secrets are generated by external components. Such secrets are configured with an ownerReference. By default, the kubernetes-replicator will delete the diff --git a/replicate/common/consts.go b/replicate/common/consts.go index 0ab0d7cd..db18cb38 100644 --- a/replicate/common/consts.go +++ b/replicate/common/consts.go @@ -11,4 +11,5 @@ const ( 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" ) diff --git a/replicate/configmap/configmaps.go b/replicate/configmap/configmaps.go index fe9fff01..8790c012 100644 --- a/replicate/configmap/configmaps.go +++ b/replicate/configmap/configmaps.go @@ -77,6 +77,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac targetCopy.OwnerReferences = nil } + stripLabels, ok := source.Annotations[common.StripLabels] + if ok && stripLabels == "true" { + targetCopy.Labels = make(map[string]string) + } + if targetCopy.Data == nil { targetCopy.Data = make(map[string]string) } @@ -164,6 +169,10 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa resourceCopy.OwnerReferences = source.OwnerReferences } + stripLabels, ok := source.Annotations[common.StripLabels] + if ok && stripLabels == "true" { + resourceCopy.Labels = make(map[string]string) + } if resourceCopy.Data == nil { resourceCopy.Data = make(map[string]string) } diff --git a/replicate/role/roles.go b/replicate/role/roles.go index 7b368b3b..a25c481b 100644 --- a/replicate/role/roles.go +++ b/replicate/role/roles.go @@ -79,6 +79,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac targetCopy.OwnerReferences = nil } + stripLabels, ok := source.Annotations[common.StripLabels] + if ok && stripLabels == "true" { + targetCopy.Labels = make(map[string]string) + } + targetCopy.Rules = source.Rules logger.Infof("updating target %s/%s", target.Namespace, target.Name) @@ -141,9 +146,13 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa } labelsCopy := make(map[string]string) - if source.Labels != nil { - for key, value := range source.Labels { - labelsCopy[key] = value + + stripLabels, ok := source.Annotations[common.StripLabels] + if !ok && stripLabels != "true" { + if source.Labels != nil { + for key, value := range source.Labels { + labelsCopy[key] = value + } } } diff --git a/replicate/rolebinding/rolebindings.go b/replicate/rolebinding/rolebindings.go index c23f8426..9c9f6f93 100644 --- a/replicate/rolebinding/rolebindings.go +++ b/replicate/rolebinding/rolebindings.go @@ -80,6 +80,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac targetCopy.OwnerReferences = nil } + stripLabels, ok := source.Annotations[common.StripLabels] + if ok && stripLabels == "true" { + targetCopy.Labels = make(map[string]string) + } + targetCopy.Subjects = source.Subjects log.Infof("updating target %s/%s", target.Namespace, target.Name) @@ -139,10 +144,15 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa } labelsCopy := make(map[string]string) - if source.Labels != nil { - for key, value := range source.Labels { - labelsCopy[key] = value + + stripLabels, ok := source.Annotations[common.StripLabels] + if !ok && stripLabels != "true" { + if source.Labels != nil { + for key, value := range source.Labels { + labelsCopy[key] = value + } } + } targetCopy.Name = source.Name diff --git a/replicate/secret/secrets.go b/replicate/secret/secrets.go index eb9c13e9..95a8f3fd 100644 --- a/replicate/secret/secrets.go +++ b/replicate/secret/secrets.go @@ -76,6 +76,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac targetCopy := target.DeepCopy() + stripLabels, ok := source.Annotations[common.StripLabels] + if ok && stripLabels == "true" { + targetCopy.Labels = make(map[string]string) + } + keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] if !ok || keepOwnerReferences != "true" { targetCopy.OwnerReferences = nil @@ -172,9 +177,13 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa sort.Strings(replicatedKeys) labelsCopy := make(map[string]string) - if source.Labels != nil { - for key, value := range source.Labels { - labelsCopy[key] = value + + stripLabels, ok := source.Annotations[common.StripLabels] + if !ok && stripLabels != "true" { + if source.Labels != nil { + for key, value := range source.Labels { + labelsCopy[key] = value + } } } diff --git a/replicate/secret/secrets_test.go b/replicate/secret/secrets_test.go index 02e655f8..fa1e8155 100644 --- a/replicate/secret/secrets_test.go +++ b/replicate/secret/secrets_test.go @@ -613,6 +613,79 @@ func TestSecretReplicator(t *testing.T) { require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) }) + t.Run("replication is pushed to other namespaces and strip labels", func(t *testing.T) { + sourceLabels := map[string]string{ + "foo": "bar", + "hello": "world", + } + source := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "source-pushed-to-other-strip-labels", + Namespace: ns.Name, + Annotations: map[string]string{ + common.ReplicateTo: prefix + "test2", + common.StripLabels: "true", + }, + 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.False(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) + + require.Equal(t, map[string]string(nil), updTarget.Labels) + + wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{ + UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) { + secret := oldObj.(*corev1.Secret) + if secret.Namespace == prefix+"test2" && secret.Name == source.Name { + log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj) + wg.Done() + } + }, + }) + + _, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{}) + require.NoError(t, err) + + waitWithTimeout(wg, MaxWaitTime) + close(stop) + + updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) + require.NoError(t, err) + + _, hasFoo := updTarget.Data["foo"] + require.False(t, hasFoo) + require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) + }) + t.Run("replication is pushed to other namespaces with ownerReferences", func(t *testing.T) { sourceLabels := map[string]string{ "foo": "bar", @@ -692,6 +765,85 @@ func TestSecretReplicator(t *testing.T) { require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) }) + t.Run("replication is pushed to other namespaces and strip labels", func(t *testing.T) { + sourceLabels := map[string]string{ + "foo": "bar", + "hello": "world", + } + source := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "source-pushed-to-other-with-strip-labels", + Namespace: ns.Name, + Annotations: map[string]string{ + common.ReplicateTo: prefix + "test2", + common.StripLabels: "true", + }, + Labels: sourceLabels, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "v1", + Kind: "Namespace", + Name: nsData.Name, + UID: nsData.UID, + }}, + }, + 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.False(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) + + require.Equal(t, map[string]string(nil), updTarget.Labels) + + wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{ + UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) { + secret := oldObj.(*corev1.Secret) + if secret.Namespace == prefix+"test2" && secret.Name == source.Name { + log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj) + wg.Done() + } + }, + }) + + _, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{}) + require.NoError(t, err) + + waitWithTimeout(wg, MaxWaitTime) + close(stop) + + updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) + require.NoError(t, err) + + _, hasFoo := updTarget.Data["foo"] + require.False(t, hasFoo) + require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) + }) + t.Run("replication is pushed to other namespaces by label selector", func(t *testing.T) { source := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ From dbfc1ca65481cf89c35de8829f8e9ffe913330e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 25 Nov 2021 09:27:37 +0100 Subject: [PATCH 2/5] Adjust condition in replicate/configmap/configmaps.go --- replicate/configmap/configmaps.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/replicate/configmap/configmaps.go b/replicate/configmap/configmaps.go index 8790c012..59870326 100644 --- a/replicate/configmap/configmaps.go +++ b/replicate/configmap/configmaps.go @@ -169,10 +169,6 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa resourceCopy.OwnerReferences = source.OwnerReferences } - stripLabels, ok := source.Annotations[common.StripLabels] - if ok && stripLabels == "true" { - resourceCopy.Labels = make(map[string]string) - } if resourceCopy.Data == nil { resourceCopy.Data = make(map[string]string) } @@ -209,9 +205,13 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa } labelsCopy := make(map[string]string) - if source.Labels != nil { - for key, value := range source.Labels { - labelsCopy[key] = value + + stripLabels, ok := source.Annotations[common.StripLabels] + if !ok && stripLabels != "true" { + if source.Labels != nil { + for key, value := range source.Labels { + labelsCopy[key] = value + } } } From 19a1d18627c391b6003d636bf1f89b7494704eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 25 Nov 2021 09:35:16 +0100 Subject: [PATCH 3/5] Remove keepOwnerReference and stripLabels logic in ReplicateFrom functions --- replicate/configmap/configmaps.go | 11 ----------- replicate/role/roles.go | 11 ----------- replicate/rolebinding/rolebindings.go | 10 ---------- replicate/secret/secrets.go | 11 ----------- 4 files changed, 43 deletions(-) diff --git a/replicate/configmap/configmaps.go b/replicate/configmap/configmaps.go index 59870326..3302d9d3 100644 --- a/replicate/configmap/configmaps.go +++ b/replicate/configmap/configmaps.go @@ -71,17 +71,6 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac } targetCopy := target.DeepCopy() - - keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] - if !ok || keepOwnerReferences != "true" { - targetCopy.OwnerReferences = nil - } - - stripLabels, ok := source.Annotations[common.StripLabels] - if ok && stripLabels == "true" { - targetCopy.Labels = make(map[string]string) - } - if targetCopy.Data == nil { targetCopy.Data = make(map[string]string) } diff --git a/replicate/role/roles.go b/replicate/role/roles.go index a25c481b..ec28f9e1 100644 --- a/replicate/role/roles.go +++ b/replicate/role/roles.go @@ -73,17 +73,6 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac } targetCopy := target.DeepCopy() - - keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] - if !ok || keepOwnerReferences != "true" { - targetCopy.OwnerReferences = nil - } - - stripLabels, ok := source.Annotations[common.StripLabels] - if ok && stripLabels == "true" { - targetCopy.Labels = make(map[string]string) - } - targetCopy.Rules = source.Rules logger.Infof("updating target %s/%s", target.Namespace, target.Name) diff --git a/replicate/rolebinding/rolebindings.go b/replicate/rolebinding/rolebindings.go index 9c9f6f93..f895863d 100644 --- a/replicate/rolebinding/rolebindings.go +++ b/replicate/rolebinding/rolebindings.go @@ -75,16 +75,6 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac } targetCopy := target.DeepCopy() - keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] - if !ok || keepOwnerReferences != "true" { - targetCopy.OwnerReferences = nil - } - - stripLabels, ok := source.Annotations[common.StripLabels] - if ok && stripLabels == "true" { - targetCopy.Labels = make(map[string]string) - } - targetCopy.Subjects = source.Subjects log.Infof("updating target %s/%s", target.Namespace, target.Name) diff --git a/replicate/secret/secrets.go b/replicate/secret/secrets.go index 95a8f3fd..b8d1ec0f 100644 --- a/replicate/secret/secrets.go +++ b/replicate/secret/secrets.go @@ -75,17 +75,6 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac } targetCopy := target.DeepCopy() - - stripLabels, ok := source.Annotations[common.StripLabels] - if ok && stripLabels == "true" { - targetCopy.Labels = make(map[string]string) - } - - keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences] - if !ok || keepOwnerReferences != "true" { - targetCopy.OwnerReferences = nil - } - if targetCopy.Data == nil { targetCopy.Data = make(map[string][]byte) } From 9d7f7640acbbe305aff38cddc789f4eb0df38219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 25 Nov 2021 09:41:45 +0100 Subject: [PATCH 4/5] Remove duplicate test case. --- replicate/secret/secrets_test.go | 73 -------------------------------- 1 file changed, 73 deletions(-) diff --git a/replicate/secret/secrets_test.go b/replicate/secret/secrets_test.go index fa1e8155..4c857087 100644 --- a/replicate/secret/secrets_test.go +++ b/replicate/secret/secrets_test.go @@ -613,79 +613,6 @@ func TestSecretReplicator(t *testing.T) { require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) }) - t.Run("replication is pushed to other namespaces and strip labels", func(t *testing.T) { - sourceLabels := map[string]string{ - "foo": "bar", - "hello": "world", - } - source := corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "source-pushed-to-other-strip-labels", - Namespace: ns.Name, - Annotations: map[string]string{ - common.ReplicateTo: prefix + "test2", - common.StripLabels: "true", - }, - 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.False(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) - - require.Equal(t, map[string]string(nil), updTarget.Labels) - - wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{ - UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) { - secret := oldObj.(*corev1.Secret) - if secret.Namespace == prefix+"test2" && secret.Name == source.Name { - log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj) - wg.Done() - } - }, - }) - - _, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{}) - require.NoError(t, err) - - waitWithTimeout(wg, MaxWaitTime) - close(stop) - - updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) - require.NoError(t, err) - - _, hasFoo := updTarget.Data["foo"] - require.False(t, hasFoo) - require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) - }) - t.Run("replication is pushed to other namespaces with ownerReferences", func(t *testing.T) { sourceLabels := map[string]string{ "foo": "bar", From 2011209013cb8fb0d2ee920454b8c9e4e5399965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 25 Nov 2021 09:43:12 +0100 Subject: [PATCH 5/5] Remove additional data compare in tests --- replicate/secret/secrets_test.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/replicate/secret/secrets_test.go b/replicate/secret/secrets_test.go index 4c857087..81ed90ea 100644 --- a/replicate/secret/secrets_test.go +++ b/replicate/secret/secrets_test.go @@ -746,29 +746,6 @@ func TestSecretReplicator(t *testing.T) { require.False(t, reflect.DeepEqual(sourceLabels, updTarget.Labels)) require.Equal(t, map[string]string(nil), updTarget.Labels) - - wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{ - UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) { - secret := oldObj.(*corev1.Secret) - if secret.Namespace == prefix+"test2" && secret.Name == source.Name { - log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj) - wg.Done() - } - }, - }) - - _, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{}) - require.NoError(t, err) - - waitWithTimeout(wg, MaxWaitTime) - close(stop) - - updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{}) - require.NoError(t, err) - - _, hasFoo := updTarget.Data["foo"] - require.False(t, hasFoo) - require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"]) }) t.Run("replication is pushed to other namespaces by label selector", func(t *testing.T) {