Ordered helmfile(s) from a directory (#160)

Resolves #151 #137

Notes:

- You can have either helmfile.d or helmfile.yaml/charts.yaml, not both
This commit is contained in:
KUOKA Yusuke 2018-06-14 22:20:15 +09:00 committed by GitHub
parent df264e2736
commit 2fba241122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 147 additions and 115 deletions

176
main.go
View File

@ -12,11 +12,14 @@ import (
"github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/helmexec"
"github.com/roboll/helmfile/state" "github.com/roboll/helmfile/state"
"github.com/urfave/cli" "github.com/urfave/cli"
"path/filepath"
"sort"
) )
const ( const (
DefaultHelmfile = "helmfile.yaml" DefaultHelmfile = "helmfile.yaml"
DeprecatedHelmfile = "charts.yaml" DeprecatedHelmfile = "charts.yaml"
DefaultHelmfileDirectory = "helmfile.d"
) )
var Version string var Version string
@ -66,18 +69,14 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
args := c.String("args") args := c.String("args")
if len(args) > 0 { if len(args) > 0 {
helm.SetExtraArgs(strings.Split(args, " ")...) helm.SetExtraArgs(strings.Split(args, " ")...)
} }
errs := state.SyncRepos(helm) return state.SyncRepos(helm)
return clean(state, errs) })
}, },
}, },
{ {
@ -100,11 +99,7 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
args := c.String("args") args := c.String("args")
if len(args) > 0 { if len(args) > 0 {
helm.SetExtraArgs(strings.Split(args, " ")...) helm.SetExtraArgs(strings.Split(args, " ")...)
@ -113,8 +108,8 @@ func main() {
values := c.StringSlice("values") values := c.StringSlice("values")
workers := c.Int("concurrency") workers := c.Int("concurrency")
errs := state.SyncReleases(helm, values, workers) return state.SyncReleases(helm, values, workers)
return clean(state, errs) })
}, },
}, },
{ {
@ -141,11 +136,7 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
args := c.String("args") args := c.String("args")
if len(args) > 0 { if len(args) > 0 {
helm.SetExtraArgs(strings.Split(args, " ")...) helm.SetExtraArgs(strings.Split(args, " ")...)
@ -153,18 +144,15 @@ func main() {
if c.Bool("sync-repos") { if c.Bool("sync-repos") {
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 { if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
for _, err := range errs { return errs
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
} }
} }
values := c.StringSlice("values") values := c.StringSlice("values")
workers := c.Int("concurrency") workers := c.Int("concurrency")
errs := state.DiffReleases(helm, values, workers) return state.DiffReleases(helm, values, workers)
return clean(state, errs) })
}, },
}, },
{ {
@ -187,23 +175,13 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 { if errs := state.SyncRepos(helm); errs != nil && len(errs) > 0 {
for _, err := range errs { return errs
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
} }
if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 { if errs := state.UpdateDeps(helm); errs != nil && len(errs) > 0 {
for _, err := range errs { return errs
fmt.Printf("err: %s\n", err.Error())
}
os.Exit(1)
} }
args := c.String("args") args := c.String("args")
@ -214,8 +192,8 @@ func main() {
values := c.StringSlice("values") values := c.StringSlice("values")
workers := c.Int("concurrency") workers := c.Int("concurrency")
errs := state.SyncReleases(helm, values, workers) return state.SyncReleases(helm, values, workers)
return clean(state, errs) })
}, },
}, },
{ {
@ -234,11 +212,7 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
workers := c.Int("concurrency") workers := c.Int("concurrency")
args := c.String("args") args := c.String("args")
@ -246,8 +220,8 @@ func main() {
helm.SetExtraArgs(strings.Split(args, " ")...) helm.SetExtraArgs(strings.Split(args, " ")...)
} }
errs := state.ReleaseStatuses(helm, workers) return state.ReleaseStatuses(helm, workers)
return clean(state, errs) })
}, },
}, },
{ {
@ -260,15 +234,11 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
purge := c.Bool("purge") purge := c.Bool("purge")
errs := state.DeleteReleases(helm, purge) return state.DeleteReleases(helm, purge)
return clean(state, errs) })
}, },
}, },
{ {
@ -291,11 +261,7 @@ func main() {
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
state, helm, err := before(c) return eachDesiredStateDo(c, func(state *state.HelmState, helm helmexec.Interface) []error {
if err != nil {
return err
}
cleanup := c.Bool("cleanup") cleanup := c.Bool("cleanup")
timeout := c.Int("timeout") timeout := c.Int("timeout")
@ -304,8 +270,8 @@ func main() {
helm.SetExtraArgs(strings.Split(args, " ")...) helm.SetExtraArgs(strings.Split(args, " ")...)
} }
errs := state.TestReleases(helm, cleanup, timeout) return state.TestReleases(helm, cleanup, timeout)
return clean(state, errs) })
}, },
}, },
} }
@ -317,8 +283,83 @@ func main() {
} }
} }
func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) { func eachDesiredStateDo(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error {
file := c.GlobalString("file") fileOrDirPath := c.GlobalString("file")
desiredStateFiles, err := findDesiredStateFiles(fileOrDirPath)
if err != nil {
return err
}
for _, f := range desiredStateFiles {
state, helm, err := loadDesiredStateFromFile(c, f)
if err != nil {
return err
}
errs := converge(state, helm)
if err := clean(state, errs); err != nil {
return err
}
}
return nil
}
func findDesiredStateFiles(specifiedPath string) ([]string, error) {
var helmfileDir string
if specifiedPath != "" {
if fileExistsAt(specifiedPath) {
return []string{specifiedPath}, nil
} else if directoryExistsAt(specifiedPath) {
helmfileDir = specifiedPath
} else {
return []string{}, fmt.Errorf("specified state file %s is not found", specifiedPath)
}
} else {
var defaultFile string
if fileExistsAt(DefaultHelmfile) {
defaultFile = DefaultHelmfile
} else if fileExistsAt(DeprecatedHelmfile) {
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",
DeprecatedHelmfile,
DeprecatedHelmfile,
DefaultHelmfile,
)
defaultFile = DeprecatedHelmfile
}
if directoryExistsAt(DefaultHelmfileDirectory) {
if defaultFile != "" {
return []string{}, fmt.Errorf("configuration conlict error: you can have either %s or %s, but not both", defaultFile, DefaultHelmfileDirectory)
}
helmfileDir = DefaultHelmfileDirectory
} else if defaultFile != "" {
return []string{defaultFile}, nil
} else {
return []string{}, fmt.Errorf("no state file found. It must be named %s/*.yaml, %s, or %s, or otherwise specified with the --file flag", DefaultHelmfileDirectory, DefaultHelmfile, DeprecatedHelmfile)
}
}
files, err := filepath.Glob(filepath.Join(helmfileDir, "*.yaml"))
if err != nil {
return []string{}, err
}
sort.Slice(files, func(i, j int) bool {
return files[i] < files[j]
})
return files, 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()
}
func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, helmexec.Interface, error) {
quiet := c.GlobalBool("quiet") quiet := c.GlobalBool("quiet")
kubeContext := c.GlobalString("kube-context") kubeContext := c.GlobalString("kube-context")
namespace := c.GlobalString("namespace") namespace := c.GlobalString("namespace")
@ -326,17 +367,8 @@ func before(c *cli.Context) (*state.HelmState, helmexec.Interface, error) {
st, err := state.ReadFromFile(file) st, err := state.ReadFromFile(file)
if err != nil { if err != nil {
if strings.Contains(err.Error(), fmt.Sprintf("open %s:", DefaultHelmfile)) {
var fallbackErr error
st, fallbackErr = state.ReadFromFile(DeprecatedHelmfile)
if fallbackErr != nil {
return nil, nil, fmt.Errorf("failed to read %s and %s: %v", file, DeprecatedHelmfile, err)
}
log.Printf("warn: charts.yaml is loaded: charts.yaml is deprecated in favor of helmfile.yaml. See https://github.com/roboll/helmfile/issues/25 for more information")
} else {
return nil, nil, fmt.Errorf("failed to read %s: %v", file, err) return nil, nil, fmt.Errorf("failed to read %s: %v", file, err)
} }
}
if st.Context != "" { if st.Context != "" {
if kubeContext != "" { if kubeContext != "" {