1
16
17 package jsonmergepatch
18
19 import (
20 "fmt"
21 "reflect"
22
23 "github.com/evanphx/json-patch"
24 "k8s.io/apimachinery/pkg/util/json"
25 "k8s.io/apimachinery/pkg/util/mergepatch"
26 )
27
28
29
30
31 func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
32 if len(original) == 0 {
33 original = []byte(`{}`)
34 }
35 if len(modified) == 0 {
36 modified = []byte(`{}`)
37 }
38 if len(current) == 0 {
39 current = []byte(`{}`)
40 }
41
42 addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
43 if err != nil {
44 return nil, err
45 }
46
47 addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
48 if err != nil {
49 return nil, err
50 }
51
52 deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
53 if err != nil {
54 return nil, err
55 }
56
57 deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
58 if err != nil {
59 return nil, err
60 }
61
62 hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
63 if err != nil {
64 return nil, err
65 }
66 if hasConflicts {
67 return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj))
68 }
69 patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
70 if err != nil {
71 return nil, err
72 }
73
74 var patchMap map[string]interface{}
75 err = json.Unmarshal(patch, &patchMap)
76 if err != nil {
77 return nil, fmt.Errorf("failed to unmarshal patch for precondition check: %s", patch)
78 }
79 meetPreconditions, err := meetPreconditions(patchMap, fns...)
80 if err != nil {
81 return nil, err
82 }
83 if !meetPreconditions {
84 return nil, mergepatch.NewErrPreconditionFailed(patchMap)
85 }
86
87 return patch, nil
88 }
89
90
91
92
93 func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
94 var patchMap map[string]interface{}
95 err := json.Unmarshal(patch, &patchMap)
96 if err != nil {
97 return nil, nil, err
98 }
99 filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
100 if err != nil {
101 return nil, nil, err
102 }
103 o, err := json.Marshal(filteredMap)
104 return o, filteredMap, err
105 }
106
107
108
109 func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
110 filteredMap := make(map[string]interface{})
111 var err error
112 for key, val := range m {
113 switch {
114 case keepNull && val == nil:
115 filteredMap[key] = nil
116 case val != nil:
117 switch typedVal := val.(type) {
118 case map[string]interface{}:
119
120 if len(typedVal) == 0 {
121 if !keepNull {
122 filteredMap[key] = typedVal
123 }
124 continue
125 }
126
127 var filteredSubMap map[string]interface{}
128 filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull)
129 if err != nil {
130 return nil, err
131 }
132
133
134
135 if len(filteredSubMap) != 0 {
136 filteredMap[key] = filteredSubMap
137 }
138
139 case []interface{}, string, float64, bool, int64, nil:
140
141 if !keepNull {
142 filteredMap[key] = val
143 }
144 default:
145 return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
146 }
147 }
148 }
149 return filteredMap, nil
150 }
151
152 func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) {
153
154 for _, fn := range fns {
155 if !fn(patchObj) {
156 return false, fmt.Errorf("precondition failed for: %v", patchObj)
157 }
158 }
159 return true, nil
160 }
161
View as plain text