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) {
|
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.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.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.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")
|
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.
|
--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.
|
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"
|
-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")
|
-b, --helm-binary string Path to the helm binary (default "helm")
|
||||||
-h, --help help for helmfile
|
-h, --help help for helmfile
|
||||||
-i, --interactive Request confirmation before attempting to modify clusters
|
-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
|
* Absolute paths are always resolved as absolute paths
|
||||||
* Relative paths referenced *in* the Helmfile manifest itself are relative to that manifest
|
* 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 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).
|
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
|
- Absolute paths are always resolved as absolute paths
|
||||||
- Relative paths referenced *in* the helmfile manifest itself are relative to that manifest
|
- 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 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
|
### Examples
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,27 @@
|
||||||
package filesystem
|
package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"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 {
|
type FileSystem struct {
|
||||||
ReadFile func(string) ([]byte, error)
|
ReadFile func(string) ([]byte, error)
|
||||||
ReadDir func(string) ([]fs.DirEntry, error)
|
ReadDir func(string) ([]fs.DirEntry, error)
|
||||||
|
|
@ -22,7 +38,6 @@ type FileSystem struct {
|
||||||
|
|
||||||
func DefaultFileSystem() *FileSystem {
|
func DefaultFileSystem() *FileSystem {
|
||||||
dfs := FileSystem{
|
dfs := FileSystem{
|
||||||
ReadFile: os.ReadFile,
|
|
||||||
ReadDir: os.ReadDir,
|
ReadDir: os.ReadDir,
|
||||||
DeleteFile: os.Remove,
|
DeleteFile: os.Remove,
|
||||||
Stat: os.Stat,
|
Stat: os.Stat,
|
||||||
|
|
@ -32,6 +47,8 @@ func DefaultFileSystem() *FileSystem {
|
||||||
Abs: filepath.Abs,
|
Abs: filepath.Abs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dfs.Stat = dfs.stat
|
||||||
|
dfs.ReadFile = dfs.readFile
|
||||||
dfs.FileExistsAt = dfs.fileExistsAtDefault
|
dfs.FileExistsAt = dfs.fileExistsAtDefault
|
||||||
dfs.DirectoryExistsAt = dfs.directoryExistsDefault
|
dfs.DirectoryExistsAt = dfs.directoryExistsDefault
|
||||||
dfs.FileExists = dfs.fileExistsDefault
|
dfs.FileExists = dfs.fileExistsDefault
|
||||||
|
|
@ -78,6 +95,20 @@ func FromFileSystem(params FileSystem) *FileSystem {
|
||||||
return dfs
|
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 {
|
func (filesystem *FileSystem) fileExistsAtDefault(path string) bool {
|
||||||
fileInfo, err := filesystem.Stat(path)
|
fileInfo, err := filesystem.Stat(path)
|
||||||
return err == nil && fileInfo.Mode().IsRegular()
|
return err == nil && fileInfo.Mode().IsRegular()
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,19 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 {
|
func NewTestFileSystem() FileSystem {
|
||||||
replaceffs := FileSystem{
|
replaceffs := FileSystem{
|
||||||
Stat: func(s string) (os.FileInfo, error) {
|
Stat: func(s string) (os.FileInfo, error) {
|
||||||
if strings.HasPrefix(s, "existing_file") {
|
if strings.HasPrefix(s, "existing_file") {
|
||||||
return TestFileInfo{mode: 0}, nil
|
return fileStat{mode: 0}, nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(s, "existing_dir") {
|
if strings.HasPrefix(s, "existing_dir") {
|
||||||
return TestFileInfo{mode: fs.ModeDir}, nil
|
return fileStat{mode: fs.ModeDir}, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("Error")
|
return nil, errors.New("Error")
|
||||||
},
|
},
|
||||||
|
|
@ -46,6 +35,12 @@ func TestFs_fileExistsDefault(t *testing.T) {
|
||||||
if exists {
|
if exists {
|
||||||
t.Errorf("Not expected file %s, found", "non_existing_file.txt")
|
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) {
|
func TestFs_fileExistsAtDefault(t *testing.T) {
|
||||||
|
|
@ -65,6 +60,12 @@ func TestFs_fileExistsAtDefault(t *testing.T) {
|
||||||
if exists {
|
if exists {
|
||||||
t.Errorf("Not expected file %s, found", "existing_dir")
|
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) {
|
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) {
|
func TestFs_DefaultBuilder(t *testing.T) {
|
||||||
ffs := DefaultFileSystem()
|
ffs := DefaultFileSystem()
|
||||||
if ffs.ReadFile == nil ||
|
if ffs.ReadFile == nil ||
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,23 @@ for output in $(ls -d ${dir}/tmp/*); do
|
||||||
done
|
done
|
||||||
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"
|
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"
|
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=$?
|
||||||
[ ${code} -eq 0 ] || fail "unexpected exit code returned by helmfile apply: want 0, got ${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"
|
info "Locking dependencies"
|
||||||
${helmfile} -f ${dir}/happypath.yaml deps
|
${helmfile} -f ${dir}/happypath.yaml deps
|
||||||
code=$?
|
code=$?
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue