lastappliedmanager.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. Copyright 2020 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 internal
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "k8s.io/apimachinery/pkg/api/meta"
  18. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  19. "k8s.io/apimachinery/pkg/runtime"
  20. "k8s.io/apimachinery/pkg/runtime/schema"
  21. "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
  22. "sigs.k8s.io/structured-merge-diff/v4/merge"
  23. )
  24. type lastAppliedManager struct {
  25. fieldManager Manager
  26. typeConverter TypeConverter
  27. objectConverter runtime.ObjectConvertor
  28. groupVersion schema.GroupVersion
  29. }
  30. var _ Manager = &lastAppliedManager{}
  31. // NewLastAppliedManager converts the client-side apply annotation to
  32. // server-side apply managed fields
  33. func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager {
  34. return &lastAppliedManager{
  35. fieldManager: fieldManager,
  36. typeConverter: typeConverter,
  37. objectConverter: objectConverter,
  38. groupVersion: groupVersion,
  39. }
  40. }
  41. // Update implements Manager.
  42. func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
  43. return f.fieldManager.Update(liveObj, newObj, managed, manager)
  44. }
  45. // Apply will consider the last-applied annotation
  46. // for upgrading an object managed by client-side apply to server-side apply
  47. // without conflicts.
  48. func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
  49. newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
  50. // Upgrade the client-side apply annotation only from kubectl server-side-apply.
  51. // To opt-out of this behavior, users may specify a different field manager.
  52. if manager != "kubectl" {
  53. return newLiveObj, newManaged, newErr
  54. }
  55. // Check if we have conflicts
  56. if newErr == nil {
  57. return newLiveObj, newManaged, newErr
  58. }
  59. conflicts, ok := newErr.(merge.Conflicts)
  60. if !ok {
  61. return newLiveObj, newManaged, newErr
  62. }
  63. conflictSet := conflictsToSet(conflicts)
  64. // Check if conflicts are allowed due to client-side apply,
  65. // and if so, then force apply
  66. allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj)
  67. if err != nil {
  68. return newLiveObj, newManaged, newErr
  69. }
  70. if !conflictSet.Difference(allowedConflictSet).Empty() {
  71. newConflicts := conflictsDifference(conflicts, allowedConflictSet)
  72. return newLiveObj, newManaged, newConflicts
  73. }
  74. return f.fieldManager.Apply(liveObj, newObj, managed, manager, true)
  75. }
  76. func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) {
  77. var accessor, err = meta.Accessor(liveObj)
  78. if err != nil {
  79. panic(fmt.Sprintf("couldn't get accessor: %v", err))
  80. }
  81. // If there is no client-side apply annotation, then there is nothing to do
  82. var annotations = accessor.GetAnnotations()
  83. if annotations == nil {
  84. return nil, fmt.Errorf("no last applied annotation")
  85. }
  86. var lastApplied, ok = annotations[LastAppliedConfigAnnotation]
  87. if !ok || lastApplied == "" {
  88. return nil, fmt.Errorf("no last applied annotation")
  89. }
  90. liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion)
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err)
  93. }
  94. liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
  95. if err != nil {
  96. return nil, fmt.Errorf("failed to convert live obj to typed: %v", err)
  97. }
  98. var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
  99. err = json.Unmarshal([]byte(lastApplied), lastAppliedObj)
  100. if err != nil {
  101. return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied)
  102. }
  103. if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() {
  104. return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err)
  105. }
  106. lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj)
  107. if err != nil {
  108. return nil, fmt.Errorf("failed to convert last applied to typed: %v", err)
  109. }
  110. lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet()
  111. if err != nil {
  112. return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err)
  113. }
  114. comparison, err := lastAppliedObjTyped.Compare(liveObjTyped)
  115. if err != nil {
  116. return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err)
  117. }
  118. // Remove fields in last applied that are different, added, or missing in
  119. // the live object.
  120. // Because last-applied fields don't match the live object fields,
  121. // then we don't own these fields.
  122. lastAppliedObjFieldSet = lastAppliedObjFieldSet.
  123. Difference(comparison.Modified).
  124. Difference(comparison.Added).
  125. Difference(comparison.Removed)
  126. return lastAppliedObjFieldSet, nil
  127. }
  128. // TODO: replace with merge.Conflicts.ToSet()
  129. func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set {
  130. conflictSet := fieldpath.NewSet()
  131. for _, conflict := range []merge.Conflict(conflicts) {
  132. conflictSet.Insert(conflict.Path)
  133. }
  134. return conflictSet
  135. }
  136. func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts {
  137. newConflicts := []merge.Conflict{}
  138. for _, conflict := range []merge.Conflict(conflicts) {
  139. if !s.Has(conflict.Path) {
  140. newConflicts = append(newConflicts, conflict)
  141. }
  142. }
  143. return newConflicts
  144. }