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:
KUOKA Yusuke
2019-05-28 15:33:45 +09:00
committed by GitHub
parent 4a5996d083
commit a896f801ab
10 changed files with 303 additions and 151 deletions
+20 -5
View File
@@ -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
View File
@@ -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 {
+9 -5
View File
@@ -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
+7 -6
View File
@@ -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
}