fix: Relative path in helmfile not relative to file in multi file setup (#448)

Fixes #431
This commit is contained in:
KUOKA Yusuke 2019-01-24 10:48:01 +09:00 committed by GitHub
parent 99ce8570c7
commit bf42d2519d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 225 additions and 221 deletions

View File

@ -8,20 +8,122 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
type testFs struct {
wd string
dirs map[string]bool
files map[string]string
}
func appWithFs(app *app, files map[string]string) *app {
fs := newTestFs(files)
return injectFs(app, fs)
}
func injectFs(app *app, fs *testFs) *app {
app.readFile = fs.readFile
app.glob = fs.glob
app.abs = fs.abs
app.getwd = fs.getwd
app.chdir = fs.chdir
app.fileExistsAt = fs.fileExistsAt
app.directoryExistsAt = fs.directoryExistsAt
return app
}
func newTestFs(files map[string]string) *testFs {
dirs := map[string]bool{}
for abs, _ := range files {
d := filepath.Dir(abs)
dirs[d] = true
}
return &testFs{
wd: "/path/to",
dirs: dirs,
files: files,
}
}
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.wd, 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.wd, path)]
}
return ok
}
func (f *testFs) readFile(filename string) ([]byte, error) {
var str string
var ok bool
if strings.Contains(filename, "/") {
str, ok = f.files[filename]
} else {
str, ok = f.files[filepath.Join(f.wd, filename)]
}
if !ok {
return []byte(nil), fmt.Errorf("no file found: %s", filename)
}
return []byte(str), nil
}
func (f *testFs) glob(pattern string) ([]string, error) {
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)
}
}
if len(matches) == 0 {
return []string(nil), fmt.Errorf("no file matched: %s", pattern)
}
return matches, nil
}
func (f *testFs) abs(path string) (string, error) {
var p string
if path[0] == '/' {
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
func TestVisitDesiredStatesWithReleasesFiltered(t *testing.T) {
absPaths := map[string]string{
".": "/path/to",
"/path/to/helmfile.d": "/path/to/helmfile.d",
}
dirs := map[string]bool{
"helmfile.d": true,
}
files := map[string]string{
"helmfile.yaml": `
"/path/to/helmfile.yaml": `
helmfiles:
- helmfile.d/a*.yaml
- helmfile.d/b*.yaml
@ -42,39 +144,6 @@ releases:
chart: stable/grafana
`,
}
globMatches := map[string][]string{
"/path/to/helmfile.d/a*.yaml": []string{"/path/to/helmfile.d/a1.yaml", "/path/to/helmfile.d/a2.yaml"},
"/path/to/helmfile.d/b*.yaml": []string{"/path/to/helmfile.d/b.yaml"},
}
fileExistsAt := func(path string) bool {
_, ok := files[path]
return ok
}
directoryExistsAt := func(path string) bool {
_, ok := dirs[path]
return ok
}
readFile := func(filename string) ([]byte, error) {
str, ok := files[filename]
if !ok {
return []byte(nil), fmt.Errorf("no file found: %s", filename)
}
return []byte(str), nil
}
glob := func(pattern string) ([]string, error) {
matches, ok := globMatches[pattern]
if !ok {
return []string(nil), fmt.Errorf("no file matched: %s", pattern)
}
return matches, nil
}
abs := func(path string) (string, error) {
a, ok := absPaths[path]
if !ok {
return "", fmt.Errorf("abs: unexpected path: %s", path)
}
return a, nil
}
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
return []error{}
}
@ -90,18 +159,13 @@ releases:
}
for _, testcase := range testcases {
app := &app{
readFile: readFile,
glob: glob,
abs: abs,
fileExistsAt: fileExistsAt,
directoryExistsAt: directoryExistsAt,
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
namespace: "",
env: "default",
}
app := appWithFs(&app{
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
selectors: []string{fmt.Sprintf("name=%s", testcase.name)},
namespace: "",
env: "default",
}, files)
err := app.VisitDesiredStatesWithReleasesFiltered(
"helmfile.yaml", noop,
)
@ -115,15 +179,8 @@ releases:
// See https://github.com/roboll/helmfile/issues/320
func TestVisitDesiredStatesWithReleasesFiltered_UndefinedEnv(t *testing.T) {
absPaths := map[string]string{
".": "/path/to",
"/path/to/helmfile.d": "/path/to/helmfile.d",
}
dirs := map[string]bool{
"helmfile.d": true,
}
files := map[string]string{
"helmfile.yaml": `
"/path/to/helmfile.yaml": `
environments:
prod:
@ -139,38 +196,6 @@ releases:
chart: stable/zipkin
`,
}
globMatches := map[string][]string{
"/path/to/helmfile.d/a*.yaml": []string{"/path/to/helmfile.d/a1.yaml"},
}
fileExistsAt := func(path string) bool {
_, ok := files[path]
return ok
}
directoryExistsAt := func(path string) bool {
_, ok := dirs[path]
return ok
}
readFile := func(filename string) ([]byte, error) {
str, ok := files[filename]
if !ok {
return []byte(nil), fmt.Errorf("no file found: %s", filename)
}
return []byte(str), nil
}
glob := func(pattern string) ([]string, error) {
matches, ok := globMatches[pattern]
if !ok {
return []string(nil), fmt.Errorf("no file matched: %s", pattern)
}
return matches, nil
}
abs := func(path string) (string, error) {
a, ok := absPaths[path]
if !ok {
return "", fmt.Errorf("abs: unexpected path: %s", path)
}
return a, nil
}
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
return []error{}
}
@ -185,18 +210,13 @@ releases:
}
for _, testcase := range testcases {
app := &app{
readFile: readFile,
glob: glob,
abs: abs,
fileExistsAt: fileExistsAt,
directoryExistsAt: directoryExistsAt,
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
namespace: "",
selectors: []string{},
env: testcase.name,
}
app := appWithFs(&app{
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
namespace: "",
selectors: []string{},
env: testcase.name,
}, files)
err := app.VisitDesiredStatesWithReleasesFiltered(
"helmfile.yaml", noop,
)
@ -210,15 +230,8 @@ releases:
// See https://github.com/roboll/helmfile/issues/322
func TestVisitDesiredStatesWithReleasesFiltered_Selectors(t *testing.T) {
absPaths := map[string]string{
".": "/path/to",
"/path/to/helmfile.d": "/path/to/helmfile.d",
}
dirs := map[string]bool{
"helmfile.d": true,
}
files := map[string]string{
"helmfile.yaml": `
"/path/to/helmfile.yaml": `
helmfiles:
- helmfile.d/a*.yaml
- helmfile.d/b*.yaml
@ -247,39 +260,6 @@ releases:
duplicated: yes
`,
}
globMatches := map[string][]string{
"/path/to/helmfile.d/a*.yaml": []string{"/path/to/helmfile.d/a1.yaml", "/path/to/helmfile.d/a2.yaml"},
"/path/to/helmfile.d/b*.yaml": []string{"/path/to/helmfile.d/b.yaml"},
}
fileExistsAt := func(path string) bool {
_, ok := files[path]
return ok
}
directoryExistsAt := func(path string) bool {
_, ok := dirs[path]
return ok
}
readFile := func(filename string) ([]byte, error) {
str, ok := files[filename]
if !ok {
return []byte(nil), fmt.Errorf("no file found: %s", filename)
}
return []byte(str), nil
}
glob := func(pattern string) ([]string, error) {
matches, ok := globMatches[pattern]
if !ok {
return []string(nil), fmt.Errorf("no file matched: %s", pattern)
}
return matches, nil
}
abs := func(path string) (string, error) {
a, ok := absPaths[path]
if !ok {
return "", fmt.Errorf("abs: unexpected path: %s", path)
}
return a, nil
}
testcases := []struct {
label string
@ -305,18 +285,13 @@ releases:
return []error{}
}
app := &app{
readFile: readFile,
glob: glob,
abs: abs,
fileExistsAt: fileExistsAt,
directoryExistsAt: directoryExistsAt,
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
namespace: "",
selectors: []string{testcase.label},
env: "default",
}
app := appWithFs(&app{
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
namespace: "",
selectors: []string{testcase.label},
env: "default",
}, files)
err := app.VisitDesiredStatesWithReleasesFiltered(
"helmfile.yaml", collectReleases,
@ -338,15 +313,8 @@ releases:
// See https://github.com/roboll/helmfile/issues/312
func TestVisitDesiredStatesWithReleasesFiltered_ReverseOrder(t *testing.T) {
absPaths := map[string]string{
".": "/path/to",
"/path/to/helmfile.d": "/path/to/helmfile.d",
}
dirs := map[string]bool{
"helmfile.d": true,
}
files := map[string]string{
"helmfile.yaml": `
"/path/to/helmfile.yaml": `
helmfiles:
- helmfile.d/a*.yaml
- helmfile.d/b*.yaml
@ -369,39 +337,6 @@ releases:
chart: stable/grafana
`,
}
globMatches := map[string][]string{
"/path/to/helmfile.d/a*.yaml": []string{"/path/to/helmfile.d/a1.yaml", "/path/to/helmfile.d/a2.yaml"},
"/path/to/helmfile.d/b*.yaml": []string{"/path/to/helmfile.d/b.yaml"},
}
fileExistsAt := func(path string) bool {
_, ok := files[path]
return ok
}
directoryExistsAt := func(path string) bool {
_, ok := dirs[path]
return ok
}
readFile := func(filename string) ([]byte, error) {
str, ok := files[filename]
if !ok {
return []byte(nil), fmt.Errorf("no file found: %s", filename)
}
return []byte(str), nil
}
glob := func(pattern string) ([]string, error) {
matches, ok := globMatches[pattern]
if !ok {
return []string(nil), fmt.Errorf("no file matched: %s", pattern)
}
return matches, nil
}
abs := func(path string) (string, error) {
a, ok := absPaths[path]
if !ok {
return "", fmt.Errorf("abs: unexpected path: %s", path)
}
return a, nil
}
expected := []string{"grafana", "elasticsearch", "prometheus", "zipkin"}
@ -421,20 +356,14 @@ releases:
}
return []error{}
}
app := &app{
readFile: readFile,
glob: glob,
abs: abs,
fileExistsAt: fileExistsAt,
directoryExistsAt: directoryExistsAt,
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
reverse: testcase.reverse,
namespace: "",
selectors: []string{},
env: "default",
}
app := appWithFs(&app{
kubeContext: "default",
logger: helmexec.NewLogger(os.Stderr, "debug"),
reverse: testcase.reverse,
namespace: "",
selectors: []string{},
env: "default",
}, files)
err := app.VisitDesiredStatesWithReleasesFiltered(
"helmfile.yaml", collectReleases,
)

91
main.go
View File

@ -571,6 +571,9 @@ type app struct {
env string
namespace string
selectors []string
getwd func() (string, error)
chdir func(string) error
}
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface, context) []error) error {
@ -598,6 +601,8 @@ func initAppEntry(c *cli.Context, reverse bool) (*app, string, error) {
readFile: ioutil.ReadFile,
glob: filepath.Glob,
abs: filepath.Abs,
getwd: os.Getwd,
chdir: os.Chdir,
fileExistsAt: fileExistsAt,
directoryExistsAt: directoryExistsAt,
kubeContext: kubeContext,
@ -778,16 +783,69 @@ func (r *twoPassRenderer) renderTemplate(content []byte) (*bytes.Buffer, error)
return yamlBuf, nil
}
func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
func (a *app) within(dir string, do func() error) error {
prev, err := a.getwd()
if err != nil {
return fmt.Errorf("failed getting current working direcotyr: %v", err)
}
absDir, err := a.abs(dir)
if err != nil {
return err
}
a.logger.Debugf("changing working directory to \"%s\"", absDir)
if err := a.chdir(absDir); err != nil {
return fmt.Errorf("failed changing working directory to \"%s\": %v", absDir, err)
}
appErr := do()
a.logger.Debugf("changing working directory back to \"%s\"", prev)
if chdirBackErr := a.chdir(prev); chdirBackErr != nil {
if appErr != nil {
a.logger.Warnf("%v", appErr)
}
return fmt.Errorf("failed chaging working directory back to \"%s\": %v", prev, chdirBackErr)
}
return appErr
}
func (a *app) visitStateFiles(fileOrDir string, do func(string) error) error {
desiredStateFiles, err := a.findDesiredStateFiles(fileOrDir)
if err != nil {
return err
}
noMatchInHelmfiles := true
for _, f := range desiredStateFiles {
a.logger.Debugf("Processing %s", f)
for _, relPath := range desiredStateFiles {
a.logger.Debugf("Processing %s", relPath)
var file string
var dir string
if a.directoryExistsAt(fileOrDir) {
file = fileOrDir
dir = fileOrDir
} else {
file = filepath.Base(fileOrDir)
dir = filepath.Dir(fileOrDir)
}
err := a.within(dir, func() error {
return do(file)
})
if err != nil {
return err
}
}
return nil
}
func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
noMatchInHelmfiles := true
err := a.visitStateFiles(fileOrDir, func(f string) error {
content, err := a.readFile(f)
if err != nil {
return err
@ -821,7 +879,7 @@ func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat
case *state.StateLoadError:
switch stateLoadErr.Cause.(type) {
case *state.UndefinedEnvError:
continue
return nil
default:
return err
}
@ -859,9 +917,10 @@ func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat
noMatchInHelmfiles = noMatchInHelmfiles && !processed
}
if err := clean(st, errs); err != nil {
return err
}
return clean(st, errs)
})
if err != nil {
return err
}
if noMatchInHelmfiles {
return &noMatchingHelmfileError{selectors: a.selectors, env: a.env}
@ -902,6 +961,22 @@ func (a *app) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge
return nil
}
func (a *app) findStateFilesInAbsPaths(specifiedPath string) ([]string, error) {
rels, err := a.findDesiredStateFiles(specifiedPath)
if err != nil {
return rels, err
}
files := make([]string, len(rels))
for i := range rels {
files[i], err = filepath.Abs(rels[i])
if err != nil {
return []string{}, err
}
}
return files, nil
}
func (a *app) findDesiredStateFiles(specifiedPath string) ([]string, error) {
var helmfileDir string
if specifiedPath != "" {