Handle unstructured objects correctly in IgnoreManagedFieldsTimestampsTransformer

This commit is contained in:
Joe Betz 2024-05-31 21:25:25 -04:00
parent d57acd327f
commit c942ab6900
2 changed files with 212 additions and 1 deletions

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/metrics" "k8s.io/apiserver/pkg/endpoints/metrics"
@ -51,7 +52,7 @@ func getAvoidTimestampEqualities() conversion.Equalities {
} }
var eqs = equality.Semantic.Copy() var eqs = equality.Semantic.Copy()
err := eqs.AddFunc( err := eqs.AddFuncs(
func(a, b metav1.ManagedFieldsEntry) bool { func(a, b metav1.ManagedFieldsEntry) bool {
// Two objects' managed fields are equivalent if, ignoring timestamp, // Two objects' managed fields are equivalent if, ignoring timestamp,
// the objects are deeply equal. // the objects are deeply equal.
@ -59,6 +60,14 @@ func getAvoidTimestampEqualities() conversion.Equalities {
b.Time = nil b.Time = nil
return reflect.DeepEqual(a, b) return reflect.DeepEqual(a, b)
}, },
func(a, b unstructured.Unstructured) bool {
// Check if the managed fields are equal by converting to structured types and leveraging the above
// function, then, ignoring the managed fields, equality check the rest of the unstructured data.
if !avoidTimestampEqualities.DeepEqual(a.GetManagedFields(), b.GetManagedFields()) {
return false
}
return equalIgnoringValueAtPath(a.Object, b.Object, []string{"metadata", "managedFields"})
},
) )
if err != nil { if err != nil {
@ -70,6 +79,35 @@ func getAvoidTimestampEqualities() conversion.Equalities {
return avoidTimestampEqualities return avoidTimestampEqualities
} }
func equalIgnoringValueAtPath(a, b any, path []string) bool {
if len(path) == 0 { // found the value to ignore
return true
}
aMap, aOk := a.(map[string]any)
bMap, bOk := b.(map[string]any)
if !aOk || !bOk {
return !avoidTimestampEqualities.DeepEqual(a, b)
}
if len(aMap) != len(bMap) {
return false
}
pathHead := path[0]
for k, aVal := range aMap {
bVal, ok := bMap[k]
if !ok {
return false
}
if k == pathHead {
if !equalIgnoringValueAtPath(aVal, bVal, path[1:]) {
return false
}
} else if !avoidTimestampEqualities.DeepEqual(aVal, bVal) {
return false
}
}
return true
}
// IgnoreManagedFieldsTimestampsTransformer reverts timestamp updates // IgnoreManagedFieldsTimestampsTransformer reverts timestamp updates
// if the non-managed parts of the object are equivalent // if the non-managed parts of the object are equivalent
func IgnoreManagedFieldsTimestampsTransformer( func IgnoreManagedFieldsTimestampsTransformer(

View File

@ -0,0 +1,173 @@
/*
Copyright 20214The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fieldmanager
import "testing"
func TestEqualIgnoringFieldValueAtPath(t *testing.T) {
cases := []struct {
name string
a, b map[string]any
want bool
}{
{
name: "identical objects",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value",
},
},
want: true,
},
{
name: "different metadata label value",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "prod"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value",
},
},
want: false,
},
{
name: "different spec value",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value1",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value2",
},
},
want: false,
},
{
name: "extra spec fields in object a",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value1",
"otherField": "other",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value1",
},
},
want: false,
},
{
name: "different spec field in object b",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value1",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{},
},
"spec": map[string]any{
"field": "value1",
"otherField": "other",
},
},
want: false,
},
{
name: "different managed fields should be ignored",
a: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{map[string]any{"manager": "client1"}},
},
"spec": map[string]any{
"field": "value",
},
},
b: map[string]any{
"metadata": map[string]any{
"labels": map[string]any{"env": "dev"},
"managedFields": []any{map[string]any{"manager": "client2"}},
},
"spec": map[string]any{
"field": "value",
},
},
want: true,
},
}
path := []string{"metadata", "managedFields"}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := equalIgnoringValueAtPath(c.a, c.b, path)
if actual != c.want {
t.Error("Expected equality check to return ", c.want, ", but got ", actual)
}
})
}
}