Introduce Helmfile's own filesystem abstraction to correctly unit test some components (#307)

Use abstracted FS

Signed-off-by: Arkaitz Jimenez <arkaitzj@gmail.com>

Signed-off-by: Arkaitz Jimenez <arkaitzj@gmail.com>
This commit is contained in:
Arkaitz Jimenez 2022-08-24 05:58:43 +02:00 committed by GitHub
parent a626664f46
commit cc33e7b7d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 513 additions and 425 deletions

View File

@ -17,6 +17,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/argparser" "github.com/helmfile/helmfile/pkg/argparser"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/plugins" "github.com/helmfile/helmfile/pkg/plugins"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
@ -39,16 +40,7 @@ type App struct {
FileOrDir string FileOrDir string
readFile func(string) ([]byte, error) fs *filesystem.FileSystem
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
remote *remote.Remote remote *remote.Remote
@ -81,20 +73,11 @@ func New(conf ConfigProvider) *App {
FileOrDir: conf.FileOrDir(), FileOrDir: conf.FileOrDir(),
ValuesFiles: conf.StateValuesFiles(), ValuesFiles: conf.StateValuesFiles(),
Set: conf.StateValuesSet(), Set: conf.StateValuesSet(),
fs: filesystem.DefaultFileSystem(),
}) })
} }
func Init(app *App) *App { 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 var err error
app.valsRuntime, err = plugins.ValsInstance() app.valsRuntime, err = plugins.ValsInstance()
if err != nil { if err != nil {
@ -619,19 +602,19 @@ func (a *App) within(dir string, do func() error) error {
return do() return do()
} }
prev, err := a.getwd() prev, err := a.fs.Getwd()
if err != nil { if err != nil {
return fmt.Errorf("failed getting current working direcotyr: %v", err) return fmt.Errorf("failed getting current working direcotyr: %v", err)
} }
absDir, err := a.abs(dir) absDir, err := a.fs.Abs(dir)
if err != nil { if err != nil {
return err return err
} }
a.Logger.Debugf("changing working directory to \"%s\"", absDir) 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) 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) 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 { if appErr != nil {
a.Logger.Warnf("%v", appErr) 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 { for _, relPath := range desiredStateFiles {
var file string var file string
var dir string var dir string
if a.directoryExistsAt(relPath) { if a.fs.DirectoryExistsAt(relPath) {
file = relPath file = relPath
dir = relPath dir = relPath
} else { } 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) 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 { if errAbsDir != nil {
return errAbsDir return errAbsDir
} }
@ -690,20 +673,15 @@ func (a *App) loadDesiredStateFromYaml(file string, opts ...LoadOpts) (*state.He
} }
ld := &desiredStateLoader{ ld := &desiredStateLoader{
readFile: a.readFile, fs: a.fs,
deleteFile: a.deleteFile, env: a.Env,
fileExists: a.fileExists, namespace: a.Namespace,
directoryExistsAt: a.directoryExistsAt, chart: a.Chart,
env: a.Env, logger: a.Logger,
namespace: a.Namespace, remote: a.remote,
chart: a.Chart,
logger: a.Logger,
abs: a.abs,
remote: a.remote,
overrideKubeContext: a.OverrideKubeContext, overrideKubeContext: a.OverrideKubeContext,
overrideHelmBinary: a.OverrideHelmBinary, overrideHelmBinary: a.OverrideHelmBinary,
glob: a.glob,
getHelm: a.getHelm, getHelm: a.getHelm,
valsRuntime: a.valsRuntime, valsRuntime: a.valsRuntime,
} }
@ -995,7 +973,7 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converg
opts.Environment.OverrideValues = envvals 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 f := converge
if opts.Filter { if opts.Filter {
@ -1095,18 +1073,18 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri
var helmfileDir string var helmfileDir string
if specifiedPath != "" { if specifiedPath != "" {
switch { switch {
case a.fileExistsAt(specifiedPath): case a.fs.FileExistsAt(specifiedPath):
return []string{specifiedPath}, nil return []string{specifiedPath}, nil
case a.directoryExistsAt(specifiedPath): case a.fs.DirectoryExistsAt(specifiedPath):
helmfileDir = specifiedPath helmfileDir = specifiedPath
default: default:
return []string{}, fmt.Errorf("specified state file %s is not found", specifiedPath) return []string{}, fmt.Errorf("specified state file %s is not found", specifiedPath)
} }
} else { } else {
var defaultFile string var defaultFile string
if a.fileExistsAt(DefaultHelmfile) { if a.fs.FileExistsAt(DefaultHelmfile) {
defaultFile = DefaultHelmfile defaultFile = DefaultHelmfile
} else if a.fileExistsAt(DeprecatedHelmfile) { } else if a.fs.FileExistsAt(DeprecatedHelmfile) {
log.Printf( 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", "warn: %s is being loaded: %s is deprecated in favor of %s. See https://github.com/roboll/helmfile/issues/25 for more information",
DeprecatedHelmfile, DeprecatedHelmfile,
@ -1117,7 +1095,7 @@ func (a *App) findDesiredStateFiles(specifiedPath string, opts LoadOpts) ([]stri
} }
switch { switch {
case a.directoryExistsAt(DefaultHelmfileDirectory): case a.fs.DirectoryExistsAt(DefaultHelmfileDirectory):
if defaultFile != "" { if defaultFile != "" {
return []string{}, fmt.Errorf("configuration conlict error: you can have either %s or %s, but not both", defaultFile, DefaultHelmfileDirectory) 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 { if err != nil {
return []string{}, err return []string{}, err
} }
@ -1926,28 +1904,6 @@ func (a *App) writeValues(r *Run, c WriteValuesConfigProvider) (bool, []error) {
return true, errs 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. // Error is a wrapper around an error that adds context to the error.
type Error struct { type Error struct {
msg string msg string
@ -2064,10 +2020,10 @@ func (c context) wrapErrs(errs ...error) error {
func (a *App) ShowCacheDir(c CacheConfigProvider) error { func (a *App) ShowCacheDir(c CacheConfigProvider) error {
fmt.Printf("Cache directory: %s\n", remote.CacheDir()) fmt.Printf("Cache directory: %s\n", remote.CacheDir())
if !directoryExistsAt(remote.CacheDir()) { if !a.fs.DirectoryExistsAt(remote.CacheDir()) {
return nil return nil
} }
dirs, err := os.ReadDir(remote.CacheDir()) dirs, err := a.fs.ReadDir(remote.CacheDir())
if err != nil { if err != nil {
return err return err
} }
@ -2079,7 +2035,7 @@ func (a *App) ShowCacheDir(c CacheConfigProvider) error {
} }
func (a *App) CleanCacheDir(c CacheConfigProvider) error { func (a *App) CleanCacheDir(c CacheConfigProvider) error {
if !directoryExistsAt(remote.CacheDir()) { if !a.fs.DirectoryExistsAt(remote.CacheDir()) {
return nil return nil
} }
fmt.Printf("Cleaning up cache directory: %s\n", remote.CacheDir()) fmt.Printf("Cleaning up cache directory: %s\n", remote.CacheDir())

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -90,8 +90,7 @@ func TestApply_3(t *testing.T) {
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: filesystem.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "", OverrideKubeContext: "",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -90,8 +90,7 @@ func TestApply_2(t *testing.T) {
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: filesystem.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -13,6 +12,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -129,8 +129,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: filesystem.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -15,6 +15,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -131,8 +132,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/variantdev/vals" "github.com/variantdev/vals"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
"github.com/helmfile/helmfile/pkg/testutil" "github.com/helmfile/helmfile/pkg/testutil"
@ -145,8 +145,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: tc.environment, Env: tc.environment,
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -88,8 +88,7 @@ func TestSync(t *testing.T) {
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -15,6 +15,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -131,8 +132,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: &ffs.FileSystem{Glob: filepath.Glob},
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -21,6 +21,7 @@ import (
"github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/envvar"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/state" "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.Set = make(map[string]interface{})
} }
app.readFile = fs.ReadFile app.fs = fs.ToFileSystem()
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
return app return app
} }
@ -1529,12 +1523,11 @@ func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
} }
return yamlContent, nil return yamlContent, nil
} }
fs := ffs.FromFileSystem(ffs.FileSystem{ReadFile: readFile})
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
OverrideKubeContext: "default", OverrideKubeContext: "default",
readFile: readFile, fs: fs,
glob: filepath.Glob,
abs: filepath.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), Logger: helmexec.NewLogger(os.Stderr, "debug"),
} }
@ -1593,16 +1586,11 @@ helmDefaults:
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
OverrideKubeContext: "default", OverrideKubeContext: "default",
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
glob: testFs.Glob,
abs: testFs.Abs,
directoryExistsAt: testFs.DirectoryExistsAt,
fileExistsAt: testFs.FileExistsAt,
fileExists: testFs.FileExists,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -1683,14 +1671,11 @@ helmDefaults:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
fileExists: testFs.FileExists,
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -1762,14 +1747,11 @@ foo: FOO
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
fileExists: testFs.FileExists,
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -1828,14 +1810,11 @@ foo: FOO
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
fileExists: testFs.FileExists,
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -1912,14 +1891,11 @@ helmDefaults:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
fileExists: testFs.FileExists,
glob: testFs.Glob,
abs: testFs.Abs,
Env: "test", Env: "test",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -1988,13 +1964,11 @@ releases:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -2046,14 +2020,12 @@ releases:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
st, err := app.loadDesiredStateFromYaml(statePath, LoadOpts{Reverse: true}) st, err := app.loadDesiredStateFromYaml(statePath, LoadOpts{Reverse: true})
@ -2103,13 +2075,11 @@ releases:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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{ opts := LoadOpts{
CalleePath: statePath, CalleePath: statePath,
@ -2217,13 +2187,11 @@ services:
}) })
app := &App{ app := &App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
readFile: testFs.ReadFile, fs: testFs.ToFileSystem(),
glob: testFs.Glob,
abs: testFs.Abs,
Env: "default", Env: "default",
Logger: helmexec.NewLogger(os.Stderr, "debug"), 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) expectNoCallsToHelm(app)
@ -2656,8 +2624,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -2729,8 +2696,6 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob,
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -2741,6 +2706,8 @@ releases:
valsRuntime: valsRuntime, valsRuntime: valsRuntime,
}, files) }, files)
fmt.Printf("CRAFTED APP WITH %p\n", app.fs.DirectoryExistsAt)
if err := app.Template(configImpl{}); err != nil { if err := app.Template(configImpl{}); err != nil {
t.Fatalf("%v", err) t.Fatalf("%v", err)
} }
@ -4282,8 +4249,7 @@ changing working directory back to "/path/to"
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -4465,8 +4431,7 @@ changing working directory back to "/path/to"
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -4523,8 +4488,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -4570,8 +4534,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -4631,8 +4594,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,
@ -4693,8 +4655,7 @@ releases:
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -11,6 +11,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/state" "github.com/helmfile/helmfile/pkg/state"
@ -27,14 +28,9 @@ type desiredStateLoader struct {
env string env string
namespace string namespace string
chart string chart string
fs *filesystem.FileSystem
readFile func(string) ([]byte, error) getHelm func(*state.HelmState) helmexec.Interface
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
remote *remote.Remote remote *remote.Remote
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -50,8 +46,8 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
if opts.CalleePath == "" { if opts.CalleePath == "" {
return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts) return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts)
} }
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob) storage := state.NewStorage(opts.CalleePath, ld.logger, ld.fs)
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger, ld.remote) envld := state.NewEnvironmentValuesLoader(storage, ld.fs, ld.logger, ld.remote)
handler := state.MissingFileHandlerError handler := state.MissingFileHandlerError
vals, err := envld.LoadEnvironmentValues(&handler, args, &environment.EmptyEnvironment) vals, err := envld.LoadEnvironmentValues(&handler, args, &environment.EmptyEnvironment)
if err != nil { if err != nil {
@ -120,7 +116,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
f = filepath.Join(baseDir, file) f = filepath.Join(baseDir, file)
} }
fileBytes, err := ld.readFile(f) fileBytes, err := ld.fs.ReadFile(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -166,8 +162,7 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
} }
func (a *desiredStateLoader) underlying() *state.StateCreator { 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 := state.NewCreator(a.logger, a.fs, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote)
c.DeleteFile = a.deleteFile
c.LoadFile = a.loadFile c.LoadFile = a.loadFile
return c return c
} }

View File

@ -4,13 +4,13 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -83,8 +83,7 @@ func TestDestroy_2(t *testing.T) {
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "", OverrideKubeContext: "",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -124,8 +124,7 @@ func TestDestroy(t *testing.T) {
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "default", OverrideKubeContext: "default",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -12,6 +11,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -1060,8 +1060,7 @@ changing working directory back to "/path/to"
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: "", OverrideKubeContext: "",
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"path/filepath"
"sync" "sync"
"testing" "testing"
@ -13,6 +12,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
@ -1352,8 +1352,7 @@ changing working directory back to "/path/to"
app := appWithFs(&App{ app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary, OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
abs: filepath.Abs,
OverrideKubeContext: overrideKubeContext, OverrideKubeContext: overrideKubeContext,
Env: "default", Env: "default",
Logger: logger, Logger: logger,

View File

@ -124,7 +124,7 @@ func (r *desiredStateLoader) twoPassRenderTemplateToYaml(inherited, overrode *en
} }
tmplData := state.NewEnvironmentTemplateData(*finalEnv, r.namespace, vals) 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) yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content)
if err != nil { if err != nil {
if r.logger != nil { if r.logger != nil {

View File

@ -17,16 +17,13 @@ import (
func makeLoader(files map[string]string, env string) (*desiredStateLoader, *testhelper.TestFs) { func makeLoader(files map[string]string, env string) (*desiredStateLoader, *testhelper.TestFs) {
testfs := testhelper.NewTestFs(files) testfs := testhelper.NewTestFs(files)
logger := helmexec.NewLogger(os.Stdout, "debug") 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{ return &desiredStateLoader{
env: env, env: env,
namespace: "namespace", namespace: "namespace",
logger: helmexec.NewLogger(os.Stdout, "debug"), logger: helmexec.NewLogger(os.Stdout, "debug"),
readFile: testfs.ReadFile, fs: testfs.ToFileSystem(),
fileExists: testfs.FileExists, remote: r,
abs: testfs.Abs,
glob: testfs.Glob,
remote: r,
}, testfs }, testfs
} }

View File

@ -7,6 +7,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
) )
@ -35,9 +36,9 @@ type Bus struct {
Chart string Chart string
Env environment.Environment 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) { 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 { for k, v := range context {
data[k] = v 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) bus.Logger.Debugf("hook[%s]: triggered by event \"%s\"\n", name, evt)

View File

@ -9,6 +9,7 @@ import (
"go.uber.org/zap/zaptest/observer" "go.uber.org/zap/zaptest/observer"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
) )
type runner struct { type runner struct {
@ -154,7 +155,7 @@ func TestTrigger(t *testing.T) {
Namespace: "myns", Namespace: "myns",
Env: environment.Environment{Name: "prod"}, Env: environment.Environment{Name: "prod"},
Logger: zeLogger, Logger: zeLogger,
ReadFile: readFile, Fs: &ffs.FileSystem{ReadFile: readFile},
} }
bus.Runner = &runner{} bus.Runner = &runner{}

101
pkg/filesystem/fs.go Normal file
View File

@ -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()
}

98
pkg/filesystem/fs_test.go Normal file
View File

@ -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")
}
}

View File

@ -17,6 +17,7 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/helmfile/helmfile/pkg/envvar" "github.com/helmfile/helmfile/pkg/envvar"
"github.com/helmfile/helmfile/pkg/filesystem"
) )
var disableInsecureFeatures bool var disableInsecureFeatures bool
@ -43,11 +44,9 @@ type Remote struct {
// Getter is the underlying implementation of getter used for fetching remote files // Getter is the underlying implementation of getter used for fetching remote files
Getter Getter 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. // 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) fs *filesystem.FileSystem
DirExists func(string) bool
FileExists func(string) bool
} }
func (r *Remote) Unmarshal(src string, dst interface{}) error { func (r *Remote) Unmarshal(src string, dst interface{}) error {
@ -87,7 +86,7 @@ func (r *Remote) GetBytes(goGetterSrc string) ([]byte, error) {
return nil, err return nil, err
} }
bytes, err := r.ReadFile(f) bytes, err := r.fs.ReadFile(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("read file: %v", err) 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, // 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 // and returns the path to the file in the fetched directory
func (r *Remote) Locate(urlOrPath string) (string, error) { 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 return urlOrPath, nil
} }
fetched, err := r.Fetch(urlOrPath) 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) 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) 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 cached = true
} }
} }
@ -285,17 +284,15 @@ func (g *GoGetter) Get(wd, src, dst string) error {
return nil 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 { if disableInsecureFeatures {
panic("Remote sources are disabled due to 'DISABLE_INSECURE_FEATURES'") panic("Remote sources are disabled due to 'DISABLE_INSECURE_FEATURES'")
} }
remote := &Remote{ remote := &Remote{
Logger: logger, Logger: logger,
Home: homeDir, Home: homeDir,
Getter: &GoGetter{Logger: logger}, Getter: &GoGetter{Logger: logger},
ReadFile: readFile, fs: fs,
DirExists: dirExists,
FileExists: fileExists,
} }
if remote.Home == "" { if remote.Home == "" {

View File

@ -55,12 +55,10 @@ func TestRemote_HttpsGitHub(t *testing.T) {
get: get, get: get,
} }
remote := &Remote{ remote := &Remote{
Logger: helmexec.NewLogger(os.Stderr, "debug"), Logger: helmexec.NewLogger(os.Stderr, "debug"),
Home: CacheDir(), Home: CacheDir(),
Getter: getter, Getter: getter,
ReadFile: testfs.ReadFile, fs: testfs.ToFileSystem(),
FileExists: testfs.FileExistsAt,
DirExists: testfs.DirectoryExistsAt,
} }
// FYI, go-getter in the `dir` mode accepts URL like the below. So helmfile expects URLs similar to it: // 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, get: get,
} }
remote := &Remote{ remote := &Remote{
Logger: helmexec.NewLogger(os.Stderr, "debug"), Logger: helmexec.NewLogger(os.Stderr, "debug"),
Home: CacheDir(), Home: CacheDir(),
Getter: getter, Getter: getter,
ReadFile: testfs.ReadFile, fs: testfs.ToFileSystem(),
FileExists: testfs.FileExistsAt,
DirExists: testfs.DirectoryExistsAt,
} }
url := "git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0" 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, get: get,
} }
remote := &Remote{ remote := &Remote{
Logger: helmexec.NewLogger(os.Stderr, "debug"), Logger: helmexec.NewLogger(os.Stderr, "debug"),
Home: CacheDir(), Home: CacheDir(),
Getter: getter, Getter: getter,
ReadFile: testfs.ReadFile, fs: testfs.ToFileSystem(),
FileExists: testfs.FileExistsAt,
DirExists: testfs.DirectoryExistsAt,
} }
url := "git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA=" url := "git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA="

