1
2
3
4 package nameref
5
6 import (
7 "fmt"
8 "strings"
9
10 "sigs.k8s.io/kustomize/api/filters/fieldspec"
11 "sigs.k8s.io/kustomize/api/resmap"
12 "sigs.k8s.io/kustomize/api/resource"
13 "sigs.k8s.io/kustomize/api/types"
14 "sigs.k8s.io/kustomize/kyaml/errors"
15 "sigs.k8s.io/kustomize/kyaml/kio"
16 "sigs.k8s.io/kustomize/kyaml/resid"
17 "sigs.k8s.io/kustomize/kyaml/yaml"
18 )
19
20
21 type Filter struct {
22
23
24
25
26
27
28 Referrer *resource.Resource
29
30
31
32
33 NameFieldToUpdate types.FieldSpec
34
35
36
37
38 ReferralTarget resid.Gvk
39
40
41 ReferralCandidates resmap.ResMap
42 }
43
44
45
46 func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
47 return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
48 }
49
50
51
52
53
54
55
56
57 func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
58 if err := f.confirmNodeMatchesReferrer(node); err != nil {
59
60 return nil, err
61 }
62 f.NameFieldToUpdate.Gvk = f.Referrer.GetGvk()
63 if err := node.PipeE(fieldspec.Filter{
64 FieldSpec: f.NameFieldToUpdate,
65 SetValue: f.set,
66 }); err != nil {
67 return nil, errors.WrapPrefixf(
68 err, "updating name reference in '%s' field of '%s'",
69 f.NameFieldToUpdate.Path, f.Referrer.CurId().String())
70 }
71 return node, nil
72 }
73
74
75
76 func (f Filter) set(node *yaml.RNode) error {
77 if yaml.IsMissingOrNull(node) {
78 return nil
79 }
80 switch node.YNode().Kind {
81 case yaml.ScalarNode:
82 return f.setScalar(node)
83 case yaml.MappingNode:
84 return f.setMapping(node)
85 case yaml.SequenceNode:
86 return applyFilterToSeq(seqFilter{
87 setScalarFn: f.setScalar,
88 setMappingFn: f.setMapping,
89 }, node)
90 default:
91 return fmt.Errorf("node must be a scalar, sequence or map")
92 }
93 }
94
95
96
97
98
99
100
101 func (f Filter) setMapping(node *yaml.RNode) error {
102 if node.YNode().Kind != yaml.MappingNode {
103 return fmt.Errorf("expect a mapping node")
104 }
105 nameNode, err := node.Pipe(yaml.FieldMatcher{Name: "name"})
106 if err != nil {
107 return errors.WrapPrefixf(err, "trying to match 'name' field")
108 }
109 if nameNode == nil {
110
111
112
113
114 return fmt.Errorf("path config error; no 'name' field in node")
115 }
116 candidates, err := f.filterMapCandidatesByNamespace(node)
117 if err != nil {
118 return err
119 }
120 oldName := nameNode.YNode().Value
121
122
123 referral, err := f.selectReferral(oldName, candidates, allNamesAndNamespacesAreTheSame)
124 if err != nil || referral == nil {
125
126 return err
127 }
128 f.recordTheReferral(referral)
129 if referral.GetName() == oldName && referral.GetNamespace() == "" {
130
131 return nil
132 }
133 if err = node.PipeE(yaml.FieldSetter{
134 Name: "name",
135 StringValue: referral.GetName(),
136 }); err != nil {
137 return err
138 }
139 if referral.GetNamespace() == "" {
140
141
142
143
144 return nil
145 }
146 return node.PipeE(yaml.FieldSetter{
147 Name: "namespace",
148 StringValue: referral.GetNamespace(),
149 })
150 }
151
152 func (f Filter) filterMapCandidatesByNamespace(
153 node *yaml.RNode) ([]*resource.Resource, error) {
154 namespaceNode, err := node.Pipe(yaml.FieldMatcher{Name: "namespace"})
155 if err != nil {
156 return nil, errors.WrapPrefixf(err, "trying to match 'namespace' field")
157 }
158 if namespaceNode == nil {
159 return f.ReferralCandidates.Resources(), nil
160 }
161 namespace := namespaceNode.YNode().Value
162 nsMap := f.ReferralCandidates.GroupedByOriginalNamespace()
163 if candidates, ok := nsMap[namespace]; ok {
164 return candidates, nil
165 }
166 nsMap = f.ReferralCandidates.GroupedByCurrentNamespace()
167
168 return nsMap[namespace], nil
169 }
170
171 func (f Filter) setScalar(node *yaml.RNode) error {
172
173
174 referral, err := f.selectReferral(
175 node.YNode().Value, f.ReferralCandidates.Resources(), allNamesAreTheSame)
176 if err != nil || referral == nil {
177
178 return err
179 }
180 f.recordTheReferral(referral)
181 if referral.GetName() == node.YNode().Value {
182
183 return nil
184 }
185 return node.PipeE(yaml.FieldSetter{StringValue: referral.GetName()})
186 }
187
188
189 func (f Filter) recordTheReferral(referral *resource.Resource) {
190 referral.AppendRefBy(f.Referrer.CurId())
191 }
192
193
194
195 func getRoleRefGvk(n *resource.Resource) (*resid.Gvk, error) {
196 roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
197 if err != nil {
198 return nil, err
199 }
200 if roleRef.IsNil() {
201 return nil, fmt.Errorf("roleRef cannot be found in %s", n.MustString())
202 }
203 apiGroup, err := roleRef.Pipe(yaml.Lookup("apiGroup"))
204 if err != nil {
205 return nil, err
206 }
207 if apiGroup.IsNil() {
208 return nil, fmt.Errorf(
209 "apiGroup cannot be found in roleRef %s", roleRef.MustString())
210 }
211 kind, err := roleRef.Pipe(yaml.Lookup("kind"))
212 if err != nil {
213 return nil, err
214 }
215 if kind.IsNil() {
216 return nil, fmt.Errorf(
217 "kind cannot be found in roleRef %s", roleRef.MustString())
218 }
219 return &resid.Gvk{
220 Group: apiGroup.YNode().Value,
221 Kind: kind.YNode().Value,
222 }, nil
223 }
224
225
226 type sieveFunc func(*resource.Resource) bool
227
228
229
230
231 func doSieve(list []*resource.Resource, fn sieveFunc) (s []*resource.Resource) {
232 for _, r := range list {
233 if fn(r) {
234 s = append(s, r)
235 }
236 }
237 return
238 }
239
240 func acceptAll(r *resource.Resource) bool {
241 return true
242 }
243
244 func previousNameMatches(name string) sieveFunc {
245 return func(r *resource.Resource) bool {
246 for _, id := range r.PrevIds() {
247 if id.Name == name {
248 return true
249 }
250 }
251 return false
252 }
253 }
254
255 func previousIdSelectedByGvk(gvk *resid.Gvk) sieveFunc {
256 return func(r *resource.Resource) bool {
257 for _, id := range r.PrevIds() {
258 if id.IsSelected(gvk) {
259 return true
260 }
261 }
262 return false
263 }
264 }
265
266
267
268
269
270
271
272
273
274
275
276 func (f Filter) roleRefFilter() sieveFunc {
277 if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
278 return acceptAll
279 }
280 roleRefGvk, err := getRoleRefGvk(f.Referrer)
281 if err != nil {
282 return acceptAll
283 }
284 return previousIdSelectedByGvk(roleRefGvk)
285 }
286
287 func prefixSuffixEquals(other resource.ResCtx, allowEmpty bool) sieveFunc {
288 return func(r *resource.Resource) bool {
289 return r.PrefixesSuffixesEquals(other, allowEmpty)
290 }
291 }
292
293 func (f Filter) sameCurrentNamespaceAsReferrer() sieveFunc {
294 referrerCurId := f.Referrer.CurId()
295 if referrerCurId.IsClusterScoped() {
296
297 return acceptAll
298 }
299 return func(r *resource.Resource) bool {
300 if r.CurId().IsClusterScoped() {
301
302 return true
303 }
304 if r.GetKind() == "ServiceAccount" {
305
306
307
308 return true
309 }
310 return referrerCurId.IsNsEquals(r.CurId())
311 }
312 }
313
314
315 func (f Filter) selectReferral(
316
317 oldName string,
318 candidates []*resource.Resource,
319
320 candidatesIdentical func(resources []*resource.Resource) bool) (*resource.Resource, error) {
321 candidates = doSieve(candidates, previousNameMatches(oldName))
322 candidates = doSieve(candidates, previousIdSelectedByGvk(&f.ReferralTarget))
323 candidates = doSieve(candidates, f.roleRefFilter())
324 candidates = doSieve(candidates, f.sameCurrentNamespaceAsReferrer())
325 if len(candidates) == 1 {
326 return candidates[0], nil
327 }
328 candidates = doSieve(candidates, prefixSuffixEquals(f.Referrer, true))
329 if len(candidates) > 1 {
330 candidates = doSieve(candidates, prefixSuffixEquals(f.Referrer, false))
331 }
332 if len(candidates) == 1 {
333 return candidates[0], nil
334 }
335 if len(candidates) == 0 {
336 return nil, nil
337 }
338 if candidatesIdentical(candidates) {
339
340 return candidates[0], nil
341 }
342 ids := getIds(candidates)
343 return nil, fmt.Errorf("found multiple possible referrals: %s\n%s", ids, f.failureDetails(candidates))
344 }
345
346 func (f Filter) failureDetails(resources []*resource.Resource) string {
347 msg := strings.Builder{}
348 msg.WriteString(fmt.Sprintf("\n**** Too many possible referral targets to referrer:\n%s\n", f.Referrer.MustYaml()))
349 for i, r := range resources {
350 msg.WriteString(fmt.Sprintf("--- possible referral %d:\n%s\n", i, r.MustYaml()))
351 }
352 return msg.String()
353 }
354
355 func allNamesAreTheSame(resources []*resource.Resource) bool {
356 name := resources[0].GetName()
357 for i := 1; i < len(resources); i++ {
358 if name != resources[i].GetName() {
359 return false
360 }
361 }
362 return true
363 }
364
365 func allNamesAndNamespacesAreTheSame(resources []*resource.Resource) bool {
366 name := resources[0].GetName()
367 namespace := resources[0].GetNamespace()
368 for i := 1; i < len(resources); i++ {
369 if name != resources[i].GetName() || namespace != resources[i].GetNamespace() {
370 return false
371 }
372 }
373 return true
374 }
375
376 func getIds(rs []*resource.Resource) string {
377 var result []string
378 for _, r := range rs {
379 result = append(result, r.CurId().String())
380 }
381 return strings.Join(result, ", ")
382 }
383
384 func checkEqual(k, a, b string) error {
385 if a != b {
386 return fmt.Errorf(
387 "node-referrerOriginal '%s' mismatch '%s' != '%s'",
388 k, a, b)
389 }
390 return nil
391 }
392
393 func (f Filter) confirmNodeMatchesReferrer(node *yaml.RNode) error {
394 meta, err := node.GetMeta()
395 if err != nil {
396 return err
397 }
398 gvk := f.Referrer.GetGvk()
399 if err = checkEqual(
400 "APIVersion", meta.APIVersion, gvk.ApiVersion()); err != nil {
401 return err
402 }
403 if err = checkEqual(
404 "Kind", meta.Kind, gvk.Kind); err != nil {
405 return err
406 }
407 if err = checkEqual(
408 "Name", meta.Name, f.Referrer.GetName()); err != nil {
409 return err
410 }
411 if err = checkEqual(
412 "Namespace", meta.Namespace, f.Referrer.GetNamespace()); err != nil {
413 return err
414 }
415 return nil
416 }
417
View as plain text