refactor: move common io-related functions into new file

This commit is contained in:
Jose Diaz-Gonzalez
2020-10-10 19:15:55 -04:00
committed by Jose Diaz-Gonzalez
parent 696df45306
commit 33d3722060
5 changed files with 224 additions and 147 deletions

View File

@@ -1,14 +1,12 @@
package common
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path/filepath"
"reflect"
"regexp"
@@ -439,16 +437,6 @@ func ContainerIsRunning(containerID string) bool {
return strings.TrimSpace(string(b[:])) == "true"
}
// DirectoryExists returns if a path exists and is a directory
func DirectoryExists(filePath string) bool {
fi, err := os.Stat(filePath)
if err != nil {
return false
}
return fi.IsDir()
}
// DockerContainerCreate creates a new container and returns the container ID
func DockerContainerCreate(image string, containerCreateArgs []string) (string, error) {
cmd := []string{
@@ -513,36 +501,6 @@ func DokkuApps() (apps []string, err error) {
return
}
// FileToSlice reads in all the lines from a file into a string slice
func FileToSlice(filePath string) (lines []string, err error) {
f, err := os.Open(filePath)
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(filePath string) bool {
fi, err := os.Stat(filePath)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}
// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented
func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
err := VerifyAppName(appName)
@@ -565,11 +523,6 @@ func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
return
}
// IsAbsPath returns 0 if input path is absolute
func IsAbsPath(path string) bool {
return strings.HasPrefix(path, "/")
}
// IsDeployed returns true if given app has a running container
func IsDeployed(appName string) bool {
files, err := ioutil.ReadDir(AppRoot(appName))
@@ -612,29 +565,6 @@ func GetenvWithDefault(key string, defaultValue string) (val string) {
return
}
// 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
}
// ReportSingleApp is an internal function that displays a report for an app
func ReportSingleApp(reportType string, appName string, infoFlag string, infoFlags map[string]string, trimPrefix bool, uppercaseFirstCharacter bool) error {
flags := []string{}
@@ -805,36 +735,6 @@ func PlugnTriggerSetup(triggerName string, args ...string) *sh.Session {
return sh.Command("plugn", shellArgs...)
}
// SetPermissions sets the proper owner and filemode for a given file
func SetPermissions(path string, fileMode os.FileMode) error {
if err := os.Chmod(path, fileMode); err != nil {
return err
}
systemGroup := GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku")
systemUser := GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku")
group, err := user.LookupGroup(systemGroup)
if err != nil {
return err
}
user, err := user.Lookup(systemUser)
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(path, uid, gid)
}
func times(str string, n int) (out string) {
for i := 0; i < n; i++ {
out += str

View File

@@ -10,10 +10,10 @@ import (
)
var (
testAppName = "test-app-1"
testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "")
testEnvFile = strings.Join([]string{testAppDir, "/ENV"}, "")
testEnvLine = "export testKey=TESTING"
testAppName = "test-app-1"
testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "")
testEnvFile = strings.Join([]string{testAppDir, "/ENV"}, "")
testEnvLine = "export testKey=TESTING"
testAppName2 = "01-test-app-1"
testAppDir2 = strings.Join([]string{"/home/dokku/", testAppName2}, "")
testEnvFile2 = strings.Join([]string{testAppDir2, "/ENV"}, "")
@@ -95,32 +95,6 @@ func TestCommonDokkuApps(t *testing.T) {
teardownTestApp()
}
func TestCommonFileToSlice(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
lines, err := FileToSlice(testEnvFile)
Expect(err).NotTo(HaveOccurred())
Expect(lines).To(Equal([]string{testEnvLine}))
teardownTestApp()
}
func TestCommonFileExists(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
Expect(FileExists(testEnvFile)).To(BeTrue())
teardownTestApp()
}
func TestCommonReadFirstLine(t *testing.T) {
RegisterTestingT(t)
line := ReadFirstLine(testEnvFile)
Expect(line).To(Equal(""))
Expect(setupTestApp()).To(Succeed())
line = ReadFirstLine(testEnvFile)
Expect(line).To(Equal(testEnvLine))
teardownTestApp()
}
func TestCommonStripInlineComments(t *testing.T) {
RegisterTestingT(t)
text := StripInlineComments(strings.Join([]string{testEnvLine, "# testing comment"}, " "))

191
plugins/common/io.go Normal file
View File

@@ -0,0 +1,191 @@
package common
import (
"bufio"
"fmt"
"io"
"os"
"os/user"
"strconv"
"strings"
)
// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherise, attempt to create a hard link
// between the two files. If that fail, copy the file contents from src to dst.
// FROM: https://stackoverflow.com/a/21067803/1515875
func CopyFile(src, dst string) (err error) {
sfi, err := os.Stat(src)
if err != nil {
return
}
if !sfi.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories,
// symlinks, devices, etc.)
return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
}
dfi, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
return
}
} else {
if !(dfi.Mode().IsRegular()) {
return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
}
if os.SameFile(sfi, dfi) {
return
}
}
if err = os.Link(src, dst); err == nil {
return
}
err = copyFileContents(src, dst)
return
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
// FROM: https://stackoverflow.com/a/21067803/1515875
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
// 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, "/")
}
// 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
}
// SetPermissions sets the proper owner and filemode for a given file
func SetPermissions(path string, fileMode os.FileMode) error {
if err := os.Chmod(path, fileMode); err != nil {
return err
}
systemGroup := GetenvWithDefault("DOKKU_SYSTEM_GROUP", "dokku")
systemUser := GetenvWithDefault("DOKKU_SYSTEM_USER", "dokku")
group, err := user.LookupGroup(systemGroup)
if err != nil {
return err
}
user, err := user.Lookup(systemUser)
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(path, uid, gid)
}
func WriteSliceToFile(filename string, lines []string) error {
file, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC, 0600)
if err != nil {
return err
}
w := bufio.NewWriter(file)
for _, line := range lines {
fmt.Fprintln(w, line)
}
if err = w.Flush(); err != nil {
return err
}
file.Chmod(0600)
SetPermissions(filename, 0600)
return nil
}

