...

Source file src/sigs.k8s.io/kustomize/kyaml/yaml/fns.go

Documentation: sigs.k8s.io/kustomize/kyaml/yaml

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     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  // Append creates an ElementAppender
    18  func Append(elements ...*yaml.Node) ElementAppender {
    19  	return ElementAppender{Elements: elements}
    20  }
    21  
    22  // ElementAppender adds all element to a SequenceNode's Content.
    23  // Returns Elements[0] if len(Elements) == 1, otherwise returns nil.
    24  type ElementAppender struct {
    25  	Kind string `yaml:"kind,omitempty"`
    26  
    27  	// Elem is the value to append.
    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  // ElementSetter sets the value for an Element in an associative list.
    45  // ElementSetter will append, replace or delete an element in an associative list.
    46  // To append, user a key-value pair that doesn't exist in the sequence. this
    47  // behavior is intended to handle the case that not matching element found. It's
    48  // not designed for this purpose. To append an element, please use ElementAppender.
    49  // To replace, set the key-value pair and a non-nil Element.
    50  // To delete, set the key-value pair and leave the Element as nil.
    51  // Every key must have a corresponding value.
    52  type ElementSetter struct {
    53  	Kind string `yaml:"kind,omitempty"`
    54  
    55  	// Element is the new value to set -- remove the existing element if nil
    56  	Element *Node
    57  
    58  	// Key is a list of fields on the elements. It is used to find matching elements to
    59  	// update / delete
    60  	Keys []string
    61  
    62  	// Value is a list of field values on the elements corresponding to the keys. It is
    63  	// used to find matching elements to update / delete.
    64  	Values []string
    65  }
    66  
    67  // isMappingNode returns whether node is a mapping node
    68  func (e ElementSetter) isMappingNode(node *RNode) bool {
    69  	return ErrorIfInvalid(node, yaml.MappingNode) == nil
    70  }
    71  
    72  // isMappingSetter returns is this setter intended to set a mapping node
    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  	// build the new Content slice
    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  		// empty elements are not valid -- they at least need an associative key
    95  		if IsMissingOrNull(newNode) || IsEmptyMap(newNode) {
    96  			continue
    97  		}
    98  		// keep non-mapping node in the Content when we want to match a mapping.
    99  		if !e.isMappingNode(newNode) && e.isMappingSetter() {
   100  			newContent = append(newContent, elem)
   101  			continue
   102  		}
   103  
   104  		// check if this is the element we are matching
   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  			// not the element we are looking for, keep it in the Content
   122  			if len(e.Values) > 0 {
   123  				newContent = append(newContent, elem)
   124  			}
   125  			continue
   126  		}
   127  		matchingElementFound = true
   128  
   129  		// deletion operation -- remove the element from the new Content
   130  		if e.Element == nil {
   131  			continue
   132  		}
   133  		// replace operation -- replace the element in the Content
   134  		newContent = append(newContent, e.Element)
   135  	}
   136  	rn.YNode().Content = newContent
   137  
   138  	// deletion operation -- return nil
   139  	if IsMissingOrNull(NewRNode(e.Element)) {
   140  		return nil, nil
   141  	}
   142  
   143  	// append operation -- add the element to the Content
   144  	if !matchingElementFound {
   145  		rn.YNode().Content = append(rn.YNode().Content, e.Element)
   146  	}
   147  
   148  	return NewRNode(e.Element), nil
   149  }
   150  
   151  // GetElementByIndex will return a Filter which can be applied to a sequence
   152  // node to get the element specified by the index
   153  func GetElementByIndex(index int) ElementIndexer {
   154  	return ElementIndexer{Index: index}
   155  }
   156  
   157  // ElementIndexer picks the element with a specified index. Index starts from
   158  // 0 to len(list) - 1. a hyphen ("-") means the last index.
   159  type ElementIndexer struct {
   160  	Index int
   161  }
   162  
   163  // Filter implements Filter
   164  func (i ElementIndexer) Filter(rn *RNode) (*RNode, error) {
   165  	// rn.Elements will return error if rn is not a sequence node.
   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  // Clear returns a FieldClearer
   180  func Clear(name string) FieldClearer {
   181  	return FieldClearer{Name: name}
   182  }
   183  
   184  // FieldClearer removes the field or map key.
   185  // Returns a RNode with the removed field or map entry.
   186  type FieldClearer struct {
   187  	Kind string `yaml:"kind,omitempty"`
   188  
   189  	// Name is the name of the field or key in the map.
   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  		// the name matches: remove these 2 elements from the list because
   207  		// they are treated as a fieldName/fieldValue pair.
   208  		if c.IfEmpty {
   209  			if len(value.Content) > 0 {
   210  				return true
   211  			}
   212  		}
   213  
   214  		// save the item we are about to remove
   215  		removed = NewRNode(value)
   216  		if len(rn.YNode().Content) > keyIndex+2 {
   217  			l := len(rn.YNode().Content)
   218  			// remove from the middle of the list
   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  			// remove from the end of the list
   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  // ElementMatcher returns the first element from a Sequence matching the
   246  // specified key-value pairs. If there's no match, and no configuration error,
   247  // the matcher returns nil, nil.
   248  type ElementMatcher struct {
   249  	Kind string `yaml:"kind,omitempty"`
   250  
   251  	// Keys are the list of fields upon which to match this element.
   252  	Keys []string
   253  
   254  	// Values are the list of values upon which to match this element.
   255  	Values []string
   256  
   257  	// Create will create the Element if it is not found
   258  	Create *RNode `yaml:"create,omitempty"`
   259  
   260  	// MatchAnyValue indicates that matcher should only consider the key and ignore
   261  	// the actual value in the list. Values must be empty when MatchAnyValue is
   262  	// set to true.
   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  	// SequenceNode Content is a slice of ScalarNodes.  Each ScalarNode has a
   282  	// YNode containing the primitive data.
   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  	// SequenceNode Content is a slice of MappingNodes.  Each MappingNode has Content
   296  	// with a slice of key-value pairs containing the fields.
   297  	for i := range rn.Content() {
   298  		// cast the entry to a RNode so we can operate on it
   299  		elem := NewRNode(rn.Content()[i])
   300  		var field *RNode
   301  		var err error
   302  
   303  		// only check mapping node
   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  				// this is not the element we are looking for
   321  				matchesElement = false
   322  				break
   323  			}
   324  		}
   325  		if matchesElement {
   326  			return elem, err
   327  		}
   328  	}
   329  
   330  	// create the element
   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  // FieldMatcher returns the value of a named field or map entry.
   351  type FieldMatcher struct {
   352  	Kind string `yaml:"kind,omitempty"`
   353  
   354  	// Name of the field to return
   355  	Name string `yaml:"name,omitempty"`
   356  
   357  	// YNode of the field to return.
   358  	// Optional.  Will only need to match field name if unset.
   359  	Value *RNode `yaml:"value,omitempty"`
   360  
   361  	StringValue string `yaml:"stringValue,omitempty"`
   362  
   363  	StringRegexValue string `yaml:"stringRegexValue,omitempty"`
   364  
   365  	// Create will cause the field to be created with this value
   366  	// if it is set.
   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  	// never match nil or null fields
   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  			// TODO(pwittrock): pre-compile this when unmarshalling and cache to a field
   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  // Lookup returns a PathGetter to lookup a field by its path.
   425  func Lookup(path ...string) PathGetter {
   426  	return PathGetter{Path: path}
   427  }
   428  
   429  // LookupCreate returns a PathGetter to lookup a field by its path and create it if it doesn't already
   430  // exist.
   431  func LookupCreate(kind yaml.Kind, path ...string) PathGetter {
   432  	return PathGetter{Path: path, Create: kind}
   433  }
   434  
   435  // ConventionalContainerPaths is a list of paths at which containers typically appear in workload APIs.
   436  // It is intended for use with LookupFirstMatch.
   437  var ConventionalContainerPaths = [][]string{
   438  	// e.g. Deployment, ReplicaSet, DaemonSet, Job, StatefulSet
   439  	{"spec", "template", "spec", "containers"},
   440  	// e.g. CronJob
   441  	{"spec", "jobTemplate", "spec", "template", "spec", "containers"},
   442  	// e.g. Pod
   443  	{"spec", "containers"},
   444  	// e.g. PodTemplate
   445  	{"template", "spec", "containers"},
   446  }
   447  
   448  // LookupFirstMatch returns a Filter for locating a value that may exist at one of several possible paths.
   449  // For example, it can be used with ConventionalContainerPaths to find the containers field in a standard workload resource.
   450  // If more than one of the paths exists in the resource, the first will be returned. If none exist,
   451  // nil will be returned. If an error is encountered during lookup, it will be returned.
   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  // PathGetter returns the RNode under Path.
   470  type PathGetter struct {
   471  	Kind string `yaml:"kind,omitempty"`
   472  
   473  	// Path is a slice of parts leading to the RNode to lookup.
   474  	// Each path part may be one of:
   475  	// * FieldMatcher -- e.g. "spec"
   476  	// * Map Key -- e.g. "app.k8s.io/version"
   477  	// * List Entry -- e.g. "[name=nginx]" or "[=-jar]" or "0" or "-"
   478  	//
   479  	// Map Keys and Fields are equivalent.
   480  	// See FieldMatcher for more on Fields and Map Keys.
   481  	//
   482  	// List Entries can be specified as map entry to match [fieldName=fieldValue]
   483  	// or a positional index like 0 to get the element. - (unquoted hyphen) is
   484  	// special and means the last element.
   485  	//
   486  	// See Elem for more on List Entries.
   487  	//
   488  	// Examples:
   489  	// * spec.template.spec.container with matching name: [name=nginx]
   490  	// * spec.template.spec.container.argument matching a value: [=-jar]
   491  	Path []string `yaml:"path,omitempty"`
   492  
   493  	// Create will cause missing path parts to be created as they are walked.
   494  	//
   495  	// * The leaf Node (final path) will be created with a Kind matching Create
   496  	// * Intermediary Nodes will be created as either a MappingNodes or
   497  	//   SequenceNodes as appropriate for each's Path location.
   498  	// * If a list item is specified by a index (an offset or "-"), this item will
   499  	//   not be created even Create is set.
   500  	Create yaml.Kind `yaml:"create,omitempty"`
   501  
   502  	// Style is the style to apply to created value Nodes.
   503  	// Created key Nodes keep an unspecified Style.
   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  	// iterate over path until encountering an error or missing value
   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  		// part is a number
   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  		// part is a hyphen
   545  		return GetElementByIndex(-1), nil
   546  	case part == "*":
   547  		// PathGetter is not support for wildcard matching
   548  		return nil, errors.Errorf("wildcard is not supported in PathGetter")
   549  	case IsListIndex(part):
   550  		// part is surrounded by brackets
   551  		return l.elemFilter(part)
   552  	default:
   553  		// mapping node
   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  		// append a ScalarNode
   572  		elem = NewScalarRNode(value)
   573  		elem.YNode().Style = l.Style
   574  	} else {
   575  		// append a MappingNode
   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  	// Append the Node
   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  		// if nextPart is of the form [a=b], then it is an index into a Sequence
   598  		// so the current part must be a SequenceNode
   599  		return yaml.SequenceNode
   600  	}
   601  	if IsIdxNumber(nextPart) {
   602  		return yaml.SequenceNode
   603  	}
   604  	if nextPart == "" {
   605  		// final name in the path, use the default kind provided
   606  		return defaultKind
   607  	}
   608  
   609  	// non-sequence intermediate Node
   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  // MapEntrySetter sets a map entry to a value. If it finds a key with the same
   622  // value, it will override both Key and Value RNodes, including style and any
   623  // other metadata. If it doesn't find the key, it will insert a new map entry.
   624  // It will set the field, even if it's empty or nil, unlike the FieldSetter.
   625  // This is useful for rebuilding some pre-existing RNode structure.
   626  type MapEntrySetter struct {
   627  	// Name is the name of the field or key to lookup in a MappingNode.
   628  	// If Name is unspecified, it will use the Key's Value
   629  	Name string `yaml:"name,omitempty"`
   630  
   631  	// Value is the value to set.
   632  	Value *RNode `yaml:"value,omitempty"`
   633  
   634  	// Key is the map key to set.
   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  	// create the field
   664  	rn.YNode().Content = append(
   665  		rn.YNode().Content,
   666  		s.Key.YNode(),
   667  		s.Value.YNode())
   668  	return rn, nil
   669  }
   670  
   671  // FieldSetter sets a field or map entry to a value.
   672  type FieldSetter struct {
   673  	Kind string `yaml:"kind,omitempty"`
   674  
   675  	// Name is the name of the field or key to lookup in a MappingNode.
   676  	// If Name is unspecified, and the input is a ScalarNode, FieldSetter will set the
   677  	// value on the ScalarNode.
   678  	Name string `yaml:"name,omitempty"`
   679  
   680  	// Comments for the field
   681  	Comments Comments `yaml:"comments,omitempty"`
   682  
   683  	// Value is the value to set.
   684  	// Optional if Kind is set.
   685  	Value *RNode `yaml:"value,omitempty"`
   686  
   687  	StringValue string `yaml:"stringValue,omitempty"`
   688  
   689  	// OverrideStyle can be set to override the style of the existing node
   690  	// when setting it.  Otherwise, if an existing node is found, the style is
   691  	// retained.
   692  	OverrideStyle bool `yaml:"overrideStyle,omitempty"`
   693  
   694  	// AppendKeyStyle defines the style of the key when no existing node is
   695  	// found, and a new node is appended.
   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  	// need to set style for strings not recognized by yaml 1.1 to quoted if not previously set
   705  	// TODO: fix in upstream yaml library so this can be handled with yaml SetString
   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  		// only apply the style if there is not an existing style
   718  		// or we want to override it
   719  		if !s.OverrideStyle || s.Value.YNode().Style == 0 {
   720  			// keep the original style if it exists
   721  			s.Value.YNode().Style = rn.YNode().Style
   722  		}
   723  		rn.SetYNode(s.Value.YNode())
   724  		return rn, nil
   725  	}
   726  
   727  	// Clearing nil fields:
   728  	//   1. Clear any fields with no value
   729  	//   2. Clear any "null" YAML fields unless we explicitly want to keep them
   730  	// This is to balance
   731  	//   1. Persisting 'null' values passed by the user (see issue #4628)
   732  	//   2. Avoiding producing noisy documents that add any field defaulting to
   733  	//   'nil' even if they weren't present in the source document
   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  		// only apply the style if there is not an existing style
   744  		// or we want to override it
   745  		if !s.OverrideStyle || field.YNode().Style == 0 {
   746  			// keep the original style if it exists
   747  			s.Value.YNode().Style = field.YNode().Style
   748  		}
   749  		// need to def ref the Node since field is ephemeral
   750  		field.SetYNode(s.Value.YNode())
   751  		return field, nil
   752  	}
   753  
   754  	// create the field
   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  // Tee calls the provided Filters, and returns its argument rather than the result
   770  // of the filters.
   771  // May be used to fork sub-filters from a call.
   772  // e.g. locate field, set value; locate another field, set another value
   773  func Tee(filters ...Filter) Filter {
   774  	return TeePiper{Filters: filters}
   775  }
   776  
   777  // TeePiper Calls a slice of Filters and returns its input.
   778  // May be used to fork sub-filters from a call.
   779  // e.g. locate field, set value; locate another field, set another value
   780  type TeePiper struct {
   781  	Kind string `yaml:"kind,omitempty"`
   782  
   783  	// Filters are the set of Filters run by TeePiper.
   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  // IsCreate returns true if kind is specified
   793  func IsCreate(kind yaml.Kind) bool {
   794  	return kind != 0
   795  }
   796  
   797  // IsMissingOrError returns true if rn is NOT found or err is non-nil
   798  func IsMissingOrError(rn *RNode, err error) bool {
   799  	return rn == nil || err != nil
   800  }
   801  
   802  // IsFoundOrError returns true if rn is found or err is non-nil
   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  		// node has no type, pass validation
   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  // IsListIndex returns true if p is an index into a Val.
   858  // e.g. [fieldName=fieldValue]
   859  // e.g. [=primitiveValue]
   860  func IsListIndex(p string) bool {
   861  	return strings.HasPrefix(p, "[") && strings.HasSuffix(p, "]")
   862  }
   863  
   864  // IsIdxNumber returns true if p is an index number.
   865  // e.g. 1
   866  func IsIdxNumber(p string) bool {
   867  	idx, err := strconv.Atoi(p)
   868  	return err == nil && idx >= 0
   869  }
   870  
   871  // IsWildcard returns true if p is matching every elements.
   872  // e.g. "*"
   873  func IsWildcard(p string) bool {
   874  	return p == "*"
   875  }
   876  
   877  // SplitIndexNameValue splits a lookup part Val index into the field name
   878  // and field value to match.
   879  // e.g. splits [name=nginx] into (name, nginx)
   880  // e.g. splits [=-jar] into ("", -jar)
   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