Skip to content

Commit

Permalink
feat: support kubectl-style plugin (#175)
Browse files Browse the repository at this point in the history
* feat: support kubectl-style plugin

* refactor: read the 'PATH' as default configuration
  • Loading branch information
zyy17 authored Nov 1, 2023
1 parent 53242c3 commit 72efdf2
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 8 deletions.
27 changes: 19 additions & 8 deletions cmd/gtctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"sigs.k8s.io/kind/pkg/log"

"github.com/GreptimeTeam/gtctl/pkg/logger"
"github.com/GreptimeTeam/gtctl/pkg/plugins"
"github.com/GreptimeTeam/gtctl/pkg/version"
)

Expand All @@ -41,12 +42,12 @@ func NewRootCommand() *cobra.Command {
)

cmd := &cobra.Command{
Args: cobra.NoArgs,
Use: "gtctl",
Short: "gtctl is a command-line tool for managing GreptimeDB cluster.",
Long: fmt.Sprintf("%s\ngtctl is a command-line tool for managing GreptimeDB cluster.", GtctlTextBanner),
Version: version.Get().String(),
SilenceUsage: true,
Use: "gtctl",
Short: "gtctl is a command-line tool for managing GreptimeDB cluster.",
Long: fmt.Sprintf("%s\ngtctl is a command-line tool for managing GreptimeDB cluster.", GtctlTextBanner),
Version: version.Get().String(),
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
type verboser interface {
SetVerbosity(log.Level)
Expand All @@ -71,8 +72,18 @@ func NewRootCommand() *cobra.Command {
}

func main() {
pm, err := plugins.NewManager()
if err != nil {
panic(err)
}

if err := NewRootCommand().Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
if pm.ShouldRun(err) {
if err := pm.Run(os.Args[1:]); err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
}
}
113 changes: 113 additions & 0 deletions pkg/plugins/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2023 Greptime Team
//
// 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 plugins

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

const (
// DefaultPluginPrefix is the default prefix for the plugin binary name.
DefaultPluginPrefix = "gtctl-"

// PluginSearchPathsEnvKey is the environment variable key for the plugin search paths.
// If we set this variable, the plugin manager will search the paths provided by this variable.
// If we don't set this variable, the plugin manager will search the current working directory and the $PATH.
PluginSearchPathsEnvKey = "GTCTL_PLUGIN_PATHS"
)

// Manager manages and executes the plugins.
type Manager struct {
prefix string
searchPaths []string
}

func NewManager() (*Manager, error) {
m := &Manager{
prefix: DefaultPluginPrefix,
searchPaths: []string{},
}

pluginSearchPaths := os.Getenv(PluginSearchPathsEnvKey)
if len(pluginSearchPaths) > 0 {
m.searchPaths = append(m.searchPaths, strings.Split(pluginSearchPaths, ":")...)
} else {
// Search the current working directory.
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
m.searchPaths = append(m.searchPaths, pwd)

// Search the $PATH.
pathEnv := os.Getenv("PATH")
if len(pathEnv) > 0 {
m.searchPaths = append(m.searchPaths, strings.Split(pathEnv, ":")...)
}
}

return m, nil
}

// ShouldRun returns true whether you should run the plugin.
func (m *Manager) ShouldRun(err error) bool {
// The error is returned by cobra itself.
return strings.Contains(err.Error(), "unknown command")
}

// Run searches for the plugin and runs it.
func (m *Manager) Run(args []string) error {
if len(args) < 1 {
return nil // No arguments provided, normal help message will be shown.
}

pluginPath, err := m.searchPlugins(args[0])
if err != nil {
return err
}

pluginCmd := exec.Command(pluginPath, args[1:]...)
pluginCmd.Stdin = os.Stdin
pluginCmd.Stdout = os.Stdout
pluginCmd.Stderr = os.Stderr
if err := pluginCmd.Run(); err != nil {
return fmt.Errorf("failed to run plugin '%s': %v", pluginPath, err)
}

return nil
}

func (m *Manager) searchPlugins(name string) (string, error) {
if len(m.searchPaths) == 0 {
return "", fmt.Errorf("no plugin search paths provided")
}

// Construct plugin binary name gtctl-<subcmd>.
pluginName := m.prefix + name
for _, path := range m.searchPaths {
pluginPath := filepath.Join(path, pluginName)
if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
continue
}

return pluginPath, nil
}

return "", fmt.Errorf("error: unknown command %q for \"gtctl\"", name)
}
36 changes: 36 additions & 0 deletions pkg/plugins/manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 Greptime Team
//
// 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 plugins

import (
"fmt"
"testing"
)

func TestPluginManager(t *testing.T) {
t.Setenv(PluginSearchPathsEnvKey, "./testdata")
pm, err := NewManager()
if err != nil {
t.Fatal(err)
}

pluginName := "foo-plugin"
receiveErr := fmt.Errorf("unknown command: %s", pluginName)
if pm.ShouldRun(receiveErr) {
if err := pm.Run([]string{pluginName, "1", "2", "3"}); err != nil {
t.Fatal(err)
}
}
}
4 changes: 4 additions & 0 deletions pkg/plugins/testdata/gtctl-foo-plugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash

# Just prints the args it receives.
echo "Hello from gtctl-foo-plugin.sh, args: $*"

0 comments on commit 72efdf2

Please sign in to comment.