Fix concurrent-map-iteration-and-write errors while running release hooks (#1534)
Fixes #1495
This commit is contained in:
		
							parent
							
								
									c170b5a621
								
							
						
					
					
						commit
						ab9fb2c9dc
					
				|  | @ -219,6 +219,8 @@ func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Enviro | |||
| 			if err := mergo.Merge(&finalState.ReleaseSetSpec, ¤tState.ReleaseSetSpec, mergo.WithOverride); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			finalState.RenderedValues = currentState.RenderedValues | ||||
| 		} | ||||
| 
 | ||||
| 		env = &finalState.Env | ||||
|  |  | |||
|  | @ -145,11 +145,15 @@ func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *envi | |||
| 		return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err} | ||||
| 	} | ||||
| 
 | ||||
| 	e.Defaults, err = state.loadValuesEntries(nil, state.DefaultValues, c.remote) | ||||
| 	newDefaults, err := state.loadValuesEntries(nil, state.DefaultValues, c.remote) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := mergo.Merge(&e.Defaults, newDefaults, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	state.Env = *e | ||||
| 
 | ||||
| 	return &state, nil | ||||
|  | @ -181,6 +185,12 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam | |||
| 
 | ||||
| 	state.FilePath = file | ||||
| 
 | ||||
| 	vals, err := state.Env.GetMergedValues() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("rendering values: %w", err) | ||||
| 	} | ||||
| 	state.RenderedValues = vals | ||||
| 
 | ||||
| 	return state, nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -63,6 +63,35 @@ func TestReadFromYaml_NonexistentEnv(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| type stateTestEnv struct { | ||||
| 	Files   map[string]string | ||||
| 	WorkDir string | ||||
| } | ||||
| 
 | ||||
| func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState { | ||||
| 	t.Helper() | ||||
| 
 | ||||
| 	testFs := testhelper.NewTestFs(testEnv.Files) | ||||
| 
 | ||||
| 	if testFs.Cwd == "" { | ||||
| 		testFs.Cwd = "/" | ||||
| 	} | ||||
| 
 | ||||
| 	yamlContent, ok := testEnv.Files[file] | ||||
| 	if !ok { | ||||
| 		t.Fatalf("no file named %q registered", file) | ||||
| 	} | ||||
| 
 | ||||
| 	r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt) | ||||
| 	state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil, nil, "", r). | ||||
| 		ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return state | ||||
| } | ||||
| 
 | ||||