View File

@ -146,8 +146,8 @@ func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
depMan := NewChartDependencyManager(filename, st.logger) depMan := NewChartDependencyManager(filename, st.logger)
if st.readFile != nil { if st.fs.ReadFile != nil {
depMan.readFile = st.readFile depMan.readFile = st.fs.ReadFile
} }
return resolveDependencies(st, depMan, unresolved) return resolveDependencies(st, depMan, unresolved)

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/variantdev/vals" "github.com/variantdev/vals"
@ -13,6 +12,7 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/maputil" "github.com/helmfile/helmfile/pkg/maputil"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
@ -43,12 +43,7 @@ func (e *UndefinedEnvError) Error() string {
type StateCreator struct { type StateCreator struct {
logger *zap.SugaredLogger logger *zap.SugaredLogger
readFile func(string) ([]byte, error) fs *filesystem.FileSystem
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
DeleteFile func(string) error
directoryExistsAt func(string) bool
valsRuntime vals.Evaluator valsRuntime vals.Evaluator
@ -63,17 +58,12 @@ type StateCreator struct {
remote *remote.Remote 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{ return &StateCreator{
logger: logger, logger: logger,
readFile: readFile,
fileExists: fileExists,
abs: abs,
glob: glob,
directoryExistsAt: directoryExistsAt,
Strict: true, Strict: true,
fs: fs,
valsRuntime: valsRuntime, valsRuntime: valsRuntime,
getHelm: getHelm, 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) { func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState, error) {
var state HelmState var state HelmState
state.fs = c.fs
state.FilePath = file state.FilePath = file
state.basePath = baseDir state.basePath = baseDir
@ -132,12 +123,6 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
} }
state.logger = c.logger 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 state.valsRuntime = c.valsRuntime
return &state, nil 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) { func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *environment.Environment, failOnMissingEnv bool) (*HelmState, error) {
state := *target 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 { if err != nil {
return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err} 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 // 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{}{} envVals := map[string]interface{}{}
envSpec, ok := st.Environments[name] envSpec, ok := st.Environments[name]
if ok { if ok {
@ -245,7 +230,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
envSecretFiles = append(envSecretFiles, resolved...) 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 return nil, err
} }
} }
@ -268,7 +253,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
return newEnv, nil 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 var errs []error
helm := c.getHelm(st) helm := c.getHelm(st)
@ -308,11 +293,11 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
} }
// nolint: staticcheck // nolint: staticcheck
defer func() { 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) c.logger.Warnf("removing decrypted file %s: %w", decFile, err)
} }
}() }()
bytes, err := readFile(decFile) bytes, err := c.fs.ReadFile(decFile)
if err != nil { if err != nil {
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path} results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
continue continue
@ -367,7 +352,7 @@ func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []int
var envVals map[string]interface{} var envVals map[string]interface{}
valuesEntries := append([]interface{}{}, entries...) 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 var err error
envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv) envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv)
if err != nil { if err != nil {

View File

@ -1,7 +1,6 @@
package state package state
import ( import (
"os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -10,18 +9,16 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/testhelper" "github.com/helmfile/helmfile/pkg/testhelper"
) )
func createFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) { func createFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) {
c := &StateCreator{ c := &StateCreator{
logger: logger, logger: logger,
readFile: os.ReadFile, fs: filesystem.DefaultFileSystem(),
abs: filepath.Abs, Strict: true,
DeleteFile: os.Remove,
Strict: true,
} }
return c.ParseAndLoad(content, filepath.Dir(file), file, env, true, nil) 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) t.Fatalf("no file named %q registered", file)
} }
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
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([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil) ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -148,11 +145,11 @@ releaseNamespace: mynamespace
}) })
testFs.Cwd = "/example/path/to" 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{ env := environment.Environment{
Name: "production", 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) ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, &env)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -238,8 +235,8 @@ overrideNamespace: myns
}) })
testFs.Cwd = "/example/path/to" 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())
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, nil) ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)

