feat(environment): Allow missing environment in helmfile.yaml, partly. (#294)
helmfile as of today ensures that all the targeted helmfile.yaml to have the specified environment defined in it. That is, `helmfile --environment prod -f helmfile.d/ sync` fails if any helmfile under `helmfile.d/` is missing the `production` environment. This changes the validation logic, so that helmfile fails only when all the helmfiles miss the environment. Resolves #279
This commit is contained in:
parent
7d7ca74a05
commit
1c3bfcca10
44
main.go
44
main.go
|
|
@ -539,14 +539,15 @@ func findAndIterateOverDesiredStates(fileOrDir string, converge func(*state.Helm
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allSelectorNotMatched := true
|
noTargetFoundForAllHelmfiles := true
|
||||||
for _, f := range desiredStateFiles {
|
for _, f := range desiredStateFiles {
|
||||||
logger.Debugf("Processing %s", f)
|
logger.Debugf("Processing %s", f)
|
||||||
yamlBuf, err := tmpl.NewFileRenderer(ioutil.ReadFile, "", environment.EmptyEnvironment).RenderTemplateFileToBuffer(f)
|
yamlBuf, err := tmpl.NewFileRenderer(ioutil.ReadFile, "", environment.EmptyEnvironment).RenderTemplateFileToBuffer(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
state, helm, noReleases, err := loadDesiredStateFromFile(
|
|
||||||
|
st, helm, noReleasesMatchingSelector, err := loadDesiredStateFromFile(
|
||||||
yamlBuf.Bytes(),
|
yamlBuf.Bytes(),
|
||||||
f,
|
f,
|
||||||
kubeContext,
|
kubeContext,
|
||||||
|
|
@ -555,12 +556,21 @@ func findAndIterateOverDesiredStates(fileOrDir string, converge func(*state.Helm
|
||||||
env,
|
env,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(state.Helmfiles) > 0 {
|
var noTarget bool
|
||||||
for _, globPattern := range state.Helmfiles {
|
if err != nil {
|
||||||
|
switch stateLoadErr := err.(type) {
|
||||||
|
// Addresses https://github.com/roboll/helmfile/issues/279
|
||||||
|
case *state.StateLoadError:
|
||||||
|
switch stateLoadErr.Cause.(type) {
|
||||||
|
case *state.UndefinedEnvError:
|
||||||
|
noTarget = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if len(st.Helmfiles) > 0 {
|
||||||
|
for _, globPattern := range st.Helmfiles {
|
||||||
matches, err := filepath.Glob(globPattern)
|
matches, err := filepath.Glob(globPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed processing %s: %v", globPattern, err)
|
return fmt.Errorf("failed processing %s: %v", globPattern, err)
|
||||||
|
|
@ -573,19 +583,25 @@ func findAndIterateOverDesiredStates(fileOrDir string, converge func(*state.Helm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
} else {
|
||||||
|
noTarget = noReleasesMatchingSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
allSelectorNotMatched = allSelectorNotMatched && noReleases
|
noTargetFoundForAllHelmfiles = noTargetFoundForAllHelmfiles && noTarget
|
||||||
if noReleases {
|
if noTarget {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errs := converge(state, helm)
|
errs := converge(st, helm)
|
||||||
if err := clean(state, errs); err != nil {
|
if err := clean(st, errs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allSelectorNotMatched {
|
if noTargetFoundForAllHelmfiles {
|
||||||
logger.Error("specified selector did not match any releases in any helmfile")
|
logger.Errorf(
|
||||||
|
"err: no releases found that matches specified selector(%s) and environment(%s), in any helmfile",
|
||||||
|
strings.Join(selectors, ", "),
|
||||||
|
env,
|
||||||
|
)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -651,7 +667,7 @@ func directoryExistsAt(path string) bool {
|
||||||
func loadDesiredStateFromFile(yaml []byte, file string, kubeContext, namespace string, labels []string, env string, logger *zap.SugaredLogger) (*state.HelmState, helmexec.Interface, bool, error) {
|
func loadDesiredStateFromFile(yaml []byte, file string, kubeContext, namespace string, labels []string, env string, logger *zap.SugaredLogger) (*state.HelmState, helmexec.Interface, bool, error) {
|
||||||
st, err := state.CreateFromYaml(yaml, file, env, logger)
|
st, err := state.CreateFromYaml(yaml, file, env, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, false, fmt.Errorf("failed to read %s: %v", file, err)
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.Context != "" {
|
if st.Context != "" {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,23 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type StateLoadError struct {
|
||||||
|
msg string
|
||||||
|
Cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StateLoadError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %v", e.msg, e.Cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndefinedEnvError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UndefinedEnvError) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
func CreateFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) {
|
func CreateFromYaml(content []byte, file string, env string, logger *zap.SugaredLogger) (*HelmState, error) {
|
||||||
return createFromYamlWithFileReader(content, file, env, logger, ioutil.ReadFile)
|
return createFromYamlWithFileReader(content, file, env, logger, ioutil.ReadFile)
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +39,7 @@ func createFromYamlWithFileReader(content []byte, file string, env string, logge
|
||||||
|
|
||||||
state.basePath, _ = filepath.Abs(filepath.Dir(file))
|
state.basePath, _ = filepath.Abs(filepath.Dir(file))
|
||||||
if err := yaml.UnmarshalStrict(content, &state); err != nil {
|
if err := yaml.UnmarshalStrict(content, &state); err != nil {
|
||||||
return nil, err
|
return nil, &StateLoadError{fmt.Sprintf("failed to read %s", file), err}
|
||||||
}
|
}
|
||||||
state.FilePath = file
|
state.FilePath = file
|
||||||
|
|
||||||
|
|
@ -38,7 +55,7 @@ func createFromYamlWithFileReader(content []byte, file string, env string, logge
|
||||||
|
|
||||||
e, err := state.loadEnv(env, readFile)
|
e, err := state.loadEnv(env, readFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, &StateLoadError{fmt.Sprintf("failed to read %s", file), err}
|
||||||
}
|
}
|
||||||
state.env = *e
|
state.env = *e
|
||||||
|
|
||||||
|
|
@ -92,7 +109,7 @@ func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if name != DefaultEnv {
|
} else if name != DefaultEnv {
|
||||||
return nil, fmt.Errorf("environment \"%s\" is not defined in \"%s\"", name, state.FilePath)
|
return nil, &UndefinedEnvError{msg: fmt.Sprintf("environment \"%s\" is not defined", name)}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &environment.Environment{Name: name, Values: envVals}, nil
|
return &environment.Environment{Name: name, Values: envVals}, nil
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue