...

Source file src/sigs.k8s.io/kustomize/kyaml/kio/pkgio_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  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"sigs.k8s.io/kustomize/kyaml/errors"
    14  	"sigs.k8s.io/kustomize/kyaml/filesys"
    15  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    16  	"sigs.k8s.io/kustomize/kyaml/yaml"
    17  )
    18  
    19  // LocalPackageWriter writes ResourceNodes to a filesystem
    20  type LocalPackageWriter struct {
    21  	Kind string `yaml:"kind,omitempty"`
    22  
    23  	// PackagePath is the path to the package directory.
    24  	PackagePath string `yaml:"path,omitempty"`
    25  
    26  	// KeepReaderAnnotations if set will retain the annotations set by LocalPackageReader
    27  	KeepReaderAnnotations bool `yaml:"keepReaderAnnotations,omitempty"`
    28  
    29  	// ClearAnnotations will clear annotations before writing the resources
    30  	ClearAnnotations []string `yaml:"clearAnnotations,omitempty"`
    31  
    32  	// FileSystem can be used to mock the disk file system.
    33  	FileSystem filesys.FileSystemOrOnDisk
    34  }
    35  
    36  var _ Writer = LocalPackageWriter{}
    37  
    38  func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
    39  	// set the path and index annotations if they are missing
    40  	if err := kioutil.DefaultPathAndIndexAnnotation("", nodes); err != nil {
    41  		return err
    42  	}
    43  
    44  	if !r.FileSystem.Exists(r.PackagePath) {
    45  		return errors.WrapPrefixf(os.ErrNotExist, "could not write to %q", r.PackagePath)
    46  	}
    47  	if !r.FileSystem.IsDir(r.PackagePath) {
    48  		// if the user specified input isn't a directory, the package is the directory of the
    49  		// target
    50  		r.PackagePath = filepath.Dir(r.PackagePath)
    51  	}
    52  
    53  	// setup indexes for writing Resources back to files
    54  	if err := r.errorIfMissingRequiredAnnotation(nodes); err != nil {
    55  		return err
    56  	}
    57  	outputFiles, err := r.indexByFilePath(nodes)
    58  	if err != nil {
    59  		return err
    60  	}
    61  	for k := range outputFiles {
    62  		if err = kioutil.SortNodes(outputFiles[k]); err != nil {
    63  			return errors.Wrap(err)
    64  		}
    65  	}
    66  
    67  	if !r.KeepReaderAnnotations {
    68  		r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation)
    69  		r.ClearAnnotations = append(r.ClearAnnotations, kioutil.LegacyPathAnnotation)
    70  	}
    71  
    72  	// validate outputs before writing any
    73  	for path := range outputFiles {
    74  		outputPath := filepath.Join(r.PackagePath, path)
    75  		if r.FileSystem.IsDir(outputPath) {
    76  			return fmt.Errorf("config.kubernetes.io/path cannot be a directory: %s", path)
    77  		}
    78  
    79  		err = r.FileSystem.MkdirAll(filepath.Dir(outputPath))
    80  		if err != nil {
    81  			return errors.Wrap(err)
    82  		}
    83  	}
    84  
    85  	// write files
    86  	buf := bytes.NewBuffer(nil)
    87  	for path := range outputFiles {
    88  		outputPath := filepath.Join(r.PackagePath, path)
    89  		err = r.FileSystem.MkdirAll(filepath.Dir(filepath.Join(r.PackagePath, path)))
    90  		if err != nil {
    91  			return errors.Wrap(err)
    92  		}
    93  
    94  		buf.Reset()
    95  		w := ByteWriter{
    96  			Writer:                buf,
    97  			KeepReaderAnnotations: r.KeepReaderAnnotations,
    98  			ClearAnnotations:      r.ClearAnnotations,
    99  		}
   100  		if err = w.Write(outputFiles[path]); err != nil {
   101  			return errors.Wrap(err)
   102  		}
   103  
   104  		if err := r.FileSystem.WriteFile(outputPath, buf.Bytes()); err != nil {
   105  			return errors.Wrap(err)
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (r LocalPackageWriter) errorIfMissingRequiredAnnotation(nodes []*yaml.RNode) error {
   113  	for i := range nodes {
   114  		for _, s := range requiredResourcePackageAnnotations {
   115  			key, err := nodes[i].Pipe(yaml.GetAnnotation(s))
   116  			if err != nil {
   117  				return errors.Wrap(err)
   118  			}
   119  			if key == nil || key.YNode() == nil || key.YNode().Value == "" {
   120  				return errors.Errorf(
   121  					"resources must be annotated with %s to be written to files", s)
   122  			}
   123  		}
   124  	}
   125  	return nil
   126  }
   127  
   128  func (r LocalPackageWriter) indexByFilePath(nodes []*yaml.RNode) (map[string][]*yaml.RNode, error) {
   129  	outputFiles := map[string][]*yaml.RNode{}
   130  	for i := range nodes {
   131  		// parse the file write path
   132  		node := nodes[i]
   133  		value, err := node.Pipe(yaml.GetAnnotation(kioutil.PathAnnotation))
   134  		if err != nil {
   135  			// this should never happen if errorIfMissingRequiredAnnotation was run
   136  			return nil, errors.Wrap(err)
   137  		}
   138  		path := value.YNode().Value
   139  		outputFiles[path] = append(outputFiles[path], node)
   140  
   141  		if filepath.IsAbs(path) {
   142  			return nil, errors.Errorf("package paths may not be absolute paths")
   143  		}
   144  		if strings.Contains(filepath.Clean(path), "..") {
   145  			return nil, fmt.Errorf("resource must be written under package %s: %s",
   146  				r.PackagePath, filepath.Clean(path))
   147  		}
   148  	}
   149  	return outputFiles, nil
   150  }
   151  

View as plain text