From 54c9d2105e5ce731ef109aff7b0d38a4fca29e4f Mon Sep 17 00:00:00 2001 From: Silvio Moioli Date: Mon, 23 Oct 2023 16:56:16 +0200 Subject: [PATCH] Experimental: reverse the logic, specify fields to be cleared Signed-off-by: Silvio Moioli --- pkg/apply/apply.go | 25 ++-- pkg/apply/desiredset.go | 31 +++-- pkg/apply/desiredset_compare.go | 90 +++----------- pkg/apply/desiredset_compare_test.go | 171 ++++++--------------------- pkg/apply/desiredset_process.go | 2 +- pkg/apply/fake/apply.go | 6 +- 6 files changed, 96 insertions(+), 229 deletions(-) diff --git a/pkg/apply/apply.go b/pkg/apply/apply.go index 304271ce..2541dcdb 100644 --- a/pkg/apply/apply.go +++ b/pkg/apply/apply.go @@ -82,7 +82,8 @@ type Apply interface { WithSetOwnerReference(controller, block bool) Apply WithIgnorePreviousApplied() Apply WithDiffPatch(gvk schema.GroupVersionKind, namespace, name string, patch []byte) Apply - WithFastApply(dontClearFields ...string) Apply + WithFastApply() Apply + WithClearFieldExample(gvk schema.GroupVersionKind, example runtime.Object) Apply FindOwner(obj runtime.Object) (runtime.Object, error) PurgeOrphan(obj runtime.Object) error @@ -312,12 +313,20 @@ func (a *apply) WithDiffPatch(gvk schema.GroupVersionKind, namespace, name strin // WithFastApply configures Apply to use 2-way merging, which is less CPU intensive than the default 3-way merging. // WithFastApply has the same behavior as Apply (including to honor strategic merge patch tags) apart from one case: // -// When applying an object with a nil field, WithFastApply will clear the corresponding field in the cluster object. -// If that is not wanted, specify the field in dotted notation in dontClearFields, and it will be left unchanged. +// When aobject with a nil (or `omitempty` equivalent) field is applied, WithFastApply leaves the field unchanged. // -// Instead, when applying an object with a nil field, basic Apply will decide whether to clear it or not depending on -// the **immediately previous** Apply. If the field set nil was also nil in the immediately preceding Apply, then -// it is not cleared, otherwise it is. -func (a *apply) WithFastApply(dontClearFields ...string) Apply { - return a.newDesiredSet().WithFastApply(dontClearFields...) +// (basic Apply decides whether to clear or to not clear a nil field depending on the object passed to the +// **immediately previous** Apply. If the field was also nil in the immediately preceding Apply, then it is not cleared, +// otherwise it is) +// +// If clearing on nil is necessary, use WithClearFieldExample. +func (a *apply) WithFastApply() Apply { + return a.newDesiredSet().WithFastApply() +} + +// WithClearFieldExample changes the policy for clearing fields when using WithFastApply. Specifically, any non-nil +// field in obj will be cleared in the cluster object when set to nil (or `omitempty` equivalent) in the applied object. +// Default behavior is to leave the field unchanged. +func (a *apply) WithClearFieldExample(gvk schema.GroupVersionKind, example runtime.Object) Apply { + return a.newDesiredSet().WithClearFieldExample(gvk, example) } diff --git a/pkg/apply/desiredset.go b/pkg/apply/desiredset.go index d69a9b5d..cef1d098 100644 --- a/pkg/apply/desiredset.go +++ b/pkg/apply/desiredset.go @@ -48,8 +48,8 @@ type desiredSet struct { createPlan bool plan Plan - fastApply bool - dontClearFields []string + fastApply bool + clearExamples map[schema.GroupVersionKind]runtime.Object } func (o *desiredSet) err(err error) error { @@ -243,17 +243,28 @@ func (o desiredSet) WithContext(ctx context.Context) Apply { return o } -// WithFastApply configures desiredSet to use 2-way merging, which is less CPU intensive than the default 3-way merging. +// WithFastApply configures Apply to use 2-way merging, which is less CPU intensive than the default 3-way merging. // WithFastApply has the same behavior as Apply (including to honor strategic merge patch tags) apart from one case: // -// When applying an object with a nil field, WithFastApply will clear the corresponding field in the cluster object. -// If that is not wanted, specify the field in dotted notation in dontClearFields, and it will be left unchanged. +// When aobject with a nil (or `omitempty` equivalent) field is applied, WithFastApply leaves the field unchanged. // -// Instead, when applying an object with a nil field, basic Apply will decide whether to clear it or not depending on -// the **immediately previous** Apply. If the field set nil was also nil in the immediately preceding Apply, then -// it is not cleared, otherwise it is. -func (o desiredSet) WithFastApply(dontClearFields ...string) Apply { +// (basic Apply decides whether to clear or to not clear a nil field depending on the object passed to the +// **immediately previous** Apply. If the field was also nil in the immediately preceding Apply, then it is not cleared, +// otherwise it is) +// +// If clearing on nil is necessary, use WithClearFieldExample. +func (o desiredSet) WithFastApply() Apply { o.fastApply = true - o.dontClearFields = dontClearFields + return o +} + +// WithClearFieldExample changes the policy for clearing fields when using WithFastApply. Specifically, any non-nil +// field in obj will be cleared in the cluster object when set to nil (or `omitempty` equivalent) in the applied object. +// Default behavior is to leave the field unchanged. +func (o desiredSet) WithClearFieldExample(gvk schema.GroupVersionKind, example runtime.Object) Apply { + if o.clearExamples == nil { + o.clearExamples = map[schema.GroupVersionKind]runtime.Object{} + } + o.clearExamples[gvk] = example return o } diff --git a/pkg/apply/desiredset_compare.go b/pkg/apply/desiredset_compare.go index 1a2e029e..fe644710 100644 --- a/pkg/apply/desiredset_compare.go +++ b/pkg/apply/desiredset_compare.go @@ -100,7 +100,7 @@ func emptyMaps(data map[string]interface{}, keys ...string) bool { return true } -func sanitizePatch(patch []byte, removeObjectSetAnnotation bool, dontClearFields []string) ([]byte, error) { +func sanitizePatch(patch []byte, removeObjectSetAnnotation bool) ([]byte, error) { mod := false data := map[string]interface{}{} err := json.Unmarshal(patch, &data) @@ -144,12 +144,6 @@ func sanitizePatch(patch []byte, removeObjectSetAnnotation bool, dontClearFields } } - // for all fields that should not be cleared, check the patch: - // if it happens to have a deletion for that field, remove it - for _, field := range dontClearFields { - mod = mod || removeDeletionsFromPatch(data, strings.Split(field, ".")) - } - if emptyMaps(data, "metadata", "annotations") { return []byte("{}"), nil } @@ -161,56 +155,7 @@ func sanitizePatch(patch []byte, removeObjectSetAnnotation bool, dontClearFields return json.Marshal(data) } -// removeDeletionsFromPatch removes deletions from JSON Merge Patch or a Strategic Merge Patch -func removeDeletionsFromPatch(data map[string]interface{}, field []string) bool { - // this is the last field in the path - if len(field) == 1 { - key := field[0] - if value, ok := data[key]; ok { - // if this is a JSON Merge Patch style deletion, remove it - // see https://datatracker.ietf.org/doc/html/rfc7386 - if value == nil { - delete(data, key) - return true - } - // if this is a Strategic Merge Patch style deletion, remove it - // see https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md#delete-directive - if mapValue, ok := value.(map[string]any); ok { - if patchValue, ok := mapValue["$patch"]; ok { - if stringValue, ok := patchValue.(string); ok && stringValue == "delete" { - delete(data, key) - return true - } - } - } - } - return false - } - - // this is not the last field in the path - key := field[0] - if value, ok := data[key]; ok { - // next field in the path is an object, recurse - if mapValue, ok := value.(map[string]interface{}); ok { - return removeDeletionsFromPatch(mapValue, field[1:]) - } - // next field in the path is a list, iterate - if listValue, ok := value.([]interface{}); ok { - for _, item := range listValue { - result := false - // list item is an object, recurse - if mapValue, ok := item.(map[string]interface{}); ok { - deletionResult := removeDeletionsFromPatch(mapValue, field[1:]) - result = result || deletionResult - } - return result - } - } - } - return false -} - -func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patcher, debugID string, ignoreOriginal, fastApply bool, dontClearFields []string, diffPatches [][]byte, oldObject, newObject runtime.Object) (bool, error) { +func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patcher, debugID string, ignoreOriginal, fastApply bool, clearExamples map[schema.GroupVersionKind]runtime.Object, diffPatches [][]byte, oldObject, newObject runtime.Object) (bool, error) { oldMetadata, err := meta.Accessor(oldObject) if err != nil { return false, err @@ -225,6 +170,15 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc } } + // if clearExamples is set, use the example object as the original. In the 3-way algorithm, + // this forces the patch to remove any corresponding nil fields from the current object + if clearExample, found := clearExamples[gvk]; found { + original, err = json.Marshal(clearExample) + if err != nil { + return false, err + } + } + modified, err := getModifiedBytes(gvk, newObject, fastApply) if err != nil { return false, err @@ -235,7 +189,7 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc return false, err } - patchType, patch, err := doPatch(gvk, original, modified, current, diffPatches, fastApply) + patchType, patch, err := doPatch(gvk, original, modified, current, diffPatches) if err != nil { return false, errors.Wrap(err, "patch generation") } @@ -244,7 +198,7 @@ func applyPatch(gvk schema.GroupVersionKind, reconciler Reconciler, patcher Patc return false, nil } - patch, err = sanitizePatch(patch, false, dontClearFields) + patch, err = sanitizePatch(patch, false) if err != nil { return false, err } @@ -305,7 +259,7 @@ func (o *desiredSet) compareObjects(gvk schema.GroupVersionKind, reconciler Reco GroupVersionKind: gvk, }]...) - if ran, err := applyPatch(gvk, reconciler, patcher, debugID, o.ignorePreviousApplied, o.fastApply, o.dontClearFields, diffPatches, oldObject, newObject); err != nil { + if ran, err := applyPatch(gvk, reconciler, patcher, debugID, o.ignorePreviousApplied, o.fastApply, o.clearExamples, diffPatches, oldObject, newObject); err != nil { return err } else if !ran { logrus.Debugf("DesiredSet - No change(2) %s %s/%s for %s", gvk, oldMetadata.GetNamespace(), oldMetadata.GetName(), debugID) @@ -481,7 +435,7 @@ func stripIgnores(original, modified, current []byte, patches [][]byte) ([]byte, } // doPatch is adapted from "kubectl apply" -func doPatch(gvk schema.GroupVersionKind, original, modified, current []byte, diffPatch [][]byte, fastApply bool) (types.PatchType, []byte, error) { +func doPatch(gvk schema.GroupVersionKind, original, modified, current []byte, diffPatch [][]byte) (types.PatchType, []byte, error) { var ( patchType types.PatchType patch []byte @@ -497,18 +451,10 @@ func doPatch(gvk schema.GroupVersionKind, original, modified, current []byte, di return patchType, nil, err } - if fastApply { - if patchType == types.StrategicMergePatchType { - patch, err = strategicpatch.CreateTwoWayMergePatchUsingLookupPatchMeta(current, modified, lookupPatchMeta) - } else { - patch, err = jsonpatch.CreateMergePatch(current, modified) - } + if patchType == types.StrategicMergePatchType { + patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, true) } else { - if patchType == types.StrategicMergePatchType { - patch, err = strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, true) - } else { - patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current) - } + patch, err = jsonmergepatch.CreateThreeWayJSONMergePatch(original, modified, current) } if err != nil { diff --git a/pkg/apply/desiredset_compare_test.go b/pkg/apply/desiredset_compare_test.go index 8c57e771..ef754de1 100644 --- a/pkg/apply/desiredset_compare_test.go +++ b/pkg/apply/desiredset_compare_test.go @@ -138,7 +138,7 @@ func Test_doPatchJSONMergePatch3way(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}, false) + patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}) if !tt.wantErr(t, err, fmt.Sprintf("doPatch(%v, %v, %v, %v)", tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current)) { return } @@ -296,7 +296,7 @@ func Test_doPatchStrategicMergePatch3way(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}, false) + patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}) if !tt.wantErr(t, err, fmt.Sprintf("doPatch(%v, %v, %v, %v)", tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current)) { return } @@ -309,6 +309,7 @@ func Test_doPatchStrategicMergePatch3way(t *testing.T) { func Test_doPatchJSONMergePatch2way(t *testing.T) { type args struct { gvk schema.GroupVersionKind + original []byte modified []byte current []byte } @@ -394,9 +395,21 @@ func Test_doPatchJSONMergePatch2way(t *testing.T) { wantErr: assert.NoError, }, { - name: "2wayObjectKeyNotInModifiedAndInCurrentThenRemoveKey", + name: "2wayObjectKeyNotInModifiedAndInCurrentThenDoNothing", + args: args{ + gvk: testCRDGVK, + modified: toTestCRDBytes(map[string]any{}, t), + current: toTestCRDBytes(map[string]any{"one": "one"}, t), + }, + patchType: types.MergePatchType, + patch: []byte(`{}`), + wantErr: assert.NoError, + }, + { + name: "2wayObjectKeyNotInModifiedAndInCurrentWithClearExampleThenRemoveKey", args: args{ gvk: testCRDGVK, + original: toTestCRDBytes(map[string]any{"one": "clearme"}, t), modified: toTestCRDBytes(map[string]any{}, t), current: toTestCRDBytes(map[string]any{"one": "one"}, t), }, @@ -408,7 +421,7 @@ func Test_doPatchJSONMergePatch2way(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - patchType, patch, err := doPatch(tt.args.gvk, []byte{}, tt.args.modified, tt.args.current, [][]byte{}, true) + patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}) if !tt.wantErr(t, err, fmt.Sprintf("doPatch(%v, %v, %v, %v)", tt.args.gvk, []byte{}, tt.args.modified, tt.args.current)) { return } @@ -421,6 +434,7 @@ func Test_doPatchJSONMergePatch2way(t *testing.T) { func Test_doPatchStrategicMergePatch2way(t *testing.T) { type args struct { gvk schema.GroupVersionKind + original []byte modified []byte current []byte } @@ -507,13 +521,25 @@ func Test_doPatchStrategicMergePatch2way(t *testing.T) { wantErr: assert.NoError, }, { - name: "2wayObjectKeyNotInModifiedAndInCurrentThenRemoveKey", + name: "2wayObjectKeyNotInModifiedAndInCurrentThenDoNothing", args: args{ gvk: configMapGVK, modified: toConfigMapBytes(map[string]string{}, t), current: toConfigMapBytes(map[string]string{"one": "one"}, t), }, patchType: types.StrategicMergePatchType, + patch: []byte(`{}`), + wantErr: assert.NoError, + }, + { + name: "2wayObjectKeyNotInModifiedAndInCurrentThenRemove", + args: args{ + gvk: configMapGVK, + original: toConfigMapBytes(map[string]string{"one": "clearme"}, t), + modified: toConfigMapBytes(map[string]string{}, t), + current: toConfigMapBytes(map[string]string{"one": "one", "two": "two"}, t), + }, + patchType: types.StrategicMergePatchType, patch: []byte(`{"data":null}`), wantErr: assert.NoError, }, @@ -531,14 +557,14 @@ func Test_doPatchStrategicMergePatch2way(t *testing.T) { }, t), }, patchType: types.StrategicMergePatchType, - patch: []byte(`{"spec":{"$setElementOrder/volumes":[{"name":"two"},{"name":"three"}],"volumes":[{"$retainKeys":["name"],"hostPath":null,"name":"two"},{"hostPath":{"path":"I am new"},"name":"three"},{"$patch":"delete","name":"four"}]}}`), + patch: []byte(`{"spec":{"$setElementOrder/volumes":[{"name":"two"},{"name":"three"}],"volumes":[{"$retainKeys":["name"],"name":"two"},{"hostPath":{"path":"I am new"},"name":"three"}]}}`), wantErr: assert.NoError, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - patchType, patch, err := doPatch(tt.args.gvk, []byte{}, tt.args.modified, tt.args.current, [][]byte{}, true) + patchType, patch, err := doPatch(tt.args.gvk, tt.args.original, tt.args.modified, tt.args.current, [][]byte{}) if !tt.wantErr(t, err, fmt.Sprintf("doPatch(%v, %v, %v, %v)", tt.args.gvk, []byte{}, tt.args.modified, tt.args.current)) { return } @@ -552,7 +578,6 @@ func Test_sanitizePatch(t *testing.T) { type args struct { patch []byte removeObjectSetAnnotation bool - dontClearFields []string } tests := []struct { name string @@ -565,7 +590,6 @@ func Test_sanitizePatch(t *testing.T) { args: args{ patch: []byte(`{}`), removeObjectSetAnnotation: false, - dontClearFields: []string{}, }, want: []byte(`{}`), wantErr: assert.NoError, @@ -575,7 +599,6 @@ func Test_sanitizePatch(t *testing.T) { args: args{ patch: []byte(`{1: "one"}`), removeObjectSetAnnotation: false, - dontClearFields: []string{}, }, want: nil, wantErr: assert.Error, @@ -585,7 +608,6 @@ func Test_sanitizePatch(t *testing.T) { args: args{ patch: []byte(`{"kind": "patched", "apiVersion": "patched", "status": "patched", "metadata": {"creationTimestamp": "patched", "preserve": "this"}, "preserve": "this too"}`), removeObjectSetAnnotation: false, - dontClearFields: []string{}, }, want: []byte(`{"metadata":{"preserve":"this"},"preserve":"this too"}`), wantErr: assert.NoError, @@ -595,7 +617,6 @@ func Test_sanitizePatch(t *testing.T) { args: args{ patch: []byte(`{"metadata": {"annotations": {"objectset.rio.cattle.io/test": "delete me"}}}`), removeObjectSetAnnotation: true, - dontClearFields: []string{}, }, want: []byte(`{}`), wantErr: assert.NoError, @@ -605,35 +626,14 @@ func Test_sanitizePatch(t *testing.T) { args: args{ patch: []byte(`{"metadata": {"annotations": {"objectset.rio.cattle.io/test": "do not delete me"}}}`), removeObjectSetAnnotation: false, - dontClearFields: []string{}, }, want: []byte(`{"metadata": {"annotations": {"objectset.rio.cattle.io/test": "do not delete me"}}}`), wantErr: assert.NoError, }, - { - name: "RemoveJSONPatchDeletions", - args: args{ - patch: []byte(`{"a":{"b":[{"c":[{"d":null},{"e":null},{"f":"leave f alone"}]}]},"z":"leave z alone"}`), - removeObjectSetAnnotation: true, - dontClearFields: []string{"a.b.c.d", "a.b.c.f"}, - }, - want: []byte(`{"a":{"b":[{"c":[{},{"e":null},{"f":"leave f alone"}]}]},"z":"leave z alone"}`), - wantErr: assert.NoError, - }, - { - name: "RemoveStrategicPatchDeletions", - args: args{ - patch: []byte(`{"a":{"b":[{"c":[{"d":{"$patch": "delete"}},{"e":{"$patch": "delete"}},{"f":"leave f alone"}]}]},"z":"leave z alone"}`), - removeObjectSetAnnotation: true, - dontClearFields: []string{"a.b.c.d", "a.b.c.f"}, - }, - want: []byte(`{"a":{"b":[{"c":[{},{"e":{"$patch":"delete"}},{"f":"leave f alone"}]}]},"z":"leave z alone"}`), - wantErr: assert.NoError, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := sanitizePatch(tt.args.patch, tt.args.removeObjectSetAnnotation, tt.args.dontClearFields) + got, err := sanitizePatch(tt.args.patch, tt.args.removeObjectSetAnnotation) if !tt.wantErr(t, err, fmt.Sprintf("sanitizePatch(%v, %v)", tt.args.patch, tt.args.removeObjectSetAnnotation)) { return } @@ -642,98 +642,6 @@ func Test_sanitizePatch(t *testing.T) { } } -func Test_removeDeletionsFromPatch(t *testing.T) { - type args struct { - data string - field []string - } - tests := []struct { - name string - args args - modified bool - modifiedData string - }{ - { - name: "JSONMergePatchNonExistingField", - args: args{ - data: `{"a":"z","c": {"f": null}}`, - field: []string{"containers"}, - }, - modifiedData: `{"a":"z","c": {"f": null}}`, - }, - { - name: "JSONMergePatchNonDeleteField", - args: args{ - data: `{"a":"z","c": {"f": null}}`, - field: []string{"a"}, - }, - modifiedData: `{"a":"z","c": {"f": null}}`, - }, - { - name: "JSONMergePatchDeleteField", - args: args{ - data: `{"a":"z","c": {"f": null}}`, - field: []string{"c", "f"}, - }, - modified: true, - modifiedData: `{"a":"z","c": {}}`, - }, - { - name: "JSONMergePatchDeleteFromArrayField", - args: args{ - data: `{"a":"z","c": [{"f": null}, {"g": null}]}`, - field: []string{"c", "f"}, - }, - modified: true, - modifiedData: `{"a":"z","c": [{},{"g": null}]}`, - }, - - { - name: "JSONStrategicMergePatchNonExistingField", - args: args{ - data: `{"a":"z","c": {"f": {"$patch": "delete"}}}`, - field: []string{"containers"}, - }, - modifiedData: `{"a":"z","c": {"f": {"$patch": "delete"}}}`, - }, - { - name: "JSONStrategicMergePatchNonDeleteField", - args: args{ - data: `{"a":"z","c": {"f": {"$patch": "delete"}}}`, - field: []string{"a"}, - }, - modifiedData: `{"a":"z","c": {"f": {"$patch": "delete"}}}`, - }, - { - name: "JSONStrategicMergePatchDeleteField", - args: args{ - data: `{"a":"z","c": {"f": {"$patch": "delete"}}}`, - field: []string{"c", "f"}, - }, - modified: true, - modifiedData: `{"a":"z","c": {}}`, - }, - { - name: "JSONStrategicMergePatchDeleteFromArrayField", - args: args{ - data: `{"a":"z","c": [{"f": {"$patch": "delete"}}, {"g": {"$patch": "delete"}}]}`, - field: []string{"c", "f"}, - }, - modified: true, - modifiedData: `{"a":"z","c": [{},{"g": {"$patch": "delete"}}]}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := toMap(tt.args.data, t) - assert.Equalf(t, tt.modified, removeDeletionsFromPatch(data, tt.args.field), "removeDeletionsFromPatch(%v, %v)", tt.args.data, tt.args.field) - - modifiedData := toMap(tt.modifiedData, t) - assert.Equalf(t, modifiedData, data, "removeDeletionsFromPatch(%v, %v)", tt.args.data, tt.args.field) - }) - } -} - // Utilities // testCRDGVK is the GVK of a CustomResourceDefinition, which uses MergePatchType (because it is not registered) @@ -762,17 +670,6 @@ func toBytes(obj any, t *testing.T) []byte { return res } -// toMap converts a JSON string to a map -func toMap(data string, t *testing.T) map[string]any { - t.Helper() - var obj map[string]any - err := json.Unmarshal([]byte(data), &obj) - if err != nil { - t.Fatalf("failed to unmarshal %v: %v", data, err) - } - return obj -} - // configMapGVK is the GVK of a ConfigMap, which uses StrategicMergePatchType var configMapGVK = schema.GroupVersionKind{ Group: "", diff --git a/pkg/apply/desiredset_process.go b/pkg/apply/desiredset_process.go index 22a33cf0..e609f4a4 100644 --- a/pkg/apply/desiredset_process.go +++ b/pkg/apply/desiredset_process.go @@ -268,7 +268,7 @@ func (o *desiredSet) process(debugID string, set labels.Selector, gvk schema.Gro reconciler = nil patcher = func(namespace, name string, pt types2.PatchType, data []byte) (runtime.Object, error) { - data, err := sanitizePatch(data, true, o.dontClearFields) + data, err := sanitizePatch(data, true) if err != nil { return nil, err } diff --git a/pkg/apply/fake/apply.go b/pkg/apply/fake/apply.go index 684bc6d4..61d775bd 100644 --- a/pkg/apply/fake/apply.go +++ b/pkg/apply/fake/apply.go @@ -128,6 +128,10 @@ func (f *FakeApply) WithDiffPatch(gvk schema.GroupVersionKind, namespace, name s return f } -func (f *FakeApply) WithFastApply(dontClearFields ...string) apply.Apply { +func (f *FakeApply) WithFastApply() apply.Apply { + return f +} + +func (f *FakeApply) WithClearFieldExample(gvk schema.GroupVersionKind, example runtime.Object) apply.Apply { return f }