...

Source file src/sigs.k8s.io/kustomize/kyaml/copyutil/copyutil.go

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  // The copyutil package contains libraries for copying directories of configuration.
     5  package copyutil
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/sergi/go-diff/diffmatchpatch"
    15  	"sigs.k8s.io/kustomize/kyaml/errors"
    16  	"sigs.k8s.io/kustomize/kyaml/filesys"
    17  	"sigs.k8s.io/kustomize/kyaml/sets"
    18  )
    19  
    20  // CopyDir copies a src directory to a dst directory.  CopyDir skips copying the .git directory from the src.
    21  func CopyDir(fSys filesys.FileSystem, src string, dst string) error {
    22  	return errors.Wrap(fSys.Walk(src, func(path string, info os.FileInfo, err error) error {
    23  		if err != nil {
    24  			return err
    25  		}
    26  		// don't copy the .git dir
    27  		if path != src {
    28  			rel := strings.TrimPrefix(path, src)
    29  			if IsDotGitFolder(rel) {
    30  				return nil
    31  			}
    32  		}
    33  
    34  		// path is an absolute path, rather than a path relative to src.
    35  		// e.g. if src is /path/to/package, then path might be /path/to/package/and/sub/dir
    36  		// we need the path relative to src `and/sub/dir` when we are copying the files to dest.
    37  		copyTo := strings.TrimPrefix(path, src)
    38  
    39  		// make directories that don't exist
    40  		if info.IsDir() {
    41  			return errors.Wrap(fSys.MkdirAll(filepath.Join(dst, copyTo)))
    42  		}
    43  
    44  		// copy file by reading and writing it
    45  		b, err := fSys.ReadFile(filepath.Join(src, copyTo))
    46  		if err != nil {
    47  			return errors.Wrap(err)
    48  		}
    49  		err = fSys.WriteFile(filepath.Join(dst, copyTo), b)
    50  		if err != nil {
    51  			return errors.Wrap(err)
    52  		}
    53  
    54  		return nil
    55  	}))
    56  }
    57  
    58  // Diff returns a list of files that differ between the source and destination.
    59  //
    60  // Diff is guaranteed to return a non-empty set if any files differ, but
    61  // this set is not guaranteed to contain all differing files.
    62  func Diff(sourceDir, destDir string) (sets.String, error) {
    63  	// get set of filenames in the package source
    64  	upstreamFiles := sets.String{}
    65  	err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
    66  		if err != nil {
    67  			return err
    68  		}
    69  
    70  		// skip git repo if it exists
    71  		if IsDotGitFolder(path) {
    72  			return nil
    73  		}
    74  
    75  		upstreamFiles.Insert(strings.TrimPrefix(strings.TrimPrefix(path, sourceDir), string(filepath.Separator)))
    76  		return nil
    77  	})
    78  	if err != nil {
    79  		return sets.String{}, err
    80  	}
    81  
    82  	// get set of filenames in the cloned package
    83  	localFiles := sets.String{}
    84  	err = filepath.Walk(destDir, func(path string, info os.FileInfo, err error) error {
    85  		if err != nil {
    86  			return err
    87  		}
    88  
    89  		// skip git repo if it exists
    90  		if IsDotGitFolder(path) {
    91  			return nil
    92  		}
    93  
    94  		localFiles.Insert(strings.TrimPrefix(strings.TrimPrefix(path, destDir), string(filepath.Separator)))
    95  		return nil
    96  	})
    97  	if err != nil {
    98  		return sets.String{}, err
    99  	}
   100  
   101  	// verify the source and cloned packages have the same set of filenames
   102  	diff := upstreamFiles.SymmetricDifference(localFiles)
   103  
   104  	// verify file contents match
   105  	for _, f := range upstreamFiles.Intersection(localFiles).List() {
   106  		fi, err := os.Stat(filepath.Join(destDir, f))
   107  		if err != nil {
   108  			return diff, err
   109  		}
   110  		if fi.Mode().IsDir() {
   111  			// already checked that this directory exists in the local files
   112  			continue
   113  		}
   114  
   115  		// compare upstreamFiles
   116  		b1, err := os.ReadFile(filepath.Join(destDir, f))
   117  		if err != nil {
   118  			return diff, err
   119  		}
   120  		b2, err := os.ReadFile(filepath.Join(sourceDir, f))
   121  		if err != nil {
   122  			return diff, err
   123  		}
   124  		if !bytes.Equal(b1, b2) {
   125  			fmt.Println(PrettyFileDiff(string(b1), string(b2)))
   126  			diff.Insert(f)
   127  		}
   128  	}
   129  	// return the differing files
   130  	return diff, nil
   131  }
   132  
   133  // IsDotGitFolder checks if the provided path is either the .git folder or
   134  // a file underneath the .git folder.
   135  func IsDotGitFolder(path string) bool {
   136  	cleanPath := filepath.ToSlash(filepath.Clean(path))
   137  	for _, c := range strings.Split(cleanPath, "/") {
   138  		if c == ".git" {
   139  			return true
   140  		}
   141  	}
   142  	return false
   143  }
   144  
   145  // PrettyFileDiff takes the content of two files and returns the pretty diff
   146  func PrettyFileDiff(s1, s2 string) string {
   147  	dmp := diffmatchpatch.New()
   148  	wSrc, wDst, warray := dmp.DiffLinesToRunes(s1, s2)
   149  	diffs := dmp.DiffMainRunes(wSrc, wDst, false)
   150  	diffs = dmp.DiffCharsToLines(diffs, warray)
   151  	return dmp.DiffPrettyText(diffs)
   152  }
   153  
   154  // SyncFile copies file from src file path to a dst file path by replacement
   155  // deletes dst file if src file doesn't exist
   156  func SyncFile(src, dst string) error {
   157  	srcFileInfo, err := os.Stat(src)
   158  	if err != nil {
   159  		// delete dst if source doesn't exist
   160  		if err = deleteFile(dst); err != nil {
   161  			return err
   162  		}
   163  		return nil
   164  	}
   165  
   166  	input, err := os.ReadFile(src)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	var filePerm os.FileMode
   172  
   173  	// get the destination file perm if file exists
   174  	dstFileInfo, err := os.Stat(dst)
   175  	if err != nil {
   176  		// get source file perm if destination file doesn't exist
   177  		filePerm = srcFileInfo.Mode().Perm()
   178  	} else {
   179  		filePerm = dstFileInfo.Mode().Perm()
   180  	}
   181  
   182  	err = os.WriteFile(dst, input, filePerm)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // deleteFile deletes file from path, returns no error if file doesn't exist
   191  func deleteFile(path string) error {
   192  	if _, err := os.Stat(path); err != nil {
   193  		// return nil if file doesn't exist
   194  		return nil
   195  	}
   196  	return os.Remove(path)
   197  }
   198  

View as plain text