...

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

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package fieldspec
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"sigs.k8s.io/kustomize/api/filters/filtersutil"
    11  	"sigs.k8s.io/kustomize/api/types"
    12  	"sigs.k8s.io/kustomize/kyaml/errors"
    13  	"sigs.k8s.io/kustomize/kyaml/resid"
    14  	"sigs.k8s.io/kustomize/kyaml/utils"
    15  	"sigs.k8s.io/kustomize/kyaml/yaml"
    16  )
    17  
    18  var _ yaml.Filter = Filter{}
    19  
    20  // Filter possibly mutates its object argument using a FieldSpec.
    21  // If the object matches the FieldSpec, and the node found
    22  // by following the fieldSpec's path is non-null, this filter calls
    23  // the setValue function on the node at the end of the path.
    24  // If any part of the path doesn't exist, the filter returns
    25  // without doing anything and without error, unless it was set
    26  // to create the path. If set to create, it creates a tree of maps
    27  // along the path, and the leaf node gets the setValue called on it.
    28  // Error on GVK mismatch, empty or poorly formed path.
    29  // Filter expect kustomize style paths, not JSON paths.
    30  // Filter stores internal state and should not be reused
    31  type Filter struct {
    32  	// FieldSpec contains the path to the value to set.
    33  	FieldSpec types.FieldSpec `yaml:"fieldSpec"`
    34  
    35  	// Set the field using this function
    36  	SetValue filtersutil.SetFn
    37  
    38  	// CreateKind defines the type of node to create if the field is not found
    39  	CreateKind yaml.Kind
    40  
    41  	CreateTag string
    42  
    43  	// path keeps internal state about the current path
    44  	path []string
    45  }
    46  
    47  func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
    48  	// check if the FieldSpec applies to the object
    49  	if match := isMatchGVK(fltr.FieldSpec, obj); !match {
    50  		return obj, nil
    51  	}
    52  	fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
    53  	if err := fltr.filter(obj); err != nil {
    54  		return nil, errors.WrapPrefixf(err,
    55  			"considering field '%s' of object %s", fltr.FieldSpec.Path, resid.FromRNode(obj))
    56  	}
    57  	return obj, nil
    58  }
    59  
    60  // Recursively called.
    61  func (fltr Filter) filter(obj *yaml.RNode) error {
    62  	if len(fltr.path) == 0 {
    63  		// found the field -- set its value
    64  		return fltr.SetValue(obj)
    65  	}
    66  	if obj.IsTaggedNull() || obj.IsNil() {
    67  		return nil
    68  	}
    69  	switch obj.YNode().Kind {
    70  	case yaml.SequenceNode:
    71  		return fltr.handleSequence(obj)
    72  	case yaml.MappingNode:
    73  		return fltr.handleMap(obj)
    74  	case yaml.AliasNode:
    75  		return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
    76  	default:
    77  		return errors.Errorf("expected sequence or mapping node")
    78  	}
    79  }
    80  
    81  // handleMap calls filter on the map field matching the next path element
    82  func (fltr Filter) handleMap(obj *yaml.RNode) error {
    83  	fieldName, isSeq := isSequenceField(fltr.path[0])
    84  	if fieldName == "" {
    85  		return fmt.Errorf("cannot set or create an empty field name")
    86  	}
    87  	// lookup the field matching the next path element
    88  	var operation yaml.Filter
    89  	var kind yaml.Kind
    90  	tag := yaml.NodeTagEmpty
    91  	switch {
    92  	case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
    93  		// don't create the field if we don't find it
    94  		operation = yaml.Lookup(fieldName)
    95  		if isSeq {
    96  			// The query path thinks this field should be a sequence;
    97  			// accept this hint for use later if the tag is NodeTagNull.
    98  			kind = yaml.SequenceNode
    99  		}
   100  	case len(fltr.path) <= 1:
   101  		// create the field if it is missing: use the provided node kind
   102  		operation = yaml.LookupCreate(fltr.CreateKind, fieldName)
   103  		kind = fltr.CreateKind
   104  		tag = fltr.CreateTag
   105  	default:
   106  		// create the field if it is missing: must be a mapping node
   107  		operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
   108  		kind = yaml.MappingNode
   109  		tag = yaml.NodeTagMap
   110  	}
   111  
   112  	// locate (or maybe create) the field
   113  	field, err := obj.Pipe(operation)
   114  	if err != nil {
   115  		return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
   116  	}
   117  	if field == nil {
   118  		// No error if field not found.
   119  		return nil
   120  	}
   121  
   122  	// if the value exists, but is null and kind is set,
   123  	// then change it to the creation type
   124  	// TODO: update yaml.LookupCreate to support this
   125  	if field.YNode().Tag == yaml.NodeTagNull && yaml.IsCreate(kind) {
   126  		field.YNode().Kind = kind
   127  		field.YNode().Tag = tag
   128  	}
   129  
   130  	// copy the current fltr and change the path on the copy
   131  	var next = fltr
   132  	// call filter for the next path element on the matching field
   133  	next.path = fltr.path[1:]
   134  	return next.filter(field)
   135  }
   136  
   137  // seq calls filter on all sequence elements
   138  func (fltr Filter) handleSequence(obj *yaml.RNode) error {
   139  	if err := obj.VisitElements(func(node *yaml.RNode) error {
   140  		// set an accurate FieldPath for nested elements
   141  		node.AppendToFieldPath(obj.FieldPath()...)
   142  		// recurse on each element -- re-allocating a Filter is
   143  		// not strictly required, but is more consistent with field
   144  		// and less likely to have side effects
   145  		// keep the entire path -- it does not contain parts for sequences
   146  		return fltr.filter(node)
   147  	}); err != nil {
   148  		return errors.WrapPrefixf(err,
   149  			"visit traversal on path: %v", fltr.path)
   150  	}
   151  	return nil
   152  }
   153  
   154  // isSequenceField returns true if the path element is for a sequence field.
   155  // isSequence also returns the path element with the '[]' suffix trimmed
   156  func isSequenceField(name string) (string, bool) {
   157  	shorter := strings.TrimSuffix(name, "[]")
   158  	return shorter, shorter != name
   159  }
   160  
   161  // isMatchGVK returns true if the fs.GVK matches the obj GVK.
   162  func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
   163  	if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
   164  		// kind doesn't match
   165  		return false
   166  	}
   167  
   168  	// parse the group and version from the apiVersion field
   169  	group, version := resid.ParseGroupVersion(obj.GetApiVersion())
   170  
   171  	if fs.Group != "" && fs.Group != group {
   172  		// group doesn't match
   173  		return false
   174  	}
   175  
   176  	if fs.Version != "" && fs.Version != version {
   177  		// version doesn't match
   178  		return false
   179  	}
   180  
   181  	return true
   182  }
   183  

View as plain text