...

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

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package walk
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  
    11  	"sigs.k8s.io/kustomize/kyaml/fieldmeta"
    12  	"sigs.k8s.io/kustomize/kyaml/openapi"
    13  	"sigs.k8s.io/kustomize/kyaml/yaml"
    14  	"sigs.k8s.io/kustomize/kyaml/yaml/schema"
    15  )
    16  
    17  // Walker walks the Source RNode and modifies the RNode provided to GrepFilter.
    18  type Walker struct {
    19  	// Visitor is invoked by GrepFilter
    20  	Visitor
    21  
    22  	Schema *openapi.ResourceSchema
    23  
    24  	// Source is the RNode to walk.  All Source fields and associative list elements
    25  	// will be visited.
    26  	Sources Sources
    27  
    28  	// Path is the field path to the current Source Node.
    29  	Path []string
    30  
    31  	// InferAssociativeLists if set to true will infer merge strategies for
    32  	// fields which it doesn't have the schema based on the fields in the
    33  	// list elements.
    34  	InferAssociativeLists bool
    35  
    36  	// VisitKeysAsScalars if true will call VisitScalar on map entry keys,
    37  	// providing nil as the OpenAPI schema.
    38  	VisitKeysAsScalars bool
    39  
    40  	// MergeOptions is a struct to store options for merge
    41  	MergeOptions yaml.MergeOptions
    42  }
    43  
    44  // Kind returns the kind of the first non-null node in Sources.
    45  func (l Walker) Kind() yaml.Kind {
    46  	for _, s := range l.Sources {
    47  		if !yaml.IsMissingOrNull(s) {
    48  			return s.YNode().Kind
    49  		}
    50  	}
    51  	return 0
    52  }
    53  
    54  // Walk will recursively traverse every item in the Sources and perform corresponding
    55  // actions on them
    56  func (l Walker) Walk() (*yaml.RNode, error) {
    57  	l.Schema = l.GetSchema()
    58  
    59  	// invoke the handler for the corresponding node type
    60  	switch l.Kind() {
    61  	case yaml.MappingNode:
    62  		if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.MappingNode, l.Sources...); err != nil {
    63  			return nil, err
    64  		}
    65  		return l.walkMap()
    66  	case yaml.SequenceNode:
    67  		if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.SequenceNode, l.Sources...); err != nil {
    68  			return nil, err
    69  		}
    70  		// AssociativeSequence means the items in the sequence are associative. They can be merged
    71  		// according to merge key.
    72  		if schema.IsAssociative(l.Schema, l.Sources, l.InferAssociativeLists) {
    73  			return l.walkAssociativeSequence()
    74  		}
    75  		return l.walkNonAssociativeSequence()
    76  
    77  	case yaml.ScalarNode:
    78  		if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.ScalarNode, l.Sources...); err != nil {
    79  			return nil, err
    80  		}
    81  		return l.walkScalar()
    82  	case 0:
    83  		// walk empty nodes as maps
    84  		return l.walkMap()
    85  	default:
    86  		return nil, nil
    87  	}
    88  }
    89  
    90  func (l Walker) GetSchema() *openapi.ResourceSchema {
    91  	for i := range l.Sources {
    92  		r := l.Sources[i]
    93  		if yaml.IsMissingOrNull(r) {
    94  			continue
    95  		}
    96  
    97  		fm := fieldmeta.FieldMeta{}
    98  		if err := fm.Read(r); err == nil && !fm.IsEmpty() {
    99  			// per-field schema, this is fine
   100  			if fm.Schema.Ref.String() != "" {
   101  				// resolve the reference
   102  				s, err := openapi.Resolve(&fm.Schema.Ref, openapi.Schema())
   103  				if err == nil && s != nil {
   104  					fm.Schema = *s
   105  				}
   106  			}
   107  			return &openapi.ResourceSchema{Schema: &fm.Schema}
   108  		}
   109  	}
   110  
   111  	if l.Schema != nil {
   112  		return l.Schema
   113  	}
   114  	for i := range l.Sources {
   115  		r := l.Sources[i]
   116  		if yaml.IsMissingOrNull(r) {
   117  			continue
   118  		}
   119  
   120  		m, _ := r.GetMeta()
   121  		if m.Kind == "" || m.APIVersion == "" {
   122  			continue
   123  		}
   124  
   125  		s := openapi.SchemaForResourceType(yaml.TypeMeta{Kind: m.Kind, APIVersion: m.APIVersion})
   126  		if s != nil {
   127  			return s
   128  		}
   129  	}
   130  	return nil
   131  }
   132  
   133  const (
   134  	DestIndex = iota
   135  	OriginIndex
   136  	UpdatedIndex
   137  )
   138  
   139  // Sources are a list of RNodes. First item is the dest node, followed by
   140  // multiple source nodes.
   141  type Sources []*yaml.RNode
   142  
   143  // Dest returns the destination node
   144  func (s Sources) Dest() *yaml.RNode {
   145  	if len(s) <= DestIndex {
   146  		return nil
   147  	}
   148  	return s[DestIndex]
   149  }
   150  
   151  // Origin returns the origin node
   152  func (s Sources) Origin() *yaml.RNode {
   153  	if len(s) <= OriginIndex {
   154  		return nil
   155  	}
   156  	return s[OriginIndex]
   157  }
   158  
   159  // Updated returns the updated node
   160  func (s Sources) Updated() *yaml.RNode {
   161  	if len(s) <= UpdatedIndex {
   162  		return nil
   163  	}
   164  	return s[UpdatedIndex]
   165  }
   166  
   167  func (s Sources) String() string {
   168  	var values []string
   169  	for i := range s {
   170  		str, err := s[i].String()
   171  		if err != nil {
   172  			fmt.Fprintf(os.Stderr, "%v\n", err)
   173  		}
   174  		values = append(values, str)
   175  	}
   176  	return strings.Join(values, "\n")
   177  }
   178  
   179  // setDestNode sets the destination source node
   180  func (s Sources) setDestNode(node *yaml.RNode, err error) (*yaml.RNode, error) {
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	s[0] = node
   185  	return node, nil
   186  }
   187  

View as plain text