Allow helmfile statefile to passed by stdin (#520)
* Allow helmfile statefile to passed by stdin Signed-off-by: xiaomudk <xiaomudk@gmail.com>
This commit is contained in:
		
							parent
							
								
									ce7f38ee96
								
							
						
					
					
						commit
						7844145ee3
					
				|  | @ -109,7 +109,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { | |||
| 
 | ||||
| func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalOptions) { | ||||
| 	fs.StringVarP(&globalOptions.HelmBinary, "helm-binary", "b", app.DefaultHelmBinary, "Path to the helm binary") | ||||
| 	fs.StringVarP(&globalOptions.File, "file", "f", "", "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference") | ||||
| 	fs.StringVarP(&globalOptions.File, "file", "f", "", "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference. Specify - to load the config from the standard input.") | ||||
| 	fs.StringVarP(&globalOptions.Environment, "environment", "e", "", `specify the environment name. defaults to "default"`) | ||||
| 	fs.StringArrayVar(&globalOptions.StateValuesSet, "state-values-set", nil, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") | ||||
| 	fs.StringArrayVar(&globalOptions.StateValuesFile, "state-values-file", nil, "specify state values in a YAML file") | ||||
|  |  | |||
|  | @ -530,7 +530,7 @@ Flags: | |||
|       --enable-live-output              Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr. | ||||
|                                         It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes. | ||||
|   -e, --environment string              specify the environment name. defaults to "default" | ||||
|   -f, --file helmfile.yaml              load config from file or directory. defaults to helmfile.yaml or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference | ||||
|   -f, --file helmfile.yaml              load config from file or directory. defaults to helmfile.yaml or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference. Specify - to load the config from the standard input. | ||||
|   -b, --helm-binary string              Path to the helm binary (default "helm") | ||||
|   -h, --help                            help for helmfile | ||||
|   -i, --interactive                     Request confirmation before attempting to modify clusters | ||||
|  | @ -655,6 +655,7 @@ A few rules to clear up this ambiguity: | |||
| * Absolute paths are always resolved as absolute paths | ||||
| * Relative paths referenced *in* the Helmfile manifest itself are relative to that manifest | ||||
| * Relative paths referenced on the command line are relative to the current working directory the user is in | ||||
| - Relative paths referenced from within the helmfile loaded from the standard input using `helmfile -f -` are relative to the current working directory | ||||
| 
 | ||||
| For additional context, take a look at [paths examples](paths.md). | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,6 +7,8 @@ A few rules to clear up this ambiguity: | |||
| - Absolute paths are always resolved as absolute paths | ||||
| - Relative paths referenced *in* the helmfile manifest itself are relative to that manifest | ||||
| - Relative paths referenced on the command line are relative to the current working directory the user is in | ||||
| - Relative paths referenced from within the helmfile loaded from the standard input using `helmfile -f -` are relative to the current working directory | ||||
| 
 | ||||
| 
 | ||||
| ### Examples | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,27 @@ | |||
| package filesystem | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type fileStat struct { | ||||
| 	name    string | ||||
| 	size    int64 | ||||
| 	mode    fs.FileMode | ||||
| 	modTime time.Time | ||||
| } | ||||
| 
 | ||||
| func (fs fileStat) Name() string       { return fs.name } | ||||
| func (fs fileStat) Size() int64        { return fs.size } | ||||
| func (fs fileStat) Mode() fs.FileMode  { return fs.mode } | ||||
| func (fs fileStat) ModTime() time.Time { return fs.modTime } | ||||
| func (fs fileStat) IsDir() bool        { return fs.mode.IsDir() } | ||||
| func (fs fileStat) Sys() any           { return nil } | ||||
| 
 | ||||
| type FileSystem struct { | ||||
| 	ReadFile          func(string) ([]byte, error) | ||||
| 	ReadDir           func(string) ([]fs.DirEntry, error) | ||||
|  | @ -22,7 +38,6 @@ type FileSystem struct { | |||
| 
 | ||||
| func DefaultFileSystem() *FileSystem { | ||||
| 	dfs := FileSystem{ | ||||
| 		ReadFile:   os.ReadFile, | ||||
| 		ReadDir:    os.ReadDir, | ||||
| 		DeleteFile: os.Remove, | ||||
| 		Stat:       os.Stat, | ||||
|  | @ -32,6 +47,8 @@ func DefaultFileSystem() *FileSystem { | |||
| 		Abs:        filepath.Abs, | ||||
| 	} | ||||
| 
 | ||||
| 	dfs.Stat = dfs.stat | ||||
| 	dfs.ReadFile = dfs.readFile | ||||
| 	dfs.FileExistsAt = dfs.fileExistsAtDefault | ||||
| 	dfs.DirectoryExistsAt = dfs.directoryExistsDefault | ||||
| 	dfs.FileExists = dfs.fileExistsDefault | ||||
|  | @ -78,6 +95,20 @@ func FromFileSystem(params FileSystem) *FileSystem { | |||
| 	return dfs | ||||
| } | ||||
| 
 | ||||
| func (filesystem *FileSystem) stat(name string) (os.FileInfo, error) { | ||||
| 	if name == "-" { | ||||
| 		return fileStat{mode: 0}, nil | ||||
| 	} | ||||
| 	return os.Stat(name) | ||||
| } | ||||
| 
 | ||||
| func (filesystem *FileSystem) readFile(name string) ([]byte, error) { | ||||
| 	if name == "-" { | ||||
| 		return io.ReadAll(os.Stdin) | ||||
| 	} | ||||
| 	return os.ReadFile(name) | ||||
| } | ||||
| 
 | ||||
| func (filesystem *FileSystem) fileExistsAtDefault(path string) bool { | ||||
| 	fileInfo, err := filesystem.Stat(path) | ||||
| 	return err == nil && fileInfo.Mode().IsRegular() | ||||
|  |  | |||
|  | @ -4,30 +4,19 @@ import ( | |||
| 	"errors" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type TestFileInfo struct { | ||||
| 	mode fs.FileMode | ||||
| } | ||||
| 
 | ||||
| func (tfi TestFileInfo) Name() string       { return "" } | ||||
| func (tfi TestFileInfo) Size() int64        { return 0 } | ||||
| func (tfi TestFileInfo) Mode() fs.FileMode  { return tfi.mode } | ||||
| func (tfi TestFileInfo) ModTime() time.Time { return time.Time{} } | ||||
| func (tfi TestFileInfo) IsDir() bool        { return tfi.mode.IsDir() } | ||||
| func (tfi TestFileInfo) Sys() any           { return nil } | ||||
| 
 | ||||
| func NewTestFileSystem() FileSystem { | ||||
| 	replaceffs := FileSystem{ | ||||
| 		Stat: func(s string) (os.FileInfo, error) { | ||||
| 			if strings.HasPrefix(s, "existing_file") { | ||||
| 				return TestFileInfo{mode: 0}, nil | ||||
| 				return fileStat{mode: 0}, nil | ||||
| 			} | ||||
| 			if strings.HasPrefix(s, "existing_dir") { | ||||
| 				return TestFileInfo{mode: fs.ModeDir}, nil | ||||
| 				return fileStat{mode: fs.ModeDir}, nil | ||||
| 			} | ||||
| 			return nil, errors.New("Error") | ||||
| 		}, | ||||
|  | @ -46,6 +35,12 @@ func TestFs_fileExistsDefault(t *testing.T) { | |||
| 	if exists { | ||||
| 		t.Errorf("Not expected file %s, found", "non_existing_file.txt") | ||||
| 	} | ||||
| 
 | ||||
| 	dfs := DefaultFileSystem() | ||||
| 	exists, _ = dfs.FileExists("-") | ||||
| 	if !exists { | ||||
| 		t.Errorf("Not expected file %s, not found", "-") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFs_fileExistsAtDefault(t *testing.T) { | ||||
|  | @ -65,6 +60,12 @@ func TestFs_fileExistsAtDefault(t *testing.T) { | |||
| 	if exists { | ||||
| 		t.Errorf("Not expected file %s, found", "existing_dir") | ||||
| 	} | ||||
| 
 | ||||
| 	dfs := DefaultFileSystem() | ||||
| 	exists = dfs.FileExistsAt("-") | ||||
| 	if !exists { | ||||
| 		t.Errorf("Not expected file %s, not found", "-") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFs_directoryExistsDefault(t *testing.T) { | ||||
|  | @ -80,6 +81,61 @@ func TestFs_directoryExistsDefault(t *testing.T) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFsTeadFile(t *testing.T) { | ||||
| 	cases := []struct { | ||||
| 		name      string | ||||
| 		content   []byte | ||||
| 		path      string | ||||
| 		wantError string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:    "read file", | ||||
| 			content: []byte("hello helmfile"), | ||||
| 			path:    "helmfile.yaml", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:    "read file from stdin", | ||||
| 			content: []byte("hello helmfile"), | ||||
| 			path:    "-", | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, c := range cases { | ||||
| 		t.Run(c.name, func(t *testing.T) { | ||||
| 			dir := t.TempDir() | ||||
| 			yamlPath := path.Join(dir, c.path) | ||||
| 
 | ||||
| 			dfs := DefaultFileSystem() | ||||
| 			tmpfile, err := os.Create(yamlPath) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("create file %s error: %v", yamlPath, err) | ||||
| 			} | ||||
| 			_, err = tmpfile.Write(c.content) | ||||
| 			if err != nil { | ||||
| 				t.Errorf(" write to file %s error: %v", yamlPath, err) | ||||
| 			} | ||||
| 			readPath := yamlPath | ||||
| 			if c.path == "-" { | ||||
| 				readPath = c.path | ||||
| 				oldOsStdin := os.Stdin | ||||
| 				defer func() { os.Stdin = oldOsStdin }() | ||||
| 				os.Stdin = tmpfile | ||||
| 			} | ||||
| 			if _, err = tmpfile.Seek(0, 0); err != nil { | ||||
| 				t.Errorf("file %s seek error: %v", yamlPath, err) | ||||
| 			} | ||||
| 
 | ||||
| 			want, err := dfs.readFile(readPath) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("read file %s error: %v", readPath, err) | ||||
| 			} else { | ||||
| 				if string(c.content) != string(want) { | ||||
| 					t.Errorf("nexpected error: unexpected=%s, got=%v", string(c.content), string(want)) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestFs_DefaultBuilder(t *testing.T) { | ||||
| 	ffs := DefaultFileSystem() | ||||
| 	if ffs.ReadFile == nil || | ||||
|  |  | |||
|  | @ -27,6 +27,23 @@ for output in $(ls -d ${dir}/tmp/*); do | |||
|     done | ||||
| done | ||||
| 
 | ||||
| info "Templating ${dir}/happypath.yaml from stdin" | ||||
| pushd ${dir} | ||||
| rm -rf ./tmp | ||||
| cat ./happypath.yaml | ../../${helmfile} -f - --debug template --output-dir tmp | ||||
| code=$? | ||||
| [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile template: ${code}" | ||||
| for output in $(ls -d ./tmp/*); do | ||||
|     # e.g. test/integration/tmp/happypath-877c0dd4-helmx/helmx | ||||
|     for release_dir in $(ls -d ${output}/*); do | ||||
|         release_name=$(basename ${release_dir}) | ||||
|         golden_dir=./templates-golden/v${helm_major_version}/${release_name} | ||||
|         info "Comparing template output ${release_dir}/templates with ${golden_dir}" | ||||
|         ../../diff-yamls ${golden_dir} ${release_dir}/templates || fail "unexpected diff in template result for ${release_name}" | ||||
|     done | ||||
| done | ||||
| popd | ||||
| 
 | ||||
| info "Applying ${dir}/happypath.yaml" | ||||
| bash -c "${helmfile} -f ${dir}/happypath.yaml apply --detailed-exitcode; code="'$?'"; echo Code: "'$code'"; [ "'${code}'" -eq 2 ]" || fail "unexpected exit code returned by helmfile apply" | ||||
| 
 | ||||
|  | @ -41,6 +58,13 @@ ${helmfile} -f ${dir}/happypath.yaml apply --detailed-exitcode | |||
| code=$? | ||||
| [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}" | ||||
| 
 | ||||
| info "Applying ${dir}/happypath.yaml from stdin" | ||||
| pushd ${dir} | ||||
| cat ./happypath.yaml | ../../${helmfile} -f - apply --detailed-exitcode | ||||
| code=$? | ||||
| [ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${code}" | ||||
| popd | ||||
| 
 | ||||
| info "Locking dependencies" | ||||
| ${helmfile} -f ${dir}/happypath.yaml deps | ||||
| code=$? | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue