github.com/jfrog/jfrog-cli-core/v2@v2.51.0/utils/ioutils/ioutils.go (about) 1 package ioutils 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" 8 "github.com/jfrog/jfrog-client-go/utils/errorutils" 9 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 10 "github.com/jfrog/jfrog-client-go/utils/log" 11 "golang.org/x/term" 12 "io" 13 "os" 14 "path/filepath" 15 "strings" 16 "syscall" 17 ) 18 19 // disallowUsingSavedPassword - Prevent changing username or url without changing the password. 20 // False if the user changed the username or the url. 21 func ReadCredentialsFromConsole(details, savedDetails coreutils.Credentials, disallowUsingSavedPassword bool) error { 22 if details.GetUser() == "" { 23 tempUser := "" 24 ScanFromConsole("JFrog username", &tempUser, savedDetails.GetUser()) 25 details.SetUser(tempUser) 26 disallowUsingSavedPassword = true 27 } 28 if details.GetPassword() == "" { 29 password, err := ScanJFrogPasswordFromConsole() 30 if err != nil { 31 return err 32 } 33 details.SetPassword(password) 34 if details.GetPassword() == "" && !disallowUsingSavedPassword { 35 details.SetPassword(savedDetails.GetPassword()) 36 } 37 } 38 39 return nil 40 } 41 42 func ScanJFrogPasswordFromConsole() (string, error) { 43 return ScanPasswordFromConsole("JFrog password or API key: ") 44 } 45 46 func ScanPasswordFromConsole(message string) (string, error) { 47 fmt.Print(coreutils.PrintLink(message)) 48 bytePassword, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert 49 if err != nil { 50 return "", errorutils.CheckError(err) 51 } 52 // New-line required after the password input: 53 log.Output() 54 return string(bytePassword), nil 55 } 56 57 func ScanFromConsole(caption string, scanInto *string, defaultValue string) { 58 caption = coreutils.PrintLink(caption) 59 if defaultValue != "" { 60 caption = caption + " [" + defaultValue + "]" 61 } 62 fmt.Print(caption + ": ") 63 scanner := bufio.NewScanner(os.Stdin) 64 scanner.Scan() 65 *scanInto = scanner.Text() 66 if *scanInto == "" { 67 *scanInto = defaultValue 68 } 69 *scanInto = strings.TrimSpace(*scanInto) 70 } 71 72 func DoubleWinPathSeparator(filePath string) string { 73 return strings.ReplaceAll(filePath, "\\", "\\\\") 74 } 75 76 func UnixToWinPathSeparator(filePath string) string { 77 return strings.ReplaceAll(filePath, "/", "\\\\") 78 } 79 80 func WinToUnixPathSeparator(filePath string) string { 81 return strings.ReplaceAll(filePath, "\\", "/") 82 } 83 84 // BackupFile creates a backup of the file in filePath. The backup will be found at backupPath. 85 // The returned restore function can be called to restore the file's state - the file in filePath will be replaced by the backup in backupPath. 86 // If there is no file at filePath, a backup file won't be created, and the restore function will delete the file at filePath. 87 func BackupFile(filePath, backupFileName string) (restore func() error, err error) { 88 fileInfo, err := os.Stat(filePath) 89 if errorutils.CheckError(err) != nil { 90 if os.IsNotExist(err) { 91 restore = createRestoreFileFunc(filePath, backupFileName) 92 err = nil 93 } 94 return 95 } 96 97 if err = cloneFile(filePath, backupFileName, fileInfo.Mode()); err != nil { 98 return 99 } 100 log.Debug("The file", filePath, "was backed up successfully to", backupFileName) 101 restore = createRestoreFileFunc(filePath, backupFileName) 102 return 103 } 104 105 func cloneFile(origFile, newName string, fileMode os.FileMode) (err error) { 106 from, err := os.Open(origFile) 107 if errorutils.CheckError(err) != nil { 108 return 109 } 110 defer func() { 111 err = errors.Join(err, from.Close()) 112 }() 113 114 to, err := os.OpenFile(filepath.Join(filepath.Dir(origFile), newName), os.O_RDWR|os.O_CREATE, fileMode) 115 if errorutils.CheckError(err) != nil { 116 return 117 } 118 defer func() { 119 err = errors.Join(err, to.Close()) 120 }() 121 122 if _, err = io.Copy(to, from); err != nil { 123 err = errorutils.CheckError(err) 124 } 125 return 126 } 127 128 // createRestoreFileFunc creates a function for restoring a file from its backup. 129 // The returned function replaces the file in filePath with the backup in backupPath. 130 // If there is no file at backupPath (which means there was no file at filePath when BackupFile() was called), then the function deletes the file at filePath. 131 func createRestoreFileFunc(filePath, backupFileName string) func() error { 132 return func() error { 133 backupPath := filepath.Join(filepath.Dir(filePath), backupFileName) 134 if _, err := os.Stat(backupPath); err != nil { 135 if os.IsNotExist(err) { 136 // We verify the existence of the file in the specified filePath before initiating its deletion in order to prevent errors that might occur when attempting to remove a non-existent file 137 var fileExists bool 138 fileExists, err = fileutils.IsFileExists(filePath, false) 139 if err != nil { 140 err = fmt.Errorf("failed to check for the existence of '%s' before deleting the file: %s", filePath, err.Error()) 141 return errorutils.CheckError(err) 142 } 143 if fileExists { 144 err = os.Remove(filePath) 145 } 146 return errorutils.CheckError(err) 147 } 148 return errorutils.CheckErrorf(createRestoreErrorPrefix(filePath, backupPath) + err.Error()) 149 } 150 151 if err := fileutils.MoveFile(backupPath, filePath); err != nil { 152 return errorutils.CheckError(err) 153 } 154 log.Debug("Restored the file", filePath, "successfully") 155 return nil 156 } 157 } 158 159 func createRestoreErrorPrefix(filePath, backupPath string) string { 160 return fmt.Sprintf("An error occurred while restoring the file: %s\n"+ 161 "To restore the file manually: delete %s and rename the backup file at %s (if exists) to '%s'.\n"+ 162 "Failure cause: ", 163 filePath, filePath, backupPath, filePath) 164 }