mirror of
https://github.com/dokku/dokku.git
synced 2025-12-16 20:17:44 +01:00
When running in docker, the 'config' directory can be a symlink, which causes issues during plugin installation. Refs #7308
317 lines
6.8 KiB
Go
317 lines
6.8 KiB
Go
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)
|
|
}
|