Skip to content

Commit f6ef92e

Browse files
committed
yoke: make turbulence less senstive to metadata properties
1 parent 48bb61b commit f6ef92e

File tree

6 files changed

+78
-54
lines changed

6 files changed

+78
-54
lines changed

Diff for: cmd/atc/handler.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
291291
return
292292
}
293293

294-
if next != nil && ctrl.ResourcesAreEqual(prev, next) {
294+
if next != nil && internal.ResourcesAreEqual(prev, next) {
295295
addRequestAttrs(r.Context(), slog.String("skipReason", "resources are equal"))
296296
return
297297
}
@@ -378,7 +378,7 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, controllers *atc.Contr
378378

379379
internal.RemoveAdditions(desired, next)
380380

381-
if !ctrl.ResourcesAreEqual(desired, next) {
381+
if !internal.ResourcesAreEqual(desired, next) {
382382
review.Response.Allowed = false
383383
review.Response.Result = &metav1.Status{
384384
Message: "cannot modify flight sub-resources",

Diff for: cmd/yoke/cmd_turbulence.go

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func GetTurbulenceParams(settings GlobalSettings, args []string) (*TurbulencePar
3737
params := TurbulenceParams{GlobalSettings: settings}
3838

3939
RegisterGlobalFlags(flagset, &params.GlobalSettings)
40+
4041
flagset.IntVar(&params.Context, "context", 4, "number of lines of context in diff")
4142
flagset.BoolVar(
4243
&params.ConflictsOnly,

Diff for: internal/k8s/ctrl/controller.go

+2-21
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
kcache "k8s.io/client-go/tools/cache"
2424
retryWatcher "k8s.io/client-go/tools/watch"
2525

26+
"github.com/yokecd/yoke/internal"
2627
"github.com/yokecd/yoke/internal/k8s"
2728
"github.com/yokecd/yoke/internal/xsync"
2829
)
@@ -287,7 +288,7 @@ func (ctrl *Instance) eventsFromMetaGetter(ctx context.Context, getter metadata.
287288
if err == nil {
288289
prev := cache[evt]
289290
cache[evt] = current
290-
if ResourcesAreEqual(prev, current) {
291+
if internal.ResourcesAreEqual(prev, current) {
291292
continue
292293
}
293294
}
@@ -355,26 +356,6 @@ func withJitter(duration time.Duration, percent float64) time.Duration {
355356
return time.Duration(float64(duration) - offset + jitter).Round(time.Second)
356357
}
357358

358-
func ResourcesAreEqual(a, b *unstructured.Unstructured) bool {
359-
if (a == nil) || (b == nil) {
360-
return false
361-
}
362-
363-
dropKeys := [][]string{
364-
{"metadata", "generation"},
365-
{"metadata", "resourceVersion"},
366-
{"metadata", "managedFields"},
367-
{"status"},
368-
}
369-
370-
for _, keys := range dropKeys {
371-
unstructured.RemoveNestedField(a.Object, keys...)
372-
unstructured.RemoveNestedField(b.Object, keys...)
373-
}
374-
375-
return reflect.DeepEqual(a.Object, b.Object)
376-
}
377-
378359
func safe(handler HandleFunc) HandleFunc {
379360
return func(ctx context.Context, event Event) (result Result, err error) {
380361
defer func() {

Diff for: internal/revision.go

-31
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"net/url"
99
"path"
10-
"reflect"
1110
"slices"
1211
"strconv"
1312
"strings"
@@ -125,36 +124,6 @@ func GetOwner(resource *unstructured.Unstructured) string {
125124
return namespace + "/" + release
126125
}
127126

128-
// RemoveAdditions removes fields from actual that are not in expected.
129-
// it removes the additional properties in place and returns "actual" back.
130-
// Values passed to removeAdditions are expected to be generic json compliant structures:
131-
// map[string]any, []any, or scalars.
132-
func RemoveAdditions[T any](expected, actual T) T {
133-
// Check if we can access the types safely
134-
if !reflect.ValueOf(expected).IsValid() || !reflect.ValueOf(actual).IsValid() || reflect.ValueOf(actual).Type() != reflect.ValueOf(expected).Type() {
135-
return actual
136-
}
137-
138-
switch a := any(actual).(type) {
139-
case map[string]any:
140-
e := any(expected).(map[string]any)
141-
for key := range a {
142-
if _, ok := e[key]; !ok {
143-
delete(a, key)
144-
continue
145-
}
146-
a[key] = RemoveAdditions(e[key], a[key])
147-
}
148-
case []any:
149-
e := any(expected).([]any)
150-
for i := range min(len(a), len(e)) {
151-
a[i] = RemoveAdditions(e[i], a[i])
152-
}
153-
}
154-
155-
return actual
156-
}
157-
158127
func Canonical(resource *unstructured.Unstructured) string {
159128
gvk := resource.GetObjectKind().GroupVersionKind()
160129

Diff for: internal/unstructured.go

+63
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package internal
22

33
import (
44
"encoding/json"
5+
"reflect"
56

67
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
78
)
@@ -25,3 +26,65 @@ func MustUnstructuredObject(value any) map[string]any {
2526
result, _ := UnstructuredObject(value)
2627
return result
2728
}
29+
30+
func ResourcesAreEqual(a, b *unstructured.Unstructured) bool {
31+
if (a == nil) || (b == nil) {
32+
return false
33+
}
34+
35+
dropKeys := [][]string{
36+
{"metadata", "generation"},
37+
{"metadata", "resourceVersion"},
38+
{"metadata", "managedFields"},
39+
{"status"},
40+
}
41+
42+
return reflect.DeepEqual(
43+
DropProperties(a, dropKeys).Object,
44+
DropProperties(b, dropKeys).Object,
45+
)
46+
}
47+
48+
func DropProperties(resource *unstructured.Unstructured, props [][]string) *unstructured.Unstructured {
49+
if resource == nil {
50+
return nil
51+
}
52+
53+
resource = resource.DeepCopy()
54+
55+
for _, keys := range props {
56+
unstructured.RemoveNestedField(resource.Object, keys...)
57+
}
58+
59+
return resource
60+
}
61+
62+
// RemoveAdditions removes fields from actual that are not in expected.
63+
// it removes the additional properties in place and returns "actual" back.
64+
// Values passed to removeAdditions are expected to be generic json compliant structures:
65+
// map[string]any, []any, or scalars.
66+
func RemoveAdditions[T any](expected, actual T) T {
67+
// Check if we can access the types safely
68+
if !reflect.ValueOf(expected).IsValid() || !reflect.ValueOf(actual).IsValid() || reflect.ValueOf(actual).Type() != reflect.ValueOf(expected).Type() {
69+
return actual
70+
}
71+
72+
switch a := any(actual).(type) {
73+
case map[string]any:
74+
e := any(expected).(map[string]any)
75+
for key := range a {
76+
if _, ok := e[key]; !ok {
77+
delete(a, key)
78+
continue
79+
}
80+
a[key] = RemoveAdditions(e[key], a[key])
81+
}
82+
case []any:
83+
e := any(expected).([]any)
84+
for i := range min(len(a), len(e)) {
85+
a[i] = RemoveAdditions(e[i], a[i])
86+
}
87+
}
88+
89+
return actual
90+
}

Diff for: pkg/yoke/yoke.go

+10
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,22 @@ func (commander Commander) Turbulence(ctx context.Context, params TurbulencePara
176176

177177
expected := internal.CanonicalMap(stages.Flatten())
178178

179+
ignoredProps := [][]string{
180+
{"metadata", "generation"},
181+
{"metadata", "resourceVersion"},
182+
{"metadata", "managedFields"},
183+
{"metadata", "creationTimestamp"},
184+
{"status"},
185+
}
186+
179187
actual := map[string]*unstructured.Unstructured{}
180188
for name, resource := range expected {
189+
expected[name] = internal.DropProperties(resource, ignoredProps)
181190
value, err := commander.k8s.GetInClusterState(ctx, resource)
182191
if err != nil {
183192
return fmt.Errorf("failed to get in cluster state for resource %s: %w", internal.Canonical(resource), err)
184193
}
194+
value = internal.DropProperties(value, ignoredProps)
185195
if value != nil && params.ConflictsOnly {
186196
value.Object = internal.RemoveAdditions(resource.Object, value.Object)
187197
}

0 commit comments

Comments
 (0)