View File

@ -9,6 +9,7 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/maputil" "github.com/helmfile/helmfile/pkg/maputil"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
@ -17,19 +18,19 @@ import (
type EnvironmentValuesLoader struct { type EnvironmentValuesLoader struct {
storage *Storage storage *Storage
readFile func(string) ([]byte, error) fs *filesystem.FileSystem
logger *zap.SugaredLogger logger *zap.SugaredLogger
remote *remote.Remote 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{ return &EnvironmentValuesLoader{
storage: storage, storage: storage,
readFile: readFile, fs: fs,
logger: logger, logger: logger,
remote: remote, remote: remote,
} }
} }
@ -64,7 +65,7 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str
} }
tmplData := NewEnvironmentTemplateData(env, "", map[string]interface{}{}) 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) bytes, err := r.RenderToBytes(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", f, err) return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", f, err)

View File

@ -1,13 +1,12 @@
package state package state
import ( import (
"os"
"path/filepath"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"go.uber.org/zap" "go.uber.org/zap"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
) )
@ -22,14 +21,11 @@ func newLoader() *EnvironmentValuesLoader {
storage := &Storage{ storage := &Storage{
FilePath: "./helmfile.yaml", FilePath: "./helmfile.yaml",
basePath: ".", basePath: ".",
glob: filepath.Glob, fs: ffs.DefaultFileSystem(),
logger: sugar, logger: sugar,
} }
readFile := func(s string) ([]byte, error) { return []byte{}, nil } return NewEnvironmentValuesLoader(storage, storage.fs, sugar, remote.NewRemote(sugar, "/tmp", storage.fs))
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))
} }
// See https://github.com/roboll/helmfile/pull/1169 // See https://github.com/roboll/helmfile/pull/1169

