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
					
				
							
								
								
									
										40
									
								
								main.go
								
								
								
								
							
							
						
						
									
										40
									
								
								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,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var noTarget bool
 | 
				
			||||||
		if err != nil {
 | 
							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
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							} else if len(st.Helmfiles) > 0 {
 | 
				
			||||||
		if len(state.Helmfiles) > 0 {
 | 
								for _, globPattern := range st.Helmfiles {
 | 
				
			||||||
			for _, globPattern := range state.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