fix: Various helmfile commands should not leave temp values files (#520)
Fixes #504
This commit is contained in:
		
							parent
							
								
									f5e565ea3e
								
							
						
					
					
						commit
						283dac1531
					
				|  | @ -114,6 +114,18 @@ func (c *creator) CreateFromYaml(content []byte, file string, env string) (*Helm | ||||||
| 	state.Env = *e | 	state.Env = *e | ||||||
| 
 | 
 | ||||||
| 	state.readFile = c.readFile | 	state.readFile = c.readFile | ||||||
|  | 	state.removeFile = os.Remove | ||||||
|  | 	state.fileExists = func(path string) (bool, error) { | ||||||
|  | 		_, err := os.Stat(path) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			if os.IsNotExist(err) { | ||||||
|  | 				return false, nil | ||||||
|  | 			} | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return &state, nil | 	return &state, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -47,6 +47,9 @@ type HelmState struct { | ||||||
| 
 | 
 | ||||||
| 	readFile func(string) ([]byte, error) | 	readFile func(string) ([]byte, error) | ||||||
| 
 | 
 | ||||||
|  | 	removeFile func(string) error | ||||||
|  | 	fileExists func(string) (bool, error) | ||||||
|  | 
 | ||||||
| 	runner helmexec.Runner | 	runner helmexec.Runner | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -233,8 +236,11 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu | ||||||
| 						errs = append(errs, &ReleaseError{release, err}) | 						errs = append(errs, &ReleaseError{release, err}) | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					if _, err := os.Stat(valfile); os.IsNotExist(err) { | 					ok, err := st.fileExists(valfile) | ||||||
|  | 					if err != nil { | ||||||
| 						errs = append(errs, &ReleaseError{release, err}) | 						errs = append(errs, &ReleaseError{release, err}) | ||||||
|  | 					} else if !ok { | ||||||
|  | 						errs = append(errs, &ReleaseError{release, fmt.Errorf("file does not exist: %s", valfile)}) | ||||||
| 					} | 					} | ||||||
| 					flags = append(flags, "--values", valfile) | 					flags = append(flags, "--values", valfile) | ||||||
| 				} | 				} | ||||||
|  | @ -749,7 +755,7 @@ func (st *HelmState) Clean() []error { | ||||||
| 
 | 
 | ||||||
| 	for _, release := range st.Releases { | 	for _, release := range st.Releases { | ||||||
| 		for _, value := range release.generatedValues { | 		for _, value := range release.generatedValues { | ||||||
| 			err := os.Remove(value) | 			err := st.removeFile(value) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				errs = append(errs, err) | 				errs = append(errs, err) | ||||||
| 			} | 			} | ||||||
|  | @ -1068,23 +1074,25 @@ func (st *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) { | ||||||
| 	return r.RenderToBytes(path) | 	return r.RenderToBytes(path) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | func (st *HelmState) generateTemporaryValuesFiles(values []interface{}, missingFileHandler *string) ([]string, error) { | ||||||
| 	flags := []string{} | 	generatedFiles := []string{} | ||||||
| 	if release.Namespace != "" { | 
 | ||||||
| 		flags = append(flags, "--namespace", release.Namespace) | 	for _, value := range values { | ||||||
| 	} |  | ||||||
| 	for _, value := range release.Values { |  | ||||||
| 		switch typedValue := value.(type) { | 		switch typedValue := value.(type) { | ||||||
| 		case string: | 		case string: | ||||||
| 			path := st.normalizePath(release.ValuesPathPrefix + typedValue) | 			path := st.normalizePath(typedValue) | ||||||
| 
 | 
 | ||||||
| 			if _, err := os.Stat(path); os.IsNotExist(err) { | 			ok, err := st.fileExists(path) | ||||||
| 				if release.MissingFileHandler == nil || *release.MissingFileHandler == "Error" { | 			if err != nil { | ||||||
| 					return nil, err | 				return nil, err | ||||||
| 				} else if *release.MissingFileHandler == "Warn" { | 			} | ||||||
|  | 			if !ok { | ||||||
|  | 				if missingFileHandler == nil || *missingFileHandler == "Error" { | ||||||
|  | 					return nil, fmt.Errorf("file does not exist: %s", path) | ||||||
|  | 				} else if *missingFileHandler == "Warn" { | ||||||
| 					st.logger.Warnf("skipping missing values file \"%s\"", path) | 					st.logger.Warnf("skipping missing values file \"%s\"", path) | ||||||
| 					continue | 					continue | ||||||
| 				} else if *release.MissingFileHandler == "Info" { | 				} else if *missingFileHandler == "Info" { | ||||||
| 					st.logger.Infof("skipping missing values file \"%s\"", path) | 					st.logger.Infof("skipping missing values file \"%s\"", path) | ||||||
| 					continue | 					continue | ||||||
| 				} else { | 				} else { | ||||||
|  | @ -1108,8 +1116,7 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R | ||||||
| 				return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err) | 				return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err) | ||||||
| 			} | 			} | ||||||
| 			st.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes)) | 			st.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes)) | ||||||
| 			flags = append(flags, "--values", valfile.Name()) | 			generatedFiles = append(generatedFiles, valfile.Name()) | ||||||
| 
 |  | ||||||
| 		case map[interface{}]interface{}: | 		case map[interface{}]interface{}: | ||||||
| 			valfile, err := ioutil.TempFile("", "values") | 			valfile, err := ioutil.TempFile("", "values") | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -1121,14 +1128,49 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R | ||||||
| 			if err := encoder.Encode(typedValue); err != nil { | 			if err := encoder.Encode(typedValue); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			release.generatedValues = append(release.generatedValues, valfile.Name()) | 			generatedFiles = append(generatedFiles, valfile.Name()) | ||||||
| 			flags = append(flags, "--values", valfile.Name()) | 		default: | ||||||
|  | 			return nil, fmt.Errorf("unexpected type of values entry: %T", typedValue) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return generatedFiles, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { | ||||||
|  | 	flags := []string{} | ||||||
|  | 	if release.Namespace != "" { | ||||||
|  | 		flags = append(flags, "--namespace", release.Namespace) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	values := []interface{}{} | ||||||
|  | 	for _, v := range release.Values { | ||||||
|  | 		switch typedValue := v.(type) { | ||||||
|  | 		case string: | ||||||
|  | 			path := st.normalizePath(release.ValuesPathPrefix + typedValue) | ||||||
|  | 			values = append(values, path) | ||||||
|  | 		default: | ||||||
|  | 			values = append(values, v) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	generatedFiles, err := st.generateTemporaryValuesFiles(values, release.MissingFileHandler) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, f := range generatedFiles { | ||||||
|  | 		flags = append(flags, "--values", f) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	release.generatedValues = append(release.generatedValues, generatedFiles...) | ||||||
|  | 
 | ||||||
| 	for _, value := range release.Secrets { | 	for _, value := range release.Secrets { | ||||||
| 		path := st.normalizePath(release.ValuesPathPrefix + value) | 		path := st.normalizePath(release.ValuesPathPrefix + value) | ||||||
| 		if _, err := os.Stat(path); os.IsNotExist(err) { | 		ok, err := st.fileExists(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if !ok { | ||||||
| 			if release.MissingFileHandler == nil || *release.MissingFileHandler == "Error" { | 			if release.MissingFileHandler == nil || *release.MissingFileHandler == "Error" { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} else if *release.MissingFileHandler == "Warn" { | 			} else if *release.MissingFileHandler == "Warn" { | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
|  | 	"fmt" | ||||||
| 	"github.com/roboll/helmfile/helmexec" | 	"github.com/roboll/helmfile/helmexec" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -883,6 +884,95 @@ func TestHelmState_SyncReleases(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestHelmState_SyncReleasesCleanup(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name                    string | ||||||
|  | 		releases                []ReleaseSpec | ||||||
|  | 		helm                    *mockHelmExec | ||||||
|  | 		expectedNumRemovedFiles int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "normal release", | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:  "releaseName", | ||||||
|  | 					Chart: "foo", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:                    &mockHelmExec{}, | ||||||
|  | 			expectedNumRemovedFiles: 0, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "inline values", | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:  "releaseName", | ||||||
|  | 					Chart: "foo", | ||||||
|  | 					Values: []interface{}{ | ||||||
|  | 						map[interface{}]interface{}{ | ||||||
|  | 							"someList": "a,b,c", | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:                    &mockHelmExec{}, | ||||||
|  | 			expectedNumRemovedFiles: 1, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "inline values and values file", | ||||||
|  | 			releases: []ReleaseSpec{ | ||||||
|  | 				{ | ||||||
|  | 					Name:  "releaseName", | ||||||
|  | 					Chart: "foo", | ||||||
|  | 					Values: []interface{}{ | ||||||
|  | 						map[interface{}]interface{}{ | ||||||
|  | 							"someList": "a,b,c", | ||||||
|  | 						}, | ||||||
|  | 						"someFile", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			helm:                    &mockHelmExec{}, | ||||||
|  | 			expectedNumRemovedFiles: 2, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			numRemovedFiles := 0 | ||||||
|  | 			state := &HelmState{ | ||||||
|  | 				Releases: tt.releases, | ||||||
|  | 				logger:   logger, | ||||||
|  | 				readFile: func(f string) ([]byte, error) { | ||||||
|  | 					if f != "someFile" { | ||||||
|  | 						return nil, fmt.Errorf("unexpected file to read: %s", f) | ||||||
|  | 					} | ||||||
|  | 					someFileContent := []byte(`foo: bar | ||||||
|  | `) | ||||||
|  | 					return someFileContent, nil | ||||||
|  | 				}, | ||||||
|  | 				removeFile: func(f string) error { | ||||||
|  | 					numRemovedFiles += 1 | ||||||
|  | 					return nil | ||||||
|  | 				}, | ||||||
|  | 				fileExists: func(f string) (bool, error) { | ||||||
|  | 					return true, nil | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			if errs := state.SyncReleases(tt.helm, []string{}, 1); errs != nil && len(errs) > 0 { | ||||||
|  | 				t.Errorf("unexpected errors: %v", errs) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if errs := state.Clean(); errs != nil && len(errs) > 0 { | ||||||
|  | 				t.Errorf("unexpected errors: %v", errs) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if numRemovedFiles != tt.expectedNumRemovedFiles { | ||||||
|  | 				t.Errorf("unexpected number of removed files: expected %d, got %d", tt.expectedNumRemovedFiles, numRemovedFiles) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestHelmState_UpdateDeps(t *testing.T) { | func TestHelmState_UpdateDeps(t *testing.T) { | ||||||
| 	state := &HelmState{ | 	state := &HelmState{ | ||||||
| 		basePath: "/src", | 		basePath: "/src", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue