1
2
3
4 package yaml
5
6 import (
7 "fmt"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "sigs.k8s.io/kustomize/kyaml/errors"
13 yaml "sigs.k8s.io/yaml/goyaml.v3"
14 )
15
16
17
18
19
20
21 type PathMatcher struct {
22 Kind string `yaml:"kind,omitempty"`
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 Path []string `yaml:"path,omitempty"`
40
41
42
43
44 Matches map[*Node][]string
45
46
47
48 StripComments bool
49
50
51
52
53
54
55
56
57 Create yaml.Kind `yaml:"create,omitempty"`
58
59 val *RNode
60 field string
61 matchRegex string
62 }
63
64 func (p *PathMatcher) stripComments(n *Node) {
65 if n == nil {
66 return
67 }
68 if p.StripComments {
69 n.LineComment = ""
70 n.HeadComment = ""
71 n.FootComment = ""
72 for i := range n.Content {
73 p.stripComments(n.Content[i])
74 }
75 }
76 }
77
78 func (p *PathMatcher) Filter(rn *RNode) (*RNode, error) {
79 val, err := p.filter(rn)
80 if err != nil {
81 return nil, err
82 }
83 p.stripComments(val.YNode())
84 return val, err
85 }
86
87 func (p *PathMatcher) filter(rn *RNode) (*RNode, error) {
88 p.Matches = map[*Node][]string{}
89
90 if len(p.Path) == 0 {
91
92 p.appendRNode("", rn)
93 return p.val, nil
94 }
95
96 if IsIdxNumber(p.Path[0]) {
97 return p.doIndexSeq(rn)
98 }
99
100 if IsListIndex(p.Path[0]) {
101
102 return p.doSeq(rn)
103 }
104
105 if IsWildcard(p.Path[0]) {
106
107 return p.doMatchEvery(rn)
108 }
109
110 return p.doField(rn)
111 }
112
113 func (p *PathMatcher) doMatchEvery(rn *RNode) (*RNode, error) {
114 if err := rn.VisitElements(p.visitEveryElem); err != nil {
115 return nil, err
116 }
117
118 return p.val, nil
119 }
120
121 func (p *PathMatcher) visitEveryElem(elem *RNode) error {
122 fieldName := p.Path[0]
123
124 pm := &PathMatcher{Path: p.Path[1:], Create: p.Create}
125 add, err := pm.filter(elem)
126 for k, v := range pm.Matches {
127 p.Matches[k] = v
128 }
129 if err != nil || add == nil {
130 return err
131 }
132 p.append(fieldName, add.Content()...)
133
134 return nil
135 }
136
137 func (p *PathMatcher) doField(rn *RNode) (*RNode, error) {
138
139 field, err := rn.Pipe(Get(p.Path[0]))
140 if err != nil || (!IsCreate(p.Create) && field == nil) {
141 return nil, err
142 }
143
144 if IsCreate(p.Create) && field == nil {
145 var nextPart string
146 if len(p.Path) > 1 {
147 nextPart = p.Path[1]
148 }
149 nextPartKind := getPathPartKind(nextPart, p.Create)
150 field = &RNode{value: &yaml.Node{Kind: nextPartKind}}
151 err := rn.PipeE(SetField(p.Path[0], field))
152 if err != nil {
153 return nil, err
154 }
155 }
156
157
158 pm := &PathMatcher{Path: p.Path[1:], Create: p.Create}
159 p.val, err = pm.filter(field)
160 p.Matches = pm.Matches
161 return p.val, err
162 }
163
164
165 func (p *PathMatcher) doIndexSeq(rn *RNode) (*RNode, error) {
166
167 idx, err := strconv.Atoi(p.Path[0])
168 if err != nil {
169 return nil, err
170 }
171
172 elements, err := rn.Elements()
173 if err != nil {
174 return nil, err
175 }
176
177 if len(elements) == idx && IsCreate(p.Create) {
178 var nextPart string
179 if len(p.Path) > 1 {
180 nextPart = p.Path[1]
181 }
182 elem := &yaml.Node{Kind: getPathPartKind(nextPart, p.Create)}
183 err = rn.PipeE(Append(elem))
184 if err != nil {
185 return nil, errors.WrapPrefixf(err, "failed to append element for %q", p.Path[0])
186 }
187 elements = append(elements, NewRNode(elem))
188 }
189
190 if len(elements) < idx+1 {
191 return nil, fmt.Errorf("index %d specified but only %d elements found", idx, len(elements))
192 }
193
194 element := elements[idx]
195
196
197 pm := &PathMatcher{Path: p.Path[1:], Create: p.Create}
198 add, err := pm.filter(element)
199 for k, v := range pm.Matches {
200 p.Matches[k] = v
201 }
202 if err != nil || add == nil {
203 return nil, err
204 }
205 p.append("", add.Content()...)
206 return p.val, nil
207 }
208
209
210 func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) {
211
212 var err error
213 p.field, p.matchRegex, err = SplitIndexNameValue(p.Path[0])
214 if err != nil {
215 return nil, err
216 }
217
218 primitiveElement := len(p.field) == 0
219 if primitiveElement {
220 err = rn.VisitElements(p.visitPrimitiveElem)
221 } else {
222 err = rn.VisitElements(p.visitElem)
223 }
224 if err != nil {
225 return nil, err
226 }
227 if !p.val.IsNil() && len(p.val.YNode().Content) == 0 {
228 p.val = nil
229 }
230
231 if !IsCreate(p.Create) || p.val != nil {
232 return p.val, nil
233 }
234
235 var elem *yaml.Node
236 valueNode := NewScalarRNode(p.matchRegex).YNode()
237 if primitiveElement {
238 elem = valueNode
239 } else {
240 elem = &yaml.Node{
241 Kind: yaml.MappingNode,
242 Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: p.field}, valueNode},
243 }
244 }
245 err = rn.PipeE(Append(elem))
246 if err != nil {
247 return nil, errors.WrapPrefixf(err, "failed to create element for %q", p.Path[0])
248 }
249
250 return p.doSeq(rn)
251 }
252
253 func (p *PathMatcher) visitPrimitiveElem(elem *RNode) error {
254 r, err := regexp.Compile(p.matchRegex)
255 if err != nil {
256 return err
257 }
258
259 str, err := elem.String()
260 if err != nil {
261 return err
262 }
263 str = strings.TrimSpace(str)
264 if !r.MatchString(str) {
265 return nil
266 }
267
268 p.appendRNode("", elem)
269 return nil
270 }
271
272 func (p *PathMatcher) visitElem(elem *RNode) error {
273 r, err := regexp.Compile(p.matchRegex)
274 if err != nil {
275 return err
276 }
277
278
279 val := elem.Field(p.field)
280 if val == nil || val.Value == nil {
281 return nil
282 }
283 str, err := val.Value.String()
284 if err != nil {
285 return err
286 }
287 str = strings.TrimSpace(str)
288 if !r.MatchString(str) {
289 return nil
290 }
291
292
293 pm := &PathMatcher{Path: p.Path[1:], Create: p.Create}
294 add, err := pm.filter(elem)
295 for k, v := range pm.Matches {
296 p.Matches[k] = v
297 }
298 if err != nil || add == nil {
299 return err
300 }
301 p.append(str, add.Content()...)
302 return nil
303 }
304
305 func (p *PathMatcher) appendRNode(path string, node *RNode) {
306 p.append(path, node.YNode())
307 }
308
309 func (p *PathMatcher) append(path string, nodes ...*Node) {
310 if p.val == nil {
311 p.val = NewRNode(&Node{Kind: SequenceNode})
312 }
313 for i := range nodes {
314 node := nodes[i]
315 p.val.YNode().Content = append(p.val.YNode().Content, node)
316
317 if path != "" {
318 p.Matches[node] = append(p.Matches[node], path)
319 }
320 }
321 }
322
323 func cleanPath(path []string) []string {
324 var p []string
325 for _, elem := range path {
326 elem = strings.TrimSpace(elem)
327 if len(elem) == 0 {
328 continue
329 }
330 p = append(p, elem)
331 }
332 return p
333 }
334
View as plain text