Config: refactor config file loaders (#577)

* Config: fix don't create empty dir when resolving path

* Config: refactor config file loaders

* add a layer of abstraction so that config can be loaded from non-files
* use io.Reader / io.Writer abstraction to separate data operations from
file operations
* remove dryrun option from SaveConfig - now it always saves

* rename read and save methods to mention file operations

* log error when encryption prompt fails

* as the user didn't make a choice, we'd prompt again next time the file
is loaded
* add file.Writer tests
* skip permissions test for windows

* defer creating the writer on save to the last moment

* this avoids truncating file when there is error with password prompt
* add a test

* tests with StdIn cannot run in parallel
This commit is contained in:
Rauno Ots
2020-11-04 01:46:13 +01:00
committed by GitHub
parent 80bc8c7e9e
commit ee55ae5d0f
9 changed files with 397 additions and 128 deletions

View File

@@ -24,6 +24,19 @@ func Write(file string, data []byte) error {
return ioutil.WriteFile(file, data, 0770)
}
// Writer creates a writer to a file or returns an error if it fails. This
// func also ensures that all files are set to this permission (only rw access
// for the running user and the group the user is a member of)
func Writer(file string) (*os.File, error) {
basePath := filepath.Dir(file)
if !Exists(basePath) {
if err := os.MkdirAll(basePath, 0770); err != nil {
return nil, err
}
}
return os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0770)
}
// Move moves a file from a source path to a destination path
// This must be used across the codebase for compatibility with Docker volumes
// and Golang (fixes Invalid cross-device link when using os.Rename)

View File

@@ -194,3 +194,92 @@ func TestWriteAsCSV(t *testing.T) {
}
}
}
func TestWriter(t *testing.T) {
type args struct {
file string
}
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
testData := `data`
tests := []struct {
name string
args args
want *os.File
wantErr bool
}{
{
name: "invalid",
args: args{"//invalid-nofile\\"},
wantErr: true,
},
{
name: "empty",
args: args{""},
wantErr: true,
},
{
name: "relative newfile",
args: args{"newfile"},
},
{
name: "deep file",
args: args{filepath.Join(tmp, "new", "file", "multiple", "sub", "paths")},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got, err := Writer(tt.args.file)
if err != nil {
if (err != nil) != tt.wantErr {
t.Errorf("Writer() error = %v, wantErr %v", err, tt.wantErr)
}
return
}
defer os.Remove(got.Name())
fileInfo, err := os.Stat(got.Name())
if err != nil {
t.Fatal(err)
}
if !fileInfo.Mode().IsRegular() {
t.Fatalf("Writer() error = expected to get a file %s", got.Name())
}
_, err = got.WriteString(testData)
if err != nil {
t.Fatal(err)
}
err = got.Close()
if err != nil {
t.Fatal(err)
}
if data, err := ioutil.ReadFile(got.Name()); err != nil || string(data) != testData {
t.Errorf("Could not write the file, or contents were wrong: expected = %s, got =%s", testData, string(data))
}
})
}
}
func TestWriterNoPermissionFails(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Skip file permissions")
}
temp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(temp)
err = os.Chmod(temp, 0555)
if err != nil {
t.Fatal(err)
}
_, err = Writer(filepath.Join(temp, "path", "to", "somefile"))
if err == nil {
t.Error("Expected to fail when no permissions, but writer succeeded")
}
}