feat: `helmfile repos` updates all repos regardless of selector and releases (#353)
Resolves #338
This commit is contained in:
parent
6cba77d4f2
commit
f2b610afdf
163
app_test.go
163
app_test.go
|
|
@ -4,13 +4,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/roboll/helmfile/helmexec"
|
"github.com/roboll/helmfile/helmexec"
|
||||||
"github.com/roboll/helmfile/state"
|
"github.com/roboll/helmfile/state"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/193
|
// See https://github.com/roboll/helmfile/issues/193
|
||||||
func TestFindAndIterateOverDesiredStates(t *testing.T) {
|
func TestVisitDesiredStatesWithReleasesFiltered(t *testing.T) {
|
||||||
absPaths := map[string]string{
|
absPaths := map[string]string{
|
||||||
".": "/path/to",
|
".": "/path/to",
|
||||||
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
||||||
|
|
@ -73,15 +75,6 @@ releases:
|
||||||
}
|
}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
app := &app{
|
|
||||||
readFile: readFile,
|
|
||||||
glob: glob,
|
|
||||||
abs: abs,
|
|
||||||
fileExistsAt: fileExistsAt,
|
|
||||||
directoryExistsAt: directoryExistsAt,
|
|
||||||
kubeContext: "default",
|
|
||||||
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
|
||||||
}
|
|
||||||
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
return []error{}
|
return []error{}
|
||||||
}
|
}
|
||||||
|
|
@ -97,8 +90,20 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
err := app.FindAndIterateOverDesiredStates(
|
app := &app{
|
||||||
"helmfile.yaml", noop, "", []string{fmt.Sprintf("name=%s", testcase.name)}, "default",
|
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",
|
||||||
|
}
|
||||||
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
|
"helmfile.yaml", noop,
|
||||||
)
|
)
|
||||||
if testcase.expectErr && err == nil {
|
if testcase.expectErr && err == nil {
|
||||||
t.Errorf("error expected but not happened for name=%s", testcase.name)
|
t.Errorf("error expected but not happened for name=%s", testcase.name)
|
||||||
|
|
@ -109,7 +114,7 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/320
|
// See https://github.com/roboll/helmfile/issues/320
|
||||||
func TestFindAndIterateOverDesiredStates_UndefinedEnv(t *testing.T) {
|
func TestVisitDesiredStatesWithReleasesFiltered_UndefinedEnv(t *testing.T) {
|
||||||
absPaths := map[string]string{
|
absPaths := map[string]string{
|
||||||
".": "/path/to",
|
".": "/path/to",
|
||||||
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
||||||
|
|
@ -166,15 +171,6 @@ releases:
|
||||||
}
|
}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
app := &app{
|
|
||||||
readFile: readFile,
|
|
||||||
glob: glob,
|
|
||||||
abs: abs,
|
|
||||||
fileExistsAt: fileExistsAt,
|
|
||||||
directoryExistsAt: directoryExistsAt,
|
|
||||||
kubeContext: "default",
|
|
||||||
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
|
||||||
}
|
|
||||||
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
return []error{}
|
return []error{}
|
||||||
}
|
}
|
||||||
|
|
@ -189,8 +185,20 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
err := app.FindAndIterateOverDesiredStates(
|
app := &app{
|
||||||
"helmfile.yaml", noop, "", []string{}, testcase.name,
|
readFile: readFile,
|
||||||
|
glob: glob,
|
||||||
|
abs: abs,
|
||||||
|
fileExistsAt: fileExistsAt,
|
||||||
|
directoryExistsAt: directoryExistsAt,
|
||||||
|
kubeContext: "default",
|
||||||
|
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
|
namespace: "",
|
||||||
|
selectors: []string{},
|
||||||
|
env: testcase.name,
|
||||||
|
}
|
||||||
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
|
"helmfile.yaml", noop,
|
||||||
)
|
)
|
||||||
if testcase.expectErr && err == nil {
|
if testcase.expectErr && err == nil {
|
||||||
t.Errorf("error expected but not happened for environment=%s", testcase.name)
|
t.Errorf("error expected but not happened for environment=%s", testcase.name)
|
||||||
|
|
@ -201,7 +209,7 @@ releases:
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/322
|
// See https://github.com/roboll/helmfile/issues/322
|
||||||
func TestFindAndIterateOverDesiredStates_Selectors(t *testing.T) {
|
func TestVisitDesiredStatesWithReleasesFiltered_Selectors(t *testing.T) {
|
||||||
absPaths := map[string]string{
|
absPaths := map[string]string{
|
||||||
".": "/path/to",
|
".": "/path/to",
|
||||||
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
||||||
|
|
@ -229,6 +237,14 @@ releases:
|
||||||
releases:
|
releases:
|
||||||
- name: grafana
|
- name: grafana
|
||||||
chart: stable/grafana
|
chart: stable/grafana
|
||||||
|
- name: foo
|
||||||
|
chart: charts/foo
|
||||||
|
labels:
|
||||||
|
duplicated: yes
|
||||||
|
- name: foo
|
||||||
|
chart: charts/foo
|
||||||
|
labels:
|
||||||
|
duplicated: yes
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
globMatches := map[string][]string{
|
globMatches := map[string][]string{
|
||||||
|
|
@ -264,48 +280,64 @@ releases:
|
||||||
}
|
}
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
app := &app{
|
|
||||||
readFile: readFile,
|
|
||||||
glob: glob,
|
|
||||||
abs: abs,
|
|
||||||
fileExistsAt: fileExistsAt,
|
|
||||||
directoryExistsAt: directoryExistsAt,
|
|
||||||
kubeContext: "default",
|
|
||||||
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
|
||||||
}
|
|
||||||
noop := func(st *state.HelmState, helm helmexec.Interface) []error {
|
|
||||||
return []error{}
|
|
||||||
}
|
|
||||||
|
|
||||||
testcases := []struct {
|
testcases := []struct {
|
||||||
label string
|
label string
|
||||||
expectErr bool
|
expectedCount int
|
||||||
errMsg string
|
expectErr bool
|
||||||
|
errMsg string
|
||||||
}{
|
}{
|
||||||
{label: "name=prometheus", expectErr: false},
|
{label: "name=prometheus", expectedCount: 1, expectErr: false},
|
||||||
{label: "name=", expectErr: true, errMsg: "Malformed label: name=. Expected label in form k=v or k!=v"},
|
{label: "name=", expectedCount: 0, expectErr: true, errMsg: "failed processing /path/to/helmfile.d/a1.yaml: Malformed label: name=. Expected label in form k=v or k!=v"},
|
||||||
{label: "name!=", expectErr: true, errMsg: "Malformed label: name!=. Expected label in form k=v or k!=v"},
|
{label: "name!=", expectedCount: 0, expectErr: true, errMsg: "failed processing /path/to/helmfile.d/a1.yaml: Malformed label: name!=. Expected label in form k=v or k!=v"},
|
||||||
{label: "name", expectErr: true, errMsg: "Malformed label: name. Expected label in form k=v or k!=v"},
|
{label: "name", expectedCount: 0, expectErr: true, errMsg: "failed processing /path/to/helmfile.d/a1.yaml: Malformed label: name. Expected label in form k=v or k!=v"},
|
||||||
|
// See https://github.com/roboll/helmfile/issues/193
|
||||||
|
{label: "duplicated=yes", expectedCount: 0, expectErr: true, errMsg: "failed processing /path/to/helmfile.d/b.yaml: duplicate release \"foo\" found: there were 2 releases named \"foo\" matching specified selector"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testcases {
|
for _, testcase := range testcases {
|
||||||
err := app.FindAndIterateOverDesiredStates(
|
actual := []string{}
|
||||||
"helmfile.yaml", noop, "", []string{testcase.label}, "default",
|
|
||||||
|
collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
|
for _, r := range st.Releases {
|
||||||
|
actual = append(actual, r.Name)
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
|
"helmfile.yaml", collectReleases,
|
||||||
)
|
)
|
||||||
if testcase.expectErr {
|
if testcase.expectErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("error expected but not happened for name=%s", testcase.label)
|
t.Errorf("error expected but not happened for selector %s", testcase.label)
|
||||||
} else if err.Error() != testcase.errMsg {
|
} else if err.Error() != testcase.errMsg {
|
||||||
t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", testcase.errMsg, err.Error())
|
t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", testcase.errMsg, err.Error())
|
||||||
}
|
}
|
||||||
} else if !testcase.expectErr && err != nil {
|
} else if !testcase.expectErr && err != nil {
|
||||||
t.Errorf("unexpected error for name=%s: %v", testcase.label, err)
|
t.Errorf("unexpected error for selector %s: %v", testcase.label, err)
|
||||||
|
}
|
||||||
|
if len(actual) != testcase.expectedCount {
|
||||||
|
t.Errorf("unexpected release count for selector %s: expected=%d, actual=%d", testcase.label, testcase.expectedCount, len(actual))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/312
|
// See https://github.com/roboll/helmfile/issues/312
|
||||||
func TestFindAndIterateOverDesiredStates_ReverseOrder(t *testing.T) {
|
func TestVisitDesiredStatesWithReleasesFiltered_ReverseOrder(t *testing.T) {
|
||||||
absPaths := map[string]string{
|
absPaths := map[string]string{
|
||||||
".": "/path/to",
|
".": "/path/to",
|
||||||
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
"/path/to/helmfile.d": "/path/to/helmfile.d",
|
||||||
|
|
@ -399,9 +431,12 @@ releases:
|
||||||
kubeContext: "default",
|
kubeContext: "default",
|
||||||
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||||
reverse: testcase.reverse,
|
reverse: testcase.reverse,
|
||||||
|
namespace: "",
|
||||||
|
selectors: []string{},
|
||||||
|
env: "default",
|
||||||
}
|
}
|
||||||
err := app.FindAndIterateOverDesiredStates(
|
err := app.VisitDesiredStatesWithReleasesFiltered(
|
||||||
"helmfile.yaml", collectReleases, "", []string{}, "default",
|
"helmfile.yaml", collectReleases,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
|
@ -411,3 +446,29 @@ releases:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadDesiredStateFromYaml_DuplicateReleaseName(t *testing.T) {
|
||||||
|
yamlFile := "example/path/to/yaml/file"
|
||||||
|
yamlContent := []byte(`releases:
|
||||||
|
- name: myrelease1
|
||||||
|
chart: mychart1
|
||||||
|
labels:
|
||||||
|
stage: pre
|
||||||
|
foo: bar
|
||||||
|
- name: myrelease1
|
||||||
|
chart: mychart2
|
||||||
|
labels:
|
||||||
|
stage: post
|
||||||
|
`)
|
||||||
|
app := &app{
|
||||||
|
readFile: ioutil.ReadFile,
|
||||||
|
glob: filepath.Glob,
|
||||||
|
abs: filepath.Abs,
|
||||||
|
kubeContext: "default",
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
_, err := app.loadDesiredStateFromYaml(yamlContent, yamlFile, "default", "default")
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
175
main.go
175
main.go
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -24,6 +22,7 @@ import (
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -115,13 +114,17 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlags(c, func(state *state.HelmState, helm helmexec.Interface) []error {
|
return visitAllDesiredStates(c, func(state *state.HelmState, helm helmexec.Interface) (bool, []error) {
|
||||||
args := args.GetArgs(c.String("args"), state)
|
args := args.GetArgs(c.String("args"), state)
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
helm.SetExtraArgs(args...)
|
helm.SetExtraArgs(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.SyncRepos(helm)
|
errs := state.SyncRepos(helm)
|
||||||
|
|
||||||
|
ok := len(state.Repositories) > 0 && len(errs) == 0
|
||||||
|
|
||||||
|
return ok, errs
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -566,16 +569,19 @@ type app struct {
|
||||||
fileExistsAt func(string) bool
|
fileExistsAt func(string) bool
|
||||||
directoryExistsAt func(string) bool
|
directoryExistsAt func(string) bool
|
||||||
reverse bool
|
reverse bool
|
||||||
|
env string
|
||||||
|
namespace string
|
||||||
|
selectors []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
func findAndIterateOverDesiredStatesUsingFlags(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
||||||
return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, false, converge)
|
return findAndIterateOverDesiredStatesUsingFlagsWithReverse(c, false, converge)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
func initAppEntry(c *cli.Context, reverse bool) (*app, string, error) {
|
||||||
if c.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
cli.ShowAppHelp(c)
|
cli.ShowAppHelp(c)
|
||||||
return fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
|
return nil, "", fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileOrDir := c.GlobalString("file")
|
fileOrDir := c.GlobalString("file")
|
||||||
|
|
@ -598,21 +604,66 @@ func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, revers
|
||||||
kubeContext: kubeContext,
|
kubeContext: kubeContext,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
reverse: reverse,
|
reverse: reverse,
|
||||||
|
env: env,
|
||||||
|
namespace: namespace,
|
||||||
|
selectors: selectors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return app, fileOrDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func visitAllDesiredStates(c *cli.Context, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
|
||||||
|
app, fileOrDir, err := initAppEntry(c, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
|
||||||
|
if c.GlobalString("helm-binary") != "" {
|
||||||
|
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||||
|
}
|
||||||
|
return converge(st, helm)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.VisitDesiredStates(fileOrDir, convergeWithHelmBinary)
|
||||||
|
|
||||||
|
return toCliError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCliError(err error) error {
|
||||||
|
if err != nil {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case *noMatchingHelmfileError:
|
||||||
|
return cli.NewExitError(e.Error(), 2)
|
||||||
|
case *exec.ExitError:
|
||||||
|
// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
|
||||||
|
status := e.Sys().(syscall.WaitStatus)
|
||||||
|
return cli.NewExitError(e.Error(), status.ExitStatus())
|
||||||
|
case *state.DiffError:
|
||||||
|
return cli.NewExitError(e.Error(), e.Code)
|
||||||
|
default:
|
||||||
|
return cli.NewExitError(e.Error(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAndIterateOverDesiredStatesUsingFlagsWithReverse(c *cli.Context, reverse bool, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
||||||
|
app, fileOrDir, err := initAppEntry(c, reverse)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) []error {
|
convergeWithHelmBinary := func(st *state.HelmState, helm helmexec.Interface) []error {
|
||||||
if c.GlobalString("helm-binary") != "" {
|
if c.GlobalString("helm-binary") != "" {
|
||||||
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
helm.SetHelmBinary(c.GlobalString("helm-binary"))
|
||||||
}
|
}
|
||||||
return converge(st, helm)
|
return converge(st, helm)
|
||||||
}
|
}
|
||||||
if err := app.FindAndIterateOverDesiredStates(fileOrDir, convergeWithHelmBinary, namespace, selectors, env); err != nil {
|
|
||||||
switch e := err.(type) {
|
err = app.VisitDesiredStatesWithReleasesFiltered(fileOrDir, convergeWithHelmBinary)
|
||||||
case *noMatchingHelmfileError:
|
|
||||||
return cli.NewExitError(e.Error(), 2)
|
return toCliError(err)
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type noMatchingHelmfileError struct {
|
type noMatchingHelmfileError struct {
|
||||||
|
|
@ -690,11 +741,12 @@ func (r *twoPassRenderer) renderTemplate(content []byte) (*bytes.Buffer, error)
|
||||||
return yamlBuf, nil
|
return yamlBuf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error, namespace string, selectors []string, env string) error {
|
func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error {
|
||||||
desiredStateFiles, err := a.findDesiredStateFiles(fileOrDir)
|
desiredStateFiles, err := a.findDesiredStateFiles(fileOrDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
noMatchInHelmfiles := true
|
noMatchInHelmfiles := true
|
||||||
for _, f := range desiredStateFiles {
|
for _, f := range desiredStateFiles {
|
||||||
a.logger.Debugf("Processing %s", f)
|
a.logger.Debugf("Processing %s", f)
|
||||||
|
|
@ -706,8 +758,8 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s
|
||||||
// render template, in two runs
|
// render template, in two runs
|
||||||
r := &twoPassRenderer{
|
r := &twoPassRenderer{
|
||||||
reader: a.readFile,
|
reader: a.readFile,
|
||||||
env: env,
|
env: a.env,
|
||||||
namespace: namespace,
|
namespace: a.namespace,
|
||||||
filename: f,
|
filename: f,
|
||||||
logger: a.logger,
|
logger: a.logger,
|
||||||
abs: a.abs,
|
abs: a.abs,
|
||||||
|
|
@ -717,13 +769,13 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s
|
||||||
return fmt.Errorf("error during %s parsing: %v", f, err)
|
return fmt.Errorf("error during %s parsing: %v", f, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
st, noMatchInThisHelmfile, err := a.loadDesiredStateFromYaml(
|
st, err := a.loadDesiredStateFromYaml(
|
||||||
yamlBuf.Bytes(),
|
yamlBuf.Bytes(),
|
||||||
f,
|
f,
|
||||||
namespace,
|
a.namespace,
|
||||||
selectors,
|
a.env,
|
||||||
env,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
helm := helmexec.New(a.logger, a.kubeContext)
|
helm := helmexec.New(a.logger, a.kubeContext)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -746,7 +798,7 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s
|
||||||
if len(st.Helmfiles) > 0 {
|
if len(st.Helmfiles) > 0 {
|
||||||
noMatchInSubHelmfiles := true
|
noMatchInSubHelmfiles := true
|
||||||
for _, m := range st.Helmfiles {
|
for _, m := range st.Helmfiles {
|
||||||
if err := a.FindAndIterateOverDesiredStates(m, converge, namespace, selectors, env); err != nil {
|
if err := a.VisitDesiredStates(m, converge); err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case *noMatchingHelmfileError:
|
case *noMatchingHelmfileError:
|
||||||
|
|
||||||
|
|
@ -759,11 +811,9 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s
|
||||||
}
|
}
|
||||||
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles
|
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles
|
||||||
} else {
|
} else {
|
||||||
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInThisHelmfile
|
var processed bool
|
||||||
if noMatchInThisHelmfile {
|
processed, errs = converge(st, helm)
|
||||||
continue
|
noMatchInHelmfiles = noMatchInHelmfiles && !processed
|
||||||
}
|
|
||||||
errs = converge(st, helm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := clean(st, errs); err != nil {
|
if err := clean(st, errs); err != nil {
|
||||||
|
|
@ -771,7 +821,40 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if noMatchInHelmfiles {
|
if noMatchInHelmfiles {
|
||||||
return &noMatchingHelmfileError{selectors, env}
|
return &noMatchingHelmfileError{selectors: a.selectors, env: a.env}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error) error {
|
||||||
|
selectors := a.selectors
|
||||||
|
|
||||||
|
err := a.VisitDesiredStates(fileOrDir, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) {
|
||||||
|
if len(selectors) > 0 {
|
||||||
|
err := st.FilterReleases(selectors)
|
||||||
|
if err != nil {
|
||||||
|
return false, []error{err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseNameCounts := map[string]int{}
|
||||||
|
for _, r := range st.Releases {
|
||||||
|
releaseNameCounts[r.Name]++
|
||||||
|
}
|
||||||
|
for name, c := range releaseNameCounts {
|
||||||
|
if c > 1 {
|
||||||
|
return false, []error{fmt.Errorf("duplicate release \"%s\" found: there were %d releases named \"%s\" matching specified selector", name, c, name)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := converge(st, helm)
|
||||||
|
|
||||||
|
processed := len(st.Releases) != 0 && len(errs) == 0
|
||||||
|
|
||||||
|
return processed, errs
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -833,11 +916,11 @@ func directoryExistsAt(path string) bool {
|
||||||
return err == nil && fileInfo.Mode().IsDir()
|
return err == nil && fileInfo.Mode().IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace string, labels []string, env string) (*state.HelmState, bool, error) {
|
func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace string, env string) (*state.HelmState, error) {
|
||||||
c := state.NewCreator(a.logger, a.readFile, a.abs)
|
c := state.NewCreator(a.logger, a.readFile, a.abs)
|
||||||
st, err := c.CreateFromYaml(yaml, file, env)
|
st, err := c.CreateFromYaml(yaml, file, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
helmfiles := []string{}
|
helmfiles := []string{}
|
||||||
|
|
@ -845,7 +928,7 @@ func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace strin
|
||||||
helmfileRelativePattern := st.JoinBase(globPattern)
|
helmfileRelativePattern := st.JoinBase(globPattern)
|
||||||
matches, err := a.glob(helmfileRelativePattern)
|
matches, err := a.glob(helmfileRelativePattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("failed processing %s: %v", globPattern, err)
|
return nil, fmt.Errorf("failed processing %s: %v", globPattern, err)
|
||||||
}
|
}
|
||||||
sort.Strings(matches)
|
sort.Strings(matches)
|
||||||
|
|
||||||
|
|
@ -876,23 +959,6 @@ func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace strin
|
||||||
st.Namespace = namespace
|
st.Namespace = namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(labels) > 0 {
|
|
||||||
err = st.FilterReleases(labels)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseNameCounts := map[string]int{}
|
|
||||||
for _, r := range st.Releases {
|
|
||||||
releaseNameCounts[r.Name]++
|
|
||||||
}
|
|
||||||
for name, c := range releaseNameCounts {
|
|
||||||
if c > 1 {
|
|
||||||
return nil, false, fmt.Errorf("duplicate release \"%s\" found: there were %d releases named \"%s\" matching specified selector", name, c, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -902,7 +968,7 @@ func (a *app) loadDesiredStateFromYaml(yaml []byte, file string, namespace strin
|
||||||
clean(st, errs)
|
clean(st, errs)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return st, len(st.Releases) == 0, nil
|
return st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func clean(st *state.HelmState, errs []error) error {
|
func clean(st *state.HelmState, errs []error) error {
|
||||||
|
|
@ -924,16 +990,7 @@ func clean(st *state.HelmState, errs []error) error {
|
||||||
fmt.Printf("err: %v\n", e)
|
fmt.Printf("err: %v\n", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch e := errs[0].(type) {
|
return errs[0]
|
||||||
case *exec.ExitError:
|
|
||||||
// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
|
|
||||||
status := e.Sys().(syscall.WaitStatus)
|
|
||||||
os.Exit(status.ExitStatus())
|
|
||||||
case *state.DiffError:
|
|
||||||
os.Exit(e.Code)
|
|
||||||
default:
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
main_test.go
31
main_test.go
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -11,36 +10,6 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://github.com/roboll/helmfile/issues/193
|
|
||||||
func TestReadFromYaml_DuplicateReleaseName(t *testing.T) {
|
|
||||||
yamlFile := "example/path/to/yaml/file"
|
|
||||||
yamlContent := []byte(`releases:
|
|
||||||
- name: myrelease1
|
|
||||||
chart: mychart1
|
|
||||||
labels:
|
|
||||||
stage: pre
|
|
||||||
foo: bar
|
|
||||||
- name: myrelease1
|
|
||||||
chart: mychart2
|
|
||||||
labels:
|
|
||||||
stage: post
|
|
||||||
`)
|
|
||||||
app := &app{
|
|
||||||
readFile: ioutil.ReadFile,
|
|
||||||
glob: filepath.Glob,
|
|
||||||
abs: filepath.Abs,
|
|
||||||
kubeContext: "default",
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
_, _, err := app.loadDesiredStateFromYaml(yamlContent, yamlFile, "default", []string{}, "default")
|
|
||||||
if err == nil {
|
|
||||||
t.Error("error expected but not happened")
|
|
||||||
}
|
|
||||||
if err.Error() != "duplicate release \"myrelease1\" found: there were 2 releases named \"myrelease1\" matching specified selector" {
|
|
||||||
t.Errorf("unexpected error happened: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRenderer(readFile func(string) ([]byte, error), env string) *twoPassRenderer {
|
func makeRenderer(readFile func(string) ([]byte, error), env string) *twoPassRenderer {
|
||||||
return &twoPassRenderer{
|
return &twoPassRenderer{
|
||||||
reader: readFile,
|
reader: readFile,
|
||||||
|
|
|
||||||
|
|
@ -788,7 +788,7 @@ func (state *HelmState) Clean() []error {
|
||||||
// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
|
// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
|
||||||
func (state *HelmState) FilterReleases(labels []string) error {
|
func (state *HelmState) FilterReleases(labels []string) error {
|
||||||
var filteredReleases []ReleaseSpec
|
var filteredReleases []ReleaseSpec
|
||||||
releaseSet := map[string]ReleaseSpec{}
|
releaseSet := map[string][]ReleaseSpec{}
|
||||||
filters := []ReleaseFilter{}
|
filters := []ReleaseFilter{}
|
||||||
for _, label := range labels {
|
for _, label := range labels {
|
||||||
f, err := ParseLabels(label)
|
f, err := ParseLabels(label)
|
||||||
|
|
@ -808,13 +808,13 @@ func (state *HelmState) FilterReleases(labels []string) error {
|
||||||
r.Labels = map[string]string{}
|
r.Labels = map[string]string{}
|
||||||
}
|
}
|
||||||
if f.Match(r) {
|
if f.Match(r) {
|
||||||
releaseSet[r.Name] = r
|
releaseSet[r.Name] = append(releaseSet[r.Name], r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, r := range releaseSet {
|
for _, r := range releaseSet {
|
||||||
filteredReleases = append(filteredReleases, r)
|
filteredReleases = append(filteredReleases, r...)
|
||||||
}
|
}
|
||||||
state.Releases = filteredReleases
|
state.Releases = filteredReleases
|
||||||
numFound := len(filteredReleases)
|
numFound := len(filteredReleases)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue