1
2
3
4 package filters
5
6 import (
7 "fmt"
8
9 "sigs.k8s.io/kustomize/kyaml/kio"
10 "sigs.k8s.io/kustomize/kyaml/kio/kioutil"
11 "sigs.k8s.io/kustomize/kyaml/yaml"
12 "sigs.k8s.io/kustomize/kyaml/yaml/merge3"
13 )
14
15 const (
16 mergeSourceAnnotation = "config.kubernetes.io/merge-source"
17 mergeSourceOriginal = "original"
18 mergeSourceUpdated = "updated"
19 mergeSourceDest = "dest"
20 )
21
22
23
24
25
26
27
28
29 type ResourceMatcher interface {
30 IsSameResource(node1, node2 *yaml.RNode) bool
31 }
32
33
34
35
36 type ResourceMergeStrategy int
37
38 const (
39
40
41 Merge ResourceMergeStrategy = iota
42
43 KeepDest
44
45
46 KeepUpdated
47
48
49 KeepOriginal
50
51 Skip
52 )
53
54
55
56
57
58
59 type ResourceHandler interface {
60 Handle(original, updated, dest *yaml.RNode) (ResourceMergeStrategy, error)
61 }
62
63
64 type Merge3 struct {
65 OriginalPath string
66 UpdatedPath string
67 DestPath string
68 MatchFilesGlob []string
69 Matcher ResourceMatcher
70 Handler ResourceHandler
71 }
72
73 func (m Merge3) Merge() error {
74
75
76 var inputs []kio.Reader
77 dest := &kio.LocalPackageReadWriter{
78 PackagePath: m.DestPath,
79 MatchFilesGlob: m.MatchFilesGlob,
80 SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceDest},
81 }
82 inputs = append(inputs, dest)
83
84
85 inputs = append(inputs, kio.LocalPackageReader{
86 PackagePath: m.OriginalPath,
87 MatchFilesGlob: m.MatchFilesGlob,
88 SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceOriginal},
89 })
90
91
92 inputs = append(inputs, kio.LocalPackageReader{
93 PackagePath: m.UpdatedPath,
94 MatchFilesGlob: m.MatchFilesGlob,
95 SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceUpdated},
96 })
97
98 return kio.Pipeline{
99 Inputs: inputs,
100 Filters: []kio.Filter{m},
101 Outputs: []kio.Writer{dest},
102 }.Execute()
103 }
104
105
106 func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
107
108 matcher := m.Matcher
109 if matcher == nil {
110 matcher = &DefaultGVKNNMatcher{MergeOnPath: true}
111 }
112 handler := m.Handler
113 if handler == nil {
114 handler = &DefaultResourceHandler{}
115 }
116
117 tl := tuples{matcher: matcher}
118 for i := range nodes {
119 if err := tl.add(nodes[i]); err != nil {
120 return nil, err
121 }
122 }
123
124
125 var output []*yaml.RNode
126 for i := range tl.list {
127 t := tl.list[i]
128 strategy, err := handler.Handle(t.original, t.updated, t.dest)
129 if err != nil {
130 return nil, err
131 }
132 switch strategy {
133 case Merge:
134 node, err := t.merge()
135 if err != nil {
136 return nil, err
137 }
138 if node != nil {
139 output = append(output, node)
140 }
141 case KeepDest:
142 output = append(output, t.dest)
143 case KeepUpdated:
144 output = append(output, t.updated)
145 case KeepOriginal:
146 output = append(output, t.original)
147 case Skip:
148
149 }
150 }
151 return output, nil
152 }
153
154
155 type tuples struct {
156 list []*tuple
157
158
159 matcher ResourceMatcher
160 }
161
162
163
164 type DefaultGVKNNMatcher struct {
165
166
167
168 MergeOnPath bool
169 }
170
171
172 func (dm *DefaultGVKNNMatcher) IsSameResource(node1, node2 *yaml.RNode) bool {
173 if node1 == nil || node2 == nil {
174 return false
175 }
176 if err := kioutil.CopyLegacyAnnotations(node1); err != nil {
177 return false
178 }
179 if err := kioutil.CopyLegacyAnnotations(node2); err != nil {
180 return false
181 }
182
183 meta1, err := node1.GetMeta()
184 if err != nil {
185 return false
186 }
187
188 meta2, err := node2.GetMeta()
189 if err != nil {
190 return false
191 }
192
193 if meta1.Name != meta2.Name {
194 return false
195 }
196 if meta1.Namespace != meta2.Namespace {
197 return false
198 }
199 if meta1.APIVersion != meta2.APIVersion {
200 return false
201 }
202 if meta1.Kind != meta2.Kind {
203 return false
204 }
205 if dm.MergeOnPath {
206
207
208
209
210
211 if meta1.Annotations[kioutil.PathAnnotation] != meta2.Annotations[kioutil.PathAnnotation] {
212 return false
213 }
214 }
215 return true
216 }
217
218
219 func (ts *tuples) add(node *yaml.RNode) error {
220 for i := range ts.list {
221 t := ts.list[i]
222 if ts.matcher.IsSameResource(addedNode(t), node) {
223 return t.add(node)
224 }
225 }
226 t := &tuple{}
227 if err := t.add(node); err != nil {
228 return err
229 }
230 ts.list = append(ts.list, t)
231 return nil
232 }
233
234
235 func addedNode(t *tuple) *yaml.RNode {
236 if t.updated != nil {
237 return t.updated
238 }
239 if t.original != nil {
240 return t.original
241 }
242 return t.dest
243 }
244
245
246 type tuple struct {
247 original *yaml.RNode
248 updated *yaml.RNode
249 dest *yaml.RNode
250 }
251
252
253 func (t *tuple) add(node *yaml.RNode) error {
254 meta, err := node.GetMeta()
255 if err != nil {
256 return err
257 }
258 switch meta.Annotations[mergeSourceAnnotation] {
259 case mergeSourceDest:
260 if t.dest != nil {
261 return duplicateError("local", meta.Annotations[kioutil.PathAnnotation])
262 }
263 t.dest = node
264 case mergeSourceOriginal:
265 if t.original != nil {
266 return duplicateError("original upstream", meta.Annotations[kioutil.PathAnnotation])
267 }
268 t.original = node
269 case mergeSourceUpdated:
270 if t.updated != nil {
271 return duplicateError("updated upstream", meta.Annotations[kioutil.PathAnnotation])
272 }
273 t.updated = node
274 default:
275 return fmt.Errorf("no source annotation for Resource")
276 }
277 return nil
278 }
279
280
281 func (t *tuple) merge() (*yaml.RNode, error) {
282 return merge3.Merge(t.dest, t.original, t.updated)
283 }
284
285
286 func duplicateError(source, filePath string) error {
287 return fmt.Errorf(`found duplicate %q resources in file %q, please refer to "update" documentation for the fix`, source, filePath)
288 }
289
290
291
292
293
294
295
296
297 type DefaultResourceHandler struct{}
298
299 func (*DefaultResourceHandler) Handle(original, updated, dest *yaml.RNode) (ResourceMergeStrategy, error) {
300 switch {
301 case original == nil && updated == nil && dest != nil:
302
303 return KeepDest, nil
304 case updated != nil && dest == nil:
305
306 return KeepUpdated, nil
307 case original != nil && updated == nil:
308
309 return Skip, nil
310 case original != nil && dest == nil:
311
312 return Skip, nil
313 default:
314
315 return Merge, nil
316 }
317 }
318
View as plain text