Change:
Extracted from The RawExtension type allows an API object to inline arbitrary JSON. strategicpatch.CreateTwoWayMergePatch returns an error trying to map that inlined JSON to struct fields. It should be able to compute a patch for an object containing a field of that type.
cc
We need to decide what strategic merge patch should do in the following scenarios: unknown fields (can be encountered when the API or a user-defined object has a new field the compiled-in struct doesn't have yet) unmergeable fields (like RawExtension) cc
The current behavior is: unknown fields: trying to merge a unknown field will fail when lookup the metadata in the struct tags unmergeable fields: will be replaced as raw byte array Maybe we can use generic json merge for unmergeable fields?
Did that change since 1.5? In 1.5, we were getting an error during patch computation for objects with a RawExtension field
Sorry. This behavior is in 1.4.7. e.g . It will error in 1.5.
Yeah, the field gets replaced. Even in 1.4.7, an unknown field (like initcontainers in pods) or an object including a RawExtension (anything with a field of type or ) would error
My personal favor would be to instruct the patch to ignore such field, seems the cleanest approach to me. Similarly to how we allow annotating deletion and merging.
wouldn't it be confusing to submit a patch with stuff in that field, have the patch accepted, and have none of your submitted changes take effect? seems like the least surprising thing would be to treat struct fields like or as
How about both, iow. having as a default and as an opt-in. This would allow the resource author to decide how his resource should be treated. Eventually on we could return the ignored fields. Having said that we could start with right away. I think I could work on a patch implementing that if majority agrees (
I think is a good place to start when computing/applying patches for known struct fields like RawExtension. I'll open a separate issue for client-side handling of computing patches for unknown fields
Ignore the field sounds like a really good solution. WDYT of printing a warning to stdeer?
what does ignoring instead of replacing get us?
Sorry I was reading the comment on my phone between other things. Ignore my comment.

Discussion:
"strings" "k8s.io/apimachinery/pkg/util/json" <del> forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" </del> "k8s.io/apimachinery/pkg/util/mergepatch" <ins> forkedjson "k8s.io/apimachinery/third_party/forked/golang/json" </ins> ) // An alternate implementation of JSON Merge Patch

switch originalValueTyped := originalValue.(type) { case map[string]interface{}: modifiedValueTyped := modifiedValue.(map[string]interface{}) <del> fieldType, _, _, err := forkedjson.LookupPatchMetadata(t, key) </del> <ins> fieldType, fieldPatchStrategy, _, err := forkedjson.LookupPatchMetadata(t, key) </ins> if err != nil { return nil, err } <ins> if fieldPatchStrategy == replaceDirective { if !ignoreChangesAndAdditions { patch[key] = modifiedValue } continue } </ins> patchValue, err := diffMaps(originalValueTyped, modifiedValueTyped, fieldType, ignoreChangesAndAdditions, ignoreDeletions) if err != nil { return nil, err

} } <ins> if fieldPatchStrategy == replaceDirective { return false, nil } </ins> // Check the individual keys. return mapsHaveConflicts(leftType, rightType, fieldType) default:

"github.com/davecgh/go-spew/spew" "github.com/ghodss/yaml" <ins> "k8s.io/apimachinery/pkg/runtime" </ins> "k8s.io/apimachinery/pkg/util/mergepatch" )

NonMergingIntList []int MergeItemPtr      *MergeItem `patchStrategy:"merge" patchMergeKey:"name"` SimpleMap         map[string]string <ins> ReplacingItem     runtime.RawExtension `patchStrategy:"replace"` </ins> } var mergeItem MergeItem

} } } <ins> var replaceRawExtensionPatchTestCases = []StrategicMergePatchRawTestCase{ { Description: "replace RawExtension field, rest unchanched", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` name: my-object value: some-value other: current-other replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Current: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 - name: 3 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Modified: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWay: []byte(` merginglist: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWayResult: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), ThreeWay: []byte(` replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), Result: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 - name: 3 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), }, }, { Description: "replace RawExtension field and merge list", StrategicMergePatchRawTestCaseData: StrategicMergePatchRawTestCaseData{ Original: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Current: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 3 replacingItem: Some: Generic Yaml: Inside The: RawExtension Field: Period `), Modified: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWay: []byte(` merginglist: - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), TwoWayResult: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), ThreeWay: []byte(` merginglist: - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), Result: []byte(` name: my-object value: some-value other: current-other merginglist: - name: 1 - name: 3 - name: 2 replacingItem: Newly: Modified Yaml: Inside The: RawExtension `), }, }, } func TestReplaceWithRawExtension(t *testing.T) { for _, c := range replaceRawExtensionPatchTestCases { testTwoWayPatchWithoutSorting(t, c) testThreeWayPatchWithoutSorting(t, c) } } func testTwoWayPatchWithoutSorting(t *testing.T, c StrategicMergePatchRawTestCase) { original, expectedPatch, modified, expectedResult := twoWayRawTestCaseToJSONOrFail(t, c) actualPatch, err := CreateTwoWayMergePatch(original, modified, mergeItem) if err != nil { t.Errorf("error: %snin test case: %sncannot create two way patch:noriginal:%sntwoWay:%snmodified:%sncurrent:%snthreeWay:%snresult:%sn", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreationWithoutSorting(t, expectedPatch, actualPatch, c.Description) testPatchApplicationWithoutSorting(t, original, actualPatch, expectedResult, c.Description) } func testThreeWayPatchWithoutSorting(t *testing.T, c StrategicMergePatchRawTestCase) { original, modified, current, expected, result := threeWayRawTestCaseToJSONOrFail(t, c) actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, false) if err != nil { if !mergepatch.IsConflict(err) { t.Errorf("error: %snin test case: %sncannot create three way patch:noriginal:%sntwoWay:%snmodified:%sncurrent:%snthreeWay:%snresult:%sn", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if !strings.Contains(c.Description, "conflict") { t.Errorf("unexpected conflict: %snin test case: %sncannot create three way patch:noriginal:%sntwoWay:%snmodified:%sncurrent:%snthreeWay:%snresult:%sn", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } if len(c.Result) > 0 { actual, err := CreateThreeWayMergePatch(original, modified, current, mergeItem, true) if err != nil { t.Errorf("error: %snin test case: %sncannot force three way patch application:noriginal:%sntwoWay:%snmodified:%sncurrent:%snthreeWay:%snresult:%sn", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreationWithoutSorting(t, expected, actual, c.Description) testPatchApplicationWithoutSorting(t, current, actual, result, c.Description) } return } if strings.Contains(c.Description, "conflict") || len(c.Result) < 1 { t.Errorf("error: %snin test case: %snexpected conflict did not occur:noriginal:%sntwoWay:%snmodified:%sncurrent:%snthreeWay:%snresult:%sn", err, c.Description, c.Original, c.TwoWay, c.Modified, c.Current, c.ThreeWay, c.Result) return } testPatchCreationWithoutSorting(t, expected, actual, c.Description) testPatchApplicationWithoutSorting(t, current, actual, result, c.Description) } func testPatchCreationWithoutSorting(t *testing.T, expected, actual []byte, description string) { if !reflect.DeepEqual(actual, expected) { t.Errorf("error in test case: %snexpected patch:n%sngot:n%sn", description, jsonToYAMLOrError(expected), jsonToYAMLOrError(actual)) return } } func testPatchApplicationWithoutSorting(t *testing.T, original, patch, expected []byte, description string) { result, err := StrategicMergePatch(original, patch, mergeItem) if err != nil { t.Errorf("error: %snin test case: %sncannot apply patch:n%snto original:n%sn", err, description, jsonToYAMLOrError(patch), jsonToYAMLOrError(original)) return } if !reflect.DeepEqual(result, expected) { format := "error in test case: %snpatch application failed:noriginal:n%snpatch:n%snexpected:n%sngot:n%sn" t.Errorf(format, description, jsonToYAMLOrError(original), jsonToYAMLOrError(patch), jsonToYAMLOrError(expected), jsonToYAMLOrError(result)) return } } </ins>

deps = [ "//vendor:github.com/davecgh/go-spew/spew", "//vendor:github.com/ghodss/yaml", <ins> "//vendor:k8s.io/apimachinery/pkg/runtime", </ins> "//vendor:k8s.io/apimachinery/pkg/util/mergepatch", ], )