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 { | 			if err := mergo.Merge(&finalState.ReleaseSetSpec, ¤tState.ReleaseSetSpec, mergo.WithOverride); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
|  | 
 | ||||||
|  | 			finalState.RenderedValues = currentState.RenderedValues | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		env = &finalState.Env | 		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} | 		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 { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := mergo.Merge(&e.Defaults, newDefaults, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	state.Env = *e | 	state.Env = *e | ||||||
| 
 | 
 | ||||||
| 	return &state, nil | 	return &state, nil | ||||||
|  | @ -181,6 +185,12 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam | ||||||
| 
 | 
 | ||||||
| 	state.FilePath = file | 	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 | 	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) { | func TestReadFromYaml_NonDefaultEnv(t *testing.T) { | ||||||
| 	yamlFile := "/example/path/to/helmfile.yaml" | 	yamlFile := "/example/path/to/helmfile.yaml" | ||||||
| 	yamlContent := []byte(`environments: | 	yamlContent := []byte(`environments: | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package state | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/google/go-cmp/cmp" | 	"github.com/google/go-cmp/cmp" | ||||||
| 	"gopkg.in/yaml.v2" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -70,11 +69,12 @@ func TestSelectReleasesWithOverrides(t *testing.T) { | ||||||
|     type: bar |     type: bar | ||||||
| `) | `) | ||||||
| 
 | 
 | ||||||
| 	var state HelmState | 	state := stateTestEnv{ | ||||||
| 
 | 		Files: map[string]string{ | ||||||
| 	if err := yaml.Unmarshal(example, &state); err != nil { | 			"/helmfile.yaml": string(example), | ||||||
| 		t.Fatal(err) | 		}, | ||||||
| 	} | 		WorkDir: "/", | ||||||
|  | 	}.MustLoadState(t, "/helmfile.yaml", "default") | ||||||
| 
 | 
 | ||||||
| 	for _, tc := range testcases { | 	for _, tc := range testcases { | ||||||
| 		state.Selectors = tc.selector | 		state.Selectors = tc.selector | ||||||
|  |  | ||||||
|  | @ -91,6 +91,11 @@ type HelmState struct { | ||||||
| 
 | 
 | ||||||
| 	runner      helmexec.Runner | 	runner      helmexec.Runner | ||||||
| 	valsRuntime vals.Evaluator | 	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
 | // SubHelmfileSpec defines the subhelmfile path and options
 | ||||||
|  | @ -1683,10 +1688,7 @@ func (st *HelmState) GetReleasesWithOverrides() []ReleaseSpec { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { | func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { | ||||||
| 	values, err := st.Values() | 	values := st.Values() | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values) | 	rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | @ -1823,10 +1825,7 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe | ||||||
| 		Logger:        st.logger, | 		Logger:        st.logger, | ||||||
| 		ReadFile:      st.readFile, | 		ReadFile:      st.readFile, | ||||||
| 	} | 	} | ||||||
| 	vals, err := st.Values() | 	vals := st.Values() | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	data := map[string]interface{}{ | 	data := map[string]interface{}{ | ||||||
| 		"Values":          vals, | 		"Values":          vals, | ||||||
| 		"Release":         r, | 		"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) { | func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) { | ||||||
| 	vals, err := st.Values() | 	vals := st.Values() | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	templateData := st.createReleaseTemplateData(release, vals) | 	templateData := st.createReleaseTemplateData(release, vals) | ||||||
| 
 | 
 | ||||||
| 	r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), templateData) | 	r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), templateData) | ||||||
|  |  | ||||||
|  | @ -8,8 +8,12 @@ import ( | ||||||
| 	"gopkg.in/yaml.v2" | 	"gopkg.in/yaml.v2" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) Values() (map[string]interface{}, error) { | func (st *HelmState) Values() map[string]interface{} { | ||||||
| 	return st.Env.GetMergedValues() | 	if st.RenderedValues == nil { | ||||||
|  | 		panic("[bug] RenderedValues is nil") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return st.RenderedValues | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[string]interface{}) releaseTemplateData { | 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) { | func (st *HelmState) ExecuteTemplates() (*HelmState, error) { | ||||||
| 	r := *st | 	r := *st | ||||||
| 
 | 
 | ||||||
| 	vals, err := st.Values() | 	vals := st.Values() | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	for i, rt := range st.Releases { | 	for i, rt := range st.Releases { | ||||||
| 		if rt.Labels == nil { | 		if rt.Labels == nil { | ||||||
|  |  | ||||||
|  | @ -146,6 +146,7 @@ func TestHelmState_executeTemplates(t *testing.T) { | ||||||
| 						tt.input, | 						tt.input, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			r, err := state.ExecuteTemplates() | 			r, err := state.ExecuteTemplates() | ||||||
|  | @ -248,6 +249,7 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) { | ||||||
| 						tt.input, | 						tt.input, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			r, err := state.ExecuteTemplates() | 			r, err := state.ExecuteTemplates() | ||||||
|  |  | ||||||
|  | @ -1030,8 +1030,9 @@ func TestHelmState_SyncReleases(t *testing.T) { | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: tt.releases, | 					Releases: tt.releases, | ||||||
| 				}, | 				}, | ||||||
| 				logger:      logger, | 				logger:         logger, | ||||||
| 				valsRuntime: valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 { | 			if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 { | ||||||
| 				if len(errs) != len(tt.wantErrorMsgs) { | 				if len(errs) != len(tt.wantErrorMsgs) { | ||||||
|  | @ -1136,8 +1137,9 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing. | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: []ReleaseSpec{tt.release}, | 					Releases: []ReleaseSpec{tt.release}, | ||||||
| 				}, | 				}, | ||||||
| 				logger:      logger, | 				logger:         logger, | ||||||
| 				valsRuntime: valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			fs := testhelper.NewTestFs(map[string]string{}) | 			fs := testhelper.NewTestFs(map[string]string{}) | ||||||
| 			state = injectFs(state, fs) | 			state = injectFs(state, fs) | ||||||
|  | @ -1283,8 +1285,9 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: tt.releases, | 					Releases: tt.releases, | ||||||
| 				}, | 				}, | ||||||
| 				logger:      logger, | 				logger:         logger, | ||||||
| 				valsRuntime: valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			helm := &exectest.Helm{ | 			helm := &exectest.Helm{ | ||||||
| 				Lists: map[exectest.ListKey]string{}, | 				Lists: map[exectest.ListKey]string{}, | ||||||
|  | @ -1387,8 +1390,9 @@ func TestGetDeployedVersion(t *testing.T) { | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: []ReleaseSpec{tt.release}, | 					Releases: []ReleaseSpec{tt.release}, | ||||||
| 				}, | 				}, | ||||||
| 				logger:      logger, | 				logger:         logger, | ||||||
| 				valsRuntime: valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			helm := &exectest.Helm{ | 			helm := &exectest.Helm{ | ||||||
| 				Lists: map[exectest.ListKey]string{}, | 				Lists: map[exectest.ListKey]string{}, | ||||||
|  | @ -1516,8 +1520,9 @@ func TestHelmState_DiffReleases(t *testing.T) { | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: tt.releases, | 					Releases: tt.releases, | ||||||
| 				}, | 				}, | ||||||
| 				logger:      logger, | 				logger:         logger, | ||||||
| 				valsRuntime: valsRuntime, | 				valsRuntime:    valsRuntime, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false) | 			_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false) | ||||||
| 			if errs != nil && len(errs) > 0 { | 			if errs != nil && len(errs) > 0 { | ||||||
|  | @ -1596,6 +1601,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { | ||||||
| 					numRemovedFiles += 1 | 					numRemovedFiles += 1 | ||||||
| 					return nil | 					return nil | ||||||
| 				}, | 				}, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			testfs := testhelper.NewTestFs(map[string]string{ | 			testfs := testhelper.NewTestFs(map[string]string{ | ||||||
| 				"/path/to/someFile": `foo: FOO`, | 				"/path/to/someFile": `foo: FOO`, | ||||||
|  | @ -1682,6 +1688,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { | ||||||
| 					numRemovedFiles += 1 | 					numRemovedFiles += 1 | ||||||
| 					return nil | 					return nil | ||||||
| 				}, | 				}, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			testfs := testhelper.NewTestFs(map[string]string{ | 			testfs := testhelper.NewTestFs(map[string]string{ | ||||||
| 				"/path/to/someFile": `foo: bar | 				"/path/to/someFile": `foo: bar | ||||||
|  | @ -2062,7 +2069,8 @@ func TestHelmState_NoReleaseMatched(t *testing.T) { | ||||||
| 				ReleaseSetSpec: ReleaseSetSpec{ | 				ReleaseSetSpec: ReleaseSetSpec{ | ||||||
| 					Releases: releases, | 					Releases: releases, | ||||||
| 				}, | 				}, | ||||||
| 				logger: logger, | 				logger:         logger, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			state.Selectors = []string{tt.labels} | 			state.Selectors = []string{tt.labels} | ||||||
| 			errs := state.FilterReleases() | 			errs := state.FilterReleases() | ||||||
|  | @ -2233,7 +2241,8 @@ func TestHelmState_Delete(t *testing.T) { | ||||||
| 					}, | 					}, | ||||||
| 					Releases: releases, | 					Releases: releases, | ||||||
| 				}, | 				}, | ||||||
| 				logger: logger, | 				logger:         logger, | ||||||
|  | 				RenderedValues: map[string]interface{}{}, | ||||||
| 			} | 			} | ||||||
| 			helm := &exectest.Helm{ | 			helm := &exectest.Helm{ | ||||||
| 				Lists:   map[exectest.ListKey]string{}, | 				Lists:   map[exectest.ListKey]string{}, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue