From 0d6bc782153020a4a1cfb1695d7289084861b8cb Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 29 Aug 2019 12:22:51 -0700 Subject: [PATCH] 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). --- cmd/ipfs/main.go | 30 +---------- commands/context.go | 6 +++ docs/plugins.md | 3 ++ go.mod | 2 +- go.sum | 4 +- plugin/loader/loader.go | 34 ++++++++++-- plugin/plugin.go | 15 +++++- plugin/plugins/badgerds/badgerds.go | 2 +- plugin/plugins/flatfs/flatfs.go | 2 +- plugin/plugins/git/git.go | 2 +- plugin/plugins/levelds/levelds.go | 2 +- repo/fsrepo/config_test.go | 2 +- test/sharness/t0280-plugin-data/example.go | 30 +++++++++++ test/sharness/t0280-plugin.sh | 61 ++++++++++++++++++++++ 14 files changed, 153 insertions(+), 42 deletions(-) create mode 100644 test/sharness/t0280-plugin-data/example.go diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 26e890683e5c..3333c1b2025c 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -7,7 +7,6 @@ import ( "fmt" "math/rand" "os" - "path/filepath" "runtime/pprof" "strings" "time" @@ -46,24 +45,11 @@ 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 - } - } - if err := plugins.Initialize(); err != nil { return nil, fmt.Errorf("error initializing plugins: %s", err) } @@ -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 { diff --git a/commands/context.go b/commands/context.go index 7bf9e8461502..a14abb8e8b54 100644 --- a/commands/context.go +++ b/commands/context.go @@ -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 } diff --git a/docs/plugins.md b/docs/plugins.md index a40201d1e008..ea878444edfa 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -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 diff --git a/go.mod b/go.mod index d811dbff96d0..faaaf8eabd2c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 18231104834f..35dac5e5a463 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/plugin/loader/loader.go b/plugin/loader/loader.go index 7788d8e83623..face2810755f 100644 --- a/plugin/loader/loader.go +++ b/plugin/loader/loader.go @@ -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 diff --git a/plugin/plugin.go b/plugin/plugin.go index 4c028352425b..fcf86e490b90 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -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 } diff --git a/plugin/plugins/badgerds/badgerds.go b/plugin/plugins/badgerds/badgerds.go index 57476d306fbb..a9d06f491efd 100644 --- a/plugin/plugins/badgerds/badgerds.go +++ b/plugin/plugins/badgerds/badgerds.go @@ -30,7 +30,7 @@ func (*badgerdsPlugin) Version() string { return "0.1.0" } -func (*badgerdsPlugin) Init() error { +func (*badgerdsPlugin) Init(_ *plugin.Environment) error { return nil } diff --git a/plugin/plugins/flatfs/flatfs.go b/plugin/plugins/flatfs/flatfs.go index 059d211455e9..074cc590df08 100644 --- a/plugin/plugins/flatfs/flatfs.go +++ b/plugin/plugins/flatfs/flatfs.go @@ -28,7 +28,7 @@ func (*flatfsPlugin) Version() string { return "0.1.0" } -func (*flatfsPlugin) Init() error { +func (*flatfsPlugin) Init(_ *plugin.Environment) error { return nil } diff --git a/plugin/plugins/git/git.go b/plugin/plugins/git/git.go index 97310b0c44d8..23b79ad59a14 100644 --- a/plugin/plugins/git/git.go +++ b/plugin/plugins/git/git.go @@ -32,7 +32,7 @@ func (*gitPlugin) Version() string { return "0.0.1" } -func (*gitPlugin) Init() error { +func (*gitPlugin) Init(_ *plugin.Environment) error { return nil } diff --git a/plugin/plugins/levelds/levelds.go b/plugin/plugins/levelds/levelds.go index 973e9132d331..12cad713a076 100644 --- a/plugin/plugins/levelds/levelds.go +++ b/plugin/plugins/levelds/levelds.go @@ -29,7 +29,7 @@ func (*leveldsPlugin) Version() string { return "0.1.0" } -func (*leveldsPlugin) Init() error { +func (*leveldsPlugin) Init(_ *plugin.Environment) error { return nil } diff --git a/repo/fsrepo/config_test.go b/repo/fsrepo/config_test.go index 109860caed26..f7c19c307657 100644 --- a/repo/fsrepo/config_test.go +++ b/repo/fsrepo/config_test.go @@ -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) } diff --git a/test/sharness/t0280-plugin-data/example.go b/test/sharness/t0280-plugin-data/example.go new file mode 100644 index 000000000000..87b96249ad29 --- /dev/null +++ b/test/sharness/t0280-plugin-data/example.go @@ -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 +} diff --git a/test/sharness/t0280-plugin.sh b/test/sharness/t0280-plugin.sh index b739a7834bcb..a0709ef1b2b8 100755 --- a/test/sharness/t0280-plugin.sh +++ b/test/sharness/t0280-plugin.sh @@ -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" "" + +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" "" + +test_expect_success "configure the plugin" ' + ipfs config Plugins.Plugins.test-plugin.Config foobar +' + +test_plugin true "$IPFS_PATH" "foobar" + test_done