Fix Docker os.Rename invalid cross-device link issue (#386)

* Adds new file.Move func to address a bug with Golang/Docker volumes when using os.Rename

Also uses TempDir for tests instead of live directories and increases test coverage for file.Write

* Goimport the imports

* Make usage of file package name consistent so it no longer clashes with vars

* Remove outputFile if io.Copy fails
This commit is contained in:
Adrian Gallagher
2019-11-28 11:56:05 +11:00
committed by GitHub
parent 63191ce3ec
commit e20d204b19
15 changed files with 205 additions and 50 deletions

View File

@@ -279,7 +279,7 @@ func ExtractPort(host string) int {
func OutputCSV(filePath string, data [][]string) error {
_, err := ioutil.ReadFile(filePath)
if err != nil {
errTwo := WriteFile(filePath, nil)
errTwo := ioutil.WriteFile(filePath, nil, 0770)
if errTwo != nil {
return errTwo
}
@@ -295,11 +295,6 @@ func OutputCSV(filePath string, data [][]string) error {
return writer.WriteAll(data)
}
// WriteFile writes selected data to a file and returns an error
func WriteFile(file string, data []byte) error {
return ioutil.WriteFile(file, data, 0644)
}
// GetURIPath returns the path of a URL given a URI
func GetURIPath(uri string) string {
urip, err := url.Parse(uri)
@@ -353,8 +348,8 @@ func CreateDir(dir string) error {
return os.MkdirAll(dir, 0770)
}
// ChangePerm lists all the directories and files in an array
func ChangePerm(directory string) error {
// ChangePermission lists all the directories and files in an array
func ChangePermission(directory string) error {
return filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err

View File

@@ -504,11 +504,11 @@ func TestCreateDir(t *testing.T) {
}
}
func TestChangePerm(t *testing.T) {
testDir := filepath.Join(GetDefaultDataDir(runtime.GOOS), "TestFileASDFGHJ")
func TestChangePermission(t *testing.T) {
testDir := filepath.Join(os.TempDir(), "TestFileASDFGHJ")
switch runtime.GOOS {
case "windows":
err := ChangePerm("*")
err := ChangePermission("*")
if err == nil {
t.Fatal("expected an error on non-existent path")
}
@@ -516,7 +516,7 @@ func TestChangePerm(t *testing.T) {
if err != nil {
t.Fatalf("Mkdir failed. Err: %v", err)
}
err = ChangePerm(GetDefaultDataDir(runtime.GOOS))
err = ChangePermission(testDir)
if err != nil {
t.Fatalf("ChangePerm was unsuccessful. Err: %v", err)
}
@@ -529,7 +529,7 @@ func TestChangePerm(t *testing.T) {
t.Fatalf("os.Remove failed. Err: %v", err)
}
default:
err := ChangePerm("")
err := ChangePermission("")
if err == nil {
t.Fatal("expected an error on non-existent path")
}
@@ -537,7 +537,7 @@ func TestChangePerm(t *testing.T) {
if err != nil {
t.Fatalf("Mkdir failed. Err: %v", err)
}
err = ChangePerm(GetDefaultDataDir(runtime.GOOS))
err = ChangePermission(testDir)
if err != nil {
t.Fatalf("ChangePerm was unsuccessful. Err: %v", err)
}

47
common/file/file.go Normal file
View File

@@ -0,0 +1,47 @@
package file
import (
"fmt"
"io"
"io/ioutil"
"os"
)
// Write writes selected data 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 Write(file string, data []byte) error {
return ioutil.WriteFile(file, data, 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)
func Move(sourcePath, destPath string) error {
inputFile, err := os.Open(sourcePath)
if err != nil {
return err
}
outputFile, err := os.Create(destPath)
if err != nil {
inputFile.Close()
return err
}
_, err = io.Copy(outputFile, inputFile)
inputFile.Close()
outputFile.Close()
if err != nil {
if errRem := os.Remove(destPath); errRem != nil {
return fmt.Errorf(
"unable to os.Remove error: %s after io.Copy error: %s",
errRem,
err,
)
}
return err
}
return os.Remove(sourcePath)
}

106
common/file/file_test.go Normal file
View File

@@ -0,0 +1,106 @@
package file
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestWrite(t *testing.T) {
tester := func(in string) error {
err := Write(in, []byte("GoCryptoTrader"))
if err != nil {
return err
}
return os.Remove(in)
}
type testTable struct {
InFile string
ErrExpected bool
Cleanup bool
}
var tests []testTable
testFile := filepath.Join(os.TempDir(), "gcttest.txt")
switch runtime.GOOS {
case "windows":
tests = []testTable{
{InFile: "*", ErrExpected: true},
{InFile: testFile, ErrExpected: false},
}
default:
tests = []testTable{
{InFile: "", ErrExpected: true},
{InFile: testFile, ErrExpected: false},
}
}
for x := range tests {
err := tester(tests[x].InFile)
if err != nil && !tests[x].ErrExpected {
t.Errorf("Test %d failed, unexpected err %s\n", x, err)
}
}
}
func TestMove(t *testing.T) {
tester := func(in, out string, write bool) error {
if write {
if err := ioutil.WriteFile(in, []byte("GoCryptoTrader"), 0770); err != nil {
return err
}
}
if err := Move(in, out); err != nil {
return err
}
contents, err := ioutil.ReadFile(out)
if err != nil {
return err
}
if !strings.Contains(string(contents), "GoCryptoTrader") {
return fmt.Errorf("unable to find previously written data")
}
return os.Remove(out)
}
type testTable struct {
InFile string
OutFile string
Write bool
ErrExpected bool
}
var tests []testTable
switch runtime.GOOS {
case "windows":
tests = []testTable{
{InFile: "*", OutFile: "gct.txt", Write: true, ErrExpected: true},
{InFile: "*", OutFile: "gct.txt", Write: false, ErrExpected: true},
{InFile: "in.txt", OutFile: "*", Write: true, ErrExpected: true},
{InFile: "in.txt", OutFile: "gct.txt", Write: true, ErrExpected: false},
}
default:
tests = []testTable{
{InFile: "", OutFile: "gct.txt", Write: true, ErrExpected: true},
{InFile: "", OutFile: "gct.txt", Write: false, ErrExpected: true},
{InFile: "in.txt", OutFile: "", Write: true, ErrExpected: true},
{InFile: "in.txt", OutFile: "gct.txt", Write: true, ErrExpected: false},
}
}
for x := range tests {
err := tester(tests[x].InFile, tests[x].OutFile, tests[x].Write)
if err != nil && !tests[x].ErrExpected {
t.Errorf("Test %d failed, unexpected err %s\n", x, err)
}
}
}