diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go index 8c4500050..15f875091 100644 --- a/cli/mobycli/exec.go +++ b/cli/mobycli/exec.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" + "github.com/docker/compose-cli/cli/mobycli/resolvepath" apicontext "github.com/docker/compose-cli/context" "github.com/docker/compose-cli/context/store" "github.com/docker/compose-cli/metrics" @@ -60,7 +61,12 @@ func mustDelegateToMoby(ctxType string) bool { // Exec delegates to com.docker.cli if on moby context func Exec(root *cobra.Command) { - cmd := exec.Command(ComDockerCli, os.Args[1:]...) + execBinary, err := resolvepath.LookPath(ComDockerCli) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + cmd := exec.Command(execBinary, os.Args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -83,7 +89,7 @@ func Exec(root *cobra.Command) { } }() - err := cmd.Run() + err = cmd.Run() childExit <- true if err != nil { metrics.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus) diff --git a/cli/mobycli/resolvepath/lp_unix.go b/cli/mobycli/resolvepath/lp_unix.go new file mode 100644 index 000000000..44caa3648 --- /dev/null +++ b/cli/mobycli/resolvepath/lp_unix.go @@ -0,0 +1,28 @@ +// +build !windows + +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package resolvepath + +import ( + "os/exec" +) + +// LookPath simply delegate to os/exec.LookPath if not on windows +func LookPath(file string) (string, error) { + return exec.LookPath(file) +} diff --git a/cli/mobycli/resolvepath/lp_windows.go b/cli/mobycli/resolvepath/lp_windows.go new file mode 100644 index 000000000..65d9b5b89 --- /dev/null +++ b/cli/mobycli/resolvepath/lp_windows.go @@ -0,0 +1,126 @@ +/* +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. +* Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package resolvepath + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// ErrNotFound is the error resulting if a path search failed to find an executable file. +var ErrNotFound = errors.New("executable file not found in %PATH%") + +func chkStat(file string) error { + d, err := os.Stat(file) + if err != nil { + return err + } + if d.IsDir() { + return os.ErrPermission + } + return nil +} + +func hasExt(file string) bool { + i := strings.LastIndex(file, ".") + if i < 0 { + return false + } + return strings.LastIndexAny(file, `:\/`) < i +} + +func findExecutable(file string, exts []string) (string, error) { + if len(exts) == 0 { + return file, chkStat(file) + } + if hasExt(file) { + if chkStat(file) == nil { + return file, nil + } + } + for _, e := range exts { + if f := file + e; chkStat(f) == nil { + return f, nil + } + } + return "", os.ErrNotExist +} + +// LookPath searches for an executable named file in the +// directories named by the PATH environment variable. +// If file contains a slash, it is tried directly and the PATH is not consulted. +// LookPath also uses PATHEXT environment variable to match +// a suitable candidate. +// The result may be an absolute path or a path relative to the current directory. +func LookPath(file string) (string, error) { + var exts []string + x := os.Getenv(`PATHEXT`) + if x != "" { + for _, e := range strings.Split(strings.ToLower(x), `;`) { + if e == "" { + continue + } + if e[0] != '.' { + e = "." + e + } + exts = append(exts, e) + } + } else { + exts = []string{".com", ".exe", ".bat", ".cmd"} + } + + if strings.ContainsAny(file, `:\/`) { + if f, err := findExecutable(file, exts); err == nil { + return f, nil + } else { + return "", &exec.Error{file, err} + } + } + // See https://github.com/golang/go/issues/38736 + // DO NOT lookup current folder + //if f, err := findExecutable(filepath.Join(".", file), exts); err == nil { + // return f, nil + //} + path := os.Getenv("path") + for _, dir := range filepath.SplitList(path) { + // empty dir means dupicate semicolon in PATH, should not resolve files in current working dir... + if strings.TrimSpace(dir) == "" { + continue + } + if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil { + return f, nil + } + } + return "", &exec.Error{file, ErrNotFound} +} diff --git a/scripts/validate/fileheader b/scripts/validate/fileheader index b77b5a69f..8b503df36 100755 --- a/scripts/validate/fileheader +++ b/scripts/validate/fileheader @@ -24,4 +24,4 @@ fi BASEPATH="${1-}" -ltag -t "${BASEPATH}scripts/validate/template" -excludes "validate testdata" --check -v \ No newline at end of file +ltag -t "${BASEPATH}scripts/validate/template" -excludes "validate testdata resolvepath" --check -v \ No newline at end of file