28
plugins/common/io_test.go Normal file
View File

@@ -0,0 +1,28 @@
import "testing"
func TestCommonFileToSlice(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
lines, err := FileToSlice(testEnvFile)
Expect(err).NotTo(HaveOccurred())
Expect(lines).To(Equal([]string{testEnvLine}))
teardownTestApp()
}
func TestCommonFileExists(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
Expect(FileExists(testEnvFile)).To(BeTrue())
teardownTestApp()
}
func TestCommonReadFirstLine(t *testing.T) {
RegisterTestingT(t)
line := ReadFirstLine(testEnvFile)
Expect(line).To(Equal(""))
Expect(setupTestApp()).To(Succeed())
line = ReadFirstLine(testEnvFile)
Expect(line).To(Equal(testEnvLine))
teardownTestApp()
}

View File

@@ -82,21 +82,5 @@ func touchPhaseFile(appName string, phase string) error {
func writeDockerOptionsForPhase(appName string, phase string, options []string) error {
phaseFilePath := getPhaseFilePath(appName, phase)
file, err := os.OpenFile(phaseFilePath, os.O_RDWR|os.O_TRUNC, 0600)
if err != nil {
return err
}
w := bufio.NewWriter(file)
for _, option := range options {
fmt.Fprintln(w, option)
}
if err = w.Flush(); err != nil {
return fmt.Errorf("Unable to update docker options phase file %s.%s: %s", appName, phase, err.Error())
}
file.Chmod(0600)
common.SetPermissions(phaseFilePath, 0600)
return nil
return common.WriteSliceToFile(phaseFilePath, options)
}