...

Source file src/sigs.k8s.io/kustomize/kyaml/kio/byteio_writer.go

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package kio
     5  
     6  import (
     7  	"encoding/json"
     8  	"io"
     9  	"path/filepath"
    10  
    11  	"sigs.k8s.io/kustomize/kyaml/errors"
    12  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    13  	"sigs.k8s.io/kustomize/kyaml/yaml"
    14  )
    15  
    16  // ByteWriter writes ResourceNodes to bytes. Generally YAML encoding will be used but in the special
    17  // case of writing a single, bare yaml.RNode that has a kioutil.PathAnnotation indicating that the
    18  // target is a JSON file JSON encoding is used. See shouldJSONEncodeSingleBareNode below for more
    19  // information.
    20  type ByteWriter struct {
    21  	// Writer is where ResourceNodes are encoded.
    22  	Writer io.Writer
    23  
    24  	// KeepReaderAnnotations if set will keep the Reader specific annotations when writing
    25  	// the Resources, otherwise they will be cleared.
    26  	KeepReaderAnnotations bool
    27  
    28  	// ClearAnnotations is a list of annotations to clear when writing the Resources.
    29  	ClearAnnotations []string
    30  
    31  	// Style is a style that is set on the Resource Node Document.
    32  	Style yaml.Style
    33  
    34  	// FunctionConfig is the function config for an ResourceList.  If non-nil
    35  	// wrap the results in an ResourceList.
    36  	FunctionConfig *yaml.RNode
    37  
    38  	Results *yaml.RNode
    39  
    40  	// WrappingKind if set will cause ByteWriter to wrap the Resources in
    41  	// an 'items' field in this kind.  e.g. if WrappingKind is 'List',
    42  	// ByteWriter will wrap the Resources in a List .items field.
    43  	WrappingKind string
    44  
    45  	// WrappingAPIVersion is the apiVersion for WrappingKind
    46  	WrappingAPIVersion string
    47  
    48  	// Sort if set, will cause ByteWriter to sort the nodes before writing them.
    49  	Sort bool
    50  }
    51  
    52  var _ Writer = ByteWriter{}
    53  
    54  func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
    55  	// Copy the nodes to prevent writer from mutating the original nodes.
    56  	nodes := copyRNodes(inputNodes)
    57  	if w.Sort {
    58  		if err := kioutil.SortNodes(nodes); err != nil {
    59  			return errors.Wrap(err)
    60  		}
    61  	}
    62  
    63  	// Even though we use the this value further down we must check this before removing annotations
    64  	jsonEncodeSingleBareNode := w.shouldJSONEncodeSingleBareNode(nodes)
    65  
    66  	// store seqindent annotation value for each node in order to set the encoder indentation
    67  	var seqIndentsForNodes []string
    68  	for i := range nodes {
    69  		seqIndentsForNodes = append(seqIndentsForNodes, nodes[i].GetAnnotations()[kioutil.SeqIndentAnnotation])
    70  	}
    71  
    72  	for i := range nodes {
    73  		// clean resources by removing annotations set by the Reader
    74  		if !w.KeepReaderAnnotations {
    75  			_, err := nodes[i].Pipe(yaml.ClearAnnotation(kioutil.IndexAnnotation))
    76  			if err != nil {
    77  				return errors.Wrap(err)
    78  			}
    79  			_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.LegacyIndexAnnotation))
    80  			if err != nil {
    81  				return errors.Wrap(err)
    82  			}
    83  
    84  			_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.SeqIndentAnnotation))
    85  			if err != nil {
    86  				return errors.Wrap(err)
    87  			}
    88  		}
    89  		for _, a := range w.ClearAnnotations {
    90  			_, err := nodes[i].Pipe(yaml.ClearAnnotation(a))
    91  			if err != nil {
    92  				return errors.Wrap(err)
    93  			}
    94  		}
    95  
    96  		if err := yaml.ClearEmptyAnnotations(nodes[i]); err != nil {
    97  			return err
    98  		}
    99  
   100  		if w.Style != 0 {
   101  			nodes[i].YNode().Style = w.Style
   102  		}
   103  	}
   104  
   105  	if jsonEncodeSingleBareNode {
   106  		encoder := json.NewEncoder(w.Writer)
   107  		encoder.SetIndent("", "  ")
   108  		return errors.Wrap(encoder.Encode(nodes[0]))
   109  	}
   110  
   111  	encoder := yaml.NewEncoder(w.Writer)
   112  	defer encoder.Close()
   113  	// don't wrap the elements
   114  	if w.WrappingKind == "" {
   115  		for i := range nodes {
   116  			if seqIndentsForNodes[i] == string(yaml.WideSequenceStyle) {
   117  				encoder.DefaultSeqIndent()
   118  			} else {
   119  				encoder.CompactSeqIndent()
   120  			}
   121  			if err := encoder.Encode(upWrapBareSequenceNode(nodes[i].Document())); err != nil {
   122  				return errors.Wrap(err)
   123  			}
   124  		}
   125  		return nil
   126  	}
   127  	// wrap the elements in a list
   128  	items := &yaml.Node{Kind: yaml.SequenceNode}
   129  	list := &yaml.Node{
   130  		Kind:  yaml.MappingNode,
   131  		Style: w.Style,
   132  		Content: []*yaml.Node{
   133  			{Kind: yaml.ScalarNode, Value: "apiVersion"},
   134  			{Kind: yaml.ScalarNode, Value: w.WrappingAPIVersion},
   135  			{Kind: yaml.ScalarNode, Value: "kind"},
   136  			{Kind: yaml.ScalarNode, Value: w.WrappingKind},
   137  			{Kind: yaml.ScalarNode, Value: "items"}, items,
   138  		}}
   139  	if w.FunctionConfig != nil {
   140  		list.Content = append(list.Content,
   141  			&yaml.Node{Kind: yaml.ScalarNode, Value: "functionConfig"},
   142  			w.FunctionConfig.YNode())
   143  	}
   144  	if w.Results != nil {
   145  		list.Content = append(list.Content,
   146  			&yaml.Node{Kind: yaml.ScalarNode, Value: "results"},
   147  			w.Results.YNode())
   148  	}
   149  	doc := &yaml.Node{
   150  		Kind:    yaml.DocumentNode,
   151  		Content: []*yaml.Node{list}}
   152  	for i := range nodes {
   153  		items.Content = append(items.Content, nodes[i].YNode())
   154  	}
   155  	return encoder.Encode(doc)
   156  }
   157  
   158  func copyRNodes(in []*yaml.RNode) []*yaml.RNode {
   159  	out := make([]*yaml.RNode, len(in))
   160  	for i := range in {
   161  		out[i] = in[i].Copy()
   162  	}
   163  	return out
   164  }
   165  
   166  // shouldJSONEncodeSingleBareNode determines if nodes contain a single node that should not be
   167  // wrapped and has a JSON file extension, which in turn means that the node should be JSON encoded.
   168  // Note 1: this must be checked before any annotations to avoid losing information about the target
   169  //         filename extension.
   170  // Note 2: JSON encoding should only be used for single, unwrapped nodes because multiple unwrapped
   171  //         nodes cannot be represented in JSON (no multi doc support). Furthermore, the typical use
   172  //         cases for wrapping nodes would likely not include later writing the whole wrapper to a
   173  //         .json file, i.e. there is no point risking any edge case information loss e.g. comments
   174  //         disappearing, that could come from JSON encoding the whole wrapper just to ensure that
   175  //         one (or all nodes) can be read as JSON.
   176  func (w ByteWriter) shouldJSONEncodeSingleBareNode(nodes []*yaml.RNode) bool {
   177  	if w.WrappingKind == "" && len(nodes) == 1 {
   178  		if path, _, _ := kioutil.GetFileAnnotations(nodes[0]); path != "" {
   179  			filename := filepath.Base(path)
   180  			for _, glob := range JSONMatch {
   181  				if match, _ := filepath.Match(glob, filename); match {
   182  					return true
   183  				}
   184  			}
   185  		}
   186  	}
   187  	return false
   188  }
   189  
   190  // upWrapBareSequenceNode unwraps the bare sequence nodes wrapped by yaml.BareSeqNodeWrappingKey
   191  func upWrapBareSequenceNode(node *yaml.Node) *yaml.Node {
   192  	rNode := yaml.NewRNode(node)
   193  	seqNode, err := rNode.Pipe(yaml.Lookup(yaml.BareSeqNodeWrappingKey))
   194  	if err == nil && !seqNode.IsNilOrEmpty() {
   195  		return seqNode.YNode()
   196  	}
   197  	return node
   198  }
   199  

View as plain text