diff --git a/pkg/controllers/resources/persistentvolumes/translate.go b/pkg/controllers/resources/persistentvolumes/translate.go index d4f12e8d6..68424ed7b 100644 --- a/pkg/controllers/resources/persistentvolumes/translate.go +++ b/pkg/controllers/resources/persistentvolumes/translate.go @@ -22,7 +22,7 @@ func (s *persistentVolumeSyncer) translate(ctx *synccontext.SyncContext, vPv *co func (s *persistentVolumeSyncer) translateBackwards(pPv *corev1.PersistentVolume, vPvc *corev1.PersistentVolumeClaim) *corev1.PersistentVolume { // build virtual persistent volume - vObj := translate.CopyObjectWithName(pPv, types.NamespacedName{Name: pPv.Name}, false) + vObj := translate.CopyObjectWithName(pPv, types.NamespacedName{Name: pPv.Name}, false, s.excludedAnnotations...) if vPvc != nil { if vObj.Spec.ClaimRef == nil { vObj.Spec.ClaimRef = &corev1.ObjectReference{} diff --git a/pkg/util/translate/labels.go b/pkg/util/translate/labels.go index acf90a2bf..3c74c2737 100644 --- a/pkg/util/translate/labels.go +++ b/pkg/util/translate/labels.go @@ -232,7 +232,7 @@ func AnnotationsBidirectionalUpdateFunction[T client.Object](event *synccontext. if newHost == nil { newHost = map[string]string{} } - if !apiequality.Semantic.DeepEqual(event.VirtualOld.GetAnnotations(), event.Virtual.GetAnnotations()) { + if !maps.Equal(event.VirtualOld.GetAnnotations(), event.Virtual.GetAnnotations()) { newHost = mergeMaps(event.VirtualOld.GetAnnotations(), event.Virtual.GetAnnotations(), event.Host.GetAnnotations(), func(key string, value interface{}) (string, interface{}) { if stringutil.Contains(excludeAnnotations, key) { return "", nil @@ -242,7 +242,7 @@ func AnnotationsBidirectionalUpdateFunction[T client.Object](event *synccontext. return transformToHost(key, value) }) - } else if !apiequality.Semantic.DeepEqual(event.HostOld.GetAnnotations(), event.Host.GetAnnotations()) { + } else if !maps.Equal(event.HostOld.GetAnnotations(), event.Host.GetAnnotations()) { newVirtual = mergeMaps(event.HostOld.GetAnnotations(), event.Host.GetAnnotations(), event.Virtual.GetAnnotations(), func(key string, value interface{}) (string, interface{}) { if stringutil.Contains(excludeAnnotations, key) { return "", nil diff --git a/pkg/util/translate/labels_test.go b/pkg/util/translate/labels_test.go new file mode 100644 index 000000000..1811b5c11 --- /dev/null +++ b/pkg/util/translate/labels_test.go @@ -0,0 +1,132 @@ +package translate + +import ( + "testing" + + "github.com/loft-sh/vcluster/pkg/syncer/synccontext" + "gotest.tools/v3/assert" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func TestAnnotationsSync(t *testing.T) { + // exclude the default class + vAnnotations, pAnnotations := AnnotationsBidirectionalUpdate(synccontext.NewSyncEventWithOld( + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-other", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + ), "storageclass.kubernetes.io/is-default-class") + assert.DeepEqual(t, vAnnotations, map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }) + assert.DeepEqual(t, pAnnotations, map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-other", + NameAnnotation: "", + UIDAnnotation: "", + KindAnnotation: storagev1.SchemeGroupVersion.WithKind("StorageClass").String(), + HostNameAnnotation: "", + }) + + // not exclude the default class + vAnnotations, pAnnotations = AnnotationsBidirectionalUpdate(synccontext.NewSyncEventWithOld( + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-other", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + }, + }, + }, + )) + assert.DeepEqual(t, vAnnotations, map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-other", + }) + assert.DeepEqual(t, pAnnotations, map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-other", + NameAnnotation: "", + UIDAnnotation: "", + KindAnnotation: storagev1.SchemeGroupVersion.WithKind("StorageClass").String(), + HostNameAnnotation: "", + }) + + // check on creation with exclude host -> virtual + pObj := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + "not-excluded": "true", + }, + }, + } + vObj := CopyObjectWithName(pObj, types.NamespacedName{}, true, "storageclass.kubernetes.io/is-default-class") + vAnnotations = VirtualAnnotations(pObj, vObj, "storageclass.kubernetes.io/is-default-class") + assert.DeepEqual(t, vAnnotations, map[string]string{ + "not-excluded": "true", + }) + + // check on creation with exclude virtual -> host + vObj = &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "my-default", + "not-excluded": "true", + }, + }, + } + pObj = CopyObjectWithName(vObj, types.NamespacedName{}, true, "storageclass.kubernetes.io/is-default-class") + pAnnotations = HostAnnotations(vObj, pObj, "storageclass.kubernetes.io/is-default-class") + assert.DeepEqual(t, pAnnotations, map[string]string{ + "not-excluded": "true", + ManagedAnnotationsAnnotation: "not-excluded", + NameAnnotation: "", + UIDAnnotation: "", + KindAnnotation: storagev1.SchemeGroupVersion.WithKind("StorageClass").String(), + HostNameAnnotation: "", + }) +} diff --git a/pkg/util/translate/translate.go b/pkg/util/translate/translate.go index c4a2f7681..05d58ddd9 100644 --- a/pkg/util/translate/translate.go +++ b/pkg/util/translate/translate.go @@ -11,6 +11,7 @@ import ( "time" "github.com/loft-sh/vcluster/pkg/scheme" + "github.com/loft-sh/vcluster/pkg/util/stringutil" "github.com/pkg/errors" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1clientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -33,7 +34,7 @@ const ( var Owner client.Object -func CopyObjectWithName[T client.Object](obj T, name types.NamespacedName, setOwner bool) T { +func CopyObjectWithName[T client.Object](obj T, name types.NamespacedName, setOwner bool, excludedAnnotations ...string) T { target := obj.DeepCopyObject().(T) // reset metadata & translate name and namespace @@ -48,23 +49,35 @@ func CopyObjectWithName[T client.Object](obj T, name types.NamespacedName, setOw } } + stripExcludedAnnotations(target, excludedAnnotations...) return target } func HostMetadata[T client.Object](vObj T, name types.NamespacedName, excludedAnnotations ...string) T { - pObj := CopyObjectWithName(vObj, name, true) + pObj := CopyObjectWithName(vObj, name, true, excludedAnnotations...) + stripExcludedAnnotations(vObj, excludedAnnotations...) pObj.SetAnnotations(HostAnnotations(vObj, pObj, excludedAnnotations...)) pObj.SetLabels(HostLabels(vObj, nil)) return pObj } func VirtualMetadata[T client.Object](pObj T, name types.NamespacedName, excludedAnnotations ...string) T { - vObj := CopyObjectWithName(pObj, name, false) + vObj := CopyObjectWithName(pObj, name, false, excludedAnnotations...) vObj.SetAnnotations(VirtualAnnotations(pObj, nil, excludedAnnotations...)) vObj.SetLabels(VirtualLabels(pObj, nil)) return vObj } +func stripExcludedAnnotations(obj client.Object, excludedAnnotations ...string) { + annotations := obj.GetAnnotations() + for k := range annotations { + if stringutil.Contains(excludedAnnotations, k) { + delete(annotations, k) + } + } + obj.SetAnnotations(annotations) +} + func VirtualAnnotations(pObj, vObj client.Object, excluded ...string) map[string]string { excluded = append(excluded, NameAnnotation, NamespaceAnnotation, HostNameAnnotation, HostNamespaceAnnotation, UIDAnnotation, KindAnnotation, ManagedAnnotationsAnnotation, ManagedLabelsAnnotation) var toAnnotations map[string]string