jsonmerge.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package jsonmerge
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "github.com/evanphx/json-patch"
  18. "github.com/golang/glog"
  19. "k8s.io/kubernetes/pkg/util/strategicpatch"
  20. "k8s.io/kubernetes/pkg/util/yaml"
  21. )
  22. // Delta represents a change between two JSON documents.
  23. type Delta struct {
  24. original []byte
  25. edit []byte
  26. preconditions []PreconditionFunc
  27. }
  28. // PreconditionFunc is a test to verify that an incompatible change
  29. // has occurred before an Apply can be successful.
  30. type PreconditionFunc func(interface{}) (hold bool, message string)
  31. // AddPreconditions adds precondition checks to a change which must
  32. // be satisfied before an Apply is considered successful. If a
  33. // precondition returns false, the Apply is failed with
  34. // ErrPreconditionFailed.
  35. func (d *Delta) AddPreconditions(fns ...PreconditionFunc) {
  36. d.preconditions = append(d.preconditions, fns...)
  37. }
  38. // RequireKeyUnchanged creates a precondition function that fails
  39. // if the provided key is present in the diff (indicating its value
  40. // has changed).
  41. func RequireKeyUnchanged(key string) PreconditionFunc {
  42. return func(diff interface{}) (bool, string) {
  43. m, ok := diff.(map[string]interface{})
  44. if !ok {
  45. return true, ""
  46. }
  47. // the presence of key in a diff means that its value has been changed, therefore
  48. // we should fail the precondition.
  49. _, ok = m[key]
  50. if ok {
  51. return false, key + " should not be changed\n"
  52. } else {
  53. return true, ""
  54. }
  55. }
  56. }
  57. // RequireKeyUnchanged creates a precondition function that fails
  58. // if the metadata.key is present in the diff (indicating its value
  59. // has changed).
  60. func RequireMetadataKeyUnchanged(key string) PreconditionFunc {
  61. return func(diff interface{}) (bool, string) {
  62. m, ok := diff.(map[string]interface{})
  63. if !ok {
  64. return true, ""
  65. }
  66. m1, ok := m["metadata"]
  67. if !ok {
  68. return true, ""
  69. }
  70. m2, ok := m1.(map[string]interface{})
  71. if !ok {
  72. return true, ""
  73. }
  74. _, ok = m2[key]
  75. if ok {
  76. return false, "metadata." + key + " should not be changed\n"
  77. } else {
  78. return true, ""
  79. }
  80. }
  81. }
  82. // TestPreconditions test if preconditions hold given the edit
  83. func TestPreconditionsHold(edit []byte, preconditions []PreconditionFunc) (bool, string) {
  84. diff := make(map[string]interface{})
  85. if err := json.Unmarshal(edit, &diff); err != nil {
  86. return false, err.Error()
  87. }
  88. for _, fn := range preconditions {
  89. if hold, msg := fn(diff); !hold {
  90. return false, msg
  91. }
  92. }
  93. return true, ""
  94. }
  95. // NewDelta accepts two JSON or YAML documents and calculates the difference
  96. // between them. It returns a Delta object which can be used to resolve
  97. // conflicts against a third version with a common parent, or an error
  98. // if either document is in error.
  99. func NewDelta(from, to []byte) (*Delta, error) {
  100. d := &Delta{}
  101. before, err := yaml.ToJSON(from)
  102. if err != nil {
  103. return nil, err
  104. }
  105. after, err := yaml.ToJSON(to)
  106. if err != nil {
  107. return nil, err
  108. }
  109. diff, err := jsonpatch.CreateMergePatch(before, after)
  110. if err != nil {
  111. return nil, err
  112. }
  113. glog.V(6).Infof("Patch created from:\n%s\n%s\n%s", string(before), string(after), string(diff))
  114. d.original = before
  115. d.edit = diff
  116. return d, nil
  117. }
  118. // Apply attempts to apply the changes described by Delta onto latest,
  119. // returning an error if the changes cannot be applied cleanly.
  120. // IsConflicting will be true if the changes overlap, otherwise a
  121. // generic error will be returned.
  122. func (d *Delta) Apply(latest []byte) ([]byte, error) {
  123. base, err := yaml.ToJSON(latest)
  124. if err != nil {
  125. return nil, err
  126. }
  127. changes, err := jsonpatch.CreateMergePatch(d.original, base)
  128. if err != nil {
  129. return nil, err
  130. }
  131. diff1 := make(map[string]interface{})
  132. if err := json.Unmarshal(d.edit, &diff1); err != nil {
  133. return nil, err
  134. }
  135. diff2 := make(map[string]interface{})
  136. if err := json.Unmarshal(changes, &diff2); err != nil {
  137. return nil, err
  138. }
  139. for _, fn := range d.preconditions {
  140. hold1, _ := fn(diff1)
  141. hold2, _ := fn(diff2)
  142. if !hold1 || !hold2 {
  143. return nil, ErrPreconditionFailed
  144. }
  145. }
  146. glog.V(6).Infof("Testing for conflict between:\n%s\n%s", string(d.edit), string(changes))
  147. hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2)
  148. if err != nil {
  149. return nil, err
  150. }
  151. if hasConflicts {
  152. return nil, ErrConflict
  153. }
  154. return jsonpatch.MergePatch(base, d.edit)
  155. }
  156. // IsConflicting returns true if the provided error indicates a
  157. // conflict exists between the original changes and the applied
  158. // changes.
  159. func IsConflicting(err error) bool {
  160. return err == ErrConflict
  161. }
  162. // IsPreconditionFailed returns true if the provided error indicates
  163. // a Delta precondition did not succeed.
  164. func IsPreconditionFailed(err error) bool {
  165. return err == ErrPreconditionFailed
  166. }
  167. var ErrPreconditionFailed = fmt.Errorf("a precondition failed")
  168. var ErrConflict = fmt.Errorf("changes are in conflict")
  169. func (d *Delta) Edit() []byte {
  170. return d.edit
  171. }