update.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. Copyright 2018 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 merge
  14. import (
  15. "fmt"
  16. "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
  17. "sigs.k8s.io/structured-merge-diff/v4/typed"
  18. )
  19. // Converter is an interface to the conversion logic. The converter
  20. // needs to be able to convert objects from one version to another.
  21. type Converter interface {
  22. Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error)
  23. IsMissingVersionError(error) bool
  24. }
  25. // Updater is the object used to compute updated FieldSets and also
  26. // merge the object on Apply.
  27. type Updater struct {
  28. Converter Converter
  29. IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
  30. enableUnions bool
  31. }
  32. // EnableUnionFeature turns on union handling. It is disabled by default until the
  33. // feature is complete.
  34. func (s *Updater) EnableUnionFeature() {
  35. s.enableUnions = true
  36. }
  37. func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, *typed.Comparison, error) {
  38. conflicts := fieldpath.ManagedFields{}
  39. removed := fieldpath.ManagedFields{}
  40. compare, err := oldObject.Compare(newObject)
  41. if err != nil {
  42. return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
  43. }
  44. versions := map[fieldpath.APIVersion]*typed.Comparison{
  45. version: compare.ExcludeFields(s.IgnoredFields[version]),
  46. }
  47. for manager, managerSet := range managers {
  48. if manager == workflow {
  49. continue
  50. }
  51. compare, ok := versions[managerSet.APIVersion()]
  52. if !ok {
  53. var err error
  54. versionedOldObject, err := s.Converter.Convert(oldObject, managerSet.APIVersion())
  55. if err != nil {
  56. if s.Converter.IsMissingVersionError(err) {
  57. delete(managers, manager)
  58. continue
  59. }
  60. return nil, nil, fmt.Errorf("failed to convert old object: %v", err)
  61. }
  62. versionedNewObject, err := s.Converter.Convert(newObject, managerSet.APIVersion())
  63. if err != nil {
  64. if s.Converter.IsMissingVersionError(err) {
  65. delete(managers, manager)
  66. continue
  67. }
  68. return nil, nil, fmt.Errorf("failed to convert new object: %v", err)
  69. }
  70. compare, err = versionedOldObject.Compare(versionedNewObject)
  71. if err != nil {
  72. return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
  73. }
  74. versions[managerSet.APIVersion()] = compare.ExcludeFields(s.IgnoredFields[managerSet.APIVersion()])
  75. }
  76. conflictSet := managerSet.Set().Intersection(compare.Modified.Union(compare.Added))
  77. if !conflictSet.Empty() {
  78. conflicts[manager] = fieldpath.NewVersionedSet(conflictSet, managerSet.APIVersion(), false)
  79. }
  80. if !compare.Removed.Empty() {
  81. removed[manager] = fieldpath.NewVersionedSet(compare.Removed, managerSet.APIVersion(), false)
  82. }
  83. }
  84. if !force && len(conflicts) != 0 {
  85. return nil, nil, ConflictsFromManagers(conflicts)
  86. }
  87. for manager, conflictSet := range conflicts {
  88. managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(conflictSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
  89. }
  90. for manager, removedSet := range removed {
  91. managers[manager] = fieldpath.NewVersionedSet(managers[manager].Set().Difference(removedSet.Set()), managers[manager].APIVersion(), managers[manager].Applied())
  92. }
  93. for manager := range managers {
  94. if managers[manager].Set().Empty() {
  95. delete(managers, manager)
  96. }
  97. }
  98. return managers, compare, nil
  99. }
  100. // Update is the method you should call once you've merged your final
  101. // object on CREATE/UPDATE/PATCH verbs. newObject must be the object
  102. // that you intend to persist (after applying the patch if this is for a
  103. // PATCH call), and liveObject must be the original object (empty if
  104. // this is a CREATE call).
  105. func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
  106. var err error
  107. managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
  108. if err != nil {
  109. return nil, fieldpath.ManagedFields{}, err
  110. }
  111. if s.enableUnions {
  112. newObject, err = liveObject.NormalizeUnions(newObject)
  113. if err != nil {
  114. return nil, fieldpath.ManagedFields{}, err
  115. }
  116. }
  117. managers, compare, err := s.update(liveObject, newObject, version, managers, manager, true)
  118. if err != nil {
  119. return nil, fieldpath.ManagedFields{}, err
  120. }
  121. if _, ok := managers[manager]; !ok {
  122. managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
  123. }
  124. ignored := s.IgnoredFields[version]
  125. if ignored == nil {
  126. ignored = fieldpath.NewSet()
  127. }
  128. managers[manager] = fieldpath.NewVersionedSet(
  129. managers[manager].Set().Union(compare.Modified).Union(compare.Added).Difference(compare.Removed).RecursiveDifference(ignored),
  130. version,
  131. false,
  132. )
  133. if managers[manager].Set().Empty() {
  134. delete(managers, manager)
  135. }
  136. return newObject, managers, nil
  137. }
  138. // Apply should be called when Apply is run, given the current object as
  139. // well as the configuration that is applied. This will merge the object
  140. // and return it. If the object hasn't changed, nil is returned (the
  141. // managers can still have changed though).
  142. func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
  143. var err error
  144. managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
  145. if err != nil {
  146. return nil, fieldpath.ManagedFields{}, err
  147. }
  148. if s.enableUnions {
  149. configObject, err = configObject.NormalizeUnionsApply(configObject)
  150. if err != nil {
  151. return nil, fieldpath.ManagedFields{}, err
  152. }
  153. }
  154. newObject, err := liveObject.Merge(configObject)
  155. if err != nil {
  156. return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
  157. }
  158. if s.enableUnions {
  159. newObject, err = configObject.NormalizeUnionsApply(newObject)
  160. if err != nil {
  161. return nil, fieldpath.ManagedFields{}, err
  162. }
  163. }
  164. lastSet := managers[manager]
  165. set, err := configObject.ToFieldSet()
  166. if err != nil {
  167. return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
  168. }
  169. ignored := s.IgnoredFields[version]
  170. if ignored != nil {
  171. set = set.RecursiveDifference(ignored)
  172. // TODO: is this correct. If we don't remove from lastSet pruning might remove the fields?
  173. if lastSet != nil {
  174. lastSet.Set().RecursiveDifference(ignored)
  175. }
  176. }
  177. managers[manager] = fieldpath.NewVersionedSet(set, version, true)
  178. newObject, err = s.prune(newObject, managers, manager, lastSet)
  179. if err != nil {
  180. return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to prune fields: %v", err)
  181. }
  182. managers, compare, err := s.update(liveObject, newObject, version, managers, manager, force)
  183. if err != nil {
  184. return nil, fieldpath.ManagedFields{}, err
  185. }
  186. if compare.IsSame() {
  187. newObject = nil
  188. }
  189. return newObject, managers, nil
  190. }
  191. // prune will remove a field, list or map item, iff:
  192. // * applyingManager applied it last time
  193. // * applyingManager didn't apply it this time
  194. // * no other applier claims to manage it
  195. func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
  196. if lastSet == nil || lastSet.Set().Empty() {
  197. return merged, nil
  198. }
  199. convertedMerged, err := s.Converter.Convert(merged, lastSet.APIVersion())
  200. if err != nil {
  201. if s.Converter.IsMissingVersionError(err) {
  202. return merged, nil
  203. }
  204. return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
  205. }
  206. sc, tr := convertedMerged.Schema(), convertedMerged.TypeRef()
  207. pruned := convertedMerged.RemoveItems(lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr))
  208. pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
  209. if err != nil {
  210. return nil, fmt.Errorf("failed add back owned items: %v", err)
  211. }
  212. pruned, err = s.addBackDanglingItems(convertedMerged, pruned, lastSet)
  213. if err != nil {
  214. return nil, fmt.Errorf("failed add back dangling items: %v", err)
  215. }
  216. return s.Converter.Convert(pruned, managers[applyingManager].APIVersion())
  217. }
  218. // addBackOwnedItems adds back any fields, list and map items that were removed by prune,
  219. // but other appliers or updaters (or the current applier's new config) claim to own.
  220. func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
  221. var err error
  222. managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
  223. for _, managerSet := range managedFields {
  224. if _, ok := managedAtVersion[managerSet.APIVersion()]; !ok {
  225. managedAtVersion[managerSet.APIVersion()] = fieldpath.NewSet()
  226. }
  227. managedAtVersion[managerSet.APIVersion()] = managedAtVersion[managerSet.APIVersion()].Union(managerSet.Set())
  228. }
  229. // Add back owned items at pruned version first to avoid conversion failure
  230. // caused by pruned fields which are required for conversion.
  231. prunedVersion := fieldpath.APIVersion(*pruned.TypeRef().NamedType)
  232. if managed, ok := managedAtVersion[prunedVersion]; ok {
  233. merged, pruned, err = s.addBackOwnedItemsForVersion(merged, pruned, prunedVersion, managed)
  234. if err != nil {
  235. return nil, err
  236. }
  237. delete(managedAtVersion, prunedVersion)
  238. }
  239. for version, managed := range managedAtVersion {
  240. merged, pruned, err = s.addBackOwnedItemsForVersion(merged, pruned, version, managed)
  241. if err != nil {
  242. return nil, err
  243. }
  244. }
  245. return pruned, nil
  246. }
  247. // addBackOwnedItemsForVersion adds back any fields, list and map items that were removed by prune with specific managed field path at a version.
  248. // It is an extracted sub-function from addBackOwnedItems for code reuse.
  249. func (s *Updater) addBackOwnedItemsForVersion(merged, pruned *typed.TypedValue, version fieldpath.APIVersion, managed *fieldpath.Set) (*typed.TypedValue, *typed.TypedValue, error) {
  250. var err error
  251. merged, err = s.Converter.Convert(merged, version)
  252. if err != nil {
  253. if s.Converter.IsMissingVersionError(err) {
  254. return merged, pruned, nil
  255. }
  256. return nil, nil, fmt.Errorf("failed to convert merged object at version %v: %v", version, err)
  257. }
  258. pruned, err = s.Converter.Convert(pruned, version)
  259. if err != nil {
  260. if s.Converter.IsMissingVersionError(err) {
  261. return merged, pruned, nil
  262. }
  263. return nil, nil, fmt.Errorf("failed to convert pruned object at version %v: %v", version, err)
  264. }
  265. mergedSet, err := merged.ToFieldSet()
  266. if err != nil {
  267. return nil, nil, fmt.Errorf("failed to create field set from merged object at version %v: %v", version, err)
  268. }
  269. prunedSet, err := pruned.ToFieldSet()
  270. if err != nil {
  271. return nil, nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
  272. }
  273. sc, tr := merged.Schema(), merged.TypeRef()
  274. pruned = merged.RemoveItems(mergedSet.EnsureNamedFieldsAreMembers(sc, tr).Difference(prunedSet.EnsureNamedFieldsAreMembers(sc, tr).Union(managed.EnsureNamedFieldsAreMembers(sc, tr))))
  275. return merged, pruned, nil
  276. }
  277. // addBackDanglingItems makes sure that the fields list and map items removed by prune were
  278. // previously owned by the currently applying manager. This will add back fields list and map items
  279. // that are unowned or that are owned by Updaters and shouldn't be removed.
  280. func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
  281. convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion())
  282. if err != nil {
  283. if s.Converter.IsMissingVersionError(err) {
  284. return merged, nil
  285. }
  286. return nil, fmt.Errorf("failed to convert pruned object to last applied version: %v", err)
  287. }
  288. prunedSet, err := convertedPruned.ToFieldSet()
  289. if err != nil {
  290. return nil, fmt.Errorf("failed to create field set from pruned object in last applied version: %v", err)
  291. }
  292. mergedSet, err := merged.ToFieldSet()
  293. if err != nil {
  294. return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
  295. }
  296. sc, tr := merged.Schema(), merged.TypeRef()
  297. prunedSet = prunedSet.EnsureNamedFieldsAreMembers(sc, tr)
  298. mergedSet = mergedSet.EnsureNamedFieldsAreMembers(sc, tr)
  299. last := lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr)
  300. return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(last)), nil
  301. }
  302. // reconcileManagedFieldsWithSchemaChanges reconciles the managed fields with any changes to the
  303. // object's schema since the managed fields were written.
  304. //
  305. // Supports:
  306. // - changing types from atomic to granular
  307. // - changing types from granular to atomic
  308. func (s *Updater) reconcileManagedFieldsWithSchemaChanges(liveObject *typed.TypedValue, managers fieldpath.ManagedFields) (fieldpath.ManagedFields, error) {
  309. result := fieldpath.ManagedFields{}
  310. for manager, versionedSet := range managers {
  311. tv, err := s.Converter.Convert(liveObject, versionedSet.APIVersion())
  312. if s.Converter.IsMissingVersionError(err) { // okay to skip, obsolete versions will be deleted automatically anyway
  313. continue
  314. }
  315. if err != nil {
  316. return nil, err
  317. }
  318. reconciled, err := typed.ReconcileFieldSetWithSchema(versionedSet.Set(), tv)
  319. if err != nil {
  320. return nil, err
  321. }
  322. if reconciled != nil {
  323. result[manager] = fieldpath.NewVersionedSet(reconciled, versionedSet.APIVersion(), versionedSet.Applied())
  324. } else {
  325. result[manager] = versionedSet
  326. }
  327. }
  328. return result, nil
  329. }