2020-02-24 19:14:46 +03:00
package commands
/ *
Most of the plugin handler code is borrowed from the kubectl codebase .
Wherever "courtesy: kubectl" is indicated , the copyright belongs to the
respective authors with the following notice :
Copyright 2014 The Kubernetes 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 .
* /
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
2021-06-16 14:44:15 +03:00
"github.com/hasura/graphql-engine/cli/v2"
2022-11-08 12:18:00 +03:00
"github.com/hasura/graphql-engine/cli/v2/internal/errors"
2020-02-24 19:14:46 +03:00
"github.com/spf13/cobra"
)
var validPluginFilenamePrefixes = [ ] string { "hasura" }
// NewPluginsCmd returns the plugins command
func NewPluginsCmd ( ec * cli . ExecutionContext ) * cobra . Command {
pluginsCmd := & cobra . Command {
2022-12-30 06:50:48 +03:00
Use : "plugins" ,
Aliases : [ ] string { "plugin" } ,
Short : "Manage plugins for the CLI" ,
2023-01-11 16:32:41 +03:00
Long : "The functionality of the CLI can be extended by using plugins. For a list of all available plugins, run ``hasura plugins list``, or visit this repository: https://github.com/hasura/cli-plugins-index.\n\nIf you're interested in contributing, please open a PR against this repo to add new plugin." ,
2020-02-24 19:14:46 +03:00
SilenceUsage : true ,
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2022-11-08 12:18:00 +03:00
op := genOpName ( cmd , "PersistentPreRunE" )
2020-03-26 06:08:54 +03:00
cmd . Root ( ) . PersistentPreRun ( cmd , args )
2021-11-10 12:21:06 +03:00
// setup plugins path
err := ec . SetupPlugins ( )
if err != nil {
2022-11-08 12:18:00 +03:00
return errors . E ( op , fmt . Errorf ( "setting up plugins path failed: %w" , err ) )
2021-11-10 12:21:06 +03:00
}
if err := ec . PluginsConfig . Repo . EnsureCloned ( ) ; err != nil {
2022-11-08 12:18:00 +03:00
return errors . E ( op , fmt . Errorf ( "pulling latest plugins list from internet failed: %w" , err ) )
2021-11-10 12:21:06 +03:00
}
return nil
2020-02-24 19:14:46 +03:00
} ,
}
pluginsCmd . AddCommand (
newPluginsListCmd ( ec ) ,
newPluginsInstallCmd ( ec ) ,
newPluginsUnInstallCmd ( ec ) ,
newPluginsUpgradeCmd ( ec ) ,
)
return pluginsCmd
}
// PluginHandler is capable of parsing command line arguments
// and performing executable filename lookups to search
// for valid plugin files, and execute found plugins.
// courtesy: kubectl
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cmd.go
type PluginHandler 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
}
// DefaultPluginHandler implements PluginHandler
type DefaultPluginHandler struct {
ValidPrefixes [ ] string
}
// NewDefaultPluginHandler instantiates the DefaultPluginHandler with a list of
// given filename prefixes used to identify valid plugin filenames.
func NewDefaultPluginHandler ( validPrefixes [ ] string ) * DefaultPluginHandler {
return & DefaultPluginHandler {
ValidPrefixes : validPrefixes ,
}
}
// Lookup implements PluginHandler
func ( h * DefaultPluginHandler ) Lookup ( filename string ) ( string , bool ) {
for _ , prefix := range h . ValidPrefixes {
filename := filepath . Join ( ec . PluginsConfig . Paths . BinPath ( ) , fmt . Sprintf ( "%s-%s" , prefix , filename ) )
path , err := exec . LookPath ( filename )
if err != nil || len ( path ) == 0 {
continue
}
return path , true
}
return "" , false
}
// Execute implements PluginHandler
func ( h * DefaultPluginHandler ) Execute ( executablePath string , cmdArgs , environment [ ] string ) error {
2022-11-08 12:18:00 +03:00
var op errors . Op = "commands.DefaultPluginHandler.Execute"
2020-02-24 19:14:46 +03:00
// Windows does not support exec syscall.
if runtime . GOOS == "windows" {
cmd := exec . Command ( executablePath , cmdArgs ... )
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
cmd . Stdin = os . Stdin
cmd . Env = environment
err := cmd . Run ( )
if err == nil {
os . Exit ( 0 )
}
2022-11-08 12:18:00 +03:00
return errors . E ( op , err )
2020-02-24 19:14:46 +03:00
}
// invoke cmd binary relaying the environment and args given
// append executablePath to cmdArgs, as execve will make first argument the "binary name".
2022-12-30 06:50:48 +03:00
if err := syscall . Exec ( executablePath , append ( [ ] string { executablePath } , cmdArgs ... ) , environment ) ; err != nil {
2022-11-08 12:18:00 +03:00
return errors . E ( op , err )
}
return nil
2020-02-24 19:14:46 +03:00
}
// 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 PluginHandler , cmdArgs [ ] string ) error {
2022-11-08 12:18:00 +03:00
var op errors . Op = "commands.HandlePluginCommand"
2020-02-24 19:14:46 +03:00
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 nil
}
// invoke cmd binary relaying the current environment and args given
if err := pluginHandler . Execute ( foundBinaryPath , cmdArgs [ len ( remainingArgs ) : ] , os . Environ ( ) ) ; err != nil {
2022-11-08 12:18:00 +03:00
return errors . E ( op , err )
2020-02-24 19:14:46 +03:00
}
return nil
}
func limitString ( s string , length int ) string {
if len ( s ) > length && length > 3 {
s = s [ : length - 3 ] + "..."
}
return s
}