From fb467f38c8b3824cf57c97ecb8b81fc7c3674c24 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sat, 26 Jul 2025 13:33:23 +0800 Subject: [PATCH] refactor(filesystem): add CopyDir method and optimize Fetch function Signed-off-by: yxxhero --- .golangci.yaml | 2 +- pkg/app/app.go | 3 +- pkg/filesystem/fs.go | 124 +++++++++++++++++++++++++++++++++++++++++++ pkg/state/state.go | 12 +++++ 4 files changed, 138 insertions(+), 3 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 99add513..dc6d3add 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -45,7 +45,7 @@ linters: lines: 280 statements: 140 gocognit: - min-complexity: 100 + min-complexity: 110 goconst: min-len: 3 min-occurrences: 8 diff --git a/pkg/app/app.go b/pkg/app/app.go index 16a9c720..19024459 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -345,8 +345,7 @@ func (a *App) Fetch(c FetchConfigProvider) error { OutputDir: c.OutputDir(), OutputDirTemplate: c.OutputDirTemplate(), Concurrency: c.Concurrency(), - }, func() { - }) + }, func() {}) if prepErr != nil { errs = append(errs, prepErr) diff --git a/pkg/filesystem/fs.go b/pkg/filesystem/fs.go index a79fe1e9..0df2f83e 100644 --- a/pkg/filesystem/fs.go +++ b/pkg/filesystem/fs.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" "path/filepath" + "strings" "time" ) @@ -36,6 +37,7 @@ type FileSystem struct { Chdir func(string) error Abs func(string) (string, error) EvalSymlinks func(string) (string, error) + CopyDir func(src, dst string) error } func DefaultFileSystem() *FileSystem { @@ -55,6 +57,7 @@ func DefaultFileSystem() *FileSystem { dfs.DirectoryExistsAt = dfs.directoryExistsDefault dfs.FileExists = dfs.fileExistsDefault dfs.Abs = dfs.absDefault + dfs.CopyDir = dfs.copyDirDefault return &dfs } @@ -100,6 +103,9 @@ func FromFileSystem(params FileSystem) *FileSystem { if params.Dir != nil { dfs.Dir = params.Dir } + if params.CopyDir != nil { + dfs.CopyDir = params.CopyDir + } return dfs } @@ -180,3 +186,121 @@ func (filesystem *FileSystem) absDefault(path string) (string, error) { } 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 +} diff --git a/pkg/state/state.go b/pkg/state/state.go index 428cf414..7ef8d290 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -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 // a broken remote chart won't completely block their job. 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 } else if !opts.ForceDownload {