Skip to content

Commit

Permalink
Merge pull request #118 from wanghaoran1988/ocm_plugin
Browse files Browse the repository at this point in the history
Support git style ocm plugin
  • Loading branch information
Irit Goihman authored Aug 25, 2020
2 parents f90f5b6 + e575c27 commit 65dc8d1
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 2 deletions.
5 changes: 5 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,8 @@ _copr_ build when a new tag is pushed to the repository.
The _build dependencies_ section of the _copr_ configuration should include the
`jq` package is it is needed to extract the version number from the payload of
the event sent by the _GitHub_ webhook.

== Extend ocm with plugins

Just like how https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/[kubectl plugins] works, you can write your own ocm plugins and put the binary under the $PATH directory,
the plugin name should be named with prefix `ocm-`, like `ocm-foo`.
26 changes: 24 additions & 2 deletions cmd/ocm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ package main
import (
"flag"
"fmt"
"os"

_ "github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"os"
"os/exec"

"github.com/openshift-online/ocm-cli/cmd/ocm/account"
"github.com/openshift-online/ocm-cli/cmd/ocm/cluster"
Expand All @@ -44,6 +44,7 @@ import (
"github.com/openshift-online/ocm-cli/cmd/ocm/version"
"github.com/openshift-online/ocm-cli/cmd/ocm/whoami"
"github.com/openshift-online/ocm-cli/pkg/arguments"
"github.com/openshift-online/ocm-cli/pkg/plugin"
)

var root = &cobra.Command{
Expand Down Expand Up @@ -98,7 +99,28 @@ func main() {
fmt.Fprintf(os.Stderr, "Can't parse empty command line to satisfy 'glog': %v\n", err)
os.Exit(1)
}
args := os.Args
pluginHandler := plugin.NewDefaultPluginHandler([]string{"ocm"})
if len(args) > 1 {
cmdPathPieces := args[1:]

// only look for suitable extension executables if
// the specified command does not already exist
if _, _, err := root.Find(cmdPathPieces); err != nil {
found, err := plugin.HandlePluginCommand(pluginHandler, cmdPathPieces)
if err != nil {
err, ok := err.(*exec.ExitError)
if ok {
os.Exit(err.ExitCode())
}
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
if found {
os.Exit(0)
}
}
}
// Execute the root command:
root.SetArgs(os.Args[1:])
if err = root.Execute(); err != nil {
Expand Down
98 changes: 98 additions & 0 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package plugin

import (
"fmt"
"os"
"os/exec"
"strings"
)

// Handler is capable of parsing command line arguments
// and performing executable filename lookups to search
// for valid plugin files, and execute found plugins.
type Handler interface {
// exists at the given filename, or a boolean false.
// Lookup will iterate over a list of given prefixes
// in order to recognize valid plugin filenames.
// The first filepath to match a prefix is returned.
Lookup(filename string) (string, bool)
// Execute receives an executable's filepath, a slice
// of arguments, and a slice of environment variables
// to relay to the executable.
Execute(executablePath string, cmdArgs, environment []string) error
}

// DefaultHandler implements Handler
type DefaultHandler struct {
ValidPrefixes []string
}

// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
// given filename prefixes used to identify valid plugin filenames.
func NewDefaultPluginHandler(validPrefixes []string) Handler {
return &DefaultHandler{
ValidPrefixes: validPrefixes,
}
}

// Lookup implements Handler
func (h *DefaultHandler) Lookup(filename string) (string, bool) {
for _, prefix := range h.ValidPrefixes {
path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename))
if err != nil || len(path) == 0 {
continue
}
return path, true
}

return "", false
}

// Execute implements Handler
func (h *DefaultHandler) Execute(executablePath string, cmdArgs, environment []string) error {
// #nosec G204
cmd := exec.Command(executablePath, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = environment
return cmd.Run()
}

// HandlePluginCommand receives a pluginHandler and command-line arguments and attempts to find
// a plugin executable on the PATH that satisfies the given arguments.
func HandlePluginCommand(pluginHandler Handler, cmdArgs []string) (found bool, err error) {
remainingArgs := []string{} // all "non-flag" arguments

for idx := range cmdArgs {
if strings.HasPrefix(cmdArgs[idx], "-") {
break
}
remainingArgs = append(remainingArgs, strings.Replace(cmdArgs[idx], "-", "_", -1))
}

foundBinaryPath := ""

// attempt to find binary, starting at longest possible name with given cmdArgs
for len(remainingArgs) > 0 {
path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
if !found {
remainingArgs = remainingArgs[:len(remainingArgs)-1]
continue
}

foundBinaryPath = path
break
}

if len(foundBinaryPath) == 0 {
return false, nil
}

// invoke cmd binary relaying the current environment and args given
if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil {
return true, err
}

return true, nil
}

0 comments on commit 65dc8d1

Please sign in to comment.