Skip to content
Merged
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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,32 @@ type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: e30K
```

#### 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
ownerReference in the target namespace.

ownerReference won't work [across different namespaces](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents) and the secret at the destination will be removed by the kubernetes garbage collection.

To keep `ownerReferences` at the destination, set the annotation `replicator.v1.mittwald.de/keep-owner-references=true`

```yaml
apiVersion: v1
kind: Secret
metadata:
name: docker-secret-replica
annotations:
replicator.v1.mittwald.de/keep-owner-references: "true"
ownerReferences:
- apiVersion: v1
kind: Deployment
name: owner
uid: "1234"
type: kubernetes.io/tls
data:
tls.key: ""
tls.crt: ""
```

See also: https://github.com/mittwald/kubernetes-replicator/issues/120
1 change: 1 addition & 0 deletions replicate/common/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ const (
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"
)
2 changes: 1 addition & 1 deletion replicate/common/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (nw *NamespaceWatcher) create(client kubernetes.Interface, resyncPeriod tim
&v1.Namespace{},
resyncPeriod,
cache.ResourceEventHandlerFuncs{
AddFunc: namespaceAdded,
AddFunc: namespaceAdded,
UpdateFunc: namespaceUpdated,
},
)
Expand Down
10 changes: 10 additions & 0 deletions replicate/configmap/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac

targetCopy := target.DeepCopy()

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if !ok || keepOwnerReferences != "true" {
targetCopy.OwnerReferences = nil
}

if targetCopy.Data == nil {
targetCopy.Data = make(map[string]string)
}
Expand Down Expand Up @@ -154,6 +159,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
resourceCopy = new(v1.ConfigMap)
}

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if ok && keepOwnerReferences == "true" {
resourceCopy.OwnerReferences = source.OwnerReferences
}

if resourceCopy.Data == nil {
resourceCopy.Data = make(map[string]string)
}
Expand Down
10 changes: 10 additions & 0 deletions replicate/role/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac

targetCopy := target.DeepCopy()

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if !ok || keepOwnerReferences != "true" {
targetCopy.OwnerReferences = nil
}

targetCopy.Rules = source.Rules

logger.Infof("updating target %s/%s", target.Namespace, target.Name)
Expand Down Expand Up @@ -123,6 +128,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
targetCopy = new(rbacv1.Role)
}

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if ok && keepOwnerReferences == "true" {
targetCopy.OwnerReferences = source.OwnerReferences
}

if targetCopy.Rules == nil {
targetCopy.Rules = make([]rbacv1.PolicyRule, 0)
}
Expand Down
10 changes: 10 additions & 0 deletions replicate/rolebinding/rolebindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac
}

targetCopy := target.DeepCopy()
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if !ok || keepOwnerReferences != "true" {
targetCopy.OwnerReferences = nil
}

targetCopy.Subjects = source.Subjects

log.Infof("updating target %s/%s", target.Namespace, target.Name)
Expand Down Expand Up @@ -124,6 +129,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
targetCopy = new(rbacv1.RoleBinding)
}

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if ok && keepOwnerReferences == "true" {
targetCopy.OwnerReferences = source.OwnerReferences
}

if targetCopy.Annotations == nil {
targetCopy.Annotations = make(map[string]string)
}
Expand Down
10 changes: 10 additions & 0 deletions replicate/secret/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac

targetCopy := target.DeepCopy()

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if !ok || keepOwnerReferences != "true" {
targetCopy.OwnerReferences = nil
}

if targetCopy.Data == nil {
targetCopy.Data = make(map[string][]byte)
}
Expand Down Expand Up @@ -150,6 +155,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
resourceCopy = new(v1.Secret)
}

keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
if ok && keepOwnerReferences == "true" {
resourceCopy.OwnerReferences = source.OwnerReferences
}

if resourceCopy.Data == nil {
resourceCopy.Data = make(map[string][]byte)
}
Expand Down
161 changes: 160 additions & 1 deletion replicate/secret/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func TestSecretReplicator(t *testing.T) {
Name: prefix + "test",
},
}
_, err = client.CoreV1().Namespaces().Create(context.TODO(), &ns, metav1.CreateOptions{})

nsData, err := client.CoreV1().Namespaces().Create(context.TODO(), &ns, metav1.CreateOptions{})
require.NoError(t, err)

ns2 := corev1.Namespace{
Expand Down Expand Up @@ -533,6 +534,164 @@ func TestSecretReplicator(t *testing.T) {
require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"])
})

t.Run("replication is pushed to other namespaces without ownerReferences", func(t *testing.T) {
sourceLabels := map[string]string{
"foo": "bar",
"hello": "world",
}
source := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "source-pushed-to-other-without-owner-references",
Namespace: ns.Name,
Annotations: map[string]string{
common.ReplicateTo: prefix + "test2",
},
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.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels))

require.Equal(t, []metav1.OwnerReference(nil), updTarget.OwnerReferences)
require.NotEqual(t, source.OwnerReferences, updTarget.OwnerReferences)

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",
"hello": "world",
}
source := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "source-pushed-to-other-with-owner-references",
Namespace: ns.Name,
Annotations: map[string]string{
common.ReplicateTo: prefix + "test2",
common.KeepOwnerReferences: "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.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels))

require.Equal(t, source.OwnerReferences, updTarget.OwnerReferences)

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{
Expand Down