View File

@ -26,16 +26,6 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) ([]s
return flags, nil 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 { type Chartify struct {
Opts *chartify.ChartifyOpts Opts *chartify.ChartifyOpts
Clean func() 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()) 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 { } else {
r := remote.NewRemote(st.logger, "", st.readFile, directoryExistsAt, fileExistsAt) r := remote.NewRemote(st.logger, "", st.fs)
fetchedDir, err := r.Fetch(chart, cacheDir) fetchedDir, err := r.Fetch(chart, cacheDir)
if err != nil { if err != nil {
@ -107,14 +97,14 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
dir = filepath.Join(st.basePath, chart) dir = filepath.Join(st.basePath, chart)
} }
if stat, _ := os.Stat(dir); stat != nil && stat.IsDir() { 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 shouldRun = true
} }
} }
for _, d := range release.Dependencies { for _, d := range release.Dependencies {
chart := d.Chart chart := d.Chart
if st.directoryExistsAt(chart) { if st.fs.DirectoryExistsAt(chart) {
var err error var err error
// Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like: // Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like:

View File

@ -26,6 +26,7 @@ import (
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/event" "github.com/helmfile/helmfile/pkg/event"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
@ -92,14 +93,9 @@ type HelmState struct {
ReleaseSetSpec `yaml:",inline"` ReleaseSetSpec `yaml:",inline"`
logger *zap.SugaredLogger logger *zap.SugaredLogger
fs *filesystem.FileSystem
readFile func(string) ([]byte, error) tempDir func(string, string) (string, 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
valsRuntime vals.Evaluator valsRuntime vals.Evaluator
@ -553,7 +549,7 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu
errs = append(errs, newReleaseFailedError(release, err)) errs = append(errs, newReleaseFailedError(release, err))
} }
ok, err := st.fileExists(valfile) ok, err := st.fs.FileExists(valfile)
if err != nil { if err != nil {
errs = append(errs, newReleaseFailedError(release, err)) errs = append(errs, newReleaseFailedError(release, err))
} else if !ok { } 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) chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
if !opts.SkipCleanup { 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 // Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
// explicitly skipped. // explicitly skipped.
buildDeps = !skipDeps 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: // At this point, we are sure that chartPath is a local directory containing either:
// - A remote chart fetched by go-getter or // - A remote chart fetched by go-getter or
// - A local chart // - A local chart
@ -1505,7 +1501,7 @@ func (st *HelmState) WriteReleasesValues(helm helmexec.Interface, additionalValu
for _, f := range append(generatedFiles, additionalValues...) { for _, f := range append(generatedFiles, additionalValues...) {
src := map[string]interface{}{} src := map[string]interface{}{}
srcBytes, err := st.readFile(f) srcBytes, err := st.fs.ReadFile(f)
if err != nil { if err != nil {
return []error{fmt.Errorf("reading %s: %w", f, err)} 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, Chart: st.OverrideChart,
Env: st.Env, Env: st.Env,
Logger: st.logger, Logger: st.logger,
ReadFile: st.readFile, Fs: st.fs,
} }
data := map[string]interface{}{ data := map[string]interface{}{
"HelmfileCommand": helmfileCmd, "HelmfileCommand": helmfileCmd,
@ -2269,7 +2265,7 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe
Chart: st.OverrideChart, Chart: st.OverrideChart,
Env: st.Env, Env: st.Env,
Logger: st.logger, Logger: st.logger,
ReadFile: st.readFile, Fs: st.fs,
} }
vals := st.Values() vals := st.Values()
data := map[string]interface{}{ data := map[string]interface{}{
@ -2307,7 +2303,7 @@ func (st *HelmState) UpdateDeps(helm helmexec.Interface, includeTransitiveNeeds
var errs []error var errs []error
for _, release := range releases { for _, release := range releases {
if st.directoryExistsAt(release.ChartPathOrName()) { if st.fs.DirectoryExistsAt(release.ChartPathOrName()) {
if err := helm.UpdateDeps(release.ChartPathOrName()); err != nil { if err := helm.UpdateDeps(release.ChartPathOrName()); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
@ -2631,7 +2627,7 @@ func (st *HelmState) newReleaseTemplateData(release *ReleaseSpec) releaseTemplat
} }
func (st *HelmState) newReleaseTemplateFuncMap(dir string) template.FuncMap { 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() 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) { func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) {
templateData := st.newReleaseTemplateData(release) 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) rawBytes, err := r.RenderToBytes(path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -2673,8 +2669,8 @@ func (st *HelmState) storage() *Storage {
return &Storage{ return &Storage{
FilePath: st.FilePath, FilePath: st.FilePath,
basePath: st.basePath, basePath: st.basePath,
glob: st.glob,
logger: st.logger, logger: st.logger,
fs: st.fs,
} }
} }
@ -2710,7 +2706,7 @@ func (st *HelmState) ExpandedHelmfiles() ([]SubHelmfileSpec, error) {
func (st *HelmState) removeFiles(files []string) { func (st *HelmState) removeFiles(files []string) {
for _, f := range files { 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) st.logger.Warnf("Removing %s: %v", err)
} else { } else {
st.logger.Debugf("Removed %s", f) st.logger.Debugf("Removed %s", f)

View File

@ -110,7 +110,7 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
successFlag := false successFlag := false
for it, prev := 0, &release; it < 6; it++ { for it, prev := 0, &release; it < 6; it++ {
tmplData := st.createReleaseTemplateData(prev, vals) 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) r, err := release.ExecuteTemplateExpressions(renderer)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err) return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, release.Name, err)

View File

@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
) )
@ -39,7 +40,7 @@ func TestGoGetter(t *testing.T) {
st := &HelmState{ st := &HelmState{
logger: logger, logger: logger,
readFile: os.ReadFile, fs: filesystem.DefaultFileSystem(),
basePath: d, basePath: d,
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/variantdev/vals" "github.com/variantdev/vals"
"github.com/helmfile/helmfile/pkg/exectest" "github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/testhelper" "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}) var valsRuntime, _ = vals.New(vals.Options{CacheSize: 32})
func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState { func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState {
st.glob = fs.Glob st.fs = fs.ToFileSystem()
st.readFile = fs.ReadFile
st.fileExists = fs.FileExists
st.directoryExistsAt = fs.DirectoryExistsAt
return st return st
} }
@ -1715,17 +1713,17 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) {
ReleaseSetSpec: ReleaseSetSpec{ ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases, Releases: tt.releases,
}, },
logger: logger, logger: logger,
valsRuntime: valsRuntime, valsRuntime: valsRuntime,
removeFile: func(f string) error {
numRemovedFiles += 1
return nil
},
RenderedValues: map[string]interface{}{}, RenderedValues: map[string]interface{}{},
} }
testfs := testhelper.NewTestFs(map[string]string{ testfs := testhelper.NewTestFs(map[string]string{
"/path/to/someFile": `foo: FOO`, "/path/to/someFile": `foo: FOO`,
}) })
testfs.DeleteFile = func(f string) error {
numRemovedFiles += 1
return nil
}
state = injectFs(state, testfs) state = injectFs(state, testfs)
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 { if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); len(errs) > 0 {
t.Errorf("unexpected errors: %v", errs) t.Errorf("unexpected errors: %v", errs)
@ -1802,18 +1800,18 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) {
ReleaseSetSpec: ReleaseSetSpec{ ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases, Releases: tt.releases,
}, },
logger: logger, logger: logger,
valsRuntime: valsRuntime, valsRuntime: valsRuntime,
removeFile: func(f string) error {
numRemovedFiles += 1
return nil
},
RenderedValues: map[string]interface{}{}, RenderedValues: map[string]interface{}{},
} }
testfs := testhelper.NewTestFs(map[string]string{ testfs := testhelper.NewTestFs(map[string]string{
"/path/to/someFile": `foo: bar "/path/to/someFile": `foo: bar
`, `,
}) })
testfs.DeleteFile = func(f string) error {
numRemovedFiles += 1
return nil
}
state = injectFs(state, testfs) state = injectFs(state, testfs)
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, []string{}, false, false, false, false); len(errs) > 0 { if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, []string{}, false, false, false, false); len(errs) > 0 {
t.Errorf("unexpected errors: %v", errs) t.Errorf("unexpected errors: %v", errs)
@ -1960,11 +1958,13 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
}, },
}, },
logger: logger, logger: logger,
readFile: func(f string) ([]byte, error) { fs: &filesystem.FileSystem{
if f != "helmfile.lock" { ReadFile: func(f string) ([]byte, error) {
return nil, fmt.Errorf("stub: unexpected file: %s", f) if f != "helmfile.lock" {
} return nil, fmt.Errorf("stub: unexpected file: %s", f)
return nil, os.ErrNotExist }
return nil, os.ErrNotExist
},
}, },
} }
@ -2053,17 +2053,19 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
Releases: tt.releases, Releases: tt.releases,
}, },
logger: logger, logger: logger,
fileExists: func(f string) (bool, error) { fs: &filesystem.FileSystem{
if f != "foo.yaml" { FileExists: func(f string) (bool, error) {
return false, fmt.Errorf("unexpected file: %s", f) if f != "foo.yaml" {
} return false, fmt.Errorf("unexpected file: %s", f)
return true, nil }
}, return true, nil
readFile: func(f string) ([]byte, error) { },
if f != "foo.yaml" { ReadFile: func(f string) ([]byte, error) {
return nil, fmt.Errorf("unexpected file: %s", f) if f != "foo.yaml" {
} return nil, fmt.Errorf("unexpected file: %s", f)
return []byte{}, nil }
return []byte{}, nil
},
}, },
} }
errs := state.ReleaseStatuses(tt.helm, 1) errs := state.ReleaseStatuses(tt.helm, 1)

View File

@ -8,6 +8,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
) )
@ -16,17 +17,16 @@ type Storage struct {
FilePath string FilePath string
readFile func(string) ([]byte, error)
basePath string 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{ return &Storage{
FilePath: forFile, FilePath: forFile,
basePath: filepath.Dir(forFile), basePath: filepath.Dir(forFile),
logger: logger, logger: logger,
glob: glob, fs: fs,
} }
} }
@ -36,14 +36,14 @@ func (st *Storage) resolveFile(missingFileHandler *string, tpe, path string) ([]
var files []string var files []string
var err error var err error
if remote.IsRemote(path) { 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") fetchedFilePath, err := r.Fetch(path, "values")
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if fileExistsAt(fetchedFilePath) { if st.fs.FileExistsAt(fetchedFilePath) {
files = []string{fetchedFilePath} files = []string{fetchedFilePath}
} }
} else { } else {
@ -92,7 +92,7 @@ func (st *Storage) resolveFile(missingFileHandler *string, tpe, path string) ([]
func (st *Storage) ExpandPaths(globPattern string) ([]string, error) { func (st *Storage) ExpandPaths(globPattern string) ([]string, error) {
result := []string{} result := []string{}
absPathPattern := st.normalizePath(globPattern) absPathPattern := st.normalizePath(globPattern)
matches, err := st.glob(absPathPattern) matches, err := st.fs.Glob(absPathPattern)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed processing %s: %v", globPattern, err) return nil, fmt.Errorf("failed processing %s: %v", globPattern, err)
} }

View File

@ -3,10 +3,10 @@ package state
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
) )
@ -73,7 +73,7 @@ func TestStorage_resolveFile(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) files, skipped, err := st.resolveFile(tt.args.missingFileHandler, tt.args.title, tt.args.path)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {

View File

@ -5,6 +5,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
) )
type TestFs struct { type TestFs struct {
@ -13,6 +15,7 @@ type TestFs struct {
files map[string]string files map[string]string
GlobFixtures map[string][]string GlobFixtures map[string][]string
DeleteFile func(string) error
fileReaderCalls int fileReaderCalls int
successfulReads []string successfulReads []string
@ -34,9 +37,26 @@ func NewTestFs(files map[string]string) *TestFs {
successfulReads: []string{}, successfulReads: []string{},
GlobFixtures: map[string][]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 { func (f *TestFs) FileExistsAt(path string) bool {
var ok bool var ok bool
if strings.HasPrefix(path, "/") { if strings.HasPrefix(path, "/") {

View File

@ -1,12 +1,13 @@
package tmpl package tmpl
import "io/fs" import (
"github.com/helmfile/helmfile/pkg/filesystem"
)
type Context struct { type Context struct {
preRender bool preRender bool
basePath string basePath string
readFile func(string) ([]byte, error) fs *filesystem.FileSystem
readDir func(string) ([]fs.DirEntry, error)
} }
// SetBasePath sets the base path for the template // SetBasePath sets the base path for the template
@ -14,10 +15,6 @@ func (c *Context) SetBasePath(path string) {
c.basePath = path c.basePath = path
} }
func (c *Context) SetReadFile(f func(string) ([]byte, error)) { func (c *Context) SetFileSystem(fs *filesystem.FileSystem) {
c.readFile = f c.fs = fs
}
func (c *Context) SetReadDir(f func(string) ([]fs.DirEntry, error)) {
c.readDir = f
} }

View File

@ -220,11 +220,11 @@ func (c *Context) ReadFile(filename string) (string, error) {
path = filepath.Join(c.basePath, filename) path = filepath.Join(c.basePath, filename)
} }
if c.readFile == nil { if c.fs.ReadFile == nil {
return "", fmt.Errorf("readFile is not implemented") return "", fmt.Errorf("readFile is not implemented")
} }
bytes, err := c.readFile(path) bytes, err := c.fs.ReadFile(path)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -239,7 +239,7 @@ func (c *Context) ReadDir(path string) ([]string, error) {
contextPath = filepath.Join(c.basePath, path) contextPath = filepath.Join(c.basePath, path)
} }
entries, err := c.readDir(contextPath) entries, err := c.fs.ReadDir(contextPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("ReadDir %q: %w", contextPath, err) return nil, fmt.Errorf("ReadDir %q: %w", contextPath, err)
} }
@ -262,7 +262,7 @@ func (c *Context) ReadDirEntries(path string) ([]fs.DirEntry, error) {
} else { } else {
contextPath = filepath.Join(c.basePath, path) contextPath = filepath.Join(c.basePath, path)
} }
entries, err := c.readDir(contextPath) entries, err := c.fs.ReadDir(contextPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("ReadDirEntries %q: %w", contextPath, err) return nil, fmt.Errorf("ReadDirEntries %q: %w", contextPath, err)
} }

View File

@ -9,6 +9,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/filesystem"
) )
func TestCreateFuncMap(t *testing.T) { func TestCreateFuncMap(t *testing.T) {
@ -62,17 +64,23 @@ func TestCreateFuncMap_SkipInsecureTemplateFunctions(t *testing.T) {
skipInsecureTemplateFunctions = currentVal 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) { func TestReadFile(t *testing.T) {
expected := `foo: expected := `foo:
bar: BAR bar: BAR
` `
expectedFilename := "values.yaml" expectedFilename := "values.yaml"
ctx := &Context{basePath: ".", readFile: func(filename string) ([]byte, error) { ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)}
if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
}
return []byte(expected), nil
}}
actual, err := ctx.ReadFile(expectedFilename) actual, err := ctx.ReadFile(expectedFilename)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
@ -125,12 +133,12 @@ func TestReadDir(t *testing.T) {
} }
expectedDirname := "sampleDirectory" 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 { if dirname != expectedDirname {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname)
} }
return result, nil return result, nil
}} }})}
actual, err := ctx.ReadDir(expectedDirname) actual, err := ctx.ReadDir(expectedDirname)
require.NoError(t, err) require.NoError(t, err)
@ -146,12 +154,12 @@ func TestReadDirEntries(t *testing.T) {
} }
expectedDirname := "sampleDirectory" 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 { if dirname != expectedDirname {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedDirname, dirname)
} }
return result, nil return result, nil
}} }})}
actual, err := ctx.ReadDirEntries(expectedDirname) actual, err := ctx.ReadDirEntries(expectedDirname)
require.NoError(t, err) require.NoError(t, err)
@ -163,12 +171,7 @@ func TestReadFile_PassAbsPath(t *testing.T) {
bar: BAR bar: BAR
` `
expectedFilename, _ := filepath.Abs("values.yaml") expectedFilename, _ := filepath.Abs("values.yaml")
ctx := &Context{basePath: ".", readFile: func(filename string) ([]byte, error) { ctx := &Context{basePath: ".", fs: newFSExpecting(expectedFilename, expected)}
if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
}
return []byte(expected), nil
}}
actual, err := ctx.ReadFile(expectedFilename) actual, err := ctx.ReadFile(expectedFilename)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, actual, expected) require.Equal(t, actual, expected)

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
ffs "github.com/helmfile/helmfile/pkg/filesystem"
) )
func TestRenderTemplate_Values(t *testing.T) { func TestRenderTemplate_Values(t *testing.T) {
@ -14,12 +16,12 @@ func TestRenderTemplate_Values(t *testing.T) {
bar: FOO_BAR bar: FOO_BAR
` `
expectedFilename := "values.yaml" 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 { if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
} }
return []byte(valuesYamlContent), nil return []byte(valuesYamlContent), nil
}} }}}
buf, err := ctx.RenderTemplateToBuffer(`{{ readFile "values.yaml" | fromYaml | setValueAtPath "foo.bar" "FOO_BAR" | toYaml }}`) buf, err := ctx.RenderTemplateToBuffer(`{{ readFile "values.yaml" | fromYaml | setValueAtPath "foo.bar" "FOO_BAR" | toYaml }}`)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -43,12 +45,12 @@ func TestRenderTemplate_WithData(t *testing.T) {
"bar": "FOO_BAR", "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 { if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
} }
return []byte(valuesYamlContent), nil return []byte(valuesYamlContent), nil
}} }}}
buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -68,12 +70,12 @@ func TestRenderTemplate_AccessingMissingKeyWithGetOrNil(t *testing.T) {
` `
expectedFilename := "values.yaml" expectedFilename := "values.yaml"
data := map[string]interface{}{} 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 { if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
} }
return []byte(valuesYamlContent), nil return []byte(valuesYamlContent), nil
}} }}}
buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -93,12 +95,12 @@ func TestRenderTemplate_Defaulting(t *testing.T) {
` `
expectedFilename := "values.yaml" expectedFilename := "values.yaml"
data := map[string]interface{}{} 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 { if filename != expectedFilename {
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", expectedFilename, filename)
} }
return []byte(valuesYamlContent), nil return []byte(valuesYamlContent), nil
}} }}}
buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data) buf, err := ctx.RenderTemplateToBuffer(valuesYamlContent, data)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -110,9 +112,9 @@ func TestRenderTemplate_Defaulting(t *testing.T) {
} }
func renderTemplateToString(s string, data ...interface{}) (string, error) { 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) return nil, fmt.Errorf("unexpected call to readFile: filename=%s", filename)
}} }}}
tplString, err := ctx.RenderTemplateToBuffer(s, data...) tplString, err := ctx.RenderTemplateToBuffer(s, data...)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -3,43 +3,43 @@ package tmpl
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/helmfile/helmfile/pkg/filesystem"
) )
type FileRenderer struct { type FileRenderer struct {
ReadFile func(string) ([]byte, error) fs *filesystem.FileSystem
Context *Context Context *Context
Data interface{} 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{ return &FileRenderer{
ReadFile: readFile, fs: fs,
Context: &Context{ Context: &Context{
basePath: basePath, basePath: basePath,
readFile: readFile, fs: fs,
readDir: os.ReadDir,
}, },
Data: data, Data: data,
} }
} }
func NewFirstPassRenderer(basePath string, data interface{}) *FileRenderer { func NewFirstPassRenderer(basePath string, data interface{}) *FileRenderer {
fs := filesystem.DefaultFileSystem()
return &FileRenderer{ return &FileRenderer{
ReadFile: os.ReadFile, fs: fs,
Context: &Context{ Context: &Context{
preRender: true, preRender: true,
basePath: basePath, basePath: basePath,
readFile: os.ReadFile, fs: fs,
readDir: os.ReadDir,
}, },
Data: data, Data: data,
} }
} }
func (r *FileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) { func (r *FileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) {
content, err := r.ReadFile(file) content, err := r.fs.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -60,7 +60,7 @@ func (r *FileRenderer) RenderToBytes(path string) ([]byte, error) {
yamlBytes = yamlBuf.Bytes() yamlBytes = yamlBuf.Bytes()
} else { } else {
var err error var err error
yamlBytes, err = r.ReadFile(path) yamlBytes, err = r.fs.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load [%s]: %v", path, err) return nil, fmt.Errorf("failed to load [%s]: %v", path, err)
} }

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem"
) )
var emptyEnvTmplData = map[string]interface{}{ var emptyEnvTmplData = map[string]interface{}{
@ -23,7 +24,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) {
` `
dataFile := "data.txt" dataFile := "data.txt"
valuesTmplFile := "values.yaml.gotmpl" valuesTmplFile := "values.yaml.gotmpl"
r := NewFileRenderer(func(filename string) ([]byte, error) { r := NewFileRenderer(&filesystem.FileSystem{ReadFile: func(filename string) ([]byte, error) {
switch filename { switch filename {
case valuesTmplFile: case valuesTmplFile:
return []byte(valuesYamlTmplContent), nil return []byte(valuesYamlTmplContent), nil
@ -31,7 +32,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) {
return []byte(dataFileContent), nil return []byte(dataFileContent), nil
} }
return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename) return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename)
}, "", emptyEnvTmplData) }}, "", emptyEnvTmplData)
buf, err := r.RenderToBytes(valuesTmplFile) buf, err := r.RenderToBytes(valuesTmplFile)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
@ -50,12 +51,12 @@ func TestRenderToBytes_Yaml(t *testing.T) {
bar: '{{ readFile "data.txt" }}' bar: '{{ readFile "data.txt" }}'
` `
valuesFile := "values.yaml" valuesFile := "values.yaml"
r := NewFileRenderer(func(filename string) ([]byte, error) { r := NewFileRenderer(&filesystem.FileSystem{ReadFile: func(filename string) ([]byte, error) {
if filename == valuesFile { if filename == valuesFile {
return []byte(valuesYamlContent), nil return []byte(valuesYamlContent), nil
} }
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename) return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename)
}, "", emptyEnvTmplData) }}, "", emptyEnvTmplData)
buf, err := r.RenderToBytes(valuesFile) buf, err := r.RenderToBytes(valuesFile)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)

