mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 10:15:22 +00:00
197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// The copyutil package contains libraries for copying directories of configuration.
|
|
package copyutil
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/sergi/go-diff/diffmatchpatch"
|
|
"sigs.k8s.io/kustomize/kyaml/sets"
|
|
)
|
|
|
|
// CopyDir copies a src directory to a dst directory. CopyDir skips copying the .git directory from the src.
|
|
func CopyDir(src string, dst string) error {
|
|
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// don't copy the .git dir
|
|
if path != src {
|
|
rel := strings.TrimPrefix(path, src)
|
|
if IsDotGitFolder(rel) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// path is an absolute path, rather than a path relative to src.
|
|
// e.g. if src is /path/to/package, then path might be /path/to/package/and/sub/dir
|
|
// we need the path relative to src `and/sub/dir` when we are copying the files to dest.
|
|
copyTo := strings.TrimPrefix(path, src)
|
|
|
|
// make directories that don't exist
|
|
if info.IsDir() {
|
|
return os.MkdirAll(filepath.Join(dst, copyTo), info.Mode())
|
|
}
|
|
|
|
// copy file by reading and writing it
|
|
b, err := ioutil.ReadFile(filepath.Join(src, copyTo))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ioutil.WriteFile(filepath.Join(dst, copyTo), b, info.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Diff returns a list of files that differ between the source and destination.
|
|
//
|
|
// Diff is guaranteed to return a non-empty set if any files differ, but
|
|
// this set is not guaranteed to contain all differing files.
|
|
func Diff(sourceDir, destDir string) (sets.String, error) {
|
|
// get set of filenames in the package source
|
|
upstreamFiles := sets.String{}
|
|
err := filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// skip git repo if it exists
|
|
if IsDotGitFolder(path) {
|
|
return nil
|
|
}
|
|
|
|
upstreamFiles.Insert(strings.TrimPrefix(strings.TrimPrefix(path, sourceDir), string(filepath.Separator)))
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return sets.String{}, err
|
|
}
|
|
|
|
// get set of filenames in the cloned package
|
|
localFiles := sets.String{}
|
|
err = filepath.Walk(destDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// skip git repo if it exists
|
|
if IsDotGitFolder(path) {
|
|
return nil
|
|
}
|
|
|
|
localFiles.Insert(strings.TrimPrefix(strings.TrimPrefix(path, destDir), string(filepath.Separator)))
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return sets.String{}, err
|
|
}
|
|
|
|
// verify the source and cloned packages have the same set of filenames
|
|
diff := upstreamFiles.SymmetricDifference(localFiles)
|
|
|
|
// verify file contents match
|
|
for _, f := range upstreamFiles.Intersection(localFiles).List() {
|
|
fi, err := os.Stat(filepath.Join(destDir, f))
|
|
if err != nil {
|
|
return diff, err
|
|
}
|
|
if fi.Mode().IsDir() {
|
|
// already checked that this directory exists in the local files
|
|
continue
|
|
}
|
|
|
|
// compare upstreamFiles
|
|
b1, err := ioutil.ReadFile(filepath.Join(destDir, f))
|
|
if err != nil {
|
|
return diff, err
|
|
}
|
|
b2, err := ioutil.ReadFile(filepath.Join(sourceDir, f))
|
|
if err != nil {
|
|
return diff, err
|
|
}
|
|
if !bytes.Equal(b1, b2) {
|
|
fmt.Println(PrettyFileDiff(string(b1), string(b2)))
|
|
diff.Insert(f)
|
|
}
|
|
}
|
|
// return the differing files
|
|
return diff, nil
|
|
}
|
|
|
|
// IsDotGitFolder checks if the provided path is either the .git folder or
|
|
// a file underneath the .git folder.
|
|
func IsDotGitFolder(path string) bool {
|
|
cleanPath := filepath.ToSlash(filepath.Clean(path))
|
|
for _, c := range strings.Split(cleanPath, "/") {
|
|
if c == ".git" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PrettyFileDiff takes the content of two files and returns the pretty diff
|
|
func PrettyFileDiff(s1, s2 string) string {
|
|
dmp := diffmatchpatch.New()
|
|
wSrc, wDst, warray := dmp.DiffLinesToRunes(s1, s2)
|
|
diffs := dmp.DiffMainRunes(wSrc, wDst, false)
|
|
diffs = dmp.DiffCharsToLines(diffs, warray)
|
|
return dmp.DiffPrettyText(diffs)
|
|
}
|
|
|
|
// SyncFile copies file from src file path to a dst file path by replacement
|
|
// deletes dst file if src file doesn't exist
|
|
func SyncFile(src, dst string) error {
|
|
srcFileInfo, err := os.Stat(src)
|
|
if err != nil {
|
|
// delete dst if source doesn't exist
|
|
if err = deleteFile(dst); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
input, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var filePerm os.FileMode
|
|
|
|
// get the destination file perm if file exists
|
|
dstFileInfo, err := os.Stat(dst)
|
|
if err != nil {
|
|
// get source file perm if destination file doesn't exist
|
|
filePerm = srcFileInfo.Mode().Perm()
|
|
} else {
|
|
filePerm = dstFileInfo.Mode().Perm()
|
|
}
|
|
|
|
err = ioutil.WriteFile(dst, input, filePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// deleteFile deletes file from path, returns no error if file doesn't exist
|
|
func deleteFile(path string) error {
|
|
if _, err := os.Stat(path); err != nil {
|
|
// return nil if file doesn't exist
|
|
return nil
|
|
}
|
|
return os.Remove(path)
|
|
}
|