refactor(filesystem): add CopyDir method and optimize Fetch function

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2025-07-26 13:33:23 +08:00
parent b0911ab1a2
commit fb467f38c8
4 changed files with 138 additions and 3 deletions

View File

@ -45,7 +45,7 @@ linters:
lines: 280 lines: 280
statements: 140 statements: 140
gocognit: gocognit:
min-complexity: 100 min-complexity: 110
goconst: goconst:
min-len: 3 min-len: 3
min-occurrences: 8 min-occurrences: 8

View File

@ -345,8 +345,7 @@ func (a *App) Fetch(c FetchConfigProvider) error {
OutputDir: c.OutputDir(), OutputDir: c.OutputDir(),
OutputDirTemplate: c.OutputDirTemplate(), OutputDirTemplate: c.OutputDirTemplate(),
Concurrency: c.Concurrency(), Concurrency: c.Concurrency(),
}, func() { }, func() {})
})
if prepErr != nil { if prepErr != nil {
errs = append(errs, prepErr) errs = append(errs, prepErr)

View File

@ -5,6 +5,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
) )
@ -36,6 +37,7 @@ type FileSystem struct {
Chdir func(string) error Chdir func(string) error
Abs func(string) (string, error) Abs func(string) (string, error)
EvalSymlinks func(string) (string, error) EvalSymlinks func(string) (string, error)
CopyDir func(src, dst string) error
} }
func DefaultFileSystem() *FileSystem { func DefaultFileSystem() *FileSystem {
@ -55,6 +57,7 @@ func DefaultFileSystem() *FileSystem {
dfs.DirectoryExistsAt = dfs.directoryExistsDefault dfs.DirectoryExistsAt = dfs.directoryExistsDefault
dfs.FileExists = dfs.fileExistsDefault dfs.FileExists = dfs.fileExistsDefault
dfs.Abs = dfs.absDefault dfs.Abs = dfs.absDefault
dfs.CopyDir = dfs.copyDirDefault
return &dfs return &dfs
} }
@ -100,6 +103,9 @@ func FromFileSystem(params FileSystem) *FileSystem {
if params.Dir != nil { if params.Dir != nil {
dfs.Dir = params.Dir dfs.Dir = params.Dir
} }
if params.CopyDir != nil {
dfs.CopyDir = params.CopyDir
}
return dfs return dfs
} }
@ -180,3 +186,121 @@ func (filesystem *FileSystem) absDefault(path string) (string, error) {
} }
return filepath.Abs(path) 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
}

View File

@ -1372,6 +1372,18 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
// for a remote chart, so that the user can notice/fix the issue in a local chart while // for a remote chart, so that the user can notice/fix the issue in a local chart while
// a broken remote chart won't completely block their job. // a broken remote chart won't completely block their job.
chartPath = normalizedChart chartPath = normalizedChart
if helmfileCommand == "pull" {
// copy chart to a temporary directory
chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
if err != nil {
results <- &chartPrepareResult{err: err}
return
}
if err := st.fs.CopyDir(normalizedChart, chartPath); err != nil {
results <- &chartPrepareResult{err: err}
return
}
}
buildDeps = !skipDeps buildDeps = !skipDeps
} else if !opts.ForceDownload { } else if !opts.ForceDownload {