helmfile/pkg/filesystem/fs.go

307 lines
6.8 KiB
Go

package filesystem
import (
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
)
type fileStat struct {
name string
size int64
mode fs.FileMode
modTime time.Time
}
func (fs fileStat) Name() string { return fs.name }
func (fs fileStat) Size() int64 { return fs.size }
func (fs fileStat) Mode() fs.FileMode { return fs.mode }
func (fs fileStat) ModTime() time.Time { return fs.modTime }
func (fs fileStat) IsDir() bool { return fs.mode.IsDir() }
func (fs fileStat) Sys() any { return nil }
type FileSystem struct {
ReadFile func(string) ([]byte, error)
ReadDir func(string) ([]fs.DirEntry, error)
DeleteFile func(string) error
FileExists func(string) (bool, error)
Glob func(string) ([]string, error)
FileExistsAt func(string) bool
DirectoryExistsAt func(string) bool
Dir func(string) string
Stat func(string) (os.FileInfo, error)
Getwd func() (string, error)
Chdir func(string) error
Abs func(string) (string, error)
EvalSymlinks func(string) (string, error)
CopyDir func(src, dst string) error
}
func DefaultFileSystem() *FileSystem {
dfs := FileSystem{
ReadDir: os.ReadDir,
DeleteFile: os.Remove,
Glob: filepath.Glob,
Getwd: os.Getwd,
Chdir: os.Chdir,
EvalSymlinks: filepath.EvalSymlinks,
Dir: filepath.Dir,
}
dfs.Stat = dfs.stat
dfs.ReadFile = dfs.readFile
dfs.FileExistsAt = dfs.fileExistsAtDefault
dfs.DirectoryExistsAt = dfs.directoryExistsDefault
dfs.FileExists = dfs.fileExistsDefault
dfs.Abs = dfs.absDefault
dfs.CopyDir = dfs.copyDirDefault
return &dfs
}
func FromFileSystem(params FileSystem) *FileSystem {
dfs := DefaultFileSystem()
if params.ReadFile != nil {
dfs.ReadFile = params.ReadFile
}
if params.ReadDir != nil {
dfs.ReadDir = params.ReadDir
}
if params.DeleteFile != nil {
dfs.DeleteFile = params.DeleteFile
}
if params.FileExists != nil {
dfs.FileExists = params.FileExists
}
if params.Glob != nil {
dfs.Glob = params.Glob
}
if params.FileExistsAt != nil {
dfs.FileExistsAt = params.FileExistsAt
}
if params.DirectoryExistsAt != nil {
dfs.DirectoryExistsAt = params.DirectoryExistsAt
}
if params.Stat != nil {
dfs.Stat = params.Stat
}
if params.Getwd != nil {
dfs.Getwd = params.Getwd
}
if params.Chdir != nil {
dfs.Chdir = params.Chdir
}
if params.Abs != nil {
dfs.Abs = params.Abs
}
if params.EvalSymlinks != nil {
dfs.EvalSymlinks = params.EvalSymlinks
}
if params.Dir != nil {
dfs.Dir = params.Dir
}
if params.CopyDir != nil {
dfs.CopyDir = params.CopyDir
}
return dfs
}
func (filesystem *FileSystem) stat(name string) (os.FileInfo, error) {
if name == "-" {
return fileStat{mode: 0}, nil
}
return os.Stat(name)
}
func (filesystem *FileSystem) readFile(name string) ([]byte, error) {
if name == "-" {
return io.ReadAll(os.Stdin)
}
return os.ReadFile(name)
}
func (filesystem *FileSystem) fileExistsAtDefault(path string) bool {
path, err := filesystem.resolveSymlinks(path)
if err != nil {
return false
}
fileInfo, err := filesystem.Stat(path)
return err == nil && fileInfo.Mode().IsRegular()
}
func (filesystem *FileSystem) fileExistsDefault(path string) (bool, error) {
path, err := filesystem.resolveSymlinks(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
_, err = filesystem.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (filesystem *FileSystem) directoryExistsDefault(path string) bool {
path, err := filesystem.resolveSymlinks(path)
if err != nil {
return false
}
fileInfo, err := filesystem.Stat(path)
return err == nil && fileInfo.Mode().IsDir()
}
func (filesystem *FileSystem) resolveSymlinks(path string) (string, error) {
if !filepath.IsAbs(path) && !filepath.IsLocal(path) {
basePath, err := filesystem.Getwd()
if err != nil {
return "", err
}
basePath, err = filesystem.EvalSymlinks(basePath)
if err != nil {
return "", err
}
path, err := filesystem.EvalSymlinks(filepath.Join(basePath, path))
if err != nil {
return "", err
}
return path, nil
}
return path, nil
}
func (filesystem *FileSystem) absDefault(path string) (string, error) {
path, err := filesystem.resolveSymlinks(path)
if err != nil {
return "", err
}
return filepath.Abs(path)
}
// copyDirDefault recursively copies a directory tree, preserving permissions.
func (filesystem *FileSystem) copyDirDefault(src string, dst string) error {
src, err := filesystem.EvalSymlinks(src)
if err != nil {
return err
}
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == src {
return nil
}
if strings.HasPrefix(filepath.Base(path), ".") {
// Skip any dot files
if info.IsDir() {
return filepath.SkipDir
} else {
return nil
}
}
// The "path" has the src prefixed to it. We need to join our
// destination with the path without the src on it.
dstPath := filepath.Join(dst, path[len(src):])
// we don't want to try and copy the same file over itself.
if eq, err := SameFile(path, dstPath); eq {
return nil
} else if err != nil {
return err
}
// If we have a directory, make that subdirectory, then continue
// the walk.
if info.IsDir() {
if path == filepath.Join(src, dst) {
// dst is in src; don't walk it.
return nil
}
if err := os.MkdirAll(dstPath, 0755); err != nil {
return err
}
return nil
}
// If the current path is a symlink, recreate the symlink relative to
// the dst directory
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
target, err := os.Readlink(path)
if err != nil {
return err
}
return os.Symlink(target, dstPath)
}
// If we have a file, copy the contents.
srcF, err := os.Open(path)
if err != nil {
return err
}
defer func() {
_ = srcF.Close()
}()
dstF, err := os.Create(dstPath)
if err != nil {
return err
}
defer func() {
_ = dstF.Close()
}()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
// Chmod it
return os.Chmod(dstPath, info.Mode())
}
return filepath.Walk(src, walkFn)
}
// SameFile returns true if the two given paths refer to the same physical
// file on disk, using the unique file identifiers from the underlying
// operating system. For example, on Unix systems this checks whether the
// two files are on the same device and have the same inode.
func SameFile(a, b string) (bool, error) {
if a == b {
return true, nil
}
aInfo, err := os.Lstat(a)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
bInfo, err := os.Lstat(b)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return os.SameFile(aInfo, bInfo), nil
}