Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin feature #300

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,29 @@ Establish an SSH connection to a Shoot cluster's node.
```bash
gardenctl ssh my-node
```
### Plugins
To use [plugins](https://pkg.go.dev/plugin) , you have to make a `plugins` directory under `.garden`, and place the plugin files(with a .so extension) within that directory.

example plugin,
```
package main

import (
"fmt"
"github.com/spf13/cobra"
)

var NewCmd = &cobra.Command{ //must be `NewCmd` name
Use: "example",
Short: "A brief description of your command ",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Test called")
},
}

```

build plugin `.so` file
```
go build -buildmode=plugin
```
7 changes: 7 additions & 0 deletions internal/gen/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ func main() {
outDir = "./docs/help"
}

for _, c := range gardenctl.Commands() {
_, ok := c.Annotations["plugins"]
if ok {
gardenctl.RemoveCommand(c)
}
}

err := doc.GenMarkdownTree(gardenctl, outDir)
if err != nil {
log.Fatal(err)
Expand Down
10 changes: 10 additions & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
cmdsshpatch "github.com/gardener/gardenctl-v2/pkg/cmd/sshpatch"
cmdtarget "github.com/gardener/gardenctl-v2/pkg/cmd/target"
cmdversion "github.com/gardener/gardenctl-v2/pkg/cmd/version"
"github.com/gardener/gardenctl-v2/pkg/plugins"
)

const (
Expand Down Expand Up @@ -124,6 +125,15 @@ Find more information at: https://github.com/gardener/gardenctl-v2/blob/master/R
cmd.AddCommand(cmdrc.NewCmdRC(f, ioStreams))
cmd.AddCommand(kubeconfig.NewCmdKubeconfig(f, ioStreams))

for _, c := range plugins.Load() {
_, ok := c.Annotations["plugins"]
if !ok {
c.Annotations = map[string]string{"plugins": "yes"}
}

cmd.AddCommand(c)
}

return cmd
}

Expand Down
94 changes: 94 additions & 0 deletions pkg/plugins/plugins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors

SPDX-License-Identifier: Apache-2.0
*/

package plugins

import (
"fmt"
"os"
"path"
"path/filepath"
"plugin"

"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
)

const (
gardenHomeFolder = ".garden"
pluginsFolder = "plugins"
)

func LoadPlugins(pluginPath string) ([]*cobra.Command, error) {
plugins := []string{}

var b plugin.Symbol

var cmds []*cobra.Command

files, err := os.ReadDir(pluginPath)
if err != nil {
return nil, err
}

for _, file := range files {
filePath := filepath.Join(pluginPath, file.Name())
ext := path.Ext(filePath)

if ext == ".so" {
plugins = append(plugins, filePath)
}
}

if len(plugins) == 0 {
return nil, nil
}

for _, item := range plugins {
p, err := plugin.Open(item)
if err != nil {
return nil, fmt.Errorf("plugin open error or invalid file %w", err)
}

b, err = p.Lookup("NewCmd")
if err != nil {
return nil, fmt.Errorf("plugin cmd name not found %w", err)
}

_, ok := b.(**cobra.Command)
if !ok {
return nil, fmt.Errorf("type assertion error")
}

cmds = append(cmds, *b.(**cobra.Command))
}

return cmds, nil
}

func Load() []*cobra.Command {
var cmds []*cobra.Command

home, err := homedir.Dir()
if err != nil {
fmt.Fprintln(os.Stderr, "Plugins Error:", err)
}

pluginsPath := filepath.Join(home, gardenHomeFolder, pluginsFolder)

if _, err := os.Stat(pluginsPath); err != nil {
if os.IsNotExist(err) {
return nil
}
} else {
cmds, err = LoadPlugins(pluginsPath)
if err != nil {
fmt.Fprintln(os.Stderr, "Plugins Error:", err)
}
}

return cmds
}
38 changes: 38 additions & 0 deletions pkg/plugins/plugins_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Gardener contributors

SPDX-License-Identifier: Apache-2.0
*/

package plugins_test

import (
"os"
"path/filepath"
"testing"

"github.com/gardener/gardenctl-v2/pkg/plugins"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestPlugins(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Plugins Package Test Suite")
}

var _ = Describe("plugins", func() {
It("should return an error when plugins loading fake example.so", func() {
pluginsPath, err := os.MkdirTemp("", "plugins")
Expect(err).NotTo(HaveOccurred())

filename := filepath.Join(pluginsPath, "example.so")
_, err = os.Create(filename)
Expect(err).NotTo(HaveOccurred())

_, err = plugins.LoadPlugins(pluginsPath)
Expect(err).To(MatchError(MatchRegexp("^plugin open error or invalid file ")))
Expect(os.RemoveAll(pluginsPath)).To(Succeed())
})
})