...

Source file src/sigs.k8s.io/kustomize/api/filters/replacement/replacement.go

Documentation: sigs.k8s.io/kustomize/api/filters/replacement

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package replacement
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"sigs.k8s.io/kustomize/api/internal/utils"
    11  	"sigs.k8s.io/kustomize/api/resource"
    12  	"sigs.k8s.io/kustomize/api/types"
    13  	"sigs.k8s.io/kustomize/kyaml/errors"
    14  	"sigs.k8s.io/kustomize/kyaml/resid"
    15  	kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils"
    16  	"sigs.k8s.io/kustomize/kyaml/yaml"
    17  )
    18  
    19  type Filter struct {
    20  	Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
    21  }
    22  
    23  // Filter replaces values of targets with values from sources
    24  func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
    25  	for i, r := range f.Replacements {
    26  		if r.Source == nil || r.Targets == nil {
    27  			return nil, fmt.Errorf("replacements must specify a source and at least one target")
    28  		}
    29  		value, err := getReplacement(nodes, &f.Replacements[i])
    30  		if err != nil {
    31  			return nil, err
    32  		}
    33  		nodes, err = applyReplacement(nodes, value, r.Targets)
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  	}
    38  	return nodes, nil
    39  }
    40  
    41  func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
    42  	source, err := selectSourceNode(nodes, r.Source)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if r.Source.FieldPath == "" {
    48  		r.Source.FieldPath = types.DefaultReplacementFieldPath
    49  	}
    50  	fieldPath := kyaml_utils.SmarterPathSplitter(r.Source.FieldPath, ".")
    51  
    52  	rn, err := source.Pipe(yaml.Lookup(fieldPath...))
    53  	if err != nil {
    54  		return nil, fmt.Errorf("error looking up replacement source: %w", err)
    55  	}
    56  	if rn.IsNilOrEmpty() {
    57  		return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source.ResId)
    58  	}
    59  
    60  	return getRefinedValue(r.Source.Options, rn)
    61  }
    62  
    63  // selectSourceNode finds the node that matches the selector, returning
    64  // an error if multiple or none are found
    65  func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
    66  	var matches []*yaml.RNode
    67  	for _, n := range nodes {
    68  		ids, err := utils.MakeResIds(n)
    69  		if err != nil {
    70  			return nil, fmt.Errorf("error getting node IDs: %w", err)
    71  		}
    72  		for _, id := range ids {
    73  			if id.IsSelectedBy(selector.ResId) {
    74  				if len(matches) > 0 {
    75  					return nil, fmt.Errorf(
    76  						"multiple matches for selector %s", selector)
    77  				}
    78  				matches = append(matches, n)
    79  				break
    80  			}
    81  		}
    82  	}
    83  	if len(matches) == 0 {
    84  		return nil, fmt.Errorf("nothing selected by %s", selector)
    85  	}
    86  	return matches[0], nil
    87  }
    88  
    89  func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
    90  	if options == nil || options.Delimiter == "" {
    91  		return rn, nil
    92  	}
    93  	if rn.YNode().Kind != yaml.ScalarNode {
    94  		return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
    95  	}
    96  	value := strings.Split(yaml.GetValue(rn), options.Delimiter)
    97  	if options.Index >= len(value) || options.Index < 0 {
    98  		return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
    99  	}
   100  	n := rn.Copy()
   101  	n.YNode().Value = value[options.Index]
   102  	return n, nil
   103  }
   104  
   105  func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors []*types.TargetSelector) ([]*yaml.RNode, error) {
   106  	for _, selector := range targetSelectors {
   107  		if selector.Select == nil {
   108  			return nil, errors.Errorf("target must specify resources to select")
   109  		}
   110  		if len(selector.FieldPaths) == 0 {
   111  			selector.FieldPaths = []string{types.DefaultReplacementFieldPath}
   112  		}
   113  		for _, possibleTarget := range nodes {
   114  			ids, err := utils.MakeResIds(possibleTarget)
   115  			if err != nil {
   116  				return nil, err
   117  			}
   118  
   119  			// filter targets by label and annotation selectors
   120  			selectByAnnoAndLabel, err := selectByAnnoAndLabel(possibleTarget, selector)
   121  			if err != nil {
   122  				return nil, err
   123  			}
   124  			if !selectByAnnoAndLabel {
   125  				continue
   126  			}
   127  
   128  			// filter targets by matching resource IDs
   129  			for _, id := range ids {
   130  				if id.IsSelectedBy(selector.Select.ResId) && !containsRejectId(selector.Reject, ids) {
   131  					err := copyValueToTarget(possibleTarget, value, selector)
   132  					if err != nil {
   133  						return nil, err
   134  					}
   135  					break
   136  				}
   137  			}
   138  		}
   139  	}
   140  	return nodes, nil
   141  }
   142  
   143  func selectByAnnoAndLabel(n *yaml.RNode, t *types.TargetSelector) (bool, error) {
   144  	if matchesSelect, err := matchesAnnoAndLabelSelector(n, t.Select); !matchesSelect || err != nil {
   145  		return false, err
   146  	}
   147  	for _, reject := range t.Reject {
   148  		if reject.AnnotationSelector == "" && reject.LabelSelector == "" {
   149  			continue
   150  		}
   151  		if m, err := matchesAnnoAndLabelSelector(n, reject); m || err != nil {
   152  			return false, err
   153  		}
   154  	}
   155  	return true, nil
   156  }
   157  
   158  func matchesAnnoAndLabelSelector(n *yaml.RNode, selector *types.Selector) (bool, error) {
   159  	r := resource.Resource{RNode: *n}
   160  	annoMatch, err := r.MatchesAnnotationSelector(selector.AnnotationSelector)
   161  	if err != nil {
   162  		return false, err
   163  	}
   164  	labelMatch, err := r.MatchesLabelSelector(selector.LabelSelector)
   165  	if err != nil {
   166  		return false, err
   167  	}
   168  	return annoMatch && labelMatch, nil
   169  }
   170  
   171  func containsRejectId(rejects []*types.Selector, ids []resid.ResId) bool {
   172  	for _, r := range rejects {
   173  		if r.ResId.IsEmpty() {
   174  			continue
   175  		}
   176  		for _, id := range ids {
   177  			if id.IsSelectedBy(r.ResId) {
   178  				return true
   179  			}
   180  		}
   181  	}
   182  	return false
   183  }
   184  
   185  func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.TargetSelector) error {
   186  	for _, fp := range selector.FieldPaths {
   187  		createKind := yaml.Kind(0) // do not create
   188  		if selector.Options != nil && selector.Options.Create {
   189  			createKind = value.YNode().Kind
   190  		}
   191  		targetFieldList, err := target.Pipe(&yaml.PathMatcher{
   192  			Path:   kyaml_utils.SmarterPathSplitter(fp, "."),
   193  			Create: createKind})
   194  		if err != nil {
   195  			return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0))
   196  		}
   197  		targetFields, err := targetFieldList.Elements()
   198  		if err != nil {
   199  			return errors.WrapPrefixf(err, fieldRetrievalError(fp, createKind != 0))
   200  		}
   201  		if len(targetFields) == 0 {
   202  			return errors.Errorf(fieldRetrievalError(fp, createKind != 0))
   203  		}
   204  
   205  		for _, t := range targetFields {
   206  			if err := setFieldValue(selector.Options, t, value); err != nil {
   207  				return err
   208  			}
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  func fieldRetrievalError(fieldPath string, isCreate bool) string {
   215  	if isCreate {
   216  		return fmt.Sprintf("unable to find or create field %q in replacement target", fieldPath)
   217  	}
   218  	return fmt.Sprintf("unable to find field %q in replacement target", fieldPath)
   219  }
   220  
   221  func setFieldValue(options *types.FieldOptions, targetField *yaml.RNode, value *yaml.RNode) error {
   222  	value = value.Copy()
   223  	if options != nil && options.Delimiter != "" {
   224  		if targetField.YNode().Kind != yaml.ScalarNode {
   225  			return fmt.Errorf("delimiter option can only be used with scalar nodes")
   226  		}
   227  		tv := strings.Split(targetField.YNode().Value, options.Delimiter)
   228  		v := yaml.GetValue(value)
   229  		// TODO: Add a way to remove an element
   230  		switch {
   231  		case options.Index < 0: // prefix
   232  			tv = append([]string{v}, tv...)
   233  		case options.Index >= len(tv): // suffix
   234  			tv = append(tv, v)
   235  		default: // replace an element
   236  			tv[options.Index] = v
   237  		}
   238  		value.YNode().Value = strings.Join(tv, options.Delimiter)
   239  	}
   240  
   241  	if targetField.YNode().Kind == yaml.ScalarNode {
   242  		// For scalar, only copy the value (leave any type intact to auto-convert int->string or string->int)
   243  		targetField.YNode().Value = value.YNode().Value
   244  	} else {
   245  		targetField.SetYNode(value.YNode())
   246  	}
   247  
   248  	return nil
   249  }
   250  

View as plain text