package common import ( "bufio" "fmt" "os" "os/user" "strconv" "strings" "github.com/otiai10/copy" ) // CatFile cats the contents of a file (if it exists) func CatFile(filename string) { f, err := os.Open(filename) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { LogDebug(fmt.Sprintf("line: '%s'", scanner.Text())) } } // Copy copies a file/directory from src to dst. If the source is a file, it will also // convert line endings to unix style func Copy(src, dst string) error { fi, err := os.Stat(src) if err != nil { return err } if !fi.Mode().IsRegular() { return copy.Copy(src, dst) } // ensure file has the correct line endings result, err := CallExecCommand(ExecCommandInput{ Command: "dos2unix", Args: []string{"-l", "-n", src, dst}, }) if err != nil { return fmt.Errorf("Error running dos2unix: %s", err) } if result.ExitCode != 0 { return fmt.Errorf("Error running dos2unix: %s", result.StderrContents()) } // ensure file permissions are correct b, err := os.ReadFile(dst) if err != nil { return err } err = os.WriteFile(dst, b, fi.Mode()) if err != nil { return err } return nil } // DirectoryExists returns if a path exists and is a directory func DirectoryExists(filename string) bool { fi, err := os.Stat(filename) if err != nil { return false } return fi.IsDir() } // FileToSlice reads in all the lines from a file into a string slice func FileToSlice(filename string) (lines []string, err error) { f, err := os.Open(filename) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { text := strings.TrimSpace(scanner.Text()) if text == "" { continue } lines = append(lines, text) } err = scanner.Err() return } // FileExists returns if a path exists and is a file func FileExists(filename string) bool { fi, err := os.Stat(filename) if err != nil { return false } return fi.Mode().IsRegular() } // IsAbsPath returns 0 if input path is absolute func IsAbsPath(path string) bool { return strings.HasPrefix(path, "/") } // IsSymlink returns true if the path is a symlink // if lstat fails, it returns false func IsSymlink(path string) bool { info, err := os.Lstat(path) if err != nil { return false } return info.Mode()&os.ModeSymlink == os.ModeSymlink } // ListFilesWithPrefix lists files within a given path that have a given prefix func ListFilesWithPrefix(path string, prefix string) []string { names, err := os.ReadDir(path) if err != nil { return []string{} } files := []string{} for _, f := range names { if prefix != "" && !strings.HasPrefix(f.Name(), prefix) { continue } if f.Type().IsRegular() { files = append(files, fmt.Sprintf("%s/%s", path, f.Name())) } } return files } // ReadFirstLine gets the first line of a file that has contents and returns it // if there are no contents, an empty string is returned // will also return an empty string if the file does not exist func ReadFirstLine(filename string) (text string) { if !FileExists(filename) { return } f, err := os.Open(filename) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { if text = strings.TrimSpace(scanner.Text()); text == "" { continue } return } return } // SetPermissionInput is the input struct for SetPermissions type SetPermissionInput struct { Filename string GroupName string Mode os.FileMode Username string } // SetPermissions sets the proper owner and filemode for a given file func SetPermissions(input SetPermissionInput) error { if err := os.Chmod(input.Filename, input.Mode); err != nil { return err } if input.GroupName == "" { input.GroupName = GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku") } if input.Username == "" { input.Username = GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku") } group, err := user.LookupGroup(input.GroupName) if err != nil { return err } user, err := user.Lookup(input.Username) if err != nil { return err } uid, err := strconv.Atoi(user.Uid) if err != nil { return err } gid, err := strconv.Atoi(group.Gid) if err != nil { return err } return os.Chown(input.Filename, uid, gid) } // TouchDir creates an empty directory at the specified path func TouchDir(filename string) error { mode := os.FileMode(0700) return os.MkdirAll(filename, mode) } // TouchFile creates an empty file at the specified path func TouchFile(filename string) error { mode := os.FileMode(0600) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) if err != nil { return fmt.Errorf("Error opening file %v for creation: %v", filename, err) } defer file.Close() if err := file.Chmod(mode); err != nil { return fmt.Errorf("Error setting chown for new file %v: %v", filename, err) } if err := SetPermissions(SetPermissionInput{ Filename: filename, Mode: mode, }); err != nil { return fmt.Errorf("Error setting permissions for new file %v: %v", filename, err) } return nil } // WriteSliceToFileInput writes a slice of strings to a file type WriteSliceToFileInput struct { Filename string GroupName string Lines []string Mode os.FileMode Username string } // WriteSliceToFile writes a slice of strings to a file func WriteSliceToFile(input WriteSliceToFileInput) error { return WriteBytesToFile(WriteBytesToFileInput{ Bytes: []byte(strings.TrimSuffix(strings.Join(input.Lines, "\n"), "\n") + "\n"), Filename: input.Filename, GroupName: input.GroupName, Mode: input.Mode, Username: input.Username, }) } // WriteStringToFile writes a string to a file type WriteStringToFileInput struct { Content string Filename string GroupName string Mode os.FileMode Username string } // WriteStringToFile writes a string to a file func WriteStringToFile(input WriteStringToFileInput) error { return WriteBytesToFile(WriteBytesToFileInput{ Bytes: []byte(input.Content), Filename: input.Filename, GroupName: input.GroupName, Mode: input.Mode, Username: input.Username, }) } // WriteBytesToFileInput writes a byte array to a file type WriteBytesToFileInput struct { Bytes []byte Filename string GroupName string Mode os.FileMode Username string } // WriteBytesToFile writes a byte array to a file func WriteBytesToFile(input WriteBytesToFileInput) error { file, err := os.OpenFile(input.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, input.Mode) if err != nil { return err } defer file.Close() if _, err := file.Write(input.Bytes); err != nil { return err } if err := file.Chmod(input.Mode); err != nil { return err } permissionsInput := SetPermissionInput{ Filename: input.Filename, Mode: input.Mode, } if input.GroupName != "" { permissionsInput.GroupName = input.GroupName } if input.Username != "" { permissionsInput.Username = input.Username } return SetPermissions(permissionsInput) }