View File

@ -1,5 +1,9 @@
package tmpl package tmpl
import (
"github.com/helmfile/helmfile/pkg/filesystem"
)
type templateTextRenderer struct { type templateTextRenderer struct {
ReadText func(string) ([]byte, error) ReadText func(string) ([]byte, error)
Context *Context Context *Context
@ -11,12 +15,12 @@ type TextRenderer interface {
} }
// nolint: golint // 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{ return &templateTextRenderer{
ReadText: readFile, ReadText: fs.ReadFile,
Context: &Context{ Context: &Context{
basePath: basePath, basePath: basePath,
readFile: readFile, fs: fs,
}, },
Data: data, Data: data,
} }

View File

@ -1,10 +1,11 @@
package tmpl package tmpl
import ( import (
"os"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/filesystem"
) )
// TestTextRenderer tests the text renderer. // TestTextRenderer tests the text renderer.
@ -12,7 +13,7 @@ func TestNewTextRenderer(t *testing.T) {
tData := map[string]interface{}{ tData := map[string]interface{}{
"foo": "bar", "foo": "bar",
} }
tr := NewTextRenderer(os.ReadFile, ".", tData) tr := NewTextRenderer(filesystem.DefaultFileSystem(), ".", tData)
require.Equal(t, tData, tr.Data) require.Equal(t, tData, tr.Data)
require.Equal(t, ".", tr.Context.basePath) require.Equal(t, ".", tr.Context.basePath)
} }
@ -22,7 +23,7 @@ func TestTextRender(t *testing.T) {
tData := map[string]interface{}{ tData := map[string]interface{}{
"foot": "bart", "foot": "bart",
} }
tr := NewTextRenderer(os.ReadFile, ".", tData) tr := NewTextRenderer(filesystem.DefaultFileSystem(), ".", tData)
tests := []struct { tests := []struct {
text string text string

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
) )
@ -276,7 +277,7 @@ func TestFileRendering(t *testing.T) {
filename := fmt.Sprintf("%s/%s.gotmpl", tempDir, tc.name) filename := fmt.Sprintf("%s/%s.gotmpl", tempDir, tc.name)
os.WriteFile(filename, []byte(tc.tmplString), 0644) 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) tmpl_bytes, err := fileRenderer.RenderToBytes(filename)
if tc.wantErr { if tc.wantErr {
@ -294,8 +295,7 @@ func TestFileRendering(t *testing.T) {
func TestTmplStrings(t *testing.T) { func TestTmplStrings(t *testing.T) {
c := &tmpl.Context{} c := &tmpl.Context{}
c.SetBasePath(".") c.SetBasePath(".")
c.SetReadFile(os.ReadFile) c.SetFileSystem(filesystem.DefaultFileSystem())
c.SetReadDir(os.ReadDir)
tmpl := template.New("stringTemplateTest").Funcs(c.CreateFuncMap()) tmpl := template.New("stringTemplateTest").Funcs(c.CreateFuncMap())
tmplE2eTest.load() tmplE2eTest.load()