Categorygithub.com/steven-zou/go-plugin
repositorypackage
0.0.0-20210313090742-921d8b4a174c
Repository: https://github.com/steven-zou/go-plugin.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author

# README

go-plugin

CircleCI Codacy Badge codebeat badge

go-plugin is intent on providing a plugin framework based on the plugin feature originated from golang 1.8. go-plugin works in the plugin consumer side to handle related stages of running a plugin, including discover, build, load, upgrade, manage, call and unload.

To learn more, check our roadmap.

If you want to do contributions to this project, WELCOME!

Maintainers

Plugin specification

The plugin should follow the specifications below then it can be recognized by go-plugin framework.

Entry method

The plugin should have a single unified and exported entry method with name Execute. The full signature is listed below:

func Execute(ctx context.PluginContext) error {
    return nil
}

The entry method has a plugin context argument which extends the golang context interface and provides extra value operation methods. The detailed declarations of this context interface are shown below:

//ValueContext defines behaviors of handling values with string keys
type ValueContext interface {
    //Get value by the key
    GetValue(key string) interface{}

    //Set value to context
    SetValue(key string, value interface{})
}

//PluginContext help to provide related information/parameters to the
//plugin execution entry method.
//PluginContext inherits all from the context.Context
type PluginContext interface {
    context.Context
    ValueContext
}

go-plugin provides a base plugin context for using.

import "github.com/szlabs/go-plugin/pkg/context"

context.BasePluginContext

Metadata json file

go-plugin use a json file plugin.json to define and describe the plugin metadata. An example:

{
    "name": "go-plugin",
    "version": "0.1.0",
    "description": "A go plugin management tool",
    "maintainers": ["[email protected]"],
    "home": "https://github.com/szlabs/go-plugin.git",
    "source": {
        "mode": "local_so",
        "path": "sample.so"
    },
    "http_services": {
        "driver": "beego",
        "routes": [
            { "route": "/api/plugin/sample/:id", "method": "GET", "label": "plugin.get" },
            { "route": "/api/plugin/samples", "method": "POST", "label": "plugin.post" }
        ]
    }
}

The spec details:

FieldDescriptionRequiredSupported
nameName of the pluginYY
versionPlugin version with semver 2YY
descriptionOne sentence to describe the pluginNY
maintainersA list of mails of maintainersNY
homeThe home site or repository siteNY
source.modeThe mode of the plugin. local_so for local so file; remote_git for remote source repositoryYY
source.pathThe so file path or the remote git repositryYY
http_services.driverThe name of the http service driver which is used to enable the http servicesYN
http_services.routesA route list to map the service endpoints to the plugin method with labelsYN
http_services.routes[i].routeThe service endpoint definitionYN
http_services.routes[i].methodThe http method to apply on the routeYN
http_services.routes[i].labelAdd the label to the plugin context when calling the plugin entry methodYN

Plugin Management

The following sample code shows how to manage the plugins with go-plugin.

//Hello ...
type Hello struct {
    Name string
}

func main() {
    wk, err := os.Getwd()
    if err != nil {
        PrintError(err)
    }
    pluginBaseDir := filepath.Join(wk, "plugins")
    log.Printf("[INFO]: Plugin base dir: %s\n", pluginBaseDir)

    pluginManager := plugin.DefaultManager
    pluginManager.SetPluginBaseDir(pluginBaseDir)
    pluginManager.LoadPlugins()

    spec, executor, err := pluginManager.GetPlugin("sample")
    if err != nil {
       PrintError(err)
    }

    log.Printf("[INFO]: Plugin '%s' loaded with version '%s'\n", spec.Name, spec.Version)
    pContext := context.Background()
    pContext.SetValue("sample", &Hello{"hello go-plugin"})
    executor(pContext)
}

//PrintError ...
func PrintError(err error) {
    log.Printf("[Error]: %s\n", err)
    os.Exit(1)
}

Develop a plugin

  • Step 1: Implement func Execute(ctx context.PluginContext) error
  • Step 2: Build the plugin with command go build -buildmode=plugin -o sample.so sample.go

NOTES:

  • If the logic is loop logic in goroutine, please make sure there is an exit way by listening the context done() channel or your goroutine may escape away when unloading the plugin
  • If you need to handle multiple scenarios, just call your sub logic based on some values which are passed by plugin context. e.g:
func Execute(ctx context.PluginContext) error {
    label := ctx.GetValue("label")
    if label == nil {
        return errors.New("label is not set in the plugin context")
    }

    labelV := fmt.Sprintf("%s", label)
    switch labelV {
        case "case1":
          return call_subMethod1()
        case "case 2":
          return call_subMethod12()
        default:
          return errors.New("not suppotred")
    }
}

Next steps

  • Package the plugin.json and the so file as single *.plg file (with gzip)
  • Build the plugin from source code @git repo
  • Monitor and detect the plugin change in the plugin base dir
  • Plugin hot upgrade
  • Support http service onboarding drivers (beego first)
  • Load plugins from internet
  • Provide plugin metrics
  • Enable API and run as rest services
  • Provide GUI for plugin management
  • Provided hooks for the lifecycle of plugin
  • Add test cases and setup CI/CD

Issues

  • Currently plugins are only supported on Linux and macOS (Extened from golang plugin)
  • Memory issues if discard a loaded so