Skip to content

Commit

Permalink
fix: plugins (#128)
Browse files Browse the repository at this point in the history
* fix: build bin on new chain if which not exist

* bump poa to patch

* `tidy`

* attempt

* WIP: this runs plugins more like local-ic nested bins

* fix for loop ref

* quick plugin docs

* example plugin cleanup

* remove build flags

* add docs/PLUGINS.md
  • Loading branch information
Reecepbcups authored May 4, 2024
1 parent 54f1062 commit 2c9b62e
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 103 deletions.
83 changes: 44 additions & 39 deletions cmd/spawn/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import (
"log"
"log/slog"
"os"
"os/exec"
"path"
"plugin"
"strings"
"time"

"github.com/lmittmann/tint"
"github.com/mattn/go-isatty"
"github.com/rollchains/spawn/plugins"
"github.com/rollchains/spawn/spawn"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -58,27 +58,48 @@ func GetLogger() *slog.Logger {
return slog.Default()
}

var PluginsCmd = &cobra.Command{
Use: "plugins",
Short: "Spawn Plugins",
Aliases: []string{"plugin", "plug", "pl"},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}

func applyPluginCmds() {
plugins := &cobra.Command{
Use: "plugins",
Short: "Manage plugins",
Aliases: []string{"plugin", "plug", "pl"},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}
for name, abspath := range loadPlugins() {
name := name
abspath := abspath

info, err := spawn.ParseCobraCLICmd(abspath)
if err != nil {
GetLogger().Warn("error parsing the CLI commands from the plugin", "name", name, "error", err)
continue
}

for _, plugin := range loadPlugins() {
plugins.AddCommand(plugin.Cmd())
execCmd := &cobra.Command{
Use: name,
Short: info.Description,
Run: func(cmd *cobra.Command, args []string) {
output, err := exec.Command(abspath, args...).CombinedOutput()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(string(output))
},
}
PluginsCmd.AddCommand(execCmd)
}

rootCmd.AddCommand(plugins)
rootCmd.AddCommand(PluginsCmd)
}

func loadPlugins() map[string]*plugins.SpawnPluginBase {
p := make(map[string]*plugins.SpawnPluginBase)
// returns name and path
func loadPlugins() map[string]string {
p := make(map[string]string)

logger := GetLogger()

Expand All @@ -105,33 +126,17 @@ func loadPlugins() map[string]*plugins.SpawnPluginBase {
return nil
}

if !strings.Contains(relPath, ".so") {
return nil
}

// /home/username/.spawn/plugins/myplugin
absPath := path.Join(pluginsDir, relPath)

// read the absolute path
plug, err := plugin.Open(absPath)
if err != nil {
logger.Error(fmt.Sprintf("Error opening plugin: %v", err))
return nil
}

base, err := plug.Lookup("Plugin")
if err != nil {
logger.Error(fmt.Sprintf("Error looking up symbol: %v", err))
return nil
}

pluginInstance, ok := base.(plugins.SpawnPlugin)
if !ok {
logger.Error(fmt.Sprintf("Plugin %s does not implement the SpawnPlugin interface. Skipping", absPath))
// ensure path exist
if _, err := os.Stat(absPath); os.IsNotExist(err) {
logger.Error(fmt.Sprintf("Plugin %s does not exist. Skipping", absPath))
return nil
}

p[relPath] = plugins.NewSpawnPluginBase(pluginInstance.Cmd())

name := path.Base(absPath)
p[name] = absPath
return nil
})
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions docs/PLUGINS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Plugins

Reference: [Plugins](../plugins/README.md)
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 8 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
Expand All @@ -22,6 +26,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
Expand All @@ -40,7 +46,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 changes: 13 additions & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Plugins

Spawn plugins allow you to build custom functionality on top of Spawn. To accomplish this, you build a cobra CLI binary using the `github.com/rollchains/spawn` import, then build a bianry off of it. Saving this to `$HOME/.spawn/plugins` will allow you to use the binary as a plugin with spawn. Opening the opertunity to closed source plugins and add on features across the stack.

## Getting Started

Reference the [example spawn plugin](./example/example-plugin.go) to get started.

## Running a Plugin

Note that to use flags, you must use a `--` before flags for the child command context. Flags before the `--` apply to the root of the plugin command.

- `spawn plugin <name> [arguments] -- [--flags]`
7 changes: 6 additions & 1 deletion plugins/example/build.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# sh plugins/example/build.sh

EXPORT_LOC=$HOME/.spawn/plugins
mkdir -p $EXPORT_LOC

go build -buildmode=plugin -o $EXPORT_LOC/example.so plugins/example/example-plugin.go
NAME="example-plugin"

go build -gcflags="all=-N -l" -mod=readonly -trimpath -o $EXPORT_LOC/$NAME plugins/example/$NAME.go
echo "Plugin built and exported to $EXPORT_LOC/$NAME"
81 changes: 44 additions & 37 deletions plugins/example/example-plugin.go
Original file line number Diff line number Diff line change
@@ -1,61 +1,68 @@
package main

import (
"log"
"fmt"
"os"
"path"
"strconv"

plugins "github.com/rollchains/spawn/plugins"
"github.com/spf13/cobra"
)

// Make the plugin public
var Plugin SpawnMainExamplePlugin

var _ plugins.SpawnPlugin = &SpawnMainExamplePlugin{}
var rootCmd = &cobra.Command{
Use: "example-plugin",
Short: "Info About the spawn example-plugin",
}

const (
cmdName = "example"
)
func main() {
rootCmd.AddCommand(AddCmd(), FlagTestCmd())

type SpawnMainExamplePlugin struct {
Impl plugins.SpawnPluginBase
// hides 'completion' command
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

// Cmd implements plugins.SpawnPlugin.
func (e *SpawnMainExamplePlugin) Cmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: cmdName,
Short: cmdName + " plugin command",
func FlagTestCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "flags-test",
Short: "Test using flags with a plugin",
Example: `spawn plugin example-plugin flags-test -- --value 7`,
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
myValue, _ := cmd.Flags().GetInt("value")
fmt.Printf("my-value: %v", myValue)
},
}

rootCmd.AddCommand(&cobra.Command{
Use: "touch-file [name]",
Short: "An example plugin sub command",
Args: cobra.ExactArgs(1),
cmd.Flags().Int("value", 0, "A value you can set")

return cmd
}

func AddCmd() *cobra.Command {
return &cobra.Command{
Use: "add",
Short: "A command you can use to perform addition of 2 numbers!",
Example: `spawn plugin example-plugin add 1 2`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
cwd, err := os.Getwd()
num1, err := strconv.Atoi(args[0])
if err != nil {
log.Fatal(err)
fmt.Println("Error parsing the first number")
os.Exit(1)
}

fileName := args[0]

filePath := path.Join(cwd, fileName)
file, err := os.Create(filePath)
num2, err := strconv.Atoi(args[1])
if err != nil {
log.Fatal(err)
fmt.Println("Error parsing the second number")
os.Exit(1)
}
defer file.Close()

cmd.Printf("Created file: %s\n", filePath)
fmt.Println("add called")
fmt.Println("Performing the addition of the following numbers: ")
fmt.Printf("Num1: %v\n", num1)
fmt.Printf("Num2: %v\n", num2)
fmt.Printf("Addition of those 2 numbers is: %v\n", num1+num2)
},
})

return rootCmd
}
}
25 changes: 0 additions & 25 deletions plugins/plugin.go

This file was deleted.

59 changes: 59 additions & 0 deletions spawn/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package spawn

import (
"os/exec"
"strings"
)

type CLIInfo struct {
Description string
Cmds map[string]string
}

// Parses the CLI commands from a cobra binary to showcase values within the `plugins` subcommand.
func ParseCobraCLICmd(binAbsPath string) (CLIInfo, error) {
output, err := exec.Command(binAbsPath).Output()
if err != nil {
return CLIInfo{}, err
}

sl := strings.Split(string(output), "\n")

ci := CLIInfo{
Cmds: make(map[string]string),
}
isAvailableCmds := false
for idx, line := range sl {
if idx == 0 {
ci.Description = line
if ci.Description == "" {
ci.Description = "No description"
}
}

// if line stars with `Available Commands:`, next lines are commands
if strings.Contains(line, "Available Commands:") {
isAvailableCmds = true
continue
}

if isAvailableCmds {
content := []string{}
for _, item := range strings.Split(line, " ") {
if item != "" {
content = append(content, item)
}
}

if len(content) >= 2 {
ci.Cmds[content[0]] = strings.Join(content[1:], " ")
}

if line == "Flags:" {
isAvailableCmds = false
continue
}
}
}
return ci, nil
}

0 comments on commit 2c9b62e

Please sign in to comment.