1
16
17 package internal
18
19 import (
20 "encoding/json"
21 "fmt"
22
23 "k8s.io/apimachinery/pkg/api/meta"
24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25 "k8s.io/apimachinery/pkg/runtime"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
28 "sigs.k8s.io/structured-merge-diff/v4/merge"
29 )
30
31 type lastAppliedManager struct {
32 fieldManager Manager
33 typeConverter TypeConverter
34 objectConverter runtime.ObjectConvertor
35 groupVersion schema.GroupVersion
36 }
37
38 var _ Manager = &lastAppliedManager{}
39
40
41
42 func NewLastAppliedManager(fieldManager Manager, typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, groupVersion schema.GroupVersion) Manager {
43 return &lastAppliedManager{
44 fieldManager: fieldManager,
45 typeConverter: typeConverter,
46 objectConverter: objectConverter,
47 groupVersion: groupVersion,
48 }
49 }
50
51
52 func (f *lastAppliedManager) Update(liveObj, newObj runtime.Object, managed Managed, manager string) (runtime.Object, Managed, error) {
53 return f.fieldManager.Update(liveObj, newObj, managed, manager)
54 }
55
56
57
58
59 func (f *lastAppliedManager) Apply(liveObj, newObj runtime.Object, managed Managed, manager string, force bool) (runtime.Object, Managed, error) {
60 newLiveObj, newManaged, newErr := f.fieldManager.Apply(liveObj, newObj, managed, manager, force)
61
62
63 if manager != "kubectl" {
64 return newLiveObj, newManaged, newErr
65 }
66
67
68 if newErr == nil {
69 return newLiveObj, newManaged, newErr
70 }
71 conflicts, ok := newErr.(merge.Conflicts)
72 if !ok {
73 return newLiveObj, newManaged, newErr
74 }
75 conflictSet := conflictsToSet(conflicts)
76
77
78
79 allowedConflictSet, err := f.allowedConflictsFromLastApplied(liveObj)
80 if err != nil {
81 return newLiveObj, newManaged, newErr
82 }
83 if !conflictSet.Difference(allowedConflictSet).Empty() {
84 newConflicts := conflictsDifference(conflicts, allowedConflictSet)
85 return newLiveObj, newManaged, newConflicts
86 }
87
88 return f.fieldManager.Apply(liveObj, newObj, managed, manager, true)
89 }
90
91 func (f *lastAppliedManager) allowedConflictsFromLastApplied(liveObj runtime.Object) (*fieldpath.Set, error) {
92 var accessor, err = meta.Accessor(liveObj)
93 if err != nil {
94 panic(fmt.Sprintf("couldn't get accessor: %v", err))
95 }
96
97
98 var annotations = accessor.GetAnnotations()
99 if annotations == nil {
100 return nil, fmt.Errorf("no last applied annotation")
101 }
102 var lastApplied, ok = annotations[LastAppliedConfigAnnotation]
103 if !ok || lastApplied == "" {
104 return nil, fmt.Errorf("no last applied annotation")
105 }
106
107 liveObjVersioned, err := f.objectConverter.ConvertToVersion(liveObj, f.groupVersion)
108 if err != nil {
109 return nil, fmt.Errorf("failed to convert live obj to versioned: %v", err)
110 }
111
112 liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
113 if err != nil {
114 return nil, fmt.Errorf("failed to convert live obj to typed: %v", err)
115 }
116
117 var lastAppliedObj = &unstructured.Unstructured{Object: map[string]interface{}{}}
118 err = json.Unmarshal([]byte(lastApplied), lastAppliedObj)
119 if err != nil {
120 return nil, fmt.Errorf("failed to decode last applied obj: %v in '%s'", err, lastApplied)
121 }
122
123 if lastAppliedObj.GetAPIVersion() != f.groupVersion.String() {
124 return nil, fmt.Errorf("expected version of last applied to match live object '%s', but got '%s': %v", f.groupVersion.String(), lastAppliedObj.GetAPIVersion(), err)
125 }
126
127 lastAppliedObjTyped, err := f.typeConverter.ObjectToTyped(lastAppliedObj)
128 if err != nil {
129 return nil, fmt.Errorf("failed to convert last applied to typed: %v", err)
130 }
131
132 lastAppliedObjFieldSet, err := lastAppliedObjTyped.ToFieldSet()
133 if err != nil {
134 return nil, fmt.Errorf("failed to create fieldset for last applied object: %v", err)
135 }
136
137 comparison, err := lastAppliedObjTyped.Compare(liveObjTyped)
138 if err != nil {
139 return nil, fmt.Errorf("failed to compare last applied object and live object: %v", err)
140 }
141
142
143
144
145
146 lastAppliedObjFieldSet = lastAppliedObjFieldSet.
147 Difference(comparison.Modified).
148 Difference(comparison.Added).
149 Difference(comparison.Removed)
150
151 return lastAppliedObjFieldSet, nil
152 }
153
154
155 func conflictsToSet(conflicts merge.Conflicts) *fieldpath.Set {
156 conflictSet := fieldpath.NewSet()
157 for _, conflict := range []merge.Conflict(conflicts) {
158 conflictSet.Insert(conflict.Path)
159 }
160 return conflictSet
161 }
162
163 func conflictsDifference(conflicts merge.Conflicts, s *fieldpath.Set) merge.Conflicts {
164 newConflicts := []merge.Conflict{}
165 for _, conflict := range []merge.Conflict(conflicts) {
166 if !s.Has(conflict.Path) {
167 newConflicts = append(newConflicts, conflict)
168 }
169 }
170 return newConflicts
171 }
172
View as plain text