...

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

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

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package valueadd
     5  
     6  import (
     7  	"strings"
     8  
     9  	"sigs.k8s.io/kustomize/kyaml/filesys"
    10  	"sigs.k8s.io/kustomize/kyaml/kio"
    11  	"sigs.k8s.io/kustomize/kyaml/yaml"
    12  )
    13  
    14  // An 'Add' operation aspiring to IETF RFC 6902 JSON.
    15  //
    16  // The filter tries to add a value to a node at a particular field path.
    17  //
    18  // Kinds of target fields:
    19  //
    20  // - Non-existent target field.
    21  //
    22  //   The field will be added and the value inserted.
    23  //
    24  // - Existing field, scalar or map.
    25  //
    26  //   E.g. 'spec/template/spec/containers/[name:nginx]/image'
    27  //
    28  //   This behaves like an IETF RFC 6902 Replace operation would;
    29  //   the existing value is replaced without complaint, even though
    30  //   this  is an Add operation.  In contrast, a Replace operation
    31  //   must fail (report an error) if the field doesn't exist.
    32  //
    33  // - Existing field, list (array)
    34  //   Not supported yet.
    35  //   TODO: Honor fields with RFC-6902-style array indices
    36  //   TODO: like 'spec/template/spec/containers/2'
    37  //   TODO: Modify kyaml/yaml/PathGetter to allow this.
    38  //   The value will be inserted into the array at the given position,
    39  //   shifting other contents. To instead replace an array entry, use
    40  //   an implementation of an IETF RFC 6902 Replace operation.
    41  //
    42  // For the common case of a filepath in the field value, and a desire
    43  // to add the value to the filepath (rather than replace the filepath),
    44  // use a non-zero value of FilePathPosition (see below).
    45  type Filter struct {
    46  	// Value is the value to add.
    47  	//
    48  	// Empty values are disallowed, i.e. this filter isn't intended
    49  	// for use in erasing or removing fields. For that, use a filter
    50  	// more aligned with the IETF RFC 6902 JSON Remove operation.
    51  	//
    52  	// At the time of writing, Value's value should be a simple string,
    53  	// not a JSON document.  This particular filter focuses on easing
    54  	// injection of a single-sourced cloud project and/or cluster name
    55  	// into various fields, especially namespace and various filepath
    56  	// specifications.
    57  	Value string
    58  
    59  	// FieldPath is a JSON-style path to the field intended to hold the value.
    60  	FieldPath string
    61  
    62  	// FilePathPosition is a filepath field index.
    63  	//
    64  	// Call the value of this field _i_.
    65  	//
    66  	//   If _i_ is zero, negative or unspecified, this field has no effect.
    67  	//
    68  	//   If _i_ is > 0, then it's assumed that
    69  	//   - 'Value' is a string that can work as a directory or file name,
    70  	//   - the field value intended for replacement holds a filepath.
    71  	//
    72  	// The filepath is split into a string slice, the value is inserted
    73  	// at position [i-1], shifting the rest of the path to the right.
    74  	// A value of i==1 puts the new value at the start of the path.
    75  	// This change never converts an absolute path to a relative path,
    76  	// meaning adding a new field at position i==1 will preserve a
    77  	// leading slash. E.g. if Value == 'PEACH'
    78  	//
    79  	//                  OLD : NEW                    : FilePathPosition
    80  	//      --------------------------------------------------------
    81  	//              {empty} : PEACH                  : irrelevant
    82  	//                    / : /PEACH                 : irrelevant
    83  	//                  pie : PEACH/pie              : 1 (or less to prefix)
    84  	//                 /pie : /PEACH/pie             : 1 (or less to prefix)
    85  	//                  raw : raw/PEACH              : 2 (or more to postfix)
    86  	//                 /raw : /raw/PEACH             : 2 (or more to postfix)
    87  	//      a/nice/warm/pie : a/nice/warm/PEACH/pie  : 4
    88  	//     /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 4
    89  	//
    90  	// For robustness (liberal input, conservative output) FilePathPosition
    91  	// values that that are too large to index the split filepath result in a
    92  	// postfix rather than an error.  So use 1 to prefix, 9999 to postfix.
    93  	FilePathPosition int `json:"filePathPosition,omitempty" yaml:"filePathPosition,omitempty"`
    94  }
    95  
    96  var _ kio.Filter = Filter{}
    97  
    98  func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
    99  	_, err := kio.FilterAll(yaml.FilterFunc(
   100  		func(node *yaml.RNode) (*yaml.RNode, error) {
   101  			var fields []string
   102  			// if there is forward slash '/' in the field name, a back slash '\'
   103  			// will be used to escape it.
   104  			for _, f := range strings.Split(f.FieldPath, "/") {
   105  				if len(fields) > 0 && strings.HasSuffix(fields[len(fields)-1], "\\") {
   106  					concatField := strings.TrimSuffix(fields[len(fields)-1], "\\") + "/" + f
   107  					fields = append(fields[:len(fields)-1], concatField)
   108  				} else {
   109  					fields = append(fields, f)
   110  				}
   111  			}
   112  			// TODO: support SequenceNode.
   113  			// Presumably here one could look for array indices (digits) at
   114  			// the end of the field path (as described in IETF RFC 6902 JSON),
   115  			// and if found, take it as a signal that this should be a
   116  			// SequenceNode instead of a ScalarNode, and insert the value
   117  			// into the proper slot, shifting every over.
   118  			n, err := node.Pipe(yaml.LookupCreate(yaml.ScalarNode, fields...))
   119  			if err != nil {
   120  				return node, err
   121  			}
   122  			// TODO: allow more kinds
   123  			if err := yaml.ErrorIfInvalid(n, yaml.ScalarNode); err != nil {
   124  				return nil, err
   125  			}
   126  			newValue := f.Value
   127  			if f.FilePathPosition > 0 {
   128  				newValue = filesys.InsertPathPart(
   129  					n.YNode().Value, f.FilePathPosition-1, newValue)
   130  			}
   131  			return n.Pipe(yaml.FieldSetter{StringValue: newValue})
   132  		})).Filter(nodes)
   133  	return nodes, err
   134  }
   135  

View as plain text