feat: expand glob pattern in environment values file path (#610)
This enhances helmfile's internal environment values files loader to expand glob patterns (#606) Fixes the existing bug that helmfile was unable to load environment values file from absolute path (#549) Resolves #606 Fixes #549
This commit is contained in:
parent
4c9c42d3c5
commit
90390492a3
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/helmexec"
|
"github.com/roboll/helmfile/helmexec"
|
||||||
|
|
@ -13,119 +12,153 @@ import (
|
||||||
"gotest.tools/env"
|
"gotest.tools/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testFs struct {
|
|
||||||
wd string
|
|
||||||
dirs map[string]bool
|
|
||||||
files map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func appWithFs(app *App, files map[string]string) *App {
|
func appWithFs(app *App, files map[string]string) *App {
|
||||||
fs := newTestFs(files)
|
fs := state.NewTestFs(files)
|
||||||
return injectFs(app, fs)
|
return injectFs(app, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func injectFs(app *App, fs *testFs) *App {
|
func injectFs(app *App, fs *state.TestFs) *App {
|
||||||
app.readFile = fs.readFile
|
app.readFile = fs.ReadFile
|
||||||
app.glob = fs.glob
|
app.glob = fs.Glob
|
||||||
app.abs = fs.abs
|
app.abs = fs.Abs
|
||||||
app.getwd = fs.getwd
|
app.getwd = fs.Getwd
|
||||||
app.chdir = fs.chdir
|
app.chdir = fs.Chdir
|
||||||
app.fileExistsAt = fs.fileExistsAt
|
app.fileExistsAt = fs.FileExistsAt
|
||||||
app.directoryExistsAt = fs.directoryExistsAt
|
app.directoryExistsAt = fs.DirectoryExistsAt
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestFs(files map[string]string) *testFs {
|
func TestVisitDesiredStatesWithReleasesFiltered_ReleaseOrder(t *testing.T) {
|
||||||
dirs := map[string]bool{}
|
files := map[string]string{
|
||||||
for abs, _ := range files {
|
"/path/to/helmfile.yaml": `
|
||||||
d := filepath.Dir(abs)
|
helmfiles:
|
||||||
dirs[d] = true
|
- helmfile.d/a*.yaml
|
||||||
|
- helmfile.d/b*.yaml
|
||||||
|
`,
|
||||||
|
"/path/to/helmfile.d/a1.yaml": `
|
||||||
|
releases:
|
||||||
|
- name: zipkin
|
||||||
|
chart: stable/zipkin
|
||||||
|
`,
|
||||||
|
"/path/to/helmfile.d/a2.yaml": `
|
||||||
|
releases:
|
||||||
|
- name: prometheus
|
||||||
|
chart: stable/prometheus
|
||||||
|
`,
|
||||||
|
"/path/to/helmfile.d/b.yaml": `
|
||||||
|
releases:
|
||||||
|
- name: grafana
|
||||||
|
chart: stable/grafana
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
return &testFs{
|
fs := state.NewTestFs(files)
|
||||||
wd: "/path/to",
|
fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
|
||||||
dirs: dirs,
|
app := &App{
|
||||||
files: files,
|
KubeContext: "default",
|
||||||
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
|
Namespace: "",
|
||||||
|
Env: "default",
|
||||||
|
}
|
||||||
|
app = injectFs(app, fs)
|
||||||
|
actualOrder := []string{}
|
||||||
|
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
actualOrder = append(actualOrder, st.FilePath)
|
||||||
|
return []error{}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
|
"helmfile.yaml", noop,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedOrder := []string{"a1.yaml", "a2.yaml", "b.yaml", "helmfile.yaml"}
|
||||||
|
if !reflect.DeepEqual(actualOrder, expectedOrder) {
|
||||||
|
t.Errorf("unexpected order of processed state files: expected=%v, actual=%v", expectedOrder, actualOrder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFs) fileExistsAt(path string) bool {
|
func TestVisitDesiredStatesWithReleasesFiltered_EnvValuesFileOrder(t *testing.T) {
|
||||||
var ok bool
|
files := map[string]string{
|
||||||
if strings.Contains(path, "/") {
|
"/path/to/helmfile.yaml": `
|
||||||
_, ok = f.files[path]
|
environments:
|
||||||
} else {
|
default:
|
||||||
_, ok = f.files[filepath.Join(f.wd, path)]
|
values:
|
||||||
|
- env.*.yaml
|
||||||
|
releases:
|
||||||
|
- name: zipkin
|
||||||
|
chart: stable/zipkin
|
||||||
|
`,
|
||||||
|
"/path/to/env.1.yaml": `FOO: 1
|
||||||
|
BAR: 2
|
||||||
|
`,
|
||||||
|
"/path/to/env.2.yaml": `BAR: 3
|
||||||
|
BAZ: 4
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
fs := state.NewTestFs(files)
|
||||||
|
fs.GlobFixtures["/path/to/env.*.yaml"] = []string{"/path/to/env.2.yaml", "/path/to/env.1.yaml"}
|
||||||
|
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 err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedOrder := []string{"helmfile.yaml", "/path/to/env.1.yaml", "/path/to/env.2.yaml", "/path/to/env.1.yaml", "/path/to/env.2.yaml"}
|
||||||
|
actualOrder := fs.SuccessfulReads()
|
||||||
|
if !reflect.DeepEqual(actualOrder, expectedOrder) {
|
||||||
|
t.Errorf("unexpected order of processed state files: expected=%v, actual=%v", expectedOrder, actualOrder)
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFs) directoryExistsAt(path string) bool {
|
func TestVisitDesiredStatesWithReleasesFiltered_MissingEnvValuesFile(t *testing.T) {
|
||||||
var ok bool
|
files := map[string]string{
|
||||||
if strings.Contains(path, "/") {
|
"/path/to/helmfile.yaml": `
|
||||||
_, ok = f.dirs[path]
|
environments:
|
||||||
} else {
|
default:
|
||||||
_, ok = f.dirs[filepath.Join(f.wd, path)]
|
values:
|
||||||
|
- env.*.yaml
|
||||||
|
releases:
|
||||||
|
- name: zipkin
|
||||||
|
chart: stable/zipkin
|
||||||
|
`,
|
||||||
}
|
}
|
||||||
return ok
|
fs := state.NewTestFs(files)
|
||||||
}
|
app := &App{
|
||||||
|
KubeContext: "default",
|
||||||
func (f *testFs) readFile(filename string) ([]byte, error) {
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
var str string
|
Namespace: "",
|
||||||
var ok bool
|
Env: "default",
|
||||||
if strings.Contains(filename, "/") {
|
|
||||||
str, ok = f.files[filename]
|
|
||||||
} else {
|
|
||||||
str, ok = f.files[filepath.Join(f.wd, filename)]
|
|
||||||
}
|
}
|
||||||
if !ok {
|
app = injectFs(app, fs)
|
||||||
return []byte(nil), fmt.Errorf("no file found: %s", filename)
|
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
}
|
return []error{}
|
||||||
return []byte(str), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *testFs) glob(relPattern string) ([]string, error) {
|
|
||||||
var pattern string
|
|
||||||
if relPattern[0] == '/' {
|
|
||||||
pattern = relPattern
|
|
||||||
} else {
|
|
||||||
pattern = filepath.Join(f.wd, relPattern)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := []string{}
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
for name, _ := range f.files {
|
"helmfile.yaml", noop,
|
||||||
matched, err := filepath.Match(pattern, name)
|
)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return nil, err
|
t.Fatal("expected error did not occur")
|
||||||
}
|
|
||||||
if matched {
|
|
||||||
matches = append(matches, name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(matches) == 0 {
|
|
||||||
return []string(nil), fmt.Errorf("no file matched %s for files: %v", pattern, f.files)
|
|
||||||
}
|
|
||||||
return matches, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *testFs) abs(path string) (string, error) {
|
expected := "in ./helmfile.yaml: failed to read helmfile.yaml: no file matching env.*.yaml found"
|
||||||
var p string
|
if err.Error() != expected {
|
||||||
if path[0] == '/' {
|
t.Errorf("unexpected error: expected=%s, got=%v", expected, err)
|
||||||
p = path
|
|
||||||
} else {
|
|
||||||
p = filepath.Join(f.wd, path)
|
|
||||||
}
|
}
|
||||||
return filepath.Clean(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *testFs) getwd() (string, error) {
|
|
||||||
return f.wd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *testFs) chdir(dir string) error {
|
|
||||||
if dir == "/path/to" || dir == "/path/to/helmfile.d" {
|
|
||||||
f.wd = dir
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unexpected chdir \"%s\"", dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/193
|
// See https://github.com/roboll/helmfile/issues/193
|
||||||
|
|
@ -152,10 +185,6 @@ releases:
|
||||||
chart: stable/grafana
|
chart: stable/grafana
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
|
||||||
return []error{}
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
name string
|
name string
|
||||||
expectErr bool
|
expectErr bool
|
||||||
|
|
@ -167,13 +196,20 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
app := appWithFs(&App{
|
fs := state.NewTestFs(files)
|
||||||
|
fs.GlobFixtures["/path/to/helmfile.d/a*.yaml"] = []string{"/path/to/helmfile.d/a2.yaml", "/path/to/helmfile.d/a1.yaml"}
|
||||||
|
app := &App{
|
||||||
KubeContext: "default",
|
KubeContext: "default",
|
||||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
Selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
|
Selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
|
||||||
Namespace: "",
|
Namespace: "",
|
||||||
Env: "default",
|
Env: "default",
|
||||||
}, files)
|
}
|
||||||
|
app = injectFs(app, fs)
|
||||||
|
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
return []error{}
|
||||||
|
}
|
||||||
|
|
||||||
err := app.VisitDesiredStatesWithReleasesFiltered(
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
"helmfile.yaml", noop,
|
"helmfile.yaml", noop,
|
||||||
)
|
)
|
||||||
|
|
@ -668,7 +704,7 @@ func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
|
||||||
|
|
||||||
func TestLoadDesiredStateFromYaml_Bases(t *testing.T) {
|
func TestLoadDesiredStateFromYaml_Bases(t *testing.T) {
|
||||||
yamlFile := "/path/to/yaml/file"
|
yamlFile := "/path/to/yaml/file"
|
||||||
yamlContent := []byte(`bases:
|
yamlContent := `bases:
|
||||||
- ../base.yaml
|
- ../base.yaml
|
||||||
- ../base.gotmpl
|
- ../base.gotmpl
|
||||||
|
|
||||||
|
|
@ -685,41 +721,34 @@ releases:
|
||||||
labels:
|
labels:
|
||||||
stage: post
|
stage: post
|
||||||
<<: *default
|
<<: *default
|
||||||
`)
|
`
|
||||||
files := map[string][]byte{
|
testFs := state.NewTestFs(map[string]string{
|
||||||
yamlFile: yamlContent,
|
yamlFile: yamlContent,
|
||||||
"/path/to/base.yaml": []byte(`environments:
|
"/path/to/base.yaml": `environments:
|
||||||
default:
|
default:
|
||||||
values:
|
values:
|
||||||
- environments/default/1.yaml
|
- environments/default/1.yaml
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/1.yaml": []byte(`foo: FOO`),
|
"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
|
||||||
"/path/to/base.gotmpl": []byte(`environments:
|
"/path/to/base.gotmpl": `environments:
|
||||||
default:
|
default:
|
||||||
values:
|
values:
|
||||||
- environments/default/2.yaml
|
- environments/default/2.yaml
|
||||||
|
|
||||||
helmDefaults:
|
helmDefaults:
|
||||||
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/2.yaml": []byte(`tillerNs: TILLER_NS`),
|
"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
|
||||||
"/path/to/yaml/templates.yaml": []byte(`templates:
|
"/path/to/yaml/templates.yaml": `templates:
|
||||||
default: &default
|
default: &default
|
||||||
missingFileHandler: Warn
|
missingFileHandler: Warn
|
||||||
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
||||||
`),
|
`,
|
||||||
}
|
})
|
||||||
readFile := func(filename string) ([]byte, error) {
|
|
||||||
content, ok := files[filename]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: %s", filename)
|
|
||||||
}
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
app := &App{
|
app := &App{
|
||||||
readFile: readFile,
|
readFile: testFs.ReadFile,
|
||||||
glob: filepath.Glob,
|
glob: testFs.Glob,
|
||||||
abs: filepath.Abs,
|
abs: testFs.Abs,
|
||||||
KubeContext: "default",
|
KubeContext: "default",
|
||||||
Env: "default",
|
Env: "default",
|
||||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
|
|
@ -744,7 +773,7 @@ helmDefaults:
|
||||||
|
|
||||||
func TestLoadDesiredStateFromYaml_MultiPartTemplate(t *testing.T) {
|
func TestLoadDesiredStateFromYaml_MultiPartTemplate(t *testing.T) {
|
||||||
yamlFile := "/path/to/yaml/file"
|
yamlFile := "/path/to/yaml/file"
|
||||||
yamlContent := []byte(`bases:
|
yamlContent := `bases:
|
||||||
- ../base.yaml
|
- ../base.yaml
|
||||||
---
|
---
|
||||||
bases:
|
bases:
|
||||||
|
|
@ -771,41 +800,34 @@ releases:
|
||||||
labels:
|
labels:
|
||||||
stage: post
|
stage: post
|
||||||
<<: *default
|
<<: *default
|
||||||
`)
|
`
|
||||||
files := map[string][]byte{
|
testFs := state.NewTestFs(map[string]string{
|
||||||
yamlFile: yamlContent,
|
yamlFile: yamlContent,
|
||||||
"/path/to/base.yaml": []byte(`environments:
|
"/path/to/base.yaml": `environments:
|
||||||
default:
|
default:
|
||||||
values:
|
values:
|
||||||
- environments/default/1.yaml
|
- environments/default/1.yaml
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/1.yaml": []byte(`foo: FOO`),
|
"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
|
||||||
"/path/to/base.gotmpl": []byte(`environments:
|
"/path/to/base.gotmpl": `environments:
|
||||||
default:
|
default:
|
||||||
values:
|
values:
|
||||||
- environments/default/2.yaml
|
- environments/default/2.yaml
|
||||||
|
|
||||||
helmDefaults:
|
helmDefaults:
|
||||||
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/2.yaml": []byte(`tillerNs: TILLER_NS`),
|
"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
|
||||||
"/path/to/yaml/templates.yaml": []byte(`templates:
|
"/path/to/yaml/templates.yaml": `templates:
|
||||||
default: &default
|
default: &default
|
||||||
missingFileHandler: Warn
|
missingFileHandler: Warn
|
||||||
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
||||||
`),
|
`,
|
||||||
}
|
})
|
||||||
readFile := func(filename string) ([]byte, error) {
|
|
||||||
content, ok := files[filename]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: %s", filename)
|
|
||||||
}
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
app := &App{
|
app := &App{
|
||||||
readFile: readFile,
|
readFile: testFs.ReadFile,
|
||||||
glob: filepath.Glob,
|
glob: testFs.Glob,
|
||||||
abs: filepath.Abs,
|
abs: testFs.Abs,
|
||||||
Env: "default",
|
Env: "default",
|
||||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
}
|
}
|
||||||
|
|
@ -845,7 +867,7 @@ helmDefaults:
|
||||||
|
|
||||||
func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithNonDefaultEnv(t *testing.T) {
|
func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithNonDefaultEnv(t *testing.T) {
|
||||||
yamlFile := "/path/to/yaml/file"
|
yamlFile := "/path/to/yaml/file"
|
||||||
yamlContent := []byte(`bases:
|
yamlContent := `bases:
|
||||||
- ../base.yaml
|
- ../base.yaml
|
||||||
---
|
---
|
||||||
bases:
|
bases:
|
||||||
|
|
@ -872,41 +894,34 @@ releases:
|
||||||
labels:
|
labels:
|
||||||
stage: post
|
stage: post
|
||||||
<<: *default
|
<<: *default
|
||||||
`)
|
`
|
||||||
files := map[string][]byte{
|
testFs := state.NewTestFs(map[string]string{
|
||||||
yamlFile: yamlContent,
|
yamlFile: yamlContent,
|
||||||
"/path/to/base.yaml": []byte(`environments:
|
"/path/to/base.yaml": `environments:
|
||||||
test:
|
test:
|
||||||
values:
|
values:
|
||||||
- environments/default/1.yaml
|
- environments/default/1.yaml
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/1.yaml": []byte(`foo: FOO`),
|
"/path/to/yaml/environments/default/1.yaml": `foo: FOO`,
|
||||||
"/path/to/base.gotmpl": []byte(`environments:
|
"/path/to/base.gotmpl": `environments:
|
||||||
test:
|
test:
|
||||||
values:
|
values:
|
||||||
- environments/default/2.yaml
|
- environments/default/2.yaml
|
||||||
|
|
||||||
helmDefaults:
|
helmDefaults:
|
||||||
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
tillerNamespace: {{ .Environment.Values.tillerNs }}
|
||||||
`),
|
`,
|
||||||
"/path/to/yaml/environments/default/2.yaml": []byte(`tillerNs: TILLER_NS`),
|
"/path/to/yaml/environments/default/2.yaml": `tillerNs: TILLER_NS`,
|
||||||
"/path/to/yaml/templates.yaml": []byte(`templates:
|
"/path/to/yaml/templates.yaml": `templates:
|
||||||
default: &default
|
default: &default
|
||||||
missingFileHandler: Warn
|
missingFileHandler: Warn
|
||||||
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
||||||
`),
|
`,
|
||||||
}
|
})
|
||||||
readFile := func(filename string) ([]byte, error) {
|
|
||||||
content, ok := files[filename]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: %s", filename)
|
|
||||||
}
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
app := &App{
|
app := &App{
|
||||||
readFile: readFile,
|
readFile: testFs.ReadFile,
|
||||||
glob: filepath.Glob,
|
glob: testFs.Glob,
|
||||||
abs: filepath.Abs,
|
abs: testFs.Abs,
|
||||||
Env: "test",
|
Env: "test",
|
||||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
}
|
}
|
||||||
|
|
@ -946,7 +961,7 @@ helmDefaults:
|
||||||
|
|
||||||
func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithReverse(t *testing.T) {
|
func TestLoadDesiredStateFromYaml_MultiPartTemplate_WithReverse(t *testing.T) {
|
||||||
yamlFile := "/path/to/yaml/file"
|
yamlFile := "/path/to/yaml/file"
|
||||||
yamlContent := []byte(`
|
yamlContent := `
|
||||||
{{ readFile "templates.yaml" }}
|
{{ readFile "templates.yaml" }}
|
||||||
|
|
||||||
releases:
|
releases:
|
||||||
|
|
@ -965,26 +980,19 @@ releases:
|
||||||
- name: myrelease3
|
- name: myrelease3
|
||||||
chart: mychart3
|
chart: mychart3
|
||||||
<<: *default
|
<<: *default
|
||||||
`)
|
`
|
||||||
files := map[string][]byte{
|
testFs := state.NewTestFs(map[string]string{
|
||||||
yamlFile: yamlContent,
|
yamlFile: yamlContent,
|
||||||
"/path/to/yaml/templates.yaml": []byte(`templates:
|
"/path/to/yaml/templates.yaml": `templates:
|
||||||
default: &default
|
default: &default
|
||||||
missingFileHandler: Warn
|
missingFileHandler: Warn
|
||||||
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
values: ["` + "{{`" + `{{.Release.Name}}` + "`}}" + `/values.yaml"]
|
||||||
`),
|
`,
|
||||||
}
|
})
|
||||||
readFile := func(filename string) ([]byte, error) {
|
|
||||||
content, ok := files[filename]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: %s", filename)
|
|
||||||
}
|
|
||||||
return content, nil
|
|
||||||
}
|
|
||||||
app := &App{
|
app := &App{
|
||||||
readFile: readFile,
|
readFile: testFs.ReadFile,
|
||||||
glob: filepath.Glob,
|
glob: testFs.Glob,
|
||||||
abs: filepath.Abs,
|
abs: testFs.Abs,
|
||||||
Env: "default",
|
Env: "default",
|
||||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
Reverse: true,
|
Reverse: true,
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ func (ld *desiredStateLoader) loadFile(baseDir, file string, evaluateBases bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
||||||
c := state.NewCreator(a.logger, a.readFile, a.abs)
|
c := state.NewCreator(a.logger, a.readFile, a.abs, a.glob)
|
||||||
c.LoadFile = a.loadFile
|
c.LoadFile = a.loadFile
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
@ -108,18 +108,10 @@ func (a *desiredStateLoader) load(yaml []byte, baseDir, file string, evaluateBas
|
||||||
|
|
||||||
helmfiles := []state.SubHelmfileSpec{}
|
helmfiles := []state.SubHelmfileSpec{}
|
||||||
for _, hf := range st.Helmfiles {
|
for _, hf := range st.Helmfiles {
|
||||||
globPattern := hf.Path
|
matches, err := st.ExpandPaths([]string{hf.Path}, a.glob)
|
||||||
var absPathPattern string
|
|
||||||
if filepath.IsAbs(globPattern) {
|
|
||||||
absPathPattern = globPattern
|
|
||||||
} else {
|
|
||||||
absPathPattern = st.JoinBase(globPattern)
|
|
||||||
}
|
|
||||||
matches, err := a.glob(absPathPattern)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed processing %s: %v", globPattern, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
sort.Strings(matches)
|
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
newHelmfile := hf
|
newHelmfile := hf
|
||||||
newHelmfile.Path = match
|
newHelmfile.Path = match
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -12,14 +10,16 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeLoader(readFile func(string) ([]byte, error), env string) *desiredStateLoader {
|
func makeLoader(files map[string]string, env string) (*desiredStateLoader, *state.TestFs) {
|
||||||
|
testfs := state.NewTestFs(files)
|
||||||
return &desiredStateLoader{
|
return &desiredStateLoader{
|
||||||
readFile: readFile,
|
|
||||||
env: env,
|
env: env,
|
||||||
namespace: "namespace",
|
namespace: "namespace",
|
||||||
logger: helmexec.NewLogger(os.Stdout, "debug"),
|
logger: helmexec.NewLogger(os.Stdout, "debug"),
|
||||||
abs: filepath.Abs,
|
readFile: testfs.ReadFile,
|
||||||
}
|
abs: testfs.Abs,
|
||||||
|
glob: testfs.Glob,
|
||||||
|
}, testfs
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFromYaml_MakeEnvironmentHasNoSideEffects(t *testing.T) {
|
func TestReadFromYaml_MakeEnvironmentHasNoSideEffects(t *testing.T) {
|
||||||
|
|
@ -36,30 +36,21 @@ releases:
|
||||||
chart: mychart1
|
chart: mychart1
|
||||||
`)
|
`)
|
||||||
|
|
||||||
fileReaderCalls := 0
|
files := map[string]string{
|
||||||
// make a reader that returns a simulated context
|
"/path/to/default/values.yaml": ``,
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
"/path/to/other/default/values.yaml": `SecondPass`,
|
||||||
expectedFilename := filepath.Clean("default/values.yaml")
|
|
||||||
if !strings.HasSuffix(filename, expectedFilename) {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: expected=%s, actual=%s", expectedFilename, filename)
|
|
||||||
}
|
|
||||||
fileReaderCalls++
|
|
||||||
if fileReaderCalls == 2 {
|
|
||||||
return []byte("SecondPass"), nil
|
|
||||||
}
|
|
||||||
return []byte(""), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, testfs := makeLoader(files, "staging")
|
||||||
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var state state.HelmState
|
var state state.HelmState
|
||||||
err = yaml.Unmarshal(yamlBuf.Bytes(), &state)
|
err = yaml.Unmarshal(yamlBuf.Bytes(), &state)
|
||||||
|
|
||||||
if fileReaderCalls > 2 {
|
if testfs.FileReaderCalls() > 2 {
|
||||||
t.Error("reader should be called only twice")
|
t.Error("reader should be called only twice")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,10 +61,10 @@ releases:
|
||||||
|
|
||||||
func TestReadFromYaml_RenderTemplate(t *testing.T) {
|
func TestReadFromYaml_RenderTemplate(t *testing.T) {
|
||||||
|
|
||||||
defaultValuesYaml := []byte(`
|
defaultValuesYaml := `
|
||||||
releaseName: "hello"
|
releaseName: "hello"
|
||||||
conditionalReleaseTag: "yes"
|
conditionalReleaseTag: "yes"
|
||||||
`)
|
`
|
||||||
|
|
||||||
yamlContent := []byte(`
|
yamlContent := []byte(`
|
||||||
environments:
|
environments:
|
||||||
|
|
@ -92,16 +83,11 @@ releases:
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// make a reader that returns a simulated context
|
files := map[string]string{
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
"/path/to/default/values.yaml": defaultValuesYaml,
|
||||||
expectedFilename := filepath.Clean("default/values.yaml")
|
|
||||||
if !strings.HasSuffix(filename, expectedFilename) {
|
|
||||||
return nil, fmt.Errorf("unexpected filename: expected=%s, actual=%s", expectedFilename, filename)
|
|
||||||
}
|
|
||||||
return defaultValuesYaml, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, _ := makeLoader(files, "staging")
|
||||||
// test the double rendering
|
// test the double rendering
|
||||||
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -129,7 +115,7 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFromYaml_RenderTemplateWithValuesReferenceError(t *testing.T) {
|
func TestReadFromYaml_RenderTemplateWithValuesReferenceError(t *testing.T) {
|
||||||
defaultValuesYaml := []byte("")
|
defaultValuesYaml := ``
|
||||||
|
|
||||||
yamlContent := []byte(`
|
yamlContent := []byte(`
|
||||||
environments:
|
environments:
|
||||||
|
|
@ -145,12 +131,11 @@ releases:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// make a reader that returns a simulated context
|
files := map[string]string{
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
"/path/to/default/values.yaml": defaultValuesYaml,
|
||||||
return defaultValuesYaml, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, _ := makeLoader(files, "staging")
|
||||||
// test the double rendering
|
// test the double rendering
|
||||||
_, err := r.renderTemplatesToYaml("", "", yamlContent)
|
_, err := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
|
|
||||||
|
|
@ -164,9 +149,9 @@ releases:
|
||||||
// This does not apply to .gotmpl files, which is a nice side-effect.
|
// This does not apply to .gotmpl files, which is a nice side-effect.
|
||||||
func TestReadFromYaml_RenderTemplateWithGotmpl(t *testing.T) {
|
func TestReadFromYaml_RenderTemplateWithGotmpl(t *testing.T) {
|
||||||
|
|
||||||
defaultValuesYamlGotmpl := []byte(`
|
defaultValuesYamlGotmpl := `
|
||||||
releaseName: {{ readFile "nonIgnoredFile" }}
|
releaseName: {{ readFile "nonIgnoredFile" }}
|
||||||
`)
|
`
|
||||||
|
|
||||||
yamlContent := []byte(`
|
yamlContent := []byte(`
|
||||||
environments:
|
environments:
|
||||||
|
|
@ -182,14 +167,12 @@ releases:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
files := map[string]string{
|
||||||
if strings.HasSuffix(filename, "nonIgnoredFile") {
|
"/path/to/nonIgnoredFile": `release-a`,
|
||||||
return []byte("release-a"), nil
|
"/path/to/values.yaml.gotmpl": defaultValuesYamlGotmpl,
|
||||||
}
|
|
||||||
return defaultValuesYamlGotmpl, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, _ := makeLoader(files, "staging")
|
||||||
rendered, _ := r.renderTemplatesToYaml("", "", yamlContent)
|
rendered, _ := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
|
|
||||||
var state state.HelmState
|
var state state.HelmState
|
||||||
|
|
@ -205,18 +188,14 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadFromYaml_RenderTemplateWithNamespace(t *testing.T) {
|
func TestReadFromYaml_RenderTemplateWithNamespace(t *testing.T) {
|
||||||
defaultValuesYaml := []byte(``)
|
|
||||||
yamlContent := []byte(`releases:
|
yamlContent := []byte(`releases:
|
||||||
- name: {{ .Namespace }}-myrelease
|
- name: {{ .Namespace }}-myrelease
|
||||||
chart: mychart
|
chart: mychart
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// make a reader that returns a simulated context
|
files := map[string]string{}
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
|
||||||
return defaultValuesYaml, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, _ := makeLoader(files, "staging")
|
||||||
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
yamlBuf, err := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -243,11 +222,8 @@ releases:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
chart: mychart
|
chart: mychart
|
||||||
`)
|
`)
|
||||||
fileReader := func(filename string) ([]byte, error) {
|
|
||||||
return yamlContent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r := makeLoader(fileReader, "staging")
|
r, _ := makeLoader(map[string]string{}, "staging")
|
||||||
_, err := r.renderTemplatesToYaml("", "", yamlContent)
|
_, err := r.renderTemplatesToYaml("", "", yamlContent)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("wanted error, none returned")
|
t.Fatalf("wanted error, none returned")
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
|
|
@ -37,17 +38,19 @@ type StateCreator struct {
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
readFile func(string) ([]byte, error)
|
readFile func(string) ([]byte, error)
|
||||||
abs func(string) (string, error)
|
abs func(string) (string, error)
|
||||||
|
glob func(string) ([]string, error)
|
||||||
|
|
||||||
Strict bool
|
Strict bool
|
||||||
|
|
||||||
LoadFile func(baseDir, file string, evaluateBases bool) (*HelmState, error)
|
LoadFile func(baseDir, file string, evaluateBases bool) (*HelmState, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), abs func(string) (string, error)) *StateCreator {
|
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), abs func(string) (string, error), glob func(string) ([]string, error)) *StateCreator {
|
||||||
return &StateCreator{
|
return &StateCreator{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
readFile: readFile,
|
readFile: readFile,
|
||||||
abs: abs,
|
abs: abs,
|
||||||
|
glob: glob,
|
||||||
Strict: true,
|
Strict: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +121,7 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
|
||||||
func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *environment.Environment) (*HelmState, error) {
|
func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *environment.Environment) (*HelmState, error) {
|
||||||
state := *target
|
state := *target
|
||||||
|
|
||||||
e, err := state.loadEnvValues(env, ctxEnv, c.readFile)
|
e, err := state.loadEnvValues(env, ctxEnv, c.readFile, c.glob)
|
||||||
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}
|
||||||
}
|
}
|
||||||
|
|
@ -168,31 +171,64 @@ func (c *StateCreator) loadBases(st *HelmState, baseDir string) (*HelmState, err
|
||||||
return layers[0], nil
|
return layers[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *HelmState) loadEnvValues(name string, ctxEnv *environment.Environment, readFile func(string) ([]byte, error)) (*environment.Environment, error) {
|
func (st *HelmState) ExpandPaths(patterns []string, glob func(string) ([]string, error)) ([]string, error) {
|
||||||
|
result := []string{}
|
||||||
|
for _, globPattern := range patterns {
|
||||||
|
var absPathPattern string
|
||||||
|
if filepath.IsAbs(globPattern) {
|
||||||
|
absPathPattern = globPattern
|
||||||
|
} else {
|
||||||
|
absPathPattern = st.JoinBase(globPattern)
|
||||||
|
}
|
||||||
|
matches, err := glob(absPathPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed processing %s: %v", globPattern, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("no file matching %s found", globPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(matches)
|
||||||
|
|
||||||
|
result = append(result, matches...)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *HelmState) loadEnvValues(name string, ctxEnv *environment.Environment, readFile func(string) ([]byte, error), glob func(string) ([]string, error)) (*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 {
|
||||||
for _, envvalFile := range envSpec.Values {
|
valuesFiles, err := st.ExpandPaths(envSpec.Values, glob)
|
||||||
envvalFullPath := filepath.Join(st.basePath, envvalFile)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, envvalFullPath := range valuesFiles {
|
||||||
tmplData := EnvironmentTemplateData{Environment: environment.EmptyEnvironment, Namespace: ""}
|
tmplData := EnvironmentTemplateData{Environment: environment.EmptyEnvironment, Namespace: ""}
|
||||||
r := tmpl.NewFileRenderer(readFile, filepath.Dir(envvalFullPath), tmplData)
|
r := tmpl.NewFileRenderer(readFile, filepath.Dir(envvalFullPath), tmplData)
|
||||||
bytes, err := r.RenderToBytes(envvalFullPath)
|
bytes, err := r.RenderToBytes(envvalFullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err)
|
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFullPath, err)
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err)
|
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFullPath, err)
|
||||||
}
|
}
|
||||||
if err := mergo.Merge(&envVals, &m, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&envVals, &m, mergo.WithOverride); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load \"%s\": %v", envvalFile, err)
|
return nil, fmt.Errorf("failed to load \"%s\": %v", envvalFullPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(envSpec.Secrets) > 0 {
|
if len(envSpec.Secrets) > 0 {
|
||||||
|
secretsFiles, err := st.ExpandPaths(envSpec.Secrets, glob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
helm := helmexec.New(st.logger, "")
|
helm := helmexec.New(st.logger, "")
|
||||||
for _, secFile := range envSpec.Secrets {
|
for _, path := range secretsFiles {
|
||||||
path := filepath.Join(st.basePath, secFile)
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -212,14 +248,14 @@ func (st *HelmState) loadEnvValues(name string, ctxEnv *environment.Environment,
|
||||||
}
|
}
|
||||||
bytes, err := readFile(decFile)
|
bytes, err := readFile(decFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secFile, err)
|
return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err)
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secFile, err)
|
return nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err)
|
||||||
}
|
}
|
||||||
if err := mergo.Merge(&envVals, &m, mergo.WithOverride); err != nil {
|
if err := mergo.Merge(&envVals, &m, mergo.WithOverride); err != nil {
|
||||||
return nil, fmt.Errorf("failed to load \"%s\": %v", secFile, err)
|
return nil, fmt.Errorf("failed to load \"%s\": %v", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -99,23 +98,17 @@ bar: {{ readFile "bar.txt" }}
|
||||||
|
|
||||||
expectedValues := `env: production`
|
expectedValues := `env: production`
|
||||||
|
|
||||||
readFile := func(filename string) ([]byte, error) {
|
testFs := NewTestFs(map[string]string{
|
||||||
switch filename {
|
fooYamlFile: string(fooYamlContent),
|
||||||
case fooYamlFile:
|
barYamlFile: string(barYamlContent),
|
||||||
return fooYamlContent, nil
|
barTextFile: string(barTextContent),
|
||||||
case barYamlFile:
|
valuesFile: string(valuesContent),
|
||||||
return barYamlContent, nil
|
})
|
||||||
case barTextFile:
|
testFs.Cwd = "/example/path/to"
|
||||||
return barTextContent, nil
|
|
||||||
case valuesFile:
|
|
||||||
return valuesContent, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unexpected filename: %s", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
state, err := NewCreator(logger, readFile, filepath.Abs).ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", false, nil)
|
state, err := NewCreator(logger, testFs.ReadFile, testFs.Abs, testFs.Glob).ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := state.Env.Values
|
actual := state.Env.Values
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestFs struct {
|
||||||
|
Cwd string
|
||||||
|
dirs map[string]bool
|
||||||
|
files map[string]string
|
||||||
|
|
||||||
|
GlobFixtures map[string][]string
|
||||||
|
|
||||||
|
fileReaderCalls int
|
||||||
|
successfulReads []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestFs(files map[string]string) *TestFs {
|
||||||
|
dirs := map[string]bool{}
|
||||||
|
for abs, _ := range files {
|
||||||
|
d := filepath.Dir(abs)
|
||||||
|
dirs[d] = true
|
||||||
|
}
|
||||||
|
return &TestFs{
|
||||||
|
Cwd: "/path/to",
|
||||||
|
dirs: dirs,
|
||||||
|
files: files,
|
||||||
|
|
||||||
|
successfulReads: []string{},
|
||||||
|
|
||||||
|
GlobFixtures: map[string][]string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) FileExistsAt(path string) bool {
|
||||||
|
var ok bool
|
||||||
|
if strings.Contains(path, "/") {
|
||||||
|
_, ok = f.files[path]
|
||||||
|
} else {
|
||||||
|
_, ok = f.files[filepath.Join(f.Cwd, path)]
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) DirectoryExistsAt(path string) bool {
|
||||||
|
var ok bool
|
||||||
|
if strings.Contains(path, "/") {
|
||||||
|
_, ok = f.dirs[path]
|
||||||
|
} else {
|
||||||
|
_, ok = f.dirs[filepath.Join(f.Cwd, path)]
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) ReadFile(filename string) ([]byte, error) {
|
||||||
|
var str string
|
||||||
|
var ok bool
|
||||||
|
if filename[0] == '/' {
|
||||||
|
str, ok = f.files[filename]
|
||||||
|
} else {
|
||||||
|
str, ok = f.files[filepath.Join(f.Cwd, filename)]
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return []byte(nil), fmt.Errorf("no registered file found: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fileReaderCalls += 1
|
||||||
|
|
||||||
|
f.successfulReads = append(f.successfulReads, filename)
|
||||||
|
|
||||||
|
return []byte(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) SuccessfulReads() []string {
|
||||||
|
return f.successfulReads
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) FileReaderCalls() int {
|
||||||
|
return f.fileReaderCalls
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) Glob(relPattern string) ([]string, error) {
|
||||||
|
var pattern string
|
||||||
|
if relPattern[0] == '/' {
|
||||||
|
pattern = relPattern
|
||||||
|
} else {
|
||||||
|
pattern = filepath.Join(f.Cwd, relPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixtures, ok := f.GlobFixtures[pattern]
|
||||||
|
if ok {
|
||||||
|
return fixtures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := []string{}
|
||||||
|
for name, _ := range f.files {
|
||||||
|
matched, err := filepath.Match(pattern, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
matches = append(matches, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) Abs(path string) (string, error) {
|
||||||
|
var p string
|
||||||
|
if path[0] == '/' {
|
||||||
|
p = path
|
||||||
|
} else {
|
||||||
|
p = filepath.Join(f.Cwd, path)
|
||||||
|
}
|
||||||
|
return filepath.Clean(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) Getwd() (string, error) {
|
||||||
|
return f.Cwd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TestFs) Chdir(dir string) error {
|
||||||
|
if _, ok := f.dirs[dir]; ok {
|
||||||
|
f.Cwd = dir
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unexpected chdir \"%s\"", dir)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue