diff --git a/io/fileutils.go b/io/fileutils.go index 59c467e..53b4776 100644 --- a/io/fileutils.go +++ b/io/fileutils.go @@ -17,6 +17,8 @@ import ( "strconv" "strings" "time" + + "github.com/jfrog/gofrog/log" ) const ( @@ -325,8 +327,7 @@ func MoveFile(sourcePath, destPath string) (err error) { return } - var outputFile *os.File - outputFile, err = os.Create(destPath) + outputFile, err := createFileForWriting(destPath) if err != nil { return } @@ -353,6 +354,25 @@ func MoveFile(sourcePath, destPath string) (err error) { return } +// Create file for writing. If file already exists, it will be truncated. +// If the file exists and is read-only, the function will try to change the file permissions to read-write. +// The caller should update the permissions and close the file when done. +func createFileForWriting(destPath string) (*os.File, error) { + // Try to create the destination file + outputFile, err := os.Create(destPath) + if err == nil { + return outputFile, nil + } + + log.Debug(fmt.Sprintf("Couldn't to open the destination file: '%s' due to %s. Attempting to set the file permissions to read-write.", destPath, err.Error())) + if chmodErr := os.Chmod(destPath, 0600); chmodErr != nil { + return nil, errors.Join(err, chmodErr) + } + + // Try to open the destination file again + return os.Create(destPath) +} + // Return the list of files and directories in the specified path func ListFiles(path string, includeDirs bool) ([]string, error) { sep := GetFileSeparator() diff --git a/io/fileutils_test.go b/io/fileutils_test.go index 3e356b7..235288b 100644 --- a/io/fileutils_test.go +++ b/io/fileutils_test.go @@ -93,3 +93,70 @@ func TestCreateTempDir(t *testing.T) { assert.NoError(t, os.RemoveAll(tempDir)) }() } + +func TestMoveFile_New(t *testing.T) { + // Init test + sourcePath, destPath := initMoveTest(t) + + // Move file + assert.NoError(t, MoveFile(sourcePath, destPath)) + + // Assert expected file paths + assert.FileExists(t, destPath) + assert.NoFileExists(t, sourcePath) +} + +func TestMoveFile_Override(t *testing.T) { + // Init test + sourcePath, destPath := initMoveTest(t) + err := os.WriteFile(destPath, []byte("dst"), 0600) + assert.NoError(t, err) + + // Move file + assert.NoError(t, MoveFile(sourcePath, destPath)) + + // Assert file overidden + assert.FileExists(t, destPath) + destFileContent, err := os.ReadFile(destPath) + assert.NoError(t, err) + assert.Equal(t, "src", string(destFileContent)) + + // Assert source file removed + assert.NoFileExists(t, sourcePath) +} + +func TestMoveFile_NoPerm(t *testing.T) { + // Init test + sourcePath, destPath := initMoveTest(t) + err := os.WriteFile(destPath, []byte("dst"), 0600) + assert.NoError(t, err) + + // Remove all permissions from destination file + assert.NoError(t, os.Chmod(destPath, 0000)) + _, err = os.Create(destPath) + assert.Error(t, err) + + // Move file + assert.NoError(t, MoveFile(sourcePath, destPath)) + + // Assert file overidden + assert.FileExists(t, destPath) + destFileContent, err := os.ReadFile(destPath) + assert.NoError(t, err) + assert.Equal(t, "src", string(destFileContent)) + + // Assert source file removed + assert.NoFileExists(t, sourcePath) +} + +func initMoveTest(t *testing.T) (sourcePath, destPath string) { + // Create source and destination paths + tmpDir := t.TempDir() + sourcePath = filepath.Join(tmpDir, "src") + destPath = filepath.Join(tmpDir, "dst") + + // Write content to source file + err := os.WriteFile(sourcePath, []byte("src"), 0600) + assert.NoError(t, err) + return +}