Skip to content

Commit d864000

Browse files
committed
Implement .metadata.ownerReferences handling
1 parent fc50abb commit d864000

File tree

9 files changed

+232
-2
lines changed

9 files changed

+232
-2
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,32 @@ type: kubernetes.io/dockerconfigjson
193193
data:
194194
.dockerconfigjson: e30K
195195
```
196+
197+
#### Special case: Resource with .metadata.ownerReferences
198+
199+
Sometimes, secrets are generated by external components. Such secrets are configured with an ownerReference. By default, the kubernetes-replicator will delete the
200+
ownerReference in the target namespace.
201+
202+
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.
203+
204+
To keep `ownerReferences` at the destination, set the annotation `replicator.v1.mittwald.de/keep-owner-references=true`
205+
206+
```yaml
207+
apiVersion: v1
208+
kind: Secret
209+
metadata:
210+
name: docker-secret-replica
211+
annotations:
212+
replicator.v1.mittwald.de/keep-owner-references: "true"
213+
ownerReferences:
214+
- apiVersion: v1
215+
kind: Deployment
216+
name: owner
217+
uid: "1234"
218+
type: kubernetes.io/tls
219+
data:
220+
tls.key: ""
221+
tls.crt: ""
222+
```
223+
224+
See also: https://github.com/mittwald/kubernetes-replicator/issues/120

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ grantClusterAdmin: false
99
args: []
1010
# - -resync-period=30m
1111
# - -allow-all=false
12+
# - -strip-owner-reference=true
1213

1314
serviceAccount:
1415
create: true

replicate/common/consts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ const (
1010
ReplicationAllowedNamespaces = "replicator.v1.mittwald.de/replication-allowed-namespaces"
1111
ReplicateTo = "replicator.v1.mittwald.de/replicate-to"
1212
ReplicateToMatching = "replicator.v1.mittwald.de/replicate-to-matching"
13+
KeepOwnerReferences = "replicator.v1.mittwald.de/keep-owner-references"
1314
)

replicate/common/namespaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (nw *NamespaceWatcher) create(client kubernetes.Interface, resyncPeriod tim
6161
&v1.Namespace{},
6262
resyncPeriod,
6363
cache.ResourceEventHandlerFuncs{
64-
AddFunc: namespaceAdded,
64+
AddFunc: namespaceAdded,
6565
UpdateFunc: namespaceUpdated,
6666
},
6767
)

replicate/configmap/configmaps.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac
7272

7373
targetCopy := target.DeepCopy()
7474

75+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
76+
if !ok || keepOwnerReferences != "true" {
77+
targetCopy.OwnerReferences = nil
78+
}
79+
7580
if targetCopy.Data == nil {
7681
targetCopy.Data = make(map[string]string)
7782
}
@@ -154,6 +159,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
154159
resourceCopy = new(v1.ConfigMap)
155160
}
156161

162+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
163+
if ok && keepOwnerReferences == "true" {
164+
resourceCopy.OwnerReferences = source.OwnerReferences
165+
}
166+
157167
if resourceCopy.Data == nil {
158168
resourceCopy.Data = make(map[string]string)
159169
}

replicate/role/roles.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac
7474

7575
targetCopy := target.DeepCopy()
7676

77+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
78+
if !ok || keepOwnerReferences != "true" {
79+
targetCopy.OwnerReferences = nil
80+
}
81+
7782
targetCopy.Rules = source.Rules
7883

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

131+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
132+
if ok && keepOwnerReferences == "true" {
133+
targetCopy.OwnerReferences = source.OwnerReferences
134+
}
135+
126136
if targetCopy.Rules == nil {
127137
targetCopy.Rules = make([]rbacv1.PolicyRule, 0)
128138
}

replicate/rolebinding/rolebindings.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac
7575
}
7676

7777
targetCopy := target.DeepCopy()
78+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
79+
if !ok || keepOwnerReferences != "true" {
80+
targetCopy.OwnerReferences = nil
81+
}
82+
7883
targetCopy.Subjects = source.Subjects
7984

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

132+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
133+
if ok && keepOwnerReferences == "true" {
134+
targetCopy.OwnerReferences = source.OwnerReferences
135+
}
136+
127137
if targetCopy.Annotations == nil {
128138
targetCopy.Annotations = make(map[string]string)
129139
}

replicate/secret/secrets.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func (r *Replicator) ReplicateDataFrom(sourceObj interface{}, targetObj interfac
7676

7777
targetCopy := target.DeepCopy()
7878

79+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
80+
if !ok || keepOwnerReferences != "true" {
81+
targetCopy.OwnerReferences = nil
82+
}
83+
7984
if targetCopy.Data == nil {
8085
targetCopy.Data = make(map[string][]byte)
8186
}
@@ -150,6 +155,11 @@ func (r *Replicator) ReplicateObjectTo(sourceObj interface{}, target *v1.Namespa
150155
resourceCopy = new(v1.Secret)
151156
}
152157

158+
keepOwnerReferences, ok := source.Annotations[common.KeepOwnerReferences]
159+
if ok && keepOwnerReferences == "true" {
160+
resourceCopy.OwnerReferences = source.OwnerReferences
161+
}
162+
153163
if resourceCopy.Data == nil {
154164
resourceCopy.Data = make(map[string][]byte)
155165
}

replicate/secret/secrets_test.go

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ func TestSecretReplicator(t *testing.T) {
9090
Name: prefix + "test",
9191
},
9292
}
93-
_, err = client.CoreV1().Namespaces().Create(context.TODO(), &ns, metav1.CreateOptions{})
93+
94+
nsData, err := client.CoreV1().Namespaces().Create(context.TODO(), &ns, metav1.CreateOptions{})
9495
require.NoError(t, err)
9596

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

537+
t.Run("replication is pushed to other namespaces without ownerReferences", func(t *testing.T) {
538+
sourceLabels := map[string]string{
539+
"foo": "bar",
540+
"hello": "world",
541+
}
542+
source := corev1.Secret{
543+
ObjectMeta: metav1.ObjectMeta{
544+
Name: "source-pushed-to-other-without-owner-references",
545+
Namespace: ns.Name,
546+
Annotations: map[string]string{
547+
common.ReplicateTo: prefix + "test2",
548+
},
549+
Labels: sourceLabels,
550+
OwnerReferences: []metav1.OwnerReference{{
551+
APIVersion: "v1",
552+
Kind: "Namespace",
553+
Name: nsData.Name,
554+
UID: nsData.UID,
555+
}},
556+
},
557+
Type: corev1.SecretTypeOpaque,
558+
Data: map[string][]byte{
559+
"foo": []byte("Hello Foo"),
560+
"bar": []byte("Hello Bar"),
561+
},
562+
}
563+
564+
wg, stop := waitForSecrets(client, 2, EventHandlerFuncs{
565+
AddFunc: func(wg *sync.WaitGroup, obj interface{}) {
566+
secret := obj.(*corev1.Secret)
567+
if secret.Namespace == source.Namespace && secret.Name == source.Name {
568+
log.Debugf("AddFunc %+v", obj)
569+
wg.Done()
570+
} else if secret.Namespace == prefix+"test2" && secret.Name == source.Name {
571+
log.Debugf("AddFunc %+v", obj)
572+
wg.Done()
573+
}
574+
},
575+
})
576+
_, err := secrets.Create(context.TODO(), &source, metav1.CreateOptions{})
577+
require.NoError(t, err)
578+
579+
waitWithTimeout(wg, MaxWaitTime)
580+
close(stop)
581+
582+
secrets2 := client.CoreV1().Secrets(prefix + "test2")
583+
updTarget, err := secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{})
584+
585+
require.NoError(t, err)
586+
require.Equal(t, []byte("Hello Foo"), updTarget.Data["foo"])
587+
require.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels))
588+
589+
require.Equal(t, []metav1.OwnerReference(nil), updTarget.OwnerReferences)
590+
require.NotEqual(t, source.OwnerReferences, updTarget.OwnerReferences)
591+
592+
wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{
593+
UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) {
594+
secret := oldObj.(*corev1.Secret)
595+
if secret.Namespace == prefix+"test2" && secret.Name == source.Name {
596+
log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj)
597+
wg.Done()
598+
}
599+
},
600+
})
601+
602+
_, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{})
603+
require.NoError(t, err)
604+
605+
waitWithTimeout(wg, MaxWaitTime)
606+
close(stop)
607+
608+
updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{})
609+
require.NoError(t, err)
610+
611+
_, hasFoo := updTarget.Data["foo"]
612+
require.False(t, hasFoo)
613+
require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"])
614+
})
615+
616+
t.Run("replication is pushed to other namespaces with ownerReferences", func(t *testing.T) {
617+
sourceLabels := map[string]string{
618+
"foo": "bar",
619+
"hello": "world",
620+
}
621+
source := corev1.Secret{
622+
ObjectMeta: metav1.ObjectMeta{
623+
Name: "source-pushed-to-other-with-owner-references",
624+
Namespace: ns.Name,
625+
Annotations: map[string]string{
626+
common.ReplicateTo: prefix + "test2",
627+
common.KeepOwnerReferences: "true",
628+
},
629+
Labels: sourceLabels,
630+
OwnerReferences: []metav1.OwnerReference{{
631+
APIVersion: "v1",
632+
Kind: "Namespace",
633+
Name: nsData.Name,
634+
UID: nsData.UID,
635+
}},
636+
},
637+
Type: corev1.SecretTypeOpaque,
638+
Data: map[string][]byte{
639+
"foo": []byte("Hello Foo"),
640+
"bar": []byte("Hello Bar"),
641+
},
642+
}
643+
644+
wg, stop := waitForSecrets(client, 2, EventHandlerFuncs{
645+
AddFunc: func(wg *sync.WaitGroup, obj interface{}) {
646+
secret := obj.(*corev1.Secret)
647+
if secret.Namespace == source.Namespace && secret.Name == source.Name {
648+
log.Debugf("AddFunc %+v", obj)
649+
wg.Done()
650+
} else if secret.Namespace == prefix+"test2" && secret.Name == source.Name {
651+
log.Debugf("AddFunc %+v", obj)
652+
wg.Done()
653+
}
654+
},
655+
})
656+
_, err := secrets.Create(context.TODO(), &source, metav1.CreateOptions{})
657+
require.NoError(t, err)
658+
659+
waitWithTimeout(wg, MaxWaitTime)
660+
close(stop)
661+
662+
secrets2 := client.CoreV1().Secrets(prefix + "test2")
663+
updTarget, err := secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{})
664+
665+
require.NoError(t, err)
666+
require.Equal(t, []byte("Hello Foo"), updTarget.Data["foo"])
667+
require.True(t, reflect.DeepEqual(sourceLabels, updTarget.Labels))
668+
669+
require.Equal(t, source.OwnerReferences, updTarget.OwnerReferences)
670+
671+
wg, stop = waitForSecrets(client, 1, EventHandlerFuncs{
672+
UpdateFunc: func(wg *sync.WaitGroup, oldObj interface{}, newObj interface{}) {
673+
secret := oldObj.(*corev1.Secret)
674+
if secret.Namespace == prefix+"test2" && secret.Name == source.Name {
675+
log.Debugf("UpdateFunc %+v -> %+v", oldObj, newObj)
676+
wg.Done()
677+
}
678+
},
679+
})
680+
681+
_, err = secrets.Patch(context.TODO(), source.Name, types.JSONPatchType, []byte(`[{"op": "remove", "path": "/data/foo"}]`), metav1.PatchOptions{})
682+
require.NoError(t, err)
683+
684+
waitWithTimeout(wg, MaxWaitTime)
685+
close(stop)
686+
687+
updTarget, err = secrets2.Get(context.TODO(), source.Name, metav1.GetOptions{})
688+
require.NoError(t, err)
689+
690+
_, hasFoo := updTarget.Data["foo"]
691+
require.False(t, hasFoo)
692+
require.Equal(t, []byte("Hello Bar"), updTarget.Data["bar"])
693+
})
694+
536695
t.Run("replication is pushed to other namespaces by label selector", func(t *testing.T) {
537696
source := corev1.Secret{
538697
ObjectMeta: metav1.ObjectMeta{

0 commit comments

Comments
 (0)