1
16
17 package fixture
18
19 import (
20 "bytes"
21 "fmt"
22
23 "github.com/google/go-cmp/cmp"
24
25 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
26 "sigs.k8s.io/structured-merge-diff/v4/merge"
27 "sigs.k8s.io/structured-merge-diff/v4/typed"
28 )
29
30
31
32 type Parser interface {
33 Type(string) typed.ParseableType
34 }
35
36
37 type SameVersionParser struct {
38 T typed.ParseableType
39 }
40
41 func (p SameVersionParser) Type(_ string) typed.ParseableType {
42 return p.T
43 }
44
45
46
47 var DeducedParser = SameVersionParser{
48 T: typed.DeducedParseableType,
49 }
50
51
52
53
54
55
56
57
58
59
60 type State struct {
61 Live *typed.TypedValue
62 Parser Parser
63 Managers fieldpath.ManagedFields
64 Updater *merge.Updater
65 }
66
67
68
69
70
71
72
73 func FixTabsOrDie(in typed.YAMLObject) typed.YAMLObject {
74 lines := bytes.Split([]byte(in), []byte{'\n'})
75 if len(lines[0]) == 0 && len(lines) > 1 {
76 lines = lines[1:]
77 }
78
79 var prefix []byte
80 for _, c := range lines[0] {
81 if c != '\t' {
82 break
83 }
84 prefix = append(prefix, byte('\t'))
85 }
86
87 for i := range lines {
88 line := lines[i]
89
90 if i == len(lines)-1 && len(line) <= len(prefix) && bytes.TrimSpace(line) == nil {
91 lines[i] = []byte{}
92 break
93 }
94 if !bytes.HasPrefix(line, prefix) {
95 panic(fmt.Errorf("line %d doesn't start with expected number (%d) of tabs: %v", i, len(prefix), string(line)))
96 }
97 lines[i] = line[len(prefix):]
98 }
99 return typed.YAMLObject(bytes.Join(lines, []byte{'\n'}))
100 }
101
102 func (s *State) checkInit(version fieldpath.APIVersion) error {
103 if s.Live == nil {
104 obj, err := s.Parser.Type(string(version)).FromUnstructured(nil)
105 if err != nil {
106 return fmt.Errorf("failed to create new empty object: %v", err)
107 }
108 s.Live = obj
109 }
110 return nil
111 }
112
113 func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string) error {
114 err := s.checkInit(version)
115 if err != nil {
116 return err
117 }
118 s.Live, err = s.Updater.Converter.Convert(s.Live, version)
119 if err != nil {
120 return err
121 }
122 newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
123 if err != nil {
124 return err
125 }
126 s.Live = newObj
127 s.Managers = managers
128
129 return nil
130 }
131
132
133 func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manager string) error {
134 tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj), typed.AllowDuplicates)
135 if err != nil {
136 return err
137 }
138 return s.UpdateObject(tv, version, manager)
139 }
140
141 func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string, force bool) error {
142 err := s.checkInit(version)
143 if err != nil {
144 return err
145 }
146 s.Live, err = s.Updater.Converter.Convert(s.Live, version)
147 if err != nil {
148 return err
149 }
150 new, managers, err := s.Updater.Apply(s.Live, tv, version, s.Managers, manager, force)
151 if err != nil {
152 return err
153 }
154 s.Managers = managers
155 if new != nil {
156 s.Live = new
157 }
158 return nil
159 }
160
161
162 func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manager string, force bool) error {
163 tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
164 if err != nil {
165 return err
166 }
167 return s.ApplyObject(tv, version, manager, force)
168 }
169
170
171
172 func (s *State) CompareLive(obj typed.YAMLObject, version fieldpath.APIVersion) (string, error) {
173 obj = FixTabsOrDie(obj)
174 if err := s.checkInit(version); err != nil {
175 return "", err
176 }
177 tv, err := s.Parser.Type(string(version)).FromYAML(obj, typed.AllowDuplicates)
178 if err != nil {
179 return "", err
180 }
181 live, err := s.Updater.Converter.Convert(s.Live, version)
182 if err != nil {
183 return "", err
184 }
185 tvu := convertMapAnyToMapString(tv.AsValue().Unstructured())
186 liveu := convertMapAnyToMapString(live.AsValue().Unstructured())
187 return cmp.Diff(tvu, liveu), nil
188 }
189
190 func convertMapAnyToMapString(obj interface{}) interface{} {
191 switch m := obj.(type) {
192 case map[string]interface{}:
193 out := map[string]interface{}{}
194 for key, value := range m {
195 out[key] = convertMapAnyToMapString(value)
196 }
197 return out
198 case map[interface{}]interface{}:
199 out := map[string]interface{}{}
200 for key, value := range m {
201 out[key.(string)] = convertMapAnyToMapString(value)
202 }
203 return out
204 case []interface{}:
205 out := []interface{}{}
206 for _, value := range m {
207 out = append(out, convertMapAnyToMapString(value))
208 }
209 return out
210 default:
211 return obj
212 }
213 }
214
215
216 type dummyConverter struct{}
217
218 var _ merge.Converter = dummyConverter{}
219
220
221 func (dummyConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
222 if len(version) == 0 {
223 return nil, fmt.Errorf("cannot convert to invalid version: %q", version)
224 }
225 return v, nil
226 }
227
228 func (dummyConverter) IsMissingVersionError(err error) bool {
229 return false
230 }
231
232
233 type Operation interface {
234 run(*State) error
235 preprocess(Parser) (Operation, error)
236 }
237
238 func hasConflict(conflicts merge.Conflicts, conflict merge.Conflict) bool {
239 for i := range conflicts {
240 if conflict.Equals(conflicts[i]) {
241 return true
242 }
243 }
244 return false
245 }
246
247 func addedConflicts(one, other merge.Conflicts) merge.Conflicts {
248 added := merge.Conflicts{}
249 for _, conflict := range other {
250 if !hasConflict(one, conflict) {
251 added = append(added, conflict)
252 }
253 }
254 return added
255 }
256
257
258
259
260
261 type Apply struct {
262 Manager string
263 APIVersion fieldpath.APIVersion
264 Object typed.YAMLObject
265 Conflicts merge.Conflicts
266 }
267
268 var _ Operation = &Apply{}
269
270 func (a Apply) run(state *State) error {
271 p, err := a.preprocess(state.Parser)
272 if err != nil {
273 return err
274 }
275 return p.run(state)
276 }
277
278 func (a Apply) preprocess(parser Parser) (Operation, error) {
279 tv, err := parser.Type(string(a.APIVersion)).FromYAML(FixTabsOrDie(a.Object))
280 if err != nil {
281 return nil, err
282 }
283 return ApplyObject{
284 Manager: a.Manager,
285 APIVersion: a.APIVersion,
286 Object: tv,
287 Conflicts: a.Conflicts,
288 }, nil
289 }
290
291 type ApplyObject struct {
292 Manager string
293 APIVersion fieldpath.APIVersion
294 Object *typed.TypedValue
295 Conflicts merge.Conflicts
296 }
297
298 var _ Operation = &ApplyObject{}
299
300 func (a ApplyObject) run(state *State) error {
301 err := state.ApplyObject(a.Object, a.APIVersion, a.Manager, false)
302 if err != nil {
303 if _, ok := err.(merge.Conflicts); !ok || a.Conflicts == nil {
304 return err
305 }
306 }
307 if a.Conflicts != nil {
308 conflicts := merge.Conflicts{}
309 if err != nil {
310 conflicts = err.(merge.Conflicts)
311 }
312 if len(addedConflicts(a.Conflicts, conflicts)) != 0 || len(addedConflicts(conflicts, a.Conflicts)) != 0 {
313 return fmt.Errorf("Expected conflicts:\n%v\ngot\n%v\nadded:\n%v\nremoved:\n%v",
314 a.Conflicts.Error(),
315 conflicts.Error(),
316 addedConflicts(a.Conflicts, conflicts).Error(),
317 addedConflicts(conflicts, a.Conflicts).Error(),
318 )
319 }
320 }
321 return nil
322 }
323
324 func (a ApplyObject) preprocess(parser Parser) (Operation, error) {
325 return a, nil
326 }
327
328
329
330 type ForceApply struct {
331 Manager string
332 APIVersion fieldpath.APIVersion
333 Object typed.YAMLObject
334 }
335
336 var _ Operation = &ForceApply{}
337
338 func (f ForceApply) run(state *State) error {
339 return state.Apply(f.Object, f.APIVersion, f.Manager, true)
340 }
341
342 func (f ForceApply) preprocess(parser Parser) (Operation, error) {
343 tv, err := parser.Type(string(f.APIVersion)).FromYAML(FixTabsOrDie(f.Object))
344 if err != nil {
345 return nil, err
346 }
347 return ForceApplyObject{
348 Manager: f.Manager,
349 APIVersion: f.APIVersion,
350 Object: tv,
351 }, nil
352 }
353
354
355
356 type ForceApplyObject struct {
357 Manager string
358 APIVersion fieldpath.APIVersion
359 Object *typed.TypedValue
360 }
361
362 var _ Operation = &ForceApplyObject{}
363
364 func (f ForceApplyObject) run(state *State) error {
365 return state.ApplyObject(f.Object, f.APIVersion, f.Manager, true)
366 }
367
368 func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
369 return f, nil
370 }
371
372
373
374
375 type ExtractApply struct {
376 Manager string
377 APIVersion fieldpath.APIVersion
378 Object typed.YAMLObject
379 }
380
381 var _ Operation = &ExtractApply{}
382
383 func (e ExtractApply) run(state *State) error {
384 p, err := e.preprocess(state.Parser)
385 if err != nil {
386 return err
387 }
388 return p.run(state)
389 }
390
391 func (e ExtractApply) preprocess(parser Parser) (Operation, error) {
392
393 tv, err := parser.Type(string(e.APIVersion)).FromYAML(FixTabsOrDie(e.Object))
394 if err != nil {
395 return nil, err
396 }
397 return ExtractApplyObject{
398 Manager: e.Manager,
399 APIVersion: e.APIVersion,
400 Object: tv,
401 }, nil
402 }
403
404 type ExtractApplyObject struct {
405 Manager string
406 APIVersion fieldpath.APIVersion
407 Object *typed.TypedValue
408 }
409
410 var _ Operation = &ExtractApplyObject{}
411
412 func (e ExtractApplyObject) run(state *State) error {
413 if state.Live == nil {
414 return state.ApplyObject(e.Object, e.APIVersion, e.Manager, true)
415 }
416
417 current, err := state.Updater.Converter.Convert(state.Live, e.APIVersion)
418 if err != nil {
419 return err
420 }
421
422 set := fieldpath.NewSet()
423 mgr := state.Managers[e.Manager]
424 if mgr != nil {
425
426 if mgr.APIVersion() != e.APIVersion {
427 return fmt.Errorf("existing managed fieldpath set APIVersion (%s) differs from desired (%s), unable to extract", mgr.APIVersion(), e.APIVersion)
428 }
429
430
431
432 set = mgr.Set().Leaves()
433 }
434
435 extracted := current.ExtractItems(set)
436
437 obj, err := extracted.Merge(e.Object)
438 if err != nil {
439 return err
440 }
441
442 return state.ApplyObject(obj, e.APIVersion, e.Manager, true)
443 }
444
445 func (e ExtractApplyObject) preprocess(parser Parser) (Operation, error) {
446 return e, nil
447 }
448
449
450
451 type Update struct {
452 Manager string
453 APIVersion fieldpath.APIVersion
454 Object typed.YAMLObject
455 }
456
457 var _ Operation = &Update{}
458
459 func (u Update) run(state *State) error {
460 return state.Update(u.Object, u.APIVersion, u.Manager)
461 }
462
463 func (u Update) preprocess(parser Parser) (Operation, error) {
464 tv, err := parser.Type(string(u.APIVersion)).FromYAML(FixTabsOrDie(u.Object), typed.AllowDuplicates)
465 if err != nil {
466 return nil, err
467 }
468 return UpdateObject{
469 Manager: u.Manager,
470 APIVersion: u.APIVersion,
471 Object: tv,
472 }, nil
473 }
474
475
476
477 type UpdateObject struct {
478 Manager string
479 APIVersion fieldpath.APIVersion
480 Object *typed.TypedValue
481 }
482
483 var _ Operation = &Update{}
484
485 func (u UpdateObject) run(state *State) error {
486 return state.UpdateObject(u.Object, u.APIVersion, u.Manager)
487 }
488
489 func (f UpdateObject) preprocess(parser Parser) (Operation, error) {
490 return f, nil
491 }
492
493
494
495
496
497 type ChangeParser struct {
498 Parser *typed.Parser
499 }
500
501 var _ Operation = &ChangeParser{}
502
503 func (cs ChangeParser) run(state *State) error {
504 state.Parser = cs.Parser
505
506
507
508 liveWithNewSchema, err := typed.AsTyped(state.Live.AsValue(), &cs.Parser.Schema, state.Live.TypeRef())
509 if err != nil {
510 return err
511 }
512 state.Live = liveWithNewSchema
513 return nil
514 }
515
516 func (cs ChangeParser) preprocess(_ Parser) (Operation, error) {
517 return cs, nil
518 }
519
520
521
522
523
524
525
526 type TestCase struct {
527
528 Ops []Operation
529
530
531 Object typed.YAMLObject
532
533
534 APIVersion fieldpath.APIVersion
535
536
537 Managed fieldpath.ManagedFields
538
539
540 ReturnInputOnNoop bool
541
542 IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
543 }
544
545
546 func (tc TestCase) Test(parser Parser) error {
547 return tc.TestWithConverter(parser, &dummyConverter{})
548 }
549
550
551
552 func (tc TestCase) Bench(parser Parser) error {
553 return tc.BenchWithConverter(parser, &dummyConverter{})
554 }
555
556
557 func (tc TestCase) PreprocessOperations(parser Parser) error {
558 for i := range tc.Ops {
559 op, err := tc.Ops[i].preprocess(parser)
560 if err != nil {
561 return err
562 }
563 tc.Ops[i] = op
564 }
565 return nil
566 }
567
568
569
570
571
572 func (tc TestCase) BenchWithConverter(parser Parser, converter merge.Converter) error {
573 updaterBuilder := merge.UpdaterBuilder{
574 Converter: converter,
575 IgnoredFields: tc.IgnoredFields,
576 ReturnInputOnNoop: tc.ReturnInputOnNoop,
577 }
578 state := State{
579 Updater: updaterBuilder.BuildUpdater(),
580 Parser: parser,
581 }
582
583
584 for i, ops := range tc.Ops {
585 err := ops.run(&state)
586 if err != nil {
587 return fmt.Errorf("failed operation %d: %v", i, err)
588 }
589 }
590 return nil
591 }
592
593
594 func (tc TestCase) TestWithConverter(parser Parser, converter merge.Converter) error {
595 updaterBuilder := merge.UpdaterBuilder{
596 Converter: converter,
597 IgnoredFields: tc.IgnoredFields,
598 ReturnInputOnNoop: tc.ReturnInputOnNoop,
599 }
600 state := State{
601 Updater: updaterBuilder.BuildUpdater(),
602 Parser: parser,
603 }
604 for i, ops := range tc.Ops {
605 err := ops.run(&state)
606 if err != nil {
607 return fmt.Errorf("failed operation %d: %v", i, err)
608 }
609 }
610
611
612 if tc.Object != typed.YAMLObject("") {
613 comparison, err := state.CompareLive(tc.Object, tc.APIVersion)
614 if err != nil {
615 return fmt.Errorf("failed to compare live with config: %v", err)
616 }
617 if comparison != "" {
618 return fmt.Errorf("expected live and config to be the same:\n%v\n", comparison)
619 }
620 }
621
622 if tc.Managed != nil {
623 if diff := state.Managers.Difference(tc.Managed); len(diff) != 0 {
624 return fmt.Errorf("expected Managers to be:\n%v\ngot:\n%v\ndiff:\n%v", tc.Managed, state.Managers, diff)
625 }
626 }
627
628
629 for manager, set := range state.Managers {
630 if set.Set().Empty() {
631 return fmt.Errorf("expected Managers to have no empty sets, but found one managed by %v", manager)
632 }
633 }
634
635 return nil
636 }
637
View as plain text