Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replicate annotations #313

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion replicate/common/common.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 || strings.ToLower(strip) != "true" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what unexpected effects it could have to make copying the labels the default. Maybe it is safer to have to explicitly enable the copying?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thought of another argument in favor switching the default behavior: If this PR was merged, and the replicator is updated in a cluster, nothing will happen yet (because). The labels will only be copied for resources that are updated after the new replicator is deployed. That means the behavior would be more consistent if users have to specifically activate the behavior on resources where they need it.

for k, v := range val {
if strings.HasPrefix(k, ReplicatorPrefix) {
continue
}
copy[k] = v
}

output.SetAnnotations(copy)
}
}
25 changes: 15 additions & 10 deletions replicate/common/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
ReplicatorPrefix = "replicator.v1.mittwald.de"
)

var (
ReplicateFromAnnotation = ReplicatorPrefix + "/replicate-from"
ReplicatedAtAnnotation = ReplicatorPrefix + "/replicated-at"
ReplicatedFromVersionAnnotation = ReplicatorPrefix + "/replicated-from-version"
ReplicatedKeysAnnotation = ReplicatorPrefix + "/replicated-keys"
ReplicationAllowed = ReplicatorPrefix + "/replication-allowed"
ReplicationAllowedNamespaces = ReplicatorPrefix + "/replication-allowed-namespaces"
ReplicateTo = ReplicatorPrefix + "/replicate-to"
ReplicateToMatching = ReplicatorPrefix + "/replicate-to-matching"
KeepOwnerReferences = ReplicatorPrefix + "/keep-owner-references"
StripLabels = ReplicatorPrefix + "/strip-labels"
StripAnnotations = ReplicatorPrefix + "/strip-annotations"
)
4 changes: 4 additions & 0 deletions replicate/configmap/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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, ",")
Expand Down Expand Up @@ -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, ",")
Expand Down
3 changes: 3 additions & 0 deletions replicate/role/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion replicate/rolebinding/rolebindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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{})
Expand Down
3 changes: 3 additions & 0 deletions replicate/secret/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, ",")
Expand Down Expand Up @@ -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, ",")
Expand Down
102 changes: 102 additions & 0 deletions replicate/secret/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) {
Expand Down
2 changes: 2 additions & 0 deletions replicate/serviceaccount/serviceaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down