1
2
3
4 package yaml
5
6 import (
7 "fmt"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "github.com/davecgh/go-spew/spew"
13 "sigs.k8s.io/kustomize/kyaml/errors"
14 yaml "sigs.k8s.io/yaml/goyaml.v3"
15 )
16
17
18 func Append(elements ...*yaml.Node) ElementAppender {
19 return ElementAppender{Elements: elements}
20 }
21
22
23
24 type ElementAppender struct {
25 Kind string `yaml:"kind,omitempty"`
26
27
28 Elements []*yaml.Node `yaml:"elements,omitempty"`
29 }
30
31 func (a ElementAppender) Filter(rn *RNode) (*RNode, error) {
32 if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
33 return nil, err
34 }
35 for i := range a.Elements {
36 rn.YNode().Content = append(rn.Content(), a.Elements[i])
37 }
38 if len(a.Elements) == 1 {
39 return NewRNode(a.Elements[0]), nil
40 }
41 return nil, nil
42 }
43
44
45
46
47
48
49
50
51
52 type ElementSetter struct {
53 Kind string `yaml:"kind,omitempty"`
54
55
56 Element *Node
57
58
59
60 Keys []string
61
62
63
64 Values []string
65 }
66
67
68 func (e ElementSetter) isMappingNode(node *RNode) bool {
69 return ErrorIfInvalid(node, yaml.MappingNode) == nil
70 }
71
72
73 func (e ElementSetter) isMappingSetter() bool {
74 return len(e.Keys) > 0 && e.Keys[0] != "" &&
75 len(e.Values) > 0 && e.Values[0] != ""
76 }
77
78 func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
79 if len(e.Keys) == 0 {
80 e.Keys = append(e.Keys, "")
81 }
82
83 if err := ErrorIfInvalid(rn, SequenceNode); err != nil {
84 return nil, err
85 }
86
87
88 var newContent []*yaml.Node
89 matchingElementFound := false
90 for i := range rn.YNode().Content {
91 elem := rn.Content()[i]
92 newNode := NewRNode(elem)
93
94
95 if IsMissingOrNull(newNode) || IsEmptyMap(newNode) {
96 continue
97 }
98
99 if !e.isMappingNode(newNode) && e.isMappingSetter() {
100 newContent = append(newContent, elem)
101 continue
102 }
103
104
105 var val *RNode
106 var err error
107 found := true
108 for j := range e.Keys {
109 if j < len(e.Values) {
110 val, err = newNode.Pipe(FieldMatcher{Name: e.Keys[j], StringValue: e.Values[j]})
111 }
112 if err != nil {
113 return nil, err
114 }
115 if val == nil {
116 found = false
117 break
118 }
119 }
120 if !found {
121
122 if len(e.Values) > 0 {
123 newContent = append(newContent, elem)
124 }
125 continue
126 }
127 matchingElementFound = true
128
129
130 if e.Element == nil {
131 continue
132 }
133
134 newContent = append(newContent, e.Element)
135 }
136 rn.YNode().Content = newContent
137
138
139 if IsMissingOrNull(NewRNode(e.Element)) {
140 return nil, nil
141 }
142
143
144 if !matchingElementFound {
145 rn.YNode().Content = append(rn.YNode().Content, e.Element)
146 }
147
148 return NewRNode(e.Element), nil
149 }
150
151
152
153 func GetElementByIndex(index int) ElementIndexer {
154 return ElementIndexer{Index: index}
155 }
156
157
158
159 type ElementIndexer struct {
160 Index int
161 }
162
163
164 func (i ElementIndexer) Filter(rn *RNode) (*RNode, error) {
165
166 elems, err := rn.Elements()
167 if err != nil {
168 return nil, err
169 }
170 if i.Index < 0 {
171 return elems[len(elems)-1], nil
172 }
173 if i.Index >= len(elems) {
174 return nil, nil
175 }
176 return elems[i.Index], nil
177 }
178
179
180 func Clear(name string) FieldClearer {
181 return FieldClearer{Name: name}
182 }
183
184
185
186 type FieldClearer struct {
187 Kind string `yaml:"kind,omitempty"`
188
189
190 Name string `yaml:"name,omitempty"`
191
192 IfEmpty bool `yaml:"ifEmpty,omitempty"`
193 }
194
195 func (c FieldClearer) Filter(rn *RNode) (*RNode, error) {
196 if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
197 return nil, err
198 }
199
200 var removed *RNode
201 visitFieldsWhileTrue(rn.Content(), func(key, value *yaml.Node, keyIndex int) bool {
202 if key.Value != c.Name {
203 return true
204 }
205
206
207
208 if c.IfEmpty {
209 if len(value.Content) > 0 {
210 return true
211 }
212 }
213
214
215 removed = NewRNode(value)
216 if len(rn.YNode().Content) > keyIndex+2 {
217 l := len(rn.YNode().Content)
218
219 rn.YNode().Content = rn.Content()[:keyIndex]
220 rn.YNode().Content = append(
221 rn.YNode().Content,
222 rn.Content()[keyIndex+2:l]...)
223 } else {
224
225 rn.YNode().Content = rn.Content()[:keyIndex]
226 }
227 return false
228 })
229
230 return removed, nil
231 }
232
233 func MatchElement(field, value string) ElementMatcher {
234 return ElementMatcher{Keys: []string{field}, Values: []string{value}}
235 }
236
237 func MatchElementList(keys []string, values []string) ElementMatcher {
238 return ElementMatcher{Keys: keys, Values: values}
239 }
240
241 func GetElementByKey(key string) ElementMatcher {
242 return ElementMatcher{Keys: []string{key}, MatchAnyValue: true}
243 }
244
245
246
247
248 type ElementMatcher struct {
249 Kind string `yaml:"kind,omitempty"`
250
251
252 Keys []string
253
254
255 Values []string
256
257
258 Create *RNode `yaml:"create,omitempty"`
259
260
261
262
263 MatchAnyValue bool `yaml:"noValue,omitempty"`
264 }
265
266 func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) {
267 if len(e.Keys) == 0 {
268 e.Keys = append(e.Keys, "")
269 }
270 if len(e.Values) == 0 {
271 e.Values = append(e.Values, "")
272 }
273
274 if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
275 return nil, err
276 }
277 if e.MatchAnyValue && len(e.Values) != 0 && e.Values[0] != "" {
278 return nil, fmt.Errorf("Values must be empty when MatchAnyValue is set to true")
279 }
280
281
282
283 if len(e.Keys) == 0 || len(e.Keys[0]) == 0 {
284 for i := range rn.Content() {
285 if rn.Content()[i].Value == e.Values[0] {
286 return &RNode{value: rn.Content()[i]}, nil
287 }
288 }
289 if e.Create != nil {
290 return rn.Pipe(Append(e.Create.YNode()))
291 }
292 return nil, nil
293 }
294
295
296
297 for i := range rn.Content() {
298
299 elem := NewRNode(rn.Content()[i])
300 var field *RNode
301 var err error
302
303
304 if err = ErrorIfInvalid(elem, yaml.MappingNode); err != nil {
305 continue
306 }
307
308 if !e.MatchAnyValue && len(e.Keys) != len(e.Values) {
309 return nil, fmt.Errorf("length of keys must equal length of values when MatchAnyValue is false")
310 }
311
312 matchesElement := true
313 for i := range e.Keys {
314 if e.MatchAnyValue {
315 field, err = elem.Pipe(Get(e.Keys[i]))
316 } else {
317 field, err = elem.Pipe(MatchField(e.Keys[i], e.Values[i]))
318 }
319 if !IsFoundOrError(field, err) {
320
321 matchesElement = false
322 break
323 }
324 }
325 if matchesElement {
326 return elem, err
327 }
328 }
329
330
331 if e.Create != nil {
332 return rn.Pipe(Append(e.Create.YNode()))
333 }
334
335 return nil, nil
336 }
337
338 func Get(name string) FieldMatcher {
339 return FieldMatcher{Name: name}
340 }
341
342 func MatchField(name, value string) FieldMatcher {
343 return FieldMatcher{Name: name, Value: NewScalarRNode(value)}
344 }
345
346 func Match(value string) FieldMatcher {
347 return FieldMatcher{Value: NewScalarRNode(value)}
348 }
349
350
351 type FieldMatcher struct {
352 Kind string `yaml:"kind,omitempty"`
353
354
355 Name string `yaml:"name,omitempty"`
356
357
358
359 Value *RNode `yaml:"value,omitempty"`
360
361 StringValue string `yaml:"stringValue,omitempty"`
362
363 StringRegexValue string `yaml:"stringRegexValue,omitempty"`
364
365
366
367 Create *RNode `yaml:"create,omitempty"`
368 }
369
370 func (f FieldMatcher) Filter(rn *RNode) (*RNode, error) {
371 if f.StringValue != "" && f.Value == nil {
372 f.Value = NewScalarRNode(f.StringValue)
373 }
374
375
376 if IsMissingOrNull(rn) {
377 return nil, nil
378 }
379
380 if f.Name == "" {
381 if err := ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
382 return nil, err
383 }
384 switch {
385 case f.StringRegexValue != "":
386
387 rg, err := regexp.Compile(f.StringRegexValue)
388 if err != nil {
389 return nil, err
390 }
391 if match := rg.MatchString(rn.value.Value); match {
392 return rn, nil
393 }
394 return nil, nil
395 case GetValue(rn) == GetValue(f.Value):
396 return rn, nil
397 default:
398 return nil, nil
399 }
400 }
401
402 if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
403 return nil, err
404 }
405
406 var returnNode *RNode
407 requireMatchFieldValue := f.Value != nil
408 visitMappingNodeFields(rn.Content(), func(key, value *yaml.Node) {
409 if !requireMatchFieldValue || value.Value == f.Value.YNode().Value {
410 returnNode = NewRNode(value)
411 }
412 }, f.Name)
413 if returnNode != nil {
414 return returnNode, nil
415 }
416
417 if f.Create != nil {
418 return rn.Pipe(SetField(f.Name, f.Create))
419 }
420
421 return nil, nil
422 }
423
424
425 func Lookup(path ...string) PathGetter {
426 return PathGetter{Path: path}
427 }
428
429
430
431 func LookupCreate(kind yaml.Kind, path ...string) PathGetter {
432 return PathGetter{Path: path, Create: kind}
433 }
434
435
436
437 var ConventionalContainerPaths = [][]string{
438
439 {"spec", "template", "spec", "containers"},
440
441 {"spec", "jobTemplate", "spec", "template", "spec", "containers"},
442
443 {"spec", "containers"},
444
445 {"template", "spec", "containers"},
446 }
447
448
449
450
451
452 func LookupFirstMatch(paths [][]string) Filter {
453 return FilterFunc(func(object *RNode) (*RNode, error) {
454 var result *RNode
455 var err error
456 for _, path := range paths {
457 result, err = object.Pipe(PathGetter{Path: path})
458 if err != nil {
459 return nil, errors.Wrap(err)
460 }
461 if result != nil {
462 return result, nil
463 }
464 }
465 return nil, nil
466 })
467 }
468
469
470 type PathGetter struct {
471 Kind string `yaml:"kind,omitempty"`
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491 Path []string `yaml:"path,omitempty"`
492
493
494
495
496
497
498
499
500 Create yaml.Kind `yaml:"create,omitempty"`
501
502
503
504 Style yaml.Style `yaml:"style,omitempty"`
505 }
506
507 func (l PathGetter) Filter(rn *RNode) (*RNode, error) {
508 var err error
509 fieldPath := append([]string{}, rn.FieldPath()...)
510 match := rn
511
512
513 l.Path = cleanPath(l.Path)
514 for i := range l.Path {
515 var part, nextPart string
516 part = l.Path[i]
517 if len(l.Path) > i+1 {
518 nextPart = l.Path[i+1]
519 }
520 var fltr Filter
521 fltr, err = l.getFilter(part, nextPart, &fieldPath)
522 if err != nil {
523 return nil, err
524 }
525 match, err = match.Pipe(fltr)
526 if IsMissingOrError(match, err) {
527 return nil, err
528 }
529 match.AppendToFieldPath(fieldPath...)
530 }
531 return match, nil
532 }
533
534 func (l PathGetter) getFilter(part, nextPart string, fieldPath *[]string) (Filter, error) {
535 idx, err := strconv.Atoi(part)
536 switch {
537 case err == nil:
538
539 if idx < 0 {
540 return nil, fmt.Errorf("array index %d cannot be negative", idx)
541 }
542 return GetElementByIndex(idx), nil
543 case part == "-":
544
545 return GetElementByIndex(-1), nil
546 case part == "*":
547
548 return nil, errors.Errorf("wildcard is not supported in PathGetter")
549 case IsListIndex(part):
550
551 return l.elemFilter(part)
552 default:
553
554 *fieldPath = append(*fieldPath, part)
555 return l.fieldFilter(part, getPathPartKind(nextPart, l.Create))
556 }
557 }
558
559 func (l PathGetter) elemFilter(part string) (Filter, error) {
560 name, value, err := SplitIndexNameValue(part)
561 if err != nil {
562 return nil, errors.Wrap(err)
563 }
564 if !IsCreate(l.Create) {
565 return MatchElement(name, value), nil
566 }
567
568 var elem *RNode
569 primitiveElement := len(name) == 0
570 if primitiveElement {
571
572 elem = NewScalarRNode(value)
573 elem.YNode().Style = l.Style
574 } else {
575
576 match := NewRNode(&yaml.Node{Kind: yaml.ScalarNode, Value: value, Style: l.Style})
577 elem = NewRNode(&yaml.Node{
578 Kind: yaml.MappingNode,
579 Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: name}, match.YNode()},
580 Style: l.Style,
581 })
582 }
583
584 return ElementMatcher{Keys: []string{name}, Values: []string{value}, Create: elem}, nil
585 }
586
587 func (l PathGetter) fieldFilter(
588 name string, kind yaml.Kind) (Filter, error) {
589 if !IsCreate(l.Create) {
590 return Get(name), nil
591 }
592 return FieldMatcher{Name: name, Create: &RNode{value: &yaml.Node{Kind: kind, Style: l.Style}}}, nil
593 }
594
595 func getPathPartKind(nextPart string, defaultKind yaml.Kind) yaml.Kind {
596 if IsListIndex(nextPart) {
597
598
599 return yaml.SequenceNode
600 }
601 if IsIdxNumber(nextPart) {
602 return yaml.SequenceNode
603 }
604 if nextPart == "" {
605
606 return defaultKind
607 }
608
609
610 return yaml.MappingNode
611 }
612
613 func SetField(name string, value *RNode) FieldSetter {
614 return FieldSetter{Name: name, Value: value}
615 }
616
617 func Set(value *RNode) FieldSetter {
618 return FieldSetter{Value: value}
619 }
620
621
622
623
624
625
626 type MapEntrySetter struct {
627
628
629 Name string `yaml:"name,omitempty"`
630
631
632 Value *RNode `yaml:"value,omitempty"`
633
634
635 Key *RNode `yaml:"key,omitempty"`
636 }
637
638 func (s MapEntrySetter) Filter(rn *RNode) (*RNode, error) {
639 if rn == nil {
640 return nil, errors.Errorf("Can't set map entry on a nil RNode")
641 }
642 if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
643 return nil, err
644 }
645 if s.Name == "" {
646 s.Name = GetValue(s.Key)
647 }
648
649 content := rn.Content()
650 fieldStillNotFound := true
651 visitFieldsWhileTrue(content, func(key, value *yaml.Node, keyIndex int) bool {
652 if key.Value == s.Name {
653 content[keyIndex] = s.Key.YNode()
654 content[keyIndex+1] = s.Value.YNode()
655 fieldStillNotFound = false
656 }
657 return fieldStillNotFound
658 })
659 if !fieldStillNotFound {
660 return rn, nil
661 }
662
663
664 rn.YNode().Content = append(
665 rn.YNode().Content,
666 s.Key.YNode(),
667 s.Value.YNode())
668 return rn, nil
669 }
670
671
672 type FieldSetter struct {
673 Kind string `yaml:"kind,omitempty"`
674
675
676
677
678 Name string `yaml:"name,omitempty"`
679
680
681 Comments Comments `yaml:"comments,omitempty"`
682
683
684
685 Value *RNode `yaml:"value,omitempty"`
686
687 StringValue string `yaml:"stringValue,omitempty"`
688
689
690
691
692 OverrideStyle bool `yaml:"overrideStyle,omitempty"`
693
694
695
696 AppendKeyStyle Style `yaml:"appendKeyStyle,omitempty"`
697 }
698
699 func (s FieldSetter) Filter(rn *RNode) (*RNode, error) {
700 if s.StringValue != "" && s.Value == nil {
701 s.Value = NewScalarRNode(s.StringValue)
702 }
703
704
705
706 if s.Value.IsStringValue() && !s.OverrideStyle && s.Value.YNode().Style == 0 && IsYaml1_1NonString(s.Value.YNode()) {
707 s.Value.YNode().Style = yaml.DoubleQuotedStyle
708 }
709
710 if s.Name == "" {
711 if err := ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
712 return rn, err
713 }
714 if IsMissingOrNull(s.Value) {
715 return rn, nil
716 }
717
718
719 if !s.OverrideStyle || s.Value.YNode().Style == 0 {
720
721 s.Value.YNode().Style = rn.YNode().Style
722 }
723 rn.SetYNode(s.Value.YNode())
724 return rn, nil
725 }
726
727
728
729
730
731
732
733
734 if s.Value == nil || (s.Value.IsTaggedNull() && !s.Value.ShouldKeep) {
735 return rn.Pipe(Clear(s.Name))
736 }
737
738 field, err := rn.Pipe(FieldMatcher{Name: s.Name})
739 if err != nil {
740 return nil, err
741 }
742 if field != nil {
743
744
745 if !s.OverrideStyle || field.YNode().Style == 0 {
746
747 s.Value.YNode().Style = field.YNode().Style
748 }
749
750 field.SetYNode(s.Value.YNode())
751 return field, nil
752 }
753
754
755 rn.YNode().Content = append(
756 rn.YNode().Content,
757 &yaml.Node{
758 Kind: yaml.ScalarNode,
759 Value: s.Name,
760 Style: s.AppendKeyStyle,
761 HeadComment: s.Comments.HeadComment,
762 LineComment: s.Comments.LineComment,
763 FootComment: s.Comments.FootComment,
764 },
765 s.Value.YNode())
766 return s.Value, nil
767 }
768
769
770
771
772
773 func Tee(filters ...Filter) Filter {
774 return TeePiper{Filters: filters}
775 }
776
777
778
779
780 type TeePiper struct {
781 Kind string `yaml:"kind,omitempty"`
782
783
784 Filters []Filter `yaml:"filters,omitempty"`
785 }
786
787 func (t TeePiper) Filter(rn *RNode) (*RNode, error) {
788 _, err := rn.Pipe(t.Filters...)
789 return rn, err
790 }
791
792
793 func IsCreate(kind yaml.Kind) bool {
794 return kind != 0
795 }
796
797
798 func IsMissingOrError(rn *RNode, err error) bool {
799 return rn == nil || err != nil
800 }
801
802
803 func IsFoundOrError(rn *RNode, err error) bool {
804 return rn != nil || err != nil
805 }
806
807 func ErrorIfAnyInvalidAndNonNull(kind yaml.Kind, rn ...*RNode) error {
808 for i := range rn {
809 if IsMissingOrNull(rn[i]) {
810 continue
811 }
812 if err := ErrorIfInvalid(rn[i], kind); err != nil {
813 return err
814 }
815 }
816 return nil
817 }
818
819 type InvalidNodeKindError struct {
820 expectedKind yaml.Kind
821 node *RNode
822 }
823
824 func (e *InvalidNodeKindError) Error() string {
825 msg := fmt.Sprintf("wrong node kind: expected %s but got %s",
826 nodeKindString(e.expectedKind), nodeKindString(e.node.YNode().Kind))
827 if content, err := e.node.String(); err == nil {
828 msg += fmt.Sprintf(": node contents:\n%s", content)
829 }
830 return msg
831 }
832
833 func (e *InvalidNodeKindError) ActualNodeKind() Kind {
834 return e.node.YNode().Kind
835 }
836
837 func ErrorIfInvalid(rn *RNode, kind yaml.Kind) error {
838 if IsMissingOrNull(rn) {
839
840 return nil
841 }
842
843 if rn.YNode().Kind != kind {
844 return &InvalidNodeKindError{node: rn, expectedKind: kind}
845 }
846
847 if kind == yaml.MappingNode {
848 if len(rn.YNode().Content)%2 != 0 {
849 return errors.Errorf(
850 "yaml MappingNodes must have even length contents: %v", spew.Sdump(rn))
851 }
852 }
853
854 return nil
855 }
856
857
858
859
860 func IsListIndex(p string) bool {
861 return strings.HasPrefix(p, "[") && strings.HasSuffix(p, "]")
862 }
863
864
865
866 func IsIdxNumber(p string) bool {
867 idx, err := strconv.Atoi(p)
868 return err == nil && idx >= 0
869 }
870
871
872
873 func IsWildcard(p string) bool {
874 return p == "*"
875 }
876
877
878
879
880
881 func SplitIndexNameValue(p string) (string, string, error) {
882 elem := strings.TrimSuffix(p, "]")
883 elem = strings.TrimPrefix(elem, "[")
884 parts := strings.SplitN(elem, "=", 2)
885 if len(parts) == 1 {
886 return "", "", fmt.Errorf("list path element must contain fieldName=fieldValue for element to match")
887 }
888 return parts[0], parts[1], nil
889 }
890
View as plain text