plugins: add support for plugin configs

For now, configs specified in `daemon --init-config` and `init CONFIG` are not
available. We should fix this eventually but isn't necessary for now (and
supporting this will be annoying).
This commit is contained in:
Steven Allen 2019-08-29 12:22:51 -07:00
parent d2a1ce3c5c
commit 150b6dd1bd
15 changed files with 156 additions and 45 deletions

View File

@ -7,7 +7,6 @@ import (
"fmt"
"math/rand"
"os"
"path/filepath"
"runtime/pprof"
"strings"
"time"
@ -46,22 +45,9 @@ const (
)
func loadPlugins(repoPath string) (*loader.PluginLoader, error) {
pluginpath := filepath.Join(repoPath, "plugins")
plugins, err := loader.NewPluginLoader()
plugins, err := loader.NewPluginLoader(repoPath)
if err != nil {
return nil, fmt.Errorf("error loading preloaded plugins: %s", err)
}
// check if repo is accessible before loading plugins
ok, err := checkPermissions(repoPath)
if err != nil {
return nil, err
}
if ok {
if err := plugins.LoadDirectory(pluginpath); err != nil {
return nil, err
}
return nil, fmt.Errorf("error loading plugins: %s", err)
}
if err := plugins.Initialize(); err != nil {
@ -282,20 +268,6 @@ func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) {
return http.NewClient(host, opts...), nil
}
func checkPermissions(path string) (bool, error) {
_, err := os.Open(path)
if os.IsNotExist(err) {
// repo does not exist yet - don't load plugins, but also don't fail
return false, nil
}
if os.IsPermission(err) {
// repo is not accessible. error out.
return false, fmt.Errorf("error opening repository at %s: permission denied", path)
}
return true, nil
}
// commandDetails returns a command's details for the command given by |path|.
func commandDetails(path []string) cmdDetails {
if len(path) == 0 {

View File

@ -57,6 +57,12 @@ func (c *Context) GetNode() (*core.IpfsNode, error) {
return nil, errors.New("nil ConstructNode function")
}
c.node, err = c.ConstructNode()
if err == nil {
// Pre-load the config from the repo to avoid re-parsing it from disk.
if cfg, err := c.node.Repo.Config(); err != nil {
c.config = cfg
}
}
}
return c.node, err
}

View File

@ -21,6 +21,9 @@ directory (by default `~/.ipfs/plugins`).
## Plugin Types
Plugins can implement one or more plugin types, defined in the
[plugin](https://godoc.org/github.com/ipfs/go-ipfs/plugin) package.
### IPLD
IPLD plugins add support for additional formats to `ipfs dag` and other IPLD

2
go.mod
View File

@ -31,7 +31,7 @@ require (
github.com/ipfs/go-ipfs-blockstore v0.1.0
github.com/ipfs/go-ipfs-chunker v0.0.1
github.com/ipfs/go-ipfs-cmds v0.1.0
github.com/ipfs/go-ipfs-config v0.0.6
github.com/ipfs/go-ipfs-config v0.0.11
github.com/ipfs/go-ipfs-ds-help v0.0.1
github.com/ipfs/go-ipfs-exchange-interface v0.0.1
github.com/ipfs/go-ipfs-exchange-offline v0.0.1

4
go.sum
View File

@ -251,8 +251,8 @@ github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcB
github.com/ipfs/go-ipfs-cmds v0.1.0 h1:0CEde9EcxByej8+L6d1PST57J4ambRPyCTjLG5Ymou8=
github.com/ipfs/go-ipfs-cmds v0.1.0/go.mod h1:TiK4e7/V31tuEb8YWDF8lN3qrnDH+BS7ZqWIeYJlAs8=
github.com/ipfs/go-ipfs-config v0.0.5/go.mod h1:IGkVTacurWv9WFKc7IBPjHGM/7hi6+PEClqUb/l2BIM=
github.com/ipfs/go-ipfs-config v0.0.6 h1:jzK9Tl8S0oWBir3F5ObtGgnHRPdqQ0MYiCmwXtV3Ps4=
github.com/ipfs/go-ipfs-config v0.0.6/go.mod h1:IGkVTacurWv9WFKc7IBPjHGM/7hi6+PEClqUb/l2BIM=
github.com/ipfs/go-ipfs-config v0.0.11 h1:5/4nas2CQXiKr2/MLxU24GDGTBvtstQIQezuk7ltOQQ=
github.com/ipfs/go-ipfs-config v0.0.11/go.mod h1:wveA8UT5ywN26oKStByzmz1CO6cXwLKKM6Jn/Hfw08I=
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ=
github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=

View File

@ -3,8 +3,11 @@ package loader
import (
"fmt"
"os"
"path/filepath"
"strings"
config "github.com/ipfs/go-ipfs-config"
cserialize "github.com/ipfs/go-ipfs-config/serialize"
coredag "github.com/ipfs/go-ipfs/core/coredag"
plugin "github.com/ipfs/go-ipfs/plugin"
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
@ -83,16 +86,32 @@ type PluginLoader struct {
state loaderState
plugins map[string]plugin.Plugin
started []plugin.Plugin
config config.Plugins
repo string
}
// NewPluginLoader creates new plugin loader
func NewPluginLoader() (*PluginLoader, error) {
loader := &PluginLoader{plugins: make(map[string]plugin.Plugin, len(preloadPlugins))}
func NewPluginLoader(repo string) (*PluginLoader, error) {
loader := &PluginLoader{plugins: make(map[string]plugin.Plugin, len(preloadPlugins)), repo: repo}
if repo != "" {
cfg, err := cserialize.Load(filepath.Join(repo, config.DefaultConfigFile))
switch err {
case cserialize.ErrNotInitialized:
case nil:
loader.config = cfg.Plugins
default:
return nil, err
}
}
for _, v := range preloadPlugins {
if err := loader.Load(v); err != nil {
return nil, err
}
}
if err := loader.LoadDirectory(filepath.Join(repo, "plugins")); err != nil {
return nil, err
}
return loader, nil
}
@ -125,6 +144,10 @@ func (loader *PluginLoader) Load(pl plugin.Plugin) error {
"while trying to load dynamically: %s",
name, ppl.Version(), pl.Version())
}
if loader.config.Plugins[name].Disabled {
log.Infof("not loading disabled plugin %s", name)
return nil
}
loader.plugins[name] = pl
return nil
}
@ -164,8 +187,11 @@ func (loader *PluginLoader) Initialize() error {
if err := loader.transition(loaderLoading, loaderInitializing); err != nil {
return err
}
for _, p := range loader.plugins {
err := p.Init()
for name, p := range loader.plugins {
err := p.Init(&plugin.Environment{
Repo: loader.repo,
Config: loader.config.Plugins[name].Config,
})
if err != nil {
loader.state = loaderFailed
return err

View File

@ -1,12 +1,25 @@
package plugin
// Environment is the environment passed into the plugin on init.
type Environment struct {
// Path to the IPFS repo.
Repo string
// The plugin's config, if specified.
Config interface{}
}
// Plugin is base interface for all kinds of go-ipfs plugins
// It will be included in interfaces of different Plugins
type Plugin interface {
// Name should return unique name of the plugin
Name() string
// Version returns current version of the plugin
Version() string
// Init is called once when the Plugin is being loaded
Init() error
// The plugin is passed an environment containing the path to the
// (possibly uninitialized) IPFS repo and the plugin's config.
Init(env *Environment) error
}

View File

@ -30,7 +30,7 @@ func (*badgerdsPlugin) Version() string {
return "0.1.0"
}
func (*badgerdsPlugin) Init() error {
func (*badgerdsPlugin) Init(_ *plugin.Environment) error {
return nil
}

View File

@ -28,7 +28,7 @@ func (*flatfsPlugin) Version() string {
return "0.1.0"
}
func (*flatfsPlugin) Init() error {
func (*flatfsPlugin) Init(_ *plugin.Environment) error {
return nil
}

View File

@ -32,7 +32,7 @@ func (*gitPlugin) Version() string {
return "0.0.1"
}
func (*gitPlugin) Init() error {
func (*gitPlugin) Init(_ *plugin.Environment) error {
return nil
}

View File

@ -29,7 +29,7 @@ func (*leveldsPlugin) Version() string {
return "0.1.0"
}
func (*leveldsPlugin) Init() error {
func (*leveldsPlugin) Init(_ *plugin.Environment) error {
return nil
}

View File

@ -75,7 +75,7 @@ var measureConfig = []byte(`{
}`)
func TestDefaultDatastoreConfig(t *testing.T) {
loader, err := loader.NewPluginLoader()
loader, err := loader.NewPluginLoader("")
if err != nil {
t.Fatal(err)
}

View File

@ -22,9 +22,9 @@ test_expect_success "ipfs init fails" '
# Under Windows/Cygwin the error message is different,
# so we use the STD_ERR_MSG prereq.
if test_have_prereq STD_ERR_MSG; then
init_err_msg="Error: error opening repository at $IPFS_PATH: permission denied"
init_err_msg="Error: error loading plugins: open $IPFS_PATH/config: permission denied"
else
init_err_msg="Error: mkdir $IPFS_PATH: The system cannot find the path specified."
init_err_msg="Error: error loading plugins: open $IPFS_PATH/config: The system cannot find the path specified."
fi
test_expect_success "ipfs init output looks good" '

View File

@ -0,0 +1,30 @@
package main
import (
"fmt"
"os"
"github.com/ipfs/go-ipfs/plugin"
)
var Plugins = []plugin.Plugin{
&testPlugin{},
}
var _ = Plugins // used
type testPlugin struct{}
func (*testPlugin) Name() string {
return "test-plugin"
}
func (*testPlugin) Version() string {
return "0.1.0"
}
func (*testPlugin) Init(env *plugin.Environment) error {
fmt.Fprintf(os.Stderr, "testplugin %s\n", env.Repo)
fmt.Fprintf(os.Stderr, "testplugin %v\n", env.Config)
return nil
}

View File

@ -8,6 +8,12 @@ test_description="Test plugin loading"
. lib/test-lib.sh
if ! test_have_prereq PLUGIN; then
skip_all='skipping plugin tests, plugins not available'
test_done
fi
test_init_ipfs
test_expect_success "ipfs id succeeds" '
@ -28,4 +34,59 @@ test_expect_success "cleanup bad plugin" '
rm "$IPFS_PATH/plugins/foo.so"
'
test_expect_success "install test plugin" '
go build \
-asmflags=all="-trimpath=${GOPATH}" -gcflags=all="-trimpath=${GOPATH}" \
-buildmode=plugin -o "$IPFS_PATH/plugins/example.so" ../t0280-plugin-data/example.go &&
chmod +x "$IPFS_PATH/plugins/example.so"
'
test_plugin() {
local loads="$1"
local repo="$2"
local config="$3"
rm -f id_raw_output id_output id_output_expected
test_expect_success "id runs" '
ipfs id 2>id_raw_output >/dev/null
'
test_expect_success "filter test plugin output" '
sed -ne "s/^testplugin //p" id_raw_output >id_output
'
if [ "$loads" != "true" ]; then
test_expect_success "plugin doesn't load" '
test_must_be_empty id_output
'
else
test_expect_success "plugin produces the correct output" '
echo "$repo" >id_output_expected &&
echo "$config" >>id_output_expected &&
test_cmp id_output id_output_expected
'
fi
}
test_plugin true "$IPFS_PATH" "<nil>"
test_expect_success "disable the plugin" '
ipfs config --json Plugins.Plugins.test-plugin.Disabled true
'
test_plugin false
test_expect_success "re-enable the plugin" '
ipfs config --json Plugins.Plugins.test-plugin.Disabled false
'
test_plugin true "$IPFS_PATH" "<nil>"
test_expect_success "configure the plugin" '
ipfs config Plugins.Plugins.test-plugin.Config foobar
'
test_plugin true "$IPFS_PATH" "foobar"
test_done