mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-18 16:03:03 +02:00
feat: optionally allow missing environment values/secrets files (#620)
```yaml
environments:
default:
missingFileHandler: Warn
values:
- path/to/values.yaml
secrets:
- path/to/secrets.yaml
```
`missingFileHandler` set to `Warn`, `Info`, or `Debug` results in helmfile NOT stop when `path/to/values.yaml` or `path/to/secrets.yaml` is missing.
Resolves #548
While implementing the above feature, I also found a bug that has been causing #559. This also fixes that.
To verify it is actually fixed, create an example helmfile.yaml that looks like the below, and run `helmfile diff`:
```
$ cat helmfile.yaml
environments:
default:
secrets:
- env-secrets.yaml
releases:
- name: myapp
chart: nginx
namespace: default
secrets: [secrets.yaml] # Notice this file does not exist
values:
- ingress:
enabled: true
$ helmfile diff
could not deduce `environment:` block, configuring only .Environment.Name. error: failed to read helmfile.yaml.part.0: environment values file matching "env-secrets.yaml" does not exist
in ./helmfile.yaml: failed to read helmfile.yaml: environment values file matching "env-secrets.yaml" does not exist
```
Fixes #559
This commit is contained in:
+20
-5
@@ -26,6 +26,7 @@ type App struct {
|
||||
Selectors []string
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
glob func(string) ([]string, error)
|
||||
abs func(string) (string, error)
|
||||
fileExistsAt func(string) bool
|
||||
@@ -42,6 +43,7 @@ func Init(app *App) *App {
|
||||
app.getwd = os.Getwd
|
||||
app.chdir = os.Chdir
|
||||
app.fileExistsAt = fileExistsAt
|
||||
app.fileExists = fileExists
|
||||
app.directoryExistsAt = directoryExistsAt
|
||||
return app
|
||||
}
|
||||
@@ -113,11 +115,12 @@ func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error {
|
||||
|
||||
func (a *App) loadDesiredStateFromYaml(file string) (*state.HelmState, error) {
|
||||
ld := &desiredStateLoader{
|
||||
readFile: a.readFile,
|
||||
env: a.Env,
|
||||
namespace: a.Namespace,
|
||||
logger: a.Logger,
|
||||
abs: a.abs,
|
||||
readFile: a.readFile,
|
||||
fileExists: a.fileExists,
|
||||
env: a.Env,
|
||||
namespace: a.Namespace,
|
||||
logger: a.Logger,
|
||||
abs: a.abs,
|
||||
|
||||
Reverse: a.Reverse,
|
||||
KubeContext: a.KubeContext,
|
||||
@@ -316,6 +319,18 @@ func fileExistsAt(path string) bool {
|
||||
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()
|
||||
|
||||
+83
-23
@@ -24,6 +24,7 @@ func injectFs(app *App, fs *state.TestFs) *App {
|
||||
app.getwd = fs.Getwd
|
||||
app.chdir = fs.Chdir
|
||||
app.fileExistsAt = fs.FileExistsAt
|
||||
app.fileExists = fs.FileExists
|
||||
app.directoryExistsAt = fs.DirectoryExistsAt
|
||||
return app
|
||||
}
|
||||
@@ -155,12 +156,66 @@ releases:
|
||||
t.Fatal("expected error did not occur")
|
||||
}
|
||||
|
||||
expected := "in ./helmfile.yaml: failed to read helmfile.yaml: no file matching env.*.yaml found"
|
||||
expected := "in ./helmfile.yaml: failed to read helmfile.yaml: environment values file matching \"env.*.yaml\" does not exist"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("unexpected error: expected=%s, got=%v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisitDesiredStatesWithReleasesFiltered_MissingEnvValuesFileHandler(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
handler string
|
||||
filePattern string
|
||||
expectErr bool
|
||||
}{
|
||||
{name: "error handler with no files matching glob", handler: "Error", filePattern: "env.*.yaml", expectErr: true},
|
||||
{name: "warn handler with no files matching glob", handler: "Warn", filePattern: "env.*.yaml", expectErr: false},
|
||||
{name: "info handler with no files matching glob", handler: "Info", filePattern: "env.*.yaml", expectErr: false},
|
||||
{name: "debug handler with no files matching glob", handler: "Debug", filePattern: "env.*.yaml", expectErr: false},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
testcase := testcases[i]
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": fmt.Sprintf(`
|
||||
environments:
|
||||
default:
|
||||
missingFileHandler: %s
|
||||
values:
|
||||
- %s
|
||||
releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
`, testcase.handler, testcase.filePattern),
|
||||
}
|
||||
fs := state.NewTestFs(files)
|
||||
app := &App{
|
||||
KubeContext: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
}
|
||||
app = injectFs(app, fs)
|
||||
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||
return []error{}
|
||||
}
|
||||
|
||||
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||
"helmfile.yaml", noop,
|
||||
)
|
||||
if testcase.expectErr && err == nil {
|
||||
t.Fatal("expected error did not occur")
|
||||
}
|
||||
|
||||
if !testcase.expectErr && err != nil {
|
||||
t.Errorf("not error expected, but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/roboll/helmfile/issues/193
|
||||
func TestVisitDesiredStatesWithReleasesFiltered(t *testing.T) {
|
||||
files := map[string]string{
|
||||
@@ -746,16 +801,18 @@ helmDefaults:
|
||||
`,
|
||||
})
|
||||
app := &App{
|
||||
readFile: testFs.ReadFile,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
KubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
readFile: testFs.ReadFile,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
fileExistsAt: testFs.FileExistsAt,
|
||||
fileExists: testFs.FileExists,
|
||||
KubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
}
|
||||
st, err := app.loadDesiredStateFromYaml(yamlFile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if st.HelmDefaults.TillerNamespace != "TILLER_NS" {
|
||||
@@ -825,11 +882,12 @@ helmDefaults:
|
||||
`,
|
||||
})
|
||||
app := &App{
|
||||
readFile: testFs.ReadFile,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
readFile: testFs.ReadFile,
|
||||
fileExists: testFs.FileExists,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
}
|
||||
st, err := app.loadDesiredStateFromYaml(yamlFile)
|
||||
if err != nil {
|
||||
@@ -900,11 +958,12 @@ foo: FOO
|
||||
`,
|
||||
})
|
||||
app := &App{
|
||||
readFile: testFs.ReadFile,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
readFile: testFs.ReadFile,
|
||||
fileExists: testFs.FileExists,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "default",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
}
|
||||
st, err := app.loadDesiredStateFromYaml(yamlFile)
|
||||
if err != nil {
|
||||
@@ -978,11 +1037,12 @@ helmDefaults:
|
||||
`,
|
||||
})
|
||||
app := &App{
|
||||
readFile: testFs.ReadFile,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "test",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
readFile: testFs.ReadFile,
|
||||
fileExists: testFs.FileExists,
|
||||
glob: testFs.Glob,
|
||||
abs: testFs.Abs,
|
||||
Env: "test",
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
}
|
||||
st, err := app.loadDesiredStateFromYaml(yamlFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,9 +19,10 @@ type desiredStateLoader struct {
|
||||
env string
|
||||
namespace string
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
@@ -96,7 +97,7 @@ func (ld *desiredStateLoader) loadFile(inheritedEnv *environment.Environment, ba
|
||||
}
|
||||
|
||||
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
||||
c := state.NewCreator(a.logger, a.readFile, a.abs, a.glob)
|
||||
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob)
|
||||
c.LoadFile = a.loadFile
|
||||
return c
|
||||
}
|
||||
@@ -109,10 +110,13 @@ func (a *desiredStateLoader) load(yaml []byte, baseDir, file string, evaluateBas
|
||||
|
||||
helmfiles := []state.SubHelmfileSpec{}
|
||||
for _, hf := range st.Helmfiles {
|
||||
matches, err := st.ExpandPaths([]string{hf.Path}, a.glob)
|
||||
matches, err := st.ExpandPaths(hf.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
return nil, fmt.Errorf("no file matching %s found", hf.Path)
|
||||
}
|
||||
for _, match := range matches {
|
||||
newHelmfile := hf
|
||||
newHelmfile.Path = match
|
||||
|
||||
@@ -13,12 +13,13 @@ import (
|
||||
func makeLoader(files map[string]string, env string) (*desiredStateLoader, *state.TestFs) {
|
||||
testfs := state.NewTestFs(files)
|
||||
return &desiredStateLoader{
|
||||
env: env,
|
||||
namespace: "namespace",
|
||||
logger: helmexec.NewLogger(os.Stdout, "debug"),
|
||||
readFile: testfs.ReadFile,
|
||||
abs: testfs.Abs,
|
||||
glob: testfs.Glob,
|
||||
env: env,
|
||||
namespace: "namespace",
|
||||
logger: helmexec.NewLogger(os.Stdout, "debug"),
|
||||
readFile: testfs.ReadFile,
|
||||
fileExists: testfs.FileExists,
|
||||
abs: testfs.Abs,
|
||||
glob: testfs.Glob,
|
||||
}, testfs
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user