diff --git a/pkg/app/app.go b/pkg/app/app.go index f69c6403..d68202bc 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -17,6 +17,7 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/argparser" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/plugins" "github.com/helmfile/helmfile/pkg/remote" @@ -39,16 +40,7 @@ type App struct { FileOrDir string - readFile func(string) ([]byte, error) - deleteFile func(string) error - fileExists func(string) (bool, error) - glob func(string) ([]string, error) - abs func(string) (string, error) - fileExistsAt func(string) bool - directoryExistsAt func(string) bool - - getwd func() (string, error) - chdir func(string) error + fs *filesystem.FileSystem remote *remote.Remote @@ -81,20 +73,11 @@ func New(conf ConfigProvider) *App { FileOrDir: conf.FileOrDir(), ValuesFiles: conf.StateValuesFiles(), Set: conf.StateValuesSet(), + fs: filesystem.DefaultFileSystem(), }) } func Init(app *App) *App { - app.readFile = os.ReadFile - app.deleteFile = os.Remove - app.glob = filepath.Glob - app.abs = filepath.Abs - app.getwd = os.Getwd - app.chdir = os.Chdir - app.fileExistsAt = fileExistsAt - app.fileExists = fileExists - app.directoryExistsAt = directoryExistsAt - var err error app.valsRuntime, err = plugins.ValsInstance() if err != nil { @@ -619,19 +602,19 @@ func (a *App) within(dir string, do func() error) error { return do() } - prev, err := a.getwd() + prev, err := a.fs.Getwd() if err != nil { return fmt.Errorf("failed getting current working direcotyr: %v", err) } - absDir, err := a.abs(dir) + absDir, err := a.fs.Abs(dir) if err != nil { return err } a.Logger.Debugf("changing working directory to \"%s\"", absDir) - if err := a.chdir(absDir); err != nil { + if err := a.fs.Chdir(absDir); err != nil { return fmt.Errorf("failed changing working directory to \"%s\": %v", absDir, err) } @@ -639,7 +622,7 @@ func (a *App) within(dir string, do func() error) error { a.Logger.Debugf("changing working directory back to \"%s\"", prev) - if chdirBackErr := a.chdir(prev); chdirBackErr != nil { + if chdirBackErr := a.fs.Chdir(prev); chdirBackErr != nil { if appErr != nil { a.Logger.Warnf("%v", appErr) } @@ -658,7 +641,7 @@ func (a *App) visitStateFiles(fileOrDir string, opts LoadOpts, do func(string, s for _, relPath := range desiredStateFiles { var file string var dir string - if a.directoryExistsAt(relPath) { + if a.fs.DirectoryExistsAt(relPath) { file = relPath dir = relPath } else { @@ -668,7 +651,7 @@ func (a *App) visitStateFiles(fileOrDir string, opts LoadOpts, do func(string, s a.Logger.Debugf("processing file \"%s\" in directory \"%s\"", file, dir) - absd, errAbsDir := a.abs(dir) + absd, errAbsDir := a.fs.Abs(dir) if errAbsDir != nil { return errAbsDir } @@ -690,20 +673,15 @@ func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.He } ld := &desiredStateLoader{ - readFile: a.readFile, - deleteFile: a.deleteFile, - fileExists: a.fileExists, - directoryExistsAt: a.directoryExistsAt, - env: a.Env, - namespace: a.Namespace, - chart: a.Chart, - logger: a.Logger, - abs: a.abs, - remote: a.remote, + fs: a.fs, + env: a.Env, + namespace: a.Namespace, + chart: a.Chart, + logger: a.Logger, + remote: a.remote, overrideKubeContext: a.OverrideKubeContext, overrideHelmBinary: a.OverrideHelmBinary, - glob: a.glob, getHelm: a.getHelm, valsRuntime: a.valsRuntime, } @@ -995,7 +973,7 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converg opts.Environment.OverrideValues = envvals } - a.remote = remote.NewRemote(a.Logger, "", a.readFile, a.directoryExistsAt, a.fileExistsAt) + a.remote = remote.NewRemote(a.Logger, "", a.fs) f := converge if opts.Filter { @@ -1095,18 +1073,18 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri var helmfileDir string if specifiedPath != "" { switch { - case a.fileExistsAt(specifiedPath): + case a.fs.FileExistsAt(specifiedPath): return []string{specifiedPath}, nil - case a.directoryExistsAt(specifiedPath): + case a.fs.DirectoryExistsAt(specifiedPath): helmfileDir = specifiedPath default: return []string{}, fmt.Errorf("specified state file %s is not found", specifiedPath) } } else { var defaultFile string - if a.fileExistsAt(DefaultHelmfile) { + if a.fs.FileExistsAt(DefaultHelmfile) { defaultFile = DefaultHelmfile - } else if a.fileExistsAt(DeprecatedHelmfile) { + } else if a.fs.FileExistsAt(DeprecatedHelmfile) { log.Printf( "warn: %s is being loaded: %s is deprecated in favor of %s. See https://github.com/roboll/helmfile/issues/25 for more information", DeprecatedHelmfile, @@ -1117,7 +1095,7 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri } switch { - case a.directoryExistsAt(DefaultHelmfileDirectory): + case a.fs.DirectoryExistsAt(DefaultHelmfileDirectory): if defaultFile != "" { return []string{}, fmt.Errorf("configuration conlict error: you can have either %s or %s, but not both", defaultFile, DefaultHelmfileDirectory) } @@ -1130,7 +1108,7 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri } } - files, err := a.glob(filepath.Join(helmfileDir, "*.y*ml")) + files, err := a.fs.Glob(filepath.Join(helmfileDir, "*.y*ml")) if err != nil { return []string{}, err } @@ -1926,28 +1904,6 @@ func (a *App) writeValues(r *Run, c WriteValuesConfigProvider) (bool, []error) { return true, errs } -func fileExistsAt(path string) bool { - fileInfo, err := os.Stat(path) - return err == nil && fileInfo.Mode().IsRegular() -} - -func fileExists(path string) (bool, error) { - _, err := os.Stat(path) - - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return true, nil -} - -func directoryExistsAt(path string) bool { - fileInfo, err := os.Stat(path) - return err == nil && fileInfo.Mode().IsDir() -} - // Error is a wrapper around an error that adds context to the error. type Error struct { msg string @@ -2064,10 +2020,10 @@ func (c context) wrapErrs(errs ...error) error { func (a *App) ShowCacheDir(c CacheConfigProvider) error { fmt.Printf("Cache directory: %s\n", remote.CacheDir()) - if !directoryExistsAt(remote.CacheDir()) { + if !a.fs.DirectoryExistsAt(remote.CacheDir()) { return nil } - dirs, err := os.ReadDir(remote.CacheDir()) + dirs, err := a.fs.ReadDir(remote.CacheDir()) if err != nil { return err } @@ -2079,7 +2035,7 @@ func (a *App) ShowCacheDir(c CacheConfigProvider) error { } func (a *App) CleanCacheDir(c CacheConfigProvider) error { - if !directoryExistsAt(remote.CacheDir()) { + if !a.fs.DirectoryExistsAt(remote.CacheDir()) { return nil } fmt.Printf("Cleaning up cache directory: %s\n", remote.CacheDir()) diff --git a/pkg/app/app_apply_nokubectx_test.go b/pkg/app/app_apply_nokubectx_test.go index 56a6b781..dfedac3c 100644 --- a/pkg/app/app_apply_nokubectx_test.go +++ b/pkg/app/app_apply_nokubectx_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -90,8 +90,7 @@ func TestApply_3(t *testing.T) { app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: filesystem.DefaultFileSystem(), OverrideKubeContext: "", Env: "default", Logger: logger, diff --git a/pkg/app/app_apply_test.go b/pkg/app/app_apply_test.go index d7d81da9..b0f8401f 100644 --- a/pkg/app/app_apply_test.go +++ b/pkg/app/app_apply_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -90,8 +90,7 @@ func TestApply_2(t *testing.T) { app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: filesystem.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/app_diff_test.go b/pkg/app/app_diff_test.go index e4a0c833..50abe6f4 100644 --- a/pkg/app/app_diff_test.go +++ b/pkg/app/app_diff_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -13,6 +12,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -129,8 +129,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: filesystem.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/app_lint_test.go b/pkg/app/app_lint_test.go index 085057c7..20ca6844 100644 --- a/pkg/app/app_lint_test.go +++ b/pkg/app/app_lint_test.go @@ -15,6 +15,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -131,8 +132,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/app_list_test.go b/pkg/app/app_list_test.go index 9b71c958..0ec63bba 100644 --- a/pkg/app/app_list_test.go +++ b/pkg/app/app_list_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/variantdev/vals" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testutil" @@ -145,8 +145,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: tc.environment, Logger: logger, diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go index 034002ba..5920c19c 100644 --- a/pkg/app/app_sync_test.go +++ b/pkg/app/app_sync_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -88,8 +88,7 @@ func TestSync(t *testing.T) { app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/app_template_test.go b/pkg/app/app_template_test.go index e76c04c7..f9d7acc6 100644 --- a/pkg/app/app_template_test.go +++ b/pkg/app/app_template_test.go @@ -15,6 +15,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -131,8 +132,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: &ffs.FileSystem{Glob: filepath.Glob}, OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 069c3c99..e43cf657 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -21,6 +21,7 @@ import ( "github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/state" @@ -40,14 +41,7 @@ func injectFs(app *App, fs *testhelper.TestFs) *App { app.Set = make(map[string]interface{}) } - app.readFile = fs.ReadFile - app.glob = fs.Glob - app.abs = fs.Abs - app.getwd = fs.Getwd - app.chdir = fs.Chdir - app.fileExistsAt = fs.FileExistsAt - app.fileExists = fs.FileExists - app.directoryExistsAt = fs.DirectoryExistsAt + app.fs = fs.ToFileSystem() return app } @@ -1529,12 +1523,11 @@ func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) { } return yamlContent, nil } + fs := ffs.FromFileSystem(ffs.FileSystem{ReadFile: readFile}) app := &App{ OverrideHelmBinary: DefaultHelmBinary, OverrideKubeContext: "default", - readFile: readFile, - glob: filepath.Glob, - abs: filepath.Abs, + fs: fs, Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } @@ -1593,16 +1586,11 @@ helmDefaults: app := &App{ OverrideHelmBinary: DefaultHelmBinary, OverrideKubeContext: "default", - readFile: testFs.ReadFile, - glob: testFs.Glob, - abs: testFs.Abs, - directoryExistsAt: testFs.DirectoryExistsAt, - fileExistsAt: testFs.FileExistsAt, - fileExists: testFs.FileExists, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, "", app.readFile, app.directoryExistsAt, app.fileExistsAt) + app.remote = remote.NewRemote(app.Logger, "", app.fs) expectNoCallsToHelm(app) @@ -1683,14 +1671,11 @@ helmDefaults: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - fileExists: testFs.FileExists, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -1762,14 +1747,11 @@ foo: FOO }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - fileExists: testFs.FileExists, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -1828,14 +1810,11 @@ foo: FOO }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - fileExists: testFs.FileExists, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -1912,14 +1891,11 @@ helmDefaults: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - fileExists: testFs.FileExists, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "test", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -1988,13 +1964,11 @@ releases: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -2046,14 +2020,12 @@ releases: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) st, err := app.loadDesiredStateFromYaml(statePath, LoadOpts{Reverse: true}) @@ -2103,13 +2075,11 @@ releases: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) opts := LoadOpts{ CalleePath: statePath, @@ -2217,13 +2187,11 @@ services: }) app := &App{ OverrideHelmBinary: DefaultHelmBinary, - readFile: testFs.ReadFile, - glob: testFs.Glob, - abs: testFs.Abs, + fs: testFs.ToFileSystem(), Env: "default", Logger: helmexec.NewLogger(os.Stderr, "debug"), } - app.remote = remote.NewRemote(app.Logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + app.remote = remote.NewRemote(app.Logger, testFs.Cwd, app.fs) expectNoCallsToHelm(app) @@ -2656,8 +2624,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -2729,8 +2696,6 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -2741,6 +2706,8 @@ releases: valsRuntime: valsRuntime, }, files) + fmt.Printf("CRAFTED APP WITH %p\n", app.fs.DirectoryExistsAt) + if err := app.Template(configImpl{}); err != nil { t.Fatalf("%v", err) } @@ -4282,8 +4249,7 @@ changing working directory back to "/path/to" app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -4465,8 +4431,7 @@ changing working directory back to "/path/to" app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -4523,8 +4488,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -4570,8 +4534,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -4631,8 +4594,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, @@ -4693,8 +4655,7 @@ releases: app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/desired_state_file_loader.go b/pkg/app/desired_state_file_loader.go index e29ec660..ffa11735 100644 --- a/pkg/app/desired_state_file_loader.go +++ b/pkg/app/desired_state_file_loader.go @@ -11,6 +11,7 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/state" @@ -27,14 +28,9 @@ type desiredStateLoader struct { env string namespace string chart string + fs *filesystem.FileSystem - readFile func(string) ([]byte, error) - deleteFile func(string) error - fileExists func(string) (bool, error) - abs func(string) (string, error) - glob func(string) ([]string, error) - directoryExistsAt func(string) bool - getHelm func(*state.HelmState) helmexec.Interface + getHelm func(*state.HelmState) helmexec.Interface remote *remote.Remote logger *zap.SugaredLogger @@ -50,8 +46,8 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e if opts.CalleePath == "" { return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts) } - storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob) - envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger, ld.remote) + storage := state.NewStorage(opts.CalleePath, ld.logger, ld.fs) + envld := state.NewEnvironmentValuesLoader(storage, ld.fs, ld.logger, ld.remote) handler := state.MissingFileHandlerError vals, err := envld.LoadEnvironmentValues(&handler, args, &environment.EmptyEnvironment) if err != nil { @@ -120,7 +116,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e f = filepath.Join(baseDir, file) } - fileBytes, err := ld.readFile(f) + fileBytes, err := ld.fs.ReadFile(f) if err != nil { return nil, err } @@ -166,8 +162,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e } func (a *desiredStateLoader) underlying() *state.StateCreator { - c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.directoryExistsAt, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote) - c.DeleteFile = a.deleteFile + c := state.NewCreator(a.logger, a.fs, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote) c.LoadFile = a.loadFile return c } diff --git a/pkg/app/destroy_nokubectx_test.go b/pkg/app/destroy_nokubectx_test.go index 97991f4c..41bfb742 100644 --- a/pkg/app/destroy_nokubectx_test.go +++ b/pkg/app/destroy_nokubectx_test.go @@ -4,13 +4,13 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -83,8 +83,7 @@ func TestDestroy_2(t *testing.T) { app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "", Env: "default", Logger: logger, diff --git a/pkg/app/destroy_test.go b/pkg/app/destroy_test.go index 786aa429..8a7a5234 100644 --- a/pkg/app/destroy_test.go +++ b/pkg/app/destroy_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -124,8 +124,7 @@ func TestDestroy(t *testing.T) { app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "default", Env: "default", Logger: logger, diff --git a/pkg/app/diff_nokubectx_test.go b/pkg/app/diff_nokubectx_test.go index c624fffe..87809c3b 100644 --- a/pkg/app/diff_nokubectx_test.go +++ b/pkg/app/diff_nokubectx_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -12,6 +11,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -1060,8 +1060,7 @@ changing working directory back to "/path/to" app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: "", Env: "default", Logger: logger, diff --git a/pkg/app/diff_test.go b/pkg/app/diff_test.go index b798079c..427f0ebd 100644 --- a/pkg/app/diff_test.go +++ b/pkg/app/diff_test.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "io" - "path/filepath" "sync" "testing" @@ -13,6 +12,7 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/exectest" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -1352,8 +1352,7 @@ changing working directory back to "/path/to" app := appWithFs(&App{ OverrideHelmBinary: DefaultHelmBinary, - glob: filepath.Glob, - abs: filepath.Abs, + fs: ffs.DefaultFileSystem(), OverrideKubeContext: overrideKubeContext, Env: "default", Logger: logger, diff --git a/pkg/app/two_pass_renderer.go b/pkg/app/two_pass_renderer.go index af441cda..670fe4ea 100644 --- a/pkg/app/two_pass_renderer.go +++ b/pkg/app/two_pass_renderer.go @@ -124,7 +124,7 @@ func (r *desiredStateLoader) twoPassRenderTemplateToYaml(inherited, overrode *en } tmplData := state.NewEnvironmentTemplateData(*finalEnv, r.namespace, vals) - secondPassRenderer := tmpl.NewFileRenderer(r.readFile, baseDir, tmplData) + secondPassRenderer := tmpl.NewFileRenderer(r.fs, baseDir, tmplData) yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content) if err != nil { if r.logger != nil { diff --git a/pkg/app/two_pass_renderer_test.go b/pkg/app/two_pass_renderer_test.go index 5c6732a9..06028a02 100644 --- a/pkg/app/two_pass_renderer_test.go +++ b/pkg/app/two_pass_renderer_test.go @@ -17,16 +17,13 @@ import ( func makeLoader(files map[string]string, env string) (*desiredStateLoader, *testhelper.TestFs) { testfs := testhelper.NewTestFs(files) logger := helmexec.NewLogger(os.Stdout, "debug") - r := remote.NewRemote(logger, testfs.Cwd, testfs.ReadFile, testfs.DirectoryExistsAt, testfs.FileExistsAt) + r := remote.NewRemote(logger, testfs.Cwd, testfs.ToFileSystem()) return &desiredStateLoader{ - env: env, - namespace: "namespace", - logger: helmexec.NewLogger(os.Stdout, "debug"), - readFile: testfs.ReadFile, - fileExists: testfs.FileExists, - abs: testfs.Abs, - glob: testfs.Glob, - remote: r, + env: env, + namespace: "namespace", + logger: helmexec.NewLogger(os.Stdout, "debug"), + fs: testfs.ToFileSystem(), + remote: r, }, testfs } diff --git a/pkg/event/bus.go b/pkg/event/bus.go index 284615cf..fe0b245c 100644 --- a/pkg/event/bus.go +++ b/pkg/event/bus.go @@ -7,6 +7,7 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/tmpl" ) @@ -35,9 +36,9 @@ type Bus struct { Chart string Env environment.Environment + Fs *filesystem.FileSystem - ReadFile func(string) ([]byte, error) - Logger *zap.SugaredLogger + Logger *zap.SugaredLogger } func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}) (bool, error) { @@ -100,7 +101,7 @@ func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{} for k, v := range context { data[k] = v } - render := tmpl.NewTextRenderer(bus.ReadFile, bus.BasePath, data) + render := tmpl.NewTextRenderer(bus.Fs, bus.BasePath, data) bus.Logger.Debugf("hook[%s]: triggered by event \"%s\"\n", name, evt) diff --git a/pkg/event/bus_test.go b/pkg/event/bus_test.go index 9cef6d89..b7e509cb 100644 --- a/pkg/event/bus_test.go +++ b/pkg/event/bus_test.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap/zaptest/observer" "github.com/helmfile/helmfile/pkg/environment" + ffs "github.com/helmfile/helmfile/pkg/filesystem" ) type runner struct { @@ -154,7 +155,7 @@ func TestTrigger(t *testing.T) { Namespace: "myns", Env: environment.Environment{Name: "prod"}, Logger: zeLogger, - ReadFile: readFile, + Fs: &ffs.FileSystem{ReadFile: readFile}, } bus.Runner = &runner{} diff --git a/pkg/filesystem/fs.go b/pkg/filesystem/fs.go new file mode 100644 index 00000000..cc003954 --- /dev/null +++ b/pkg/filesystem/fs.go @@ -0,0 +1,101 @@ +package filesystem + +import ( + "io/fs" + "os" + "path/filepath" +) + +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 + Stat func(string) (os.FileInfo, error) + Getwd func() (string, error) + Chdir func(string) error + Abs func(string) (string, error) +} + +func DefaultFileSystem() *FileSystem { + dfs := FileSystem{ + ReadFile: os.ReadFile, + ReadDir: os.ReadDir, + DeleteFile: os.Remove, + Stat: os.Stat, + Glob: filepath.Glob, + Getwd: os.Getwd, + Chdir: os.Chdir, + Abs: filepath.Abs, + } + + dfs.FileExistsAt = dfs.fileExistsAtDefault + dfs.DirectoryExistsAt = dfs.directoryExistsDefault + dfs.FileExists = dfs.fileExistsDefault + 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 + } + + return dfs +} + +func (filesystem *FileSystem) fileExistsAtDefault(path string) bool { + fileInfo, err := filesystem.Stat(path) + return err == nil && fileInfo.Mode().IsRegular() +} + +func (filesystem *FileSystem) fileExistsDefault(path string) (bool, error) { + _, 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 { + fileInfo, err := filesystem.Stat(path) + return err == nil && fileInfo.Mode().IsDir() +} diff --git a/pkg/filesystem/fs_test.go b/pkg/filesystem/fs_test.go new file mode 100644 index 00000000..3193dc95 --- /dev/null +++ b/pkg/filesystem/fs_test.go @@ -0,0 +1,98 @@ +package filesystem + +import ( + "errors" + "io/fs" + "os" + "strings" + "testing" + "time" +) + +type TestFileInfo struct { + mode fs.FileMode +} + +func (tfi TestFileInfo) Name() string { return "" } +func (tfi TestFileInfo) Size() int64 { return 0 } +func (tfi TestFileInfo) Mode() fs.FileMode { return tfi.mode } +func (tfi TestFileInfo) ModTime() time.Time { return time.Time{} } +func (tfi TestFileInfo) IsDir() bool { return tfi.mode.IsDir() } +func (tfi TestFileInfo) Sys() any { return nil } + +func NewTestFileSystem() FileSystem { + replaceffs := FileSystem{ + Stat: func(s string) (os.FileInfo, error) { + if strings.HasPrefix(s, "existing_file") { + return TestFileInfo{mode: 0}, nil + } + if strings.HasPrefix(s, "existing_dir") { + return TestFileInfo{mode: fs.ModeDir}, nil + } + return nil, errors.New("Error") + }, + } + return *FromFileSystem(replaceffs) +} + +func TestFs_fileExistsDefault(t *testing.T) { + ffs := NewTestFileSystem() + var exists, _ = ffs.FileExists("existing_file.txt") + if !exists { + t.Errorf("Expected file %s, not found", "existing_file.txt") + } + + exists, _ = ffs.FileExists("non_existing_file.txt") + if exists { + t.Errorf("Not expected file %s, found", "non_existing_file.txt") + } +} + +func TestFs_fileExistsAtDefault(t *testing.T) { + ffs := NewTestFileSystem() + + var exists = ffs.FileExistsAt("existing_file.txt") + if !exists { + t.Errorf("Expected file %s, not found", "existing_file.txt") + } + + exists = ffs.FileExistsAt("non_existing_file.txt") + if exists { + t.Errorf("Not expected file %s, found", "non_existing_file.txt") + } + + exists = ffs.FileExistsAt("existing_dir") + if exists { + t.Errorf("Not expected file %s, found", "existing_dir") + } +} + +func TestFs_directoryExistsDefault(t *testing.T) { + ffs := NewTestFileSystem() + var exists = ffs.DirectoryExistsAt("existing_dir") + if !exists { + t.Errorf("Expected file %s, not found", "existing_dir") + } + + exists = ffs.DirectoryExistsAt("not_existing_dir") + if exists { + t.Errorf("Not expected file %s, found", "existing_dir") + } +} + +func TestFs_DefaultBuilder(t *testing.T) { + ffs := DefaultFileSystem() + if ffs.ReadFile == nil || + ffs.ReadDir == nil || + ffs.DeleteFile == nil || + ffs.FileExists == nil || + ffs.Glob == nil || + ffs.FileExistsAt == nil || + ffs.DirectoryExistsAt == nil || + ffs.Stat == nil || + ffs.Getwd == nil || + ffs.Chdir == nil || + ffs.Abs == nil { + t.Errorf("Missing functions in DefaultFileSystem") + } +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index e903daff..ed58abcd 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -17,6 +17,7 @@ import ( "gopkg.in/yaml.v2" "github.com/helmfile/helmfile/pkg/envvar" + "github.com/helmfile/helmfile/pkg/filesystem" ) var disableInsecureFeatures bool @@ -43,11 +44,9 @@ type Remote struct { // Getter is the underlying implementation of getter used for fetching remote files Getter Getter - // ReadFile is the implementation of the file reader that reads a local file from the specified path. + // Filesystem abstraction // Inject any implementation of your choice, like an im-memory impl for testing, os.ReadFile for the real-world use. - ReadFile func(string) ([]byte, error) - DirExists func(string) bool - FileExists func(string) bool + fs *filesystem.FileSystem } func (r *Remote) Unmarshal(src string, dst interface{}) error { @@ -87,7 +86,7 @@ func (r *Remote) GetBytes(goGetterSrc string) ([]byte, error) { return nil, err } - bytes, err := r.ReadFile(f) + bytes, err := r.fs.ReadFile(f) if err != nil { return nil, fmt.Errorf("read file: %v", err) } @@ -99,7 +98,7 @@ func (r *Remote) GetBytes(goGetterSrc string) ([]byte, error) { // If the argument was an URL, it fetches the remote directory contained within the URL, // and returns the path to the file in the fetched directory func (r *Remote) Locate(urlOrPath string) (string, error) { - if r.FileExists(urlOrPath) || r.DirExists(urlOrPath) { + if r.fs.FileExistsAt(urlOrPath) || r.fs.DirectoryExistsAt(urlOrPath) { return urlOrPath, nil } fetched, err := r.Fetch(urlOrPath) @@ -217,11 +216,11 @@ func (r *Remote) Fetch(goGetterSrc string, cacheDirOpt ...string) (string, error r.Logger.Debugf("cached dir: %s", cacheDirPath) { - if r.FileExists(cacheDirPath) { + if r.fs.FileExistsAt(cacheDirPath) { return "", fmt.Errorf("%s is not directory. please remove it so that variant could use it for dependency caching", getterDst) } - if r.DirExists(cacheDirPath) { + if r.fs.DirectoryExistsAt(cacheDirPath) { cached = true } } @@ -285,17 +284,15 @@ func (g *GoGetter) Get(wd, src, dst string) error { return nil } -func NewRemote(logger *zap.SugaredLogger, homeDir string, readFile func(string) ([]byte, error), dirExists func(string) bool, fileExists func(string) bool) *Remote { +func NewRemote(logger *zap.SugaredLogger, homeDir string, fs *filesystem.FileSystem) *Remote { if disableInsecureFeatures { panic("Remote sources are disabled due to 'DISABLE_INSECURE_FEATURES'") } remote := &Remote{ - Logger: logger, - Home: homeDir, - Getter: &GoGetter{Logger: logger}, - ReadFile: readFile, - DirExists: dirExists, - FileExists: fileExists, + Logger: logger, + Home: homeDir, + Getter: &GoGetter{Logger: logger}, + fs: fs, } if remote.Home == "" { diff --git a/pkg/remote/remote_test.go b/pkg/remote/remote_test.go index 13eecfdf..506679eb 100644 --- a/pkg/remote/remote_test.go +++ b/pkg/remote/remote_test.go @@ -55,12 +55,10 @@ func TestRemote_HttpsGitHub(t *testing.T) { get: get, } remote := &Remote{ - Logger: helmexec.NewLogger(os.Stderr, "debug"), - Home: CacheDir(), - Getter: getter, - ReadFile: testfs.ReadFile, - FileExists: testfs.FileExistsAt, - DirExists: testfs.DirectoryExistsAt, + Logger: helmexec.NewLogger(os.Stderr, "debug"), + Home: CacheDir(), + Getter: getter, + fs: testfs.ToFileSystem(), } // FYI, go-getter in the `dir` mode accepts URL like the below. So helmfile expects URLs similar to it: @@ -133,12 +131,10 @@ func TestRemote_SShGitHub(t *testing.T) { get: get, } remote := &Remote{ - Logger: helmexec.NewLogger(os.Stderr, "debug"), - Home: CacheDir(), - Getter: getter, - ReadFile: testfs.ReadFile, - FileExists: testfs.FileExistsAt, - DirExists: testfs.DirectoryExistsAt, + Logger: helmexec.NewLogger(os.Stderr, "debug"), + Home: CacheDir(), + Getter: getter, + fs: testfs.ToFileSystem(), } url := "git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0" @@ -205,12 +201,10 @@ func TestRemote_SShGitHub_WithSshKey(t *testing.T) { get: get, } remote := &Remote{ - Logger: helmexec.NewLogger(os.Stderr, "debug"), - Home: CacheDir(), - Getter: getter, - ReadFile: testfs.ReadFile, - FileExists: testfs.FileExistsAt, - DirExists: testfs.DirectoryExistsAt, + Logger: helmexec.NewLogger(os.Stderr, "debug"), + Home: CacheDir(), + Getter: getter, + fs: testfs.ToFileSystem(), } url := "git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA=" diff --git a/pkg/state/chart_dependency.go b/pkg/state/chart_dependency.go index f38a2ccd..f317e040 100644 --- a/pkg/state/chart_dependency.go +++ b/pkg/state/chart_dependency.go @@ -146,8 +146,8 @@ func (st *HelmState) mergeLockedDependencies() (*HelmState, error) { depMan := NewChartDependencyManager(filename, st.logger) - if st.readFile != nil { - depMan.readFile = st.readFile + if st.fs.ReadFile != nil { + depMan.readFile = st.fs.ReadFile } return resolveDependencies(st, depMan, unresolved) diff --git a/pkg/state/create.go b/pkg/state/create.go index 50e35b4a..2829a678 100644 --- a/pkg/state/create.go +++ b/pkg/state/create.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "os" "github.com/imdario/mergo" "github.com/variantdev/vals" @@ -13,6 +12,7 @@ import ( "gopkg.in/yaml.v2" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/maputil" "github.com/helmfile/helmfile/pkg/remote" @@ -43,12 +43,7 @@ func (e *UndefinedEnvError) Error() string { type StateCreator struct { logger *zap.SugaredLogger - readFile func(string) ([]byte, error) - fileExists func(string) (bool, error) - abs func(string) (string, error) - glob func(string) ([]string, error) - DeleteFile func(string) error - directoryExistsAt func(string) bool + fs *filesystem.FileSystem valsRuntime vals.Evaluator @@ -63,17 +58,12 @@ type StateCreator struct { remote *remote.Remote } -func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), directoryExistsAt func(string) bool, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote) *StateCreator { +func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote) *StateCreator { return &StateCreator{ logger: logger, - readFile: readFile, - fileExists: fileExists, - abs: abs, - glob: glob, - directoryExistsAt: directoryExistsAt, - Strict: true, + fs: fs, valsRuntime: valsRuntime, getHelm: getHelm, @@ -87,6 +77,7 @@ func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error) func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState, error) { var state HelmState + state.fs = c.fs state.FilePath = file state.basePath = baseDir @@ -132,12 +123,6 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState, } state.logger = c.logger - - state.readFile = c.readFile - state.removeFile = os.Remove - state.fileExists = c.fileExists - state.glob = c.glob - state.directoryExistsAt = c.directoryExistsAt state.valsRuntime = c.valsRuntime return &state, nil @@ -147,7 +132,7 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState, func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *environment.Environment, failOnMissingEnv bool) (*HelmState, error) { state := *target - e, err := c.loadEnvValues(&state, env, failOnMissingEnv, ctxEnv, c.readFile, c.glob) + e, err := c.loadEnvValues(&state, env, failOnMissingEnv, ctxEnv) if err != nil { return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err} } @@ -222,7 +207,7 @@ func (c *StateCreator) loadBases(envValues *environment.Environment, st *HelmSta } // nolint: unparam -func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv *environment.Environment, readFile func(string) ([]byte, error), glob func(string) ([]string, error)) (*environment.Environment, error) { +func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv *environment.Environment) (*environment.Environment, error) { envVals := map[string]interface{}{} envSpec, ok := st.Environments[name] if ok { @@ -245,7 +230,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn envSecretFiles = append(envSecretFiles, resolved...) } - if err = c.scatterGatherEnvSecretFiles(st, envSecretFiles, envVals, readFile); err != nil { + if err = c.scatterGatherEnvSecretFiles(st, envSecretFiles, envVals); err != nil { return nil, err } } @@ -268,7 +253,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn return newEnv, nil } -func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]interface{}, readFile func(string) ([]byte, error)) error { +func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]interface{}) error { var errs []error helm := c.getHelm(st) @@ -308,11 +293,11 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles } // nolint: staticcheck defer func() { - if err := c.DeleteFile(decFile); err != nil { + if err := c.fs.DeleteFile(decFile); err != nil { c.logger.Warnf("removing decrypted file %s: %w", decFile, err) } }() - bytes, err := readFile(decFile) + bytes, err := c.fs.ReadFile(decFile) if err != nil { results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path} continue @@ -367,7 +352,7 @@ func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []int var envVals map[string]interface{} valuesEntries := append([]interface{}{}, entries...) - ld := NewEnvironmentValuesLoader(st.storage(), st.readFile, st.logger, remote) + ld := NewEnvironmentValuesLoader(st.storage(), st.fs, st.logger, remote) var err error envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv) if err != nil { diff --git a/pkg/state/create_test.go b/pkg/state/create_test.go index 21c277e9..83b17a12 100644 --- a/pkg/state/create_test.go +++ b/pkg/state/create_test.go @@ -1,7 +1,6 @@ package state import ( - "os" "path/filepath" "reflect" "testing" @@ -10,18 +9,16 @@ import ( "go.uber.org/zap" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/testhelper" ) func createFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) { c := &StateCreator{ - logger: logger, - readFile: os.ReadFile, - abs: filepath.Abs, - - DeleteFile: os.Remove, - Strict: true, + logger: logger, + fs: filesystem.DefaultFileSystem(), + Strict: true, } return c.ParseAndLoad(content, filepath.Dir(file), file, env, true, nil) } @@ -81,8 +78,8 @@ func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *H t.Fatalf("no file named %q registered", file) } - r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) - state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r). + r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem()) + state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r). ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -148,11 +145,11 @@ releaseNamespace: mynamespace }) testFs.Cwd = "/example/path/to" - r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) + r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem()) env := environment.Environment{ Name: "production", } - state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r). + state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r). ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, &env) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -238,8 +235,8 @@ overrideNamespace: myns }) testFs.Cwd = "/example/path/to" - r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) - state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r). + r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem()) + state, err := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", r). ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil) if err != nil { t.Fatalf("unexpected error: %v", err) diff --git a/pkg/state/envvals_loader.go b/pkg/state/envvals_loader.go index c11bd334..318d189b 100644 --- a/pkg/state/envvals_loader.go +++ b/pkg/state/envvals_loader.go @@ -9,6 +9,7 @@ import ( "gopkg.in/yaml.v2" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/maputil" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/tmpl" @@ -17,19 +18,19 @@ import ( type EnvironmentValuesLoader struct { storage *Storage - readFile func(string) ([]byte, error) + fs *filesystem.FileSystem logger *zap.SugaredLogger remote *remote.Remote } -func NewEnvironmentValuesLoader(storage *Storage, readFile func(string) ([]byte, error), logger *zap.SugaredLogger, remote *remote.Remote) *EnvironmentValuesLoader { +func NewEnvironmentValuesLoader(storage *Storage, fs *filesystem.FileSystem, logger *zap.SugaredLogger, remote *remote.Remote) *EnvironmentValuesLoader { return &EnvironmentValuesLoader{ - storage: storage, - readFile: readFile, - logger: logger, - remote: remote, + storage: storage, + fs: fs, + logger: logger, + remote: remote, } } @@ -64,7 +65,7 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str } tmplData := NewEnvironmentTemplateData(env, "", map[string]interface{}{}) - r := tmpl.NewFileRenderer(ld.readFile, filepath.Dir(f), tmplData) + r := tmpl.NewFileRenderer(ld.fs, filepath.Dir(f), tmplData) bytes, err := r.RenderToBytes(f) if err != nil { return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", f, err) diff --git a/pkg/state/envvals_loader_test.go b/pkg/state/envvals_loader_test.go index c162b818..5de0234a 100644 --- a/pkg/state/envvals_loader_test.go +++ b/pkg/state/envvals_loader_test.go @@ -1,13 +1,12 @@ package state import ( - "os" - "path/filepath" "testing" "github.com/google/go-cmp/cmp" "go.uber.org/zap" + ffs "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/remote" ) @@ -22,14 +21,11 @@ func newLoader() *EnvironmentValuesLoader { storage := &Storage{ FilePath: "./helmfile.yaml", basePath: ".", - glob: filepath.Glob, + fs: ffs.DefaultFileSystem(), logger: sugar, } - readFile := func(s string) ([]byte, error) { return []byte{}, nil } - dirExists := func(d string) bool { return false } - fileExists := func(f string) bool { return false } - return NewEnvironmentValuesLoader(storage, os.ReadFile, sugar, remote.NewRemote(sugar, "/tmp", readFile, dirExists, fileExists)) + return NewEnvironmentValuesLoader(storage, storage.fs, sugar, remote.NewRemote(sugar, "/tmp", storage.fs)) } // See https://github.com/roboll/helmfile/pull/1169 diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index 7fcfa52f..b1a46508 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -26,16 +26,6 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) ([]s return flags, nil } -func fileExistsAt(path string) bool { - fileInfo, err := os.Stat(path) - return err == nil && fileInfo.Mode().IsRegular() -} - -func directoryExistsAt(path string) bool { - fileInfo, err := os.Stat(path) - return err == nil && fileInfo.Mode().IsDir() -} - type Chartify struct { Opts *chartify.ChartifyOpts Clean func() @@ -70,7 +60,7 @@ func (st *HelmState) goGetterChart(chart, dir, cacheDir string, force bool) (str return "", fmt.Errorf("Parsing url from dir failed due to error %q.\nContinuing the process assuming this is a regular Helm chart or a local dir.", err.Error()) } } else { - r := remote.NewRemote(st.logger, "", st.readFile, directoryExistsAt, fileExistsAt) + r := remote.NewRemote(st.logger, "", st.fs) fetchedDir, err := r.Fetch(chart, cacheDir) if err != nil { @@ -107,14 +97,14 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp dir = filepath.Join(st.basePath, chart) } if stat, _ := os.Stat(dir); stat != nil && stat.IsDir() { - if exists, err := st.fileExists(filepath.Join(dir, "Chart.yaml")); err == nil && !exists { + if exists, err := st.fs.FileExists(filepath.Join(dir, "Chart.yaml")); err == nil && !exists { shouldRun = true } } for _, d := range release.Dependencies { chart := d.Chart - if st.directoryExistsAt(chart) { + if st.fs.DirectoryExistsAt(chart) { var err error // Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like: diff --git a/pkg/state/state.go b/pkg/state/state.go index 8a7efe1c..38d00e5d 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -26,6 +26,7 @@ import ( "github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/event" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/tmpl" @@ -92,14 +93,9 @@ type HelmState struct { ReleaseSetSpec `yaml:",inline"` - logger *zap.SugaredLogger - - readFile func(string) ([]byte, error) - removeFile func(string) error - fileExists func(string) (bool, error) - glob func(string) ([]string, error) - tempDir func(string, string) (string, error) - directoryExistsAt func(string) bool + logger *zap.SugaredLogger + fs *filesystem.FileSystem + tempDir func(string, string) (string, error) valsRuntime vals.Evaluator @@ -553,7 +549,7 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu errs = append(errs, newReleaseFailedError(release, err)) } - ok, err := st.fileExists(valfile) + ok, err := st.fs.FileExists(valfile) if err != nil { errs = append(errs, newReleaseFailedError(release, err)) } else if !ok { @@ -1118,7 +1114,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre } } - isLocal := st.directoryExistsAt(normalizeChart(st.basePath, chartName)) + isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName)) chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex) if !opts.SkipCleanup { @@ -1172,7 +1168,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre // Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is // explicitly skipped. buildDeps = !skipDeps - } else if normalizedChart := normalizeChart(st.basePath, chartPath); st.directoryExistsAt(normalizedChart) { + } else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) { // At this point, we are sure that chartPath is a local directory containing either: // - A remote chart fetched by go-getter or // - A local chart @@ -1505,7 +1501,7 @@ func (st *HelmState) WriteReleasesValues(helm helmexec.Interface, additionalValu for _, f := range append(generatedFiles, additionalValues...) { src := map[string]interface{}{} - srcBytes, err := st.readFile(f) + srcBytes, err := st.fs.ReadFile(f) if err != nil { return []error{fmt.Errorf("reading %s: %w", f, err)} } @@ -2236,7 +2232,7 @@ func (st *HelmState) triggerGlobalReleaseEvent(evt string, evtErr error, helmfil Chart: st.OverrideChart, Env: st.Env, Logger: st.logger, - ReadFile: st.readFile, + Fs: st.fs, } data := map[string]interface{}{ "HelmfileCommand": helmfileCmd, @@ -2269,7 +2265,7 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe Chart: st.OverrideChart, Env: st.Env, Logger: st.logger, - ReadFile: st.readFile, + Fs: st.fs, } vals := st.Values() data := map[string]interface{}{ @@ -2307,7 +2303,7 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds var errs []error for _, release := range releases { - if st.directoryExistsAt(release.ChartPathOrName()) { + if st.fs.DirectoryExistsAt(release.ChartPathOrName()) { if err := helm.UpdateDeps(release.ChartPathOrName()); err != nil { errs = append(errs, err) } @@ -2631,7 +2627,7 @@ func (st *HelmState) newReleaseTemplateData(release *ReleaseSpec) releaseTemplat } func (st *HelmState) newReleaseTemplateFuncMap(dir string) template.FuncMap { - r := tmpl.NewFileRenderer(st.readFile, dir, nil) + r := tmpl.NewFileRenderer(st.fs, dir, nil) return r.Context.CreateFuncMap() } @@ -2639,7 +2635,7 @@ func (st *HelmState) newReleaseTemplateFuncMap(dir string) template.FuncMap { func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) { templateData := st.newReleaseTemplateData(release) - r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), templateData) + r := tmpl.NewFileRenderer(st.fs, filepath.Dir(path), templateData) rawBytes, err := r.RenderToBytes(path) if err != nil { return nil, err @@ -2673,8 +2669,8 @@ func (st *HelmState) storage() *Storage { return &Storage{ FilePath: st.FilePath, basePath: st.basePath, - glob: st.glob, logger: st.logger, + fs: st.fs, } } @@ -2710,7 +2706,7 @@ func (st *HelmState) ExpandedHelmfiles() ([]SubHelmfileSpec, error) { func (st *HelmState) removeFiles(files []string) { for _, f := range files { - if err := st.removeFile(f); err != nil { + if err := st.fs.DeleteFile(f); err != nil { st.logger.Warnf("Removing %s: %v", err) } else { st.logger.Debugf("Removed %s", f) diff --git a/pkg/state/state_exec_tmpl.go b/pkg/state/state_exec_tmpl.go index 3ad14c79..d63fe115 100644 --- a/pkg/state/state_exec_tmpl.go +++ b/pkg/state/state_exec_tmpl.go @@ -110,7 +110,7 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) { successFlag := false for it, prev := 0, &release; it < 6; it++ { tmplData := st.createReleaseTemplateData(prev, vals) - renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData) + renderer := tmpl.NewFileRenderer(st.fs, st.basePath, tmplData) r, err := release.ExecuteTemplateExpressions(renderer) if err != nil { return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err) diff --git a/pkg/state/state_gogetter_test.go b/pkg/state/state_gogetter_test.go index c7bda8b8..1c199975 100644 --- a/pkg/state/state_gogetter_test.go +++ b/pkg/state/state_gogetter_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-cmp/cmp" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" ) @@ -39,7 +40,7 @@ func TestGoGetter(t *testing.T) { st := &HelmState{ logger: logger, - readFile: os.ReadFile, + fs: filesystem.DefaultFileSystem(), basePath: d, } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 93aed06c..db8cec6c 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -11,6 +11,7 @@ import ( "github.com/variantdev/vals" "github.com/helmfile/helmfile/pkg/exectest" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/testhelper" ) @@ -19,10 +20,7 @@ var logger = helmexec.NewLogger(os.Stdout, "warn") var valsRuntime, _ = vals.New(vals.Options{CacheSize: 32}) func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState { - st.glob = fs.Glob - st.readFile = fs.ReadFile - st.fileExists = fs.FileExists - st.directoryExistsAt = fs.DirectoryExistsAt + st.fs = fs.ToFileSystem() return st } @@ -1715,17 +1713,17 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { ReleaseSetSpec: ReleaseSetSpec{ Releases: tt.releases, }, - logger: logger, - valsRuntime: valsRuntime, - removeFile: func(f string) error { - numRemovedFiles += 1 - return nil - }, + logger: logger, + valsRuntime: valsRuntime, RenderedValues: map[string]interface{}{}, } testfs := testhelper.NewTestFs(map[string]string{ "/path/to/someFile": `foo: FOO`, }) + testfs.DeleteFile = func(f string) error { + numRemovedFiles += 1 + return nil + } state = injectFs(state, testfs) if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 { t.Errorf("unexpected errors: %v", errs) @@ -1802,18 +1800,18 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { ReleaseSetSpec: ReleaseSetSpec{ Releases: tt.releases, }, - logger: logger, - valsRuntime: valsRuntime, - removeFile: func(f string) error { - numRemovedFiles += 1 - return nil - }, + logger: logger, + valsRuntime: valsRuntime, RenderedValues: map[string]interface{}{}, } testfs := testhelper.NewTestFs(map[string]string{ "/path/to/someFile": `foo: bar `, }) + testfs.DeleteFile = func(f string) error { + numRemovedFiles += 1 + return nil + } state = injectFs(state, testfs) if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, []string{}, false, false, false, false); len(errs) > 0 { t.Errorf("unexpected errors: %v", errs) @@ -1960,11 +1958,13 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) { }, }, logger: logger, - readFile: func(f string) ([]byte, error) { - if f != "helmfile.lock" { - return nil, fmt.Errorf("stub: unexpected file: %s", f) - } - return nil, os.ErrNotExist + fs: &filesystem.FileSystem{ + ReadFile: func(f string) ([]byte, error) { + if f != "helmfile.lock" { + return nil, fmt.Errorf("stub: unexpected file: %s", f) + } + return nil, os.ErrNotExist + }, }, } @@ -2053,17 +2053,19 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { Releases: tt.releases, }, logger: logger, - fileExists: func(f string) (bool, error) { - if f != "foo.yaml" { - return false, fmt.Errorf("unexpected file: %s", f) - } - return true, nil - }, - readFile: func(f string) ([]byte, error) { - if f != "foo.yaml" { - return nil, fmt.Errorf("unexpected file: %s", f) - } - return []byte{}, nil + fs: &filesystem.FileSystem{ + FileExists: func(f string) (bool, error) { + if f != "foo.yaml" { + return false, fmt.Errorf("unexpected file: %s", f) + } + return true, nil + }, + ReadFile: func(f string) ([]byte, error) { + if f != "foo.yaml" { + return nil, fmt.Errorf("unexpected file: %s", f) + } + return []byte{}, nil + }, }, } errs := state.ReleaseStatuses(tt.helm, 1) diff --git a/pkg/state/storage.go b/pkg/state/storage.go index 321ad1d2..a96b8817 100644 --- a/pkg/state/storage.go +++ b/pkg/state/storage.go @@ -8,6 +8,7 @@ import ( "go.uber.org/zap" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/remote" ) @@ -16,17 +17,16 @@ type Storage struct { FilePath string - readFile func(string) ([]byte, error) basePath string - glob func(string) ([]string, error) + fs *filesystem.FileSystem } -func NewStorage(forFile string, logger *zap.SugaredLogger, glob func(string) ([]string, error)) *Storage { +func NewStorage(forFile string, logger *zap.SugaredLogger, fs *filesystem.FileSystem) *Storage { return &Storage{ FilePath: forFile, basePath: filepath.Dir(forFile), logger: logger, - glob: glob, + fs: fs, } } @@ -36,14 +36,14 @@ func (st *Storage) resolveFile(missingFileHandler *string, tpe, path string) ([] var files []string var err error if remote.IsRemote(path) { - r := remote.NewRemote(st.logger, "", st.readFile, directoryExistsAt, fileExistsAt) + r := remote.NewRemote(st.logger, "", st.fs) fetchedFilePath, err := r.Fetch(path, "values") if err != nil { return nil, false, err } - if fileExistsAt(fetchedFilePath) { + if st.fs.FileExistsAt(fetchedFilePath) { files = []string{fetchedFilePath} } } else { @@ -92,7 +92,7 @@ func (st *Storage) resolveFile(missingFileHandler *string, tpe, path string) ([] func (st *Storage) ExpandPaths(globPattern string) ([]string, error) { result := []string{} absPathPattern := st.normalizePath(globPattern) - matches, err := st.glob(absPathPattern) + matches, err := st.fs.Glob(absPathPattern) if err != nil { return nil, fmt.Errorf("failed processing %s: %v", globPattern, err) } diff --git a/pkg/state/storage_test.go b/pkg/state/storage_test.go index 225e1e54..eb9ec74f 100644 --- a/pkg/state/storage_test.go +++ b/pkg/state/storage_test.go @@ -3,10 +3,10 @@ package state import ( "fmt" "os" - "path/filepath" "reflect" "testing" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" ) @@ -73,7 +73,7 @@ func TestStorage_resolveFile(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - st := NewStorage(cacheDir, helmexec.NewLogger(os.Stderr, "debug"), filepath.Glob) + st := NewStorage(cacheDir, helmexec.NewLogger(os.Stderr, "debug"), filesystem.DefaultFileSystem()) files, skipped, err := st.resolveFile(tt.args.missingFileHandler, tt.args.title, tt.args.path) if (err != nil) != tt.wantErr { diff --git a/pkg/testhelper/testfs.go b/pkg/testhelper/testfs.go index 2b43b97b..7c4f779e 100644 --- a/pkg/testhelper/testfs.go +++ b/pkg/testhelper/testfs.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + ffs "github.com/helmfile/helmfile/pkg/filesystem" ) type TestFs struct { @@ -13,6 +15,7 @@ type TestFs struct { files map[string]string GlobFixtures map[string][]string + DeleteFile func(string) error fileReaderCalls int successfulReads []string @@ -34,9 +37,26 @@ func NewTestFs(files map[string]string) *TestFs { successfulReads: []string{}, GlobFixtures: map[string][]string{}, + DeleteFile: func(string) (ret error) { return }, } } +func (f *TestFs) ToFileSystem() *ffs.FileSystem { + curfs := ffs.FileSystem{ + FileExistsAt: f.FileExistsAt, + FileExists: f.FileExists, + DirectoryExistsAt: f.DirectoryExistsAt, + ReadFile: f.ReadFile, + Glob: f.Glob, + Getwd: f.Getwd, + Chdir: f.Chdir, + Abs: f.Abs, + DeleteFile: f.DeleteFile, + } + trfs := ffs.FromFileSystem(curfs) + return trfs +} + func (f *TestFs) FileExistsAt(path string) bool { var ok bool if strings.HasPrefix(path, "/") { diff --git a/pkg/tmpl/context.go b/pkg/tmpl/context.go index 2b7ff38a..ee0cb2d2 100644 --- a/pkg/tmpl/context.go +++ b/pkg/tmpl/context.go @@ -1,12 +1,13 @@ package tmpl -import "io/fs" +import ( + "github.com/helmfile/helmfile/pkg/filesystem" +) type Context struct { preRender bool basePath string - readFile func(string) ([]byte, error) - readDir func(string) ([]fs.DirEntry, error) + fs *filesystem.FileSystem } // SetBasePath sets the base path for the template @@ -14,10 +15,6 @@ func (c *Context) SetBasePath(path string) { c.basePath = path } -func (c *Context) SetReadFile(f func(string) ([]byte, error)) { - c.readFile = f -} - -func (c *Context) SetReadDir(f func(string) ([]fs.DirEntry, error)) { - c.readDir = f +func (c *Context) SetFileSystem(fs *filesystem.FileSystem) { + c.fs = fs } diff --git a/pkg/tmpl/context_funcs.go b/pkg/tmpl/context_funcs.go index aa4ffa9d..f8c79caf 100644 --- a/pkg/tmpl/context_funcs.go +++ b/pkg/tmpl/context_funcs.go @@ -220,11 +220,11 @@ func (c *Context) ReadFile(filename string) (string, error) { path = filepath.Join(c.basePath, filename) } - if c.readFile == nil { + if c.fs.ReadFile == nil { return "", fmt.Errorf("readFile is not implemented") } - bytes, err := c.readFile(path) + bytes, err := c.fs.ReadFile(path) if err != nil { return "", err } @@ -239,7 +239,7 @@ func (c *Context) ReadDir(path string) ([]string, error) { contextPath = filepath.Join(c.basePath, path) } - entries, err := c.readDir(contextPath) + entries, err := c.fs.ReadDir(contextPath) if err != nil { return nil, fmt.Errorf("ReadDir %q: %w", contextPath, err) } @@ -262,7 +262,7 @@ func (c *Context) ReadDirEntries(path string) ([]fs.DirEntry, error) { } else { contextPath = filepath.Join(c.basePath, path) } - entries, err := c.readDir(contextPath) + entries, err := c.fs.ReadDir(contextPath) if err != nil { return nil, fmt.Errorf("ReadDirEntries %q: %w", contextPath, err) } diff --git a/pkg/tmpl/context_funcs_test.go b/pkg/tmpl/context_funcs_test.go index 6cad20ea..3ce04824 100644 --- a/pkg/tmpl/context_funcs_test.go +++ b/pkg/tmpl/context_funcs_test.go @@ -9,6 +9,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/helmfile/helmfile/pkg/filesystem" ) func TestCreateFuncMap(t *testing.T) { @@ -62,17 +64,23 @@ func TestCreateFuncMap_SkipInsecureTemplateFunctions(t *testing.T) { skipInsecureTemplateFunctions = currentVal } +func newFSExpecting(expectedFilename string, expected string) *filesystem.FileSystem { + return filesystem.FromFileSystem(filesystem.FileSystem{ + ReadFile: func(filename string) ([]byte, error) { + if filename != expectedFilename { + return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) + } + return []byte(expected), nil + }, + }) +} + func TestReadFile(t *testing.T) { expected := `foo: bar: BAR ` expectedFilename := "values.yaml" - ctx := &Context{basePath: ".", readFile: func(filename string) ([]byte, error) { - if filename != expectedFilename { - return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) - } - return []byte(expected), nil - }} + ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)} actual, err := ctx.ReadFile(expectedFilename) require.NoError(t, err) require.Equal(t, expected, actual) @@ -125,12 +133,12 @@ func TestReadDir(t *testing.T) { } expectedDirname := "sampleDirectory" - ctx := &Context{basePath: ".", readDir: func(dirname string) ([]fs.DirEntry, error) { + ctx := &Context{basePath: ".", fs: filesystem.FromFileSystem(filesystem.FileSystem{ReadDir: func(dirname string) ([]fs.DirEntry, error) { if dirname != expectedDirname { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname) } return result, nil - }} + }})} actual, err := ctx.ReadDir(expectedDirname) require.NoError(t, err) @@ -146,12 +154,12 @@ func TestReadDirEntries(t *testing.T) { } expectedDirname := "sampleDirectory" - ctx := &Context{basePath: ".", readDir: func(dirname string) ([]fs.DirEntry, error) { + ctx := &Context{basePath: ".", fs: filesystem.FromFileSystem(filesystem.FileSystem{ReadDir: func(dirname string) ([]fs.DirEntry, error) { if dirname != expectedDirname { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname) } return result, nil - }} + }})} actual, err := ctx.ReadDirEntries(expectedDirname) require.NoError(t, err) @@ -163,12 +171,7 @@ func TestReadFile_PassAbsPath(t *testing.T) { bar: BAR ` expectedFilename, _ := filepath.Abs("values.yaml") - ctx := &Context{basePath: ".", readFile: func(filename string) ([]byte, error) { - if filename != expectedFilename { - return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) - } - return []byte(expected), nil - }} + ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)} actual, err := ctx.ReadFile(expectedFilename) require.NoError(t, err) require.Equal(t, actual, expected) diff --git a/pkg/tmpl/context_tmpl_test.go b/pkg/tmpl/context_tmpl_test.go index 921712cf..36cf8bea 100644 --- a/pkg/tmpl/context_tmpl_test.go +++ b/pkg/tmpl/context_tmpl_test.go @@ -4,6 +4,8 @@ import ( "fmt" "reflect" "testing" + + ffs "github.com/helmfile/helmfile/pkg/filesystem" ) func TestRenderTemplate_Values(t *testing.T) { @@ -14,12 +16,12 @@ func TestRenderTemplate_Values(t *testing.T) { bar: FOO_BAR ` expectedFilename := "values.yaml" - ctx := &Context{readFile: func(filename string) ([]byte, error) { + ctx := &Context{fs: &ffs.FileSystem{ReadFile: func(filename string) ([]byte, error) { if filename != expectedFilename { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) } return []byte(valuesYamlContent), nil - }} + }}} buf, err := ctx.RenderTemplateToBuffer(`{{ readFile "values.yaml" | fromYaml | setValueAtPath "foo.bar" "FOO_BAR" | toYaml }}`) if err != nil { t.Errorf("unexpected error: %v", err) @@ -43,12 +45,12 @@ func TestRenderTemplate_WithData(t *testing.T) { "bar": "FOO_BAR", }, } - ctx := &Context{readFile: func(filename string) ([]byte, error) { + ctx := &Context{fs: &ffs.FileSystem{ReadFile: func(filename string) ([]byte, error) { if filename != expectedFilename { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) } return []byte(valuesYamlContent), nil - }} + }}} buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) if err != nil { t.Errorf("unexpected error: %v", err) @@ -68,12 +70,12 @@ func TestRenderTemplate_AccessingMissingKeyWithGetOrNil(t *testing.T) { ` expectedFilename := "values.yaml" data := map[string]interface{}{} - ctx := &Context{readFile: func(filename string) ([]byte, error) { + ctx := &Context{fs: &ffs.FileSystem{ReadFile: func(filename string) ([]byte, error) { if filename != expectedFilename { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) } return []byte(valuesYamlContent), nil - }} + }}} buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) if err != nil { t.Errorf("unexpected error: %v", err) @@ -93,12 +95,12 @@ func TestRenderTemplate_Defaulting(t *testing.T) { ` expectedFilename := "values.yaml" data := map[string]interface{}{} - ctx := &Context{readFile: func(filename string) ([]byte, error) { + ctx := &Context{fs: &ffs.FileSystem{ReadFile: func(filename string) ([]byte, error) { if filename != expectedFilename { return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) } return []byte(valuesYamlContent), nil - }} + }}} buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) if err != nil { t.Errorf("unexpected error: %v", err) @@ -110,9 +112,9 @@ func TestRenderTemplate_Defaulting(t *testing.T) { } func renderTemplateToString(s string, data ...interface{}) (string, error) { - ctx := &Context{readFile: func(filename string) ([]byte, error) { + ctx := &Context{fs: &ffs.FileSystem{ReadFile: func(filename string) ([]byte, error) { return nil, fmt.Errorf("unexpected call to readFile: filename=%s", filename) - }} + }}} tplString, err := ctx.RenderTemplateToBuffer(s, data...) if err != nil { return "", err diff --git a/pkg/tmpl/file_renderer.go b/pkg/tmpl/file_renderer.go index 563bb1c2..df1756d4 100644 --- a/pkg/tmpl/file_renderer.go +++ b/pkg/tmpl/file_renderer.go @@ -3,43 +3,43 @@ package tmpl import ( "bytes" "fmt" - "os" "strings" + + "github.com/helmfile/helmfile/pkg/filesystem" ) type FileRenderer struct { - ReadFile func(string) ([]byte, error) - Context *Context - Data interface{} + fs *filesystem.FileSystem + Context *Context + Data interface{} } -func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, data interface{}) *FileRenderer { +func NewFileRenderer(fs *filesystem.FileSystem, basePath string, data interface{}) *FileRenderer { return &FileRenderer{ - ReadFile: readFile, + fs: fs, Context: &Context{ basePath: basePath, - readFile: readFile, - readDir: os.ReadDir, + fs: fs, }, Data: data, } } func NewFirstPassRenderer(basePath string, data interface{}) *FileRenderer { + fs := filesystem.DefaultFileSystem() return &FileRenderer{ - ReadFile: os.ReadFile, + fs: fs, Context: &Context{ preRender: true, basePath: basePath, - readFile: os.ReadFile, - readDir: os.ReadDir, + fs: fs, }, Data: data, } } func (r *FileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) { - content, err := r.ReadFile(file) + content, err := r.fs.ReadFile(file) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func (r *FileRenderer) RenderToBytes(path string) ([]byte, error) { yamlBytes = yamlBuf.Bytes() } else { var err error - yamlBytes, err = r.ReadFile(path) + yamlBytes, err = r.fs.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to load [%s]: %v", path, err) } diff --git a/pkg/tmpl/file_renderer_test.go b/pkg/tmpl/file_renderer_test.go index cd9d71ea..d1fd49c1 100644 --- a/pkg/tmpl/file_renderer_test.go +++ b/pkg/tmpl/file_renderer_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/helmfile/helmfile/pkg/environment" + "github.com/helmfile/helmfile/pkg/filesystem" ) var emptyEnvTmplData = map[string]interface{}{ @@ -23,7 +24,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) { ` dataFile := "data.txt" valuesTmplFile := "values.yaml.gotmpl" - r := NewFileRenderer(func(filename string) ([]byte, error) { + r := NewFileRenderer(&filesystem.FileSystem{ReadFile: func(filename string) ([]byte, error) { switch filename { case valuesTmplFile: return []byte(valuesYamlTmplContent), nil @@ -31,7 +32,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) { return []byte(dataFileContent), nil } return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename) - }, "", emptyEnvTmplData) + }}, "", emptyEnvTmplData) buf, err := r.RenderToBytes(valuesTmplFile) if err != nil { t.Errorf("unexpected error: %v", err) @@ -50,12 +51,12 @@ func TestRenderToBytes_Yaml(t *testing.T) { bar: '{{ readFile "data.txt" }}' ` valuesFile := "values.yaml" - r := NewFileRenderer(func(filename string) ([]byte, error) { + r := NewFileRenderer(&filesystem.FileSystem{ReadFile: func(filename string) ([]byte, error) { if filename == valuesFile { return []byte(valuesYamlContent), nil } return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename) - }, "", emptyEnvTmplData) + }}, "", emptyEnvTmplData) buf, err := r.RenderToBytes(valuesFile) if err != nil { t.Errorf("unexpected error: %v", err) diff --git a/pkg/tmpl/text_renderer.go b/pkg/tmpl/text_renderer.go index 266d7081..1f95e640 100644 --- a/pkg/tmpl/text_renderer.go +++ b/pkg/tmpl/text_renderer.go @@ -1,5 +1,9 @@ package tmpl +import ( + "github.com/helmfile/helmfile/pkg/filesystem" +) + type templateTextRenderer struct { ReadText func(string) ([]byte, error) Context *Context @@ -11,12 +15,12 @@ type TextRenderer interface { } // nolint: golint -func NewTextRenderer(readFile func(filename string) ([]byte, error), basePath string, data interface{}) *templateTextRenderer { +func NewTextRenderer(fs *filesystem.FileSystem, basePath string, data interface{}) *templateTextRenderer { return &templateTextRenderer{ - ReadText: readFile, + ReadText: fs.ReadFile, Context: &Context{ basePath: basePath, - readFile: readFile, + fs: fs, }, Data: data, } diff --git a/pkg/tmpl/text_renderer_test.go b/pkg/tmpl/text_renderer_test.go index 4947c8ce..a15e3b53 100644 --- a/pkg/tmpl/text_renderer_test.go +++ b/pkg/tmpl/text_renderer_test.go @@ -1,10 +1,11 @@ package tmpl import ( - "os" "testing" "github.com/stretchr/testify/require" + + "github.com/helmfile/helmfile/pkg/filesystem" ) // TestTextRenderer tests the text renderer. @@ -12,7 +13,7 @@ func TestNewTextRenderer(t *testing.T) { tData := map[string]interface{}{ "foo": "bar", } - tr := NewTextRenderer(os.ReadFile, ".", tData) + tr := NewTextRenderer(filesystem.DefaultFileSystem(), ".", tData) require.Equal(t, tData, tr.Data) require.Equal(t, ".", tr.Context.basePath) } @@ -22,7 +23,7 @@ func TestTextRender(t *testing.T) { tData := map[string]interface{}{ "foot": "bart", } - tr := NewTextRenderer(os.ReadFile, ".", tData) + tr := NewTextRenderer(filesystem.DefaultFileSystem(), ".", tData) tests := []struct { text string diff --git a/test/e2e/template/helmfile/tmpl_test.go b/test/e2e/template/helmfile/tmpl_test.go index 8aa7c105..29f5b354 100644 --- a/test/e2e/template/helmfile/tmpl_test.go +++ b/test/e2e/template/helmfile/tmpl_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/tmpl" ) @@ -276,7 +277,7 @@ func TestFileRendering(t *testing.T) { filename := fmt.Sprintf("%s/%s.gotmpl", tempDir, tc.name) os.WriteFile(filename, []byte(tc.tmplString), 0644) - fileRenderer := tmpl.NewFileRenderer(os.ReadFile, ".", tc.data) + fileRenderer := tmpl.NewFileRenderer(filesystem.DefaultFileSystem(), ".", tc.data) tmpl_bytes, err := fileRenderer.RenderToBytes(filename) if tc.wantErr { @@ -294,8 +295,7 @@ func TestFileRendering(t *testing.T) { func TestTmplStrings(t *testing.T) { c := &tmpl.Context{} c.SetBasePath(".") - c.SetReadFile(os.ReadFile) - c.SetReadDir(os.ReadDir) + c.SetFileSystem(filesystem.DefaultFileSystem()) tmpl := template.New("stringTemplateTest").Funcs(c.CreateFuncMap()) tmplE2eTest.load()