diff --git a/plugins/app-json/go.mod b/plugins/app-json/go.mod index c1e742f4b..76e279d9c 100644 --- a/plugins/app-json/go.mod +++ b/plugins/app-json/go.mod @@ -5,7 +5,6 @@ go 1.21 require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/otiai10/copy v1.14.0 github.com/spf13/pflag v1.0.5 golang.org/x/sync v0.6.0 ) @@ -21,6 +20,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/melbahja/goph v1.4.0 // indirect + github.com/otiai10/copy v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect diff --git a/plugins/app-json/triggers.go b/plugins/app-json/triggers.go index 843c6e448..230e423c1 100644 --- a/plugins/app-json/triggers.go +++ b/plugins/app-json/triggers.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" - "github.com/otiai10/copy" ) // TriggerAppJSONProcessDeployParallelism returns the max number of processes to deploy in parallel @@ -120,12 +119,12 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error { return common.TouchFile(fmt.Sprintf("%s.missing", processSpecificAppJSON)) } - if err := copy.Copy(repoAppJSONPath, processSpecificAppJSON); err != nil { + if err := common.Copy(repoAppJSONPath, processSpecificAppJSON); err != nil { return fmt.Errorf("Unable to extract app.json: %v", err.Error()) } if appJSONPath != "app.json" { - if err := copy.Copy(repoAppJSONPath, repoDefaultAppJSONPath); err != nil { + if err := common.Copy(repoAppJSONPath, repoDefaultAppJSONPath); err != nil { return fmt.Errorf("Unable to move app.json into place: %v", err.Error()) } } diff --git a/plugins/builder/go.mod b/plugins/builder/go.mod index ec9cbe7d8..a8d812b13 100644 --- a/plugins/builder/go.mod +++ b/plugins/builder/go.mod @@ -4,7 +4,6 @@ go 1.21 require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 - github.com/otiai10/copy v1.14.0 github.com/spf13/pflag v1.0.5 ) @@ -19,6 +18,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/melbahja/goph v1.4.0 // indirect + github.com/otiai10/copy v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect diff --git a/plugins/builder/triggers.go b/plugins/builder/triggers.go index dd794a249..37f2821c5 100644 --- a/plugins/builder/triggers.go +++ b/plugins/builder/triggers.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/dokku/dokku/plugins/common" - "github.com/otiai10/copy" ) // TriggerBuilderDetect outputs a manually selected builder for the app @@ -79,7 +78,7 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error { return fmt.Errorf("Unable to clear out temporary working directory for rewrite: %v", err.Error()) } - if err := copy.Copy(newSourceWorkDir, tmpWorkDir); err != nil { + if err := common.Copy(newSourceWorkDir, tmpWorkDir); err != nil { return fmt.Errorf("Unable to move build-dir to temporary working directory: %v", err.Error()) } @@ -87,7 +86,7 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error { return fmt.Errorf("Unable to clear out sourcecode working directory for rewrite: %v", err.Error()) } - if err := copy.Copy(tmpWorkDir, sourceWorkDir); err != nil { + if err := common.Copy(tmpWorkDir, sourceWorkDir); err != nil { return fmt.Errorf("Unable to move build-dir to sourcecode working directory: %v", err.Error()) } diff --git a/plugins/common/data.go b/plugins/common/data.go index 8aaf86a2a..06d2ecda6 100644 --- a/plugins/common/data.go +++ b/plugins/common/data.go @@ -4,8 +4,6 @@ import ( "fmt" "os" "path/filepath" - - "github.com/otiai10/copy" ) // CreateAppDataDirectory creates a data directory for the given plugin/app combination with the correct permissions @@ -68,7 +66,7 @@ func CloneAppData(pluginName string, oldAppName string, newAppName string) error } newDataDir := GetAppDataDirectory(pluginName, newAppName) - if err := copy.Copy(oldDataDir, newDataDir); err != nil { + if err := Copy(oldDataDir, newDataDir); err != nil { return fmt.Errorf("Unable to clone app data to new location: %v", err.Error()) } diff --git a/plugins/common/docker.go b/plugins/common/docker.go index 011ade00c..ef82fe2db 100644 --- a/plugins/common/docker.go +++ b/plugins/common/docker.go @@ -121,7 +121,7 @@ func CopyFromImage(appName string, image string, source string, destination stri return fmt.Errorf("Unable to copy file %s from image", source) } - // workaround for CHECKS file when owner is root. seems to only happen when running inside docker + // workaround when owner is root. seems to only happen when running inside docker dos2unixCmd := NewShellCmd(strings.Join([]string{ "dos2unix", "-l", diff --git a/plugins/common/io.go b/plugins/common/io.go index f600b5032..7a147d356 100644 --- a/plugins/common/io.go +++ b/plugins/common/io.go @@ -3,11 +3,12 @@ package common import ( "bufio" "fmt" - "io" "os" "os/user" "strconv" "strings" + + "github.com/otiai10/copy" ) // CatFile cats the contents of a file (if it exists) @@ -24,66 +25,41 @@ func CatFile(filename string) { } } -// 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) +// 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 + return err } - 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 !fi.Mode().IsRegular() { + return copy.Copy(src, dst) + } + + // ensure file has the correct line endings + dos2unixCmd := NewShellCmd(strings.Join([]string{ + "dos2unix", + "-l", + "-n", + src, + dst, + }, " ")) + dos2unixCmd.ShowOutput = false + dos2unixCmd.Execute() + + // ensure file permissions are correct + b, err := os.ReadFile(dst) if err != nil { - return + return err } - defer in.Close() - out, err := os.Create(dst) + + err = os.WriteFile(dst, b, fi.Mode()) if err != nil { - return + return err } - defer func() { - cerr := out.Close() - if err == nil { - err = cerr - } - }() - if _, err = io.Copy(out, in); err != nil { - return - } - err = out.Sync() - return + + return nil } // DirectoryExists returns if a path exists and is a directory diff --git a/plugins/ps/go.mod b/plugins/ps/go.mod index b84c48c7c..ac3d75ef5 100644 --- a/plugins/ps/go.mod +++ b/plugins/ps/go.mod @@ -8,7 +8,6 @@ require ( github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/docker-options v0.0.0-00010101000000-000000000000 github.com/gofrs/flock v0.8.1 - github.com/otiai10/copy v1.14.0 github.com/ryanuber/columnize v2.1.2+incompatible github.com/spf13/pflag v1.0.5 ) @@ -24,6 +23,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/melbahja/goph v1.4.0 // indirect + github.com/otiai10/copy v1.14.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect golang.org/x/crypto v0.18.0 // indirect diff --git a/plugins/ps/triggers.go b/plugins/ps/triggers.go index a36810c0b..38d91ec1a 100644 --- a/plugins/ps/triggers.go +++ b/plugins/ps/triggers.go @@ -14,7 +14,6 @@ import ( "github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/config" dockeroptions "github.com/dokku/dokku/plugins/docker-options" - "github.com/otiai10/copy" ) // TriggerAppRestart restarts an app @@ -90,12 +89,12 @@ func TriggerCorePostExtract(appName string, sourceWorkDir string) error { return common.TouchFile(fmt.Sprintf("%s.missing", processSpecificProcfile)) } - if err := copy.Copy(repoProcfilePath, processSpecificProcfile); err != nil { + if err := common.Copy(repoProcfilePath, processSpecificProcfile); err != nil { return fmt.Errorf("Unable to extract Procfile: %v", err.Error()) } if procfilePath != "Procfile" { - if err := copy.Copy(repoProcfilePath, repoDefaultProcfilePath); err != nil { + if err := common.Copy(repoProcfilePath, repoDefaultProcfilePath); err != nil { return fmt.Errorf("Unable to move Procfile into place: %v", err.Error()) } } diff --git a/tests/unit/ps-general-1.bats b/tests/unit/ps-general-1.bats index 94de64bc6..2f568ab7f 100644 --- a/tests/unit/ps-general-1.bats +++ b/tests/unit/ps-general-1.bats @@ -87,6 +87,13 @@ EOF rm -rf "$TMP" } +@test "(ps) handle windows newlines in procfile" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP procfile_line_endings_to_windows + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(ps:restart-policy) default policy" { run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy" echo "output: $output" @@ -131,3 +138,11 @@ EOF echo "status: $status" assert_output "$test_restart_policy" } + +procfile_line_endings_to_windows() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "setting line endings on Procfile to \n via unix2dos" + sed -i -e 's/\r*$/\r/' "$APP_REPO_DIR/Procfile" +}