Skip to content

Commit

Permalink
Implement .metadata.ownerReferences handling (#140)
Browse files Browse the repository at this point in the history
* Implement .metadata.ownerReferences handling

* Update deploy/helm-chart/kubernetes-replicator/values.yaml

Co-authored-by: Henning <me@hensur.de>

Co-authored-by: Henning <me@hensur.de>
  • Loading branch information
jkroepke and hensur authored Oct 15, 2021
1 parent fc50abb commit 43515a5
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 2 deletions.
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

0 comments on commit 43515a5

Please sign in to comment.