-
Notifications
You must be signed in to change notification settings - Fork 244
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add support for external process plugins. This adds support for external tools as plugins. If the provided command is a known command, then it's executed, otherwise, odo attempts to find a matching executable on the path with the prefix "odo-" and execute that instead. e.g. "odo tkn list" would attempt to execute "odo-tkn" and pass the args on. * Add support for Windows. * Skip on windows - plugins may not work. * Move the plugin handler test to a Gingko integration test.
- Loading branch information
Showing
3 changed files
with
191 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package plugins | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"runtime" | ||
"strings" | ||
"syscall" | ||
) | ||
|
||
// HandleCommand receives a PluginHandler and command-line arguments and attempts to find | ||
// a plugin executable on the PATH that satisfies the given arguments. | ||
func HandleCommand(handler PluginHandler, args []string) error { | ||
foundBinary, remaining := findBinary(handler, args) | ||
if foundBinary == "" { | ||
return nil | ||
} | ||
|
||
if err := handler.Execute(foundBinary, args[len(remaining):], os.Environ()); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
type execFunc func(string, []string, []string) (err error) | ||
|
||
// NewExecHandler creates and returns a new ExecHandler configured with | ||
// the prefix. | ||
func NewExecHandler(prefix string) *ExecHandler { | ||
return &ExecHandler{ | ||
Prefix: prefix, | ||
Exec: syscall.Exec, | ||
} | ||
} | ||
|
||
// PluginHandler provides functionality for finding and executing external | ||
// plugins. | ||
type PluginHandler interface { | ||
// Lookup should return the full path to an executable for the provided | ||
// command, or "" if no matching command can be found. | ||
Lookup(command string) string | ||
|
||
// Execute should execute the provided path, passing in the args and env. | ||
Execute(filename string, args, env []string) error | ||
} | ||
|
||
// ExecHandler implements PluginHandler using the "os/exec" package. | ||
type ExecHandler struct { | ||
Prefix string | ||
Exec execFunc | ||
} | ||
|
||
// Lookup implements PluginHandler, using | ||
// https://golang.org/pkg/os/exec/#LookPath to search for the command. | ||
func (h *ExecHandler) Lookup(command string) string { | ||
if runtime.GOOS == "windows" { | ||
command = command + ".exe" | ||
} | ||
path, err := exec.LookPath(fmt.Sprintf("%s-%s", h.Prefix, command)) | ||
if err == nil && len(path) != 0 { | ||
return path | ||
} | ||
return "" | ||
} | ||
|
||
// Execute implements PluginHandler.Execute | ||
func (h *ExecHandler) Execute(filename string, args, env []string) error { | ||
// Windows does not support exec syscall. | ||
if runtime.GOOS == "windows" { | ||
cmd := exec.Command(filename, args...) | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
cmd.Stdin = os.Stdin | ||
cmd.Env = env | ||
err := cmd.Run() | ||
if err == nil { | ||
os.Exit(0) | ||
} | ||
return err | ||
} | ||
return h.Exec(filename, append([]string{filename}, args...), env) | ||
} | ||
|
||
func findBinary(handler PluginHandler, args []string) (string, []string) { | ||
found := "" | ||
remaining := []string{} | ||
for idx := range args { | ||
if strings.HasPrefix(args[idx], "-") { | ||
break | ||
} | ||
remaining = append(remaining, strings.Replace(args[idx], "-", "_", -1)) | ||
} | ||
for len(remaining) > 0 { | ||
path := handler.Lookup(strings.Join(remaining, "-")) | ||
if path == "" { | ||
remaining = remaining[:len(remaining)-1] | ||
continue | ||
} | ||
|
||
found = path | ||
break | ||
} | ||
return found, remaining | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package integration | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"runtime" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/openshift/odo/pkg/odo/cli/plugins" | ||
) | ||
|
||
var sampleScript = []byte(` | ||
#!/bin/sh | ||
echo 'hello' | ||
`) | ||
|
||
var _ = Describe("odo plugin functionality", func() { | ||
var tempDir string | ||
var origPath = os.Getenv("PATH") | ||
var handler plugins.PluginHandler | ||
var _ = BeforeEach(func() { | ||
var err error | ||
tempDir, err = ioutil.TempDir(os.TempDir(), "odo") | ||
Expect(err).NotTo(HaveOccurred()) | ||
os.Setenv("PATH", fmt.Sprintf("%s:%s", origPath, tempDir)) | ||
var baseScriptName = "tst-script" | ||
scriptName := path.Join(tempDir, baseScriptName) | ||
err = ioutil.WriteFile(scriptName, sampleScript, 0755) | ||
Expect(err).NotTo(HaveOccurred()) | ||
handler = plugins.NewExecHandler("tst") | ||
}) | ||
|
||
var _ = AfterEach(func() { | ||
err := os.RemoveAll(tempDir) | ||
Expect(err).NotTo(HaveOccurred()) | ||
os.Setenv("PATH", origPath) | ||
}) | ||
|
||
Context("when an executable with the correct prefix exists on the path", func() { | ||
It("finds the plugin", func() { | ||
if runtime.GOOS == "windows" { | ||
Skip("doesn't find scripts on Windows platform") | ||
} | ||
found := handler.Lookup("script") | ||
Expect(found).Should(Equal(filepath.Join(tempDir, "tst-script"))) | ||
}) | ||
}) | ||
|
||
Context("when no executable with the correct prefix exists on the path", func() { | ||
It("does not find the plugin", func() { | ||
if runtime.GOOS == "windows" { | ||
Skip("doesn't find scripts on Windows platform") | ||
} | ||
found := handler.Lookup("unknown") | ||
Expect(found).Should(Equal("")) | ||
}) | ||
}) | ||
}) |