| func TestReadFromYaml_NonDefaultEnv(t *testing.T) { | ||||
| 	yamlFile := "/example/path/to/helmfile.yaml" | ||||
| 	yamlContent := []byte(`environments: | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ package state | |||
| 
 | ||||
| import ( | ||||
| 	"github.com/google/go-cmp/cmp" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
|  | @ -70,11 +69,12 @@ func TestSelectReleasesWithOverrides(t *testing.T) { | |||
|     type: bar | ||||
| `) | ||||
| 
 | ||||
| 	var state HelmState | ||||
| 
 | ||||
| 	if err := yaml.Unmarshal(example, &state); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	state := stateTestEnv{ | ||||
| 		Files: map[string]string{ | ||||
| 			"/helmfile.yaml": string(example), | ||||
| 		}, | ||||
| 		WorkDir: "/", | ||||
| 	}.MustLoadState(t, "/helmfile.yaml", "default") | ||||
| 
 | ||||
| 	for _, tc := range testcases { | ||||
| 		state.Selectors = tc.selector | ||||
|  |  | |||
|  | @ -91,6 +91,11 @@ type HelmState struct { | |||
| 
 | ||||
| 	runner      helmexec.Runner | ||||
| 	valsRuntime vals.Evaluator | ||||
| 
 | ||||
| 	// RenderedValues is the helmfile-wide values that is `.Values`
 | ||||
| 	// which is accessible from within the whole helmfile go template.
 | ||||
| 	// Note that this is usually computed by DesiredStateLoader from ReleaseSetSpec.Env
 | ||||
| 	RenderedValues map[string]interface{} | ||||
| } | ||||
| 
 | ||||
| // SubHelmfileSpec defines the subhelmfile path and options
 | ||||
|  | @ -1683,10 +1688,7 @@ func (st *HelmState) GetReleasesWithOverrides() []ReleaseSpec { | |||
| } | ||||
| 
 | ||||
| func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { | ||||
| 	values, err := st.Values() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	values := st.Values() | ||||
| 	rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
|  | @ -1823,10 +1825,7 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe | |||
| 		Logger:        st.logger, | ||||
| 		ReadFile:      st.readFile, | ||||
| 	} | ||||
| 	vals, err := st.Values() | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	vals := st.Values() | ||||
| 	data := map[string]interface{}{ | ||||
| 		"Values":          vals, | ||||
| 		"Release":         r, | ||||
|  | @ -2147,10 +2146,7 @@ func (st *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec, | |||
| } | ||||
| 
 | ||||
| func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) { | ||||
| 	vals, err := st.Values() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	vals := st.Values() | ||||
| 	templateData := st.createReleaseTemplateData(release, vals) | ||||
| 
 | ||||
| 	r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), templateData) | ||||
|  |  | |||
|  | @ -8,8 +8,12 @@ import ( | |||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
| 
 | ||||
| func (st *HelmState) Values() (map[string]interface{}, error) { | ||||
| 	return st.Env.GetMergedValues() | ||||
| func (st *HelmState) Values() map[string]interface{} { | ||||
| 	if st.RenderedValues == nil { | ||||
| 		panic("[bug] RenderedValues is nil") | ||||
| 	} | ||||
| 
 | ||||
| 	return st.RenderedValues | ||||
| } | ||||
| 
 | ||||
| func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[string]interface{}) releaseTemplateData { | ||||
|  | @ -78,10 +82,7 @@ func updateBoolTemplatedValues(r *ReleaseSpec) error { | |||
| func (st *HelmState) ExecuteTemplates() (*HelmState, error) { | ||||
| 	r := *st | ||||
| 
 | ||||
| 	vals, err := st.Values() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	vals := st.Values() | ||||
| 
 | ||||
| 	for i, rt := range st.Releases { | ||||
| 		if rt.Labels == nil { | ||||
|  |  | |||
|  | @ -146,6 +146,7 @@ func TestHelmState_executeTemplates(t *testing.T) { | |||
| 						tt.input, | ||||
| 					}, | ||||
| 				}, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 
 | ||||
| 			r, err := state.ExecuteTemplates() | ||||
|  | @ -248,6 +249,7 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) { | |||
| 						tt.input, | ||||
| 					}, | ||||
| 				}, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 
 | ||||
| 			r, err := state.ExecuteTemplates() | ||||
|  |  | |||
|  | @ -1030,8 +1030,9 @@ func TestHelmState_SyncReleases(t *testing.T) { | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: tt.releases, | ||||
| 				}, | ||||
| 				logger:      logger, | ||||
| 				valsRuntime: valsRuntime, | ||||
| 				logger:         logger, | ||||
| 				valsRuntime:    valsRuntime, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 { | ||||
| 				if len(errs) != len(tt.wantErrorMsgs) { | ||||
|  | @ -1136,8 +1137,9 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing. | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: []ReleaseSpec{tt.release}, | ||||
| 				}, | ||||
| 				logger:      logger, | ||||
| 				valsRuntime: valsRuntime, | ||||
| 				logger:         logger, | ||||
| 				valsRuntime:    valsRuntime, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			fs := testhelper.NewTestFs(map[string]string{}) | ||||
| 			state = injectFs(state, fs) | ||||
|  | @ -1283,8 +1285,9 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: tt.releases, | ||||
| 				}, | ||||
| 				logger:      logger, | ||||
| 				valsRuntime: valsRuntime, | ||||
| 				logger:         logger, | ||||
| 				valsRuntime:    valsRuntime, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			helm := &exectest.Helm{ | ||||
| 				Lists: map[exectest.ListKey]string{}, | ||||
|  | @ -1387,8 +1390,9 @@ func TestGetDeployedVersion(t *testing.T) { | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: []ReleaseSpec{tt.release}, | ||||
| 				}, | ||||
| 				logger:      logger, | ||||
| 				valsRuntime: valsRuntime, | ||||
| 				logger:         logger, | ||||
| 				valsRuntime:    valsRuntime, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			helm := &exectest.Helm{ | ||||
| 				Lists: map[exectest.ListKey]string{}, | ||||
|  | @ -1516,8 +1520,9 @@ func TestHelmState_DiffReleases(t *testing.T) { | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: tt.releases, | ||||
| 				}, | ||||
| 				logger:      logger, | ||||
| 				valsRuntime: valsRuntime, | ||||
| 				logger:         logger, | ||||
| 				valsRuntime:    valsRuntime, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false) | ||||
| 			if errs != nil && len(errs) > 0 { | ||||
|  | @ -1596,6 +1601,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { | |||
| 					numRemovedFiles += 1 | ||||
| 					return nil | ||||
| 				}, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			testfs := testhelper.NewTestFs(map[string]string{ | ||||
| 				"/path/to/someFile": `foo: FOO`, | ||||
|  | @ -1682,6 +1688,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { | |||
| 					numRemovedFiles += 1 | ||||
| 					return nil | ||||
| 				}, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			testfs := testhelper.NewTestFs(map[string]string{ | ||||
| 				"/path/to/someFile": `foo: bar | ||||
|  | @ -2062,7 +2069,8 @@ func TestHelmState_NoReleaseMatched(t *testing.T) { | |||
| 				ReleaseSetSpec: ReleaseSetSpec{ | ||||
| 					Releases: releases, | ||||
| 				}, | ||||
| 				logger: logger, | ||||
| 				logger:         logger, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			state.Selectors = []string{tt.labels} | ||||
| 			errs := state.FilterReleases() | ||||
|  | @ -2233,7 +2241,8 @@ func TestHelmState_Delete(t *testing.T) { | |||
| 					}, | ||||
| 					Releases: releases, | ||||
| 				}, | ||||
| 				logger: logger, | ||||
| 				logger:         logger, | ||||
| 				RenderedValues: map[string]interface{}{}, | ||||
| 			} | ||||
| 			helm := &exectest.Helm{ | ||||
| 				Lists:   map[exectest.ListKey]string{}, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue