@cryptotaxi247 / kubo / commits / 150b6dd1b

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).

Steven Allen committed Aug 29, 2019 at 12:22 UTC 150b6dd1bddc1c99dacc49c17e3e1b7e089917ae
15 files changed +156 -45
cmd/ipfs/main.go
+2 -30
@@ -7,7 +7,6 @@ import (
7 "fmt"
8 "math/rand"
9 "os"
10 - "path/filepath"
10 "runtime/pprof"
11 "strings"
12 "time"
@@ -46,22 +45,9 @@ const (
45 )
46
47 func loadPlugins(repoPath string) (*loader.PluginLoader, error) {
49 - pluginpath := filepath.Join(repoPath, "plugins")
50 -
51 - plugins, err := loader.NewPluginLoader()
48 + plugins, err := loader.NewPluginLoader(repoPath)
49 if err != nil {
53 - return nil, fmt.Errorf("error loading preloaded plugins: %s", err)
54 - }
55 -
56 - // check if repo is accessible before loading plugins
57 - ok, err := checkPermissions(repoPath)
58 - if err != nil {
59 - return nil, err
60 - }
61 - if ok {
62 - if err := plugins.LoadDirectory(pluginpath); err != nil {
63 - return nil, err
64 - }
50 + return nil, fmt.Errorf("error loading plugins: %s", err)
51 }
52
53 if err := plugins.Initialize(); err != nil {
@@ -282,20 +268,6 @@ func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) {
268 return http.NewClient(host, opts...), nil
269 }
270
285 -func checkPermissions(path string) (bool, error) {
286 - _, err := os.Open(path)
287 - if os.IsNotExist(err) {
288 - // repo does not exist yet - don't load plugins, but also don't fail
289 - return false, nil
290 - }
291 - if os.IsPermission(err) {
292 - // repo is not accessible. error out.
293 - return false, fmt.Errorf("error opening repository at %s: permission denied", path)
294 - }
295 -
296 - return true, nil
297 -}
298 -
271 // commandDetails returns a command's details for the command given by |path|.
272 func commandDetails(path []string) cmdDetails {
273 if len(path) == 0 {
commands/context.go
+6
@@ -57,6 +57,12 @@ func (c *Context) GetNode() (*core.IpfsNode, error) {
57 return nil, errors.New("nil ConstructNode function")
58 }
59 c.node, err = c.ConstructNode()
60 + if err == nil {
61 + // Pre-load the config from the repo to avoid re-parsing it from disk.
62 + if cfg, err := c.node.Repo.Config(); err != nil {
63 + c.config = cfg
64 + }
65 + }
66 }
67 return c.node, err
68 }
docs/plugins.md
+3
@@ -21,6 +21,9 @@ directory (by default `~/.ipfs/plugins`).
21
22 ## Plugin Types
23
24 +Plugins can implement one or more plugin types, defined in the
25 +[plugin](https://godoc.org/github.com/ipfs/go-ipfs/plugin) package.
26 +
27 ### IPLD
28
29 IPLD plugins add support for additional formats to `ipfs dag` and other IPLD
go.mod
+1 -1
@@ -31,7 +31,7 @@ require (
31 github.com/ipfs/go-ipfs-blockstore v0.1.0
32 github.com/ipfs/go-ipfs-chunker v0.0.1
33 github.com/ipfs/go-ipfs-cmds v0.1.0
34 - github.com/ipfs/go-ipfs-config v0.0.6
34 + github.com/ipfs/go-ipfs-config v0.0.11
35 github.com/ipfs/go-ipfs-ds-help v0.0.1
36 github.com/ipfs/go-ipfs-exchange-interface v0.0.1
37 github.com/ipfs/go-ipfs-exchange-offline v0.0.1
go.sum
+2 -2
@@ -251,8 +251,8 @@ github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcB
251 github.com/ipfs/go-ipfs-cmds v0.1.0 h1:0CEde9EcxByej8+L6d1PST57J4ambRPyCTjLG5Ymou8=
252 github.com/ipfs/go-ipfs-cmds v0.1.0/go.mod h1:TiK4e7/V31tuEb8YWDF8lN3qrnDH+BS7ZqWIeYJlAs8=
253 github.com/ipfs/go-ipfs-config v0.0.5/go.mod h1:IGkVTacurWv9WFKc7IBPjHGM/7hi6+PEClqUb/l2BIM=
254 -github.com/ipfs/go-ipfs-config v0.0.6 h1:jzK9Tl8S0oWBir3F5ObtGgnHRPdqQ0MYiCmwXtV3Ps4=
255 -github.com/ipfs/go-ipfs-config v0.0.6/go.mod h1:IGkVTacurWv9WFKc7IBPjHGM/7hi6+PEClqUb/l2BIM=
254 +github.com/ipfs/go-ipfs-config v0.0.11 h1:5/4nas2CQXiKr2/MLxU24GDGTBvtstQIQezuk7ltOQQ=
255 +github.com/ipfs/go-ipfs-config v0.0.11/go.mod h1:wveA8UT5ywN26oKStByzmz1CO6cXwLKKM6Jn/Hfw08I=
256 github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
257 github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ=
258 github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
plugin/loader/loader.go
+30 -4
@@ -3,8 +3,11 @@ package loader
3 import (
4 "fmt"
5 "os"
6 + "path/filepath"
7 "strings"
8
9 + config "github.com/ipfs/go-ipfs-config"
10 + cserialize "github.com/ipfs/go-ipfs-config/serialize"
11 coredag "github.com/ipfs/go-ipfs/core/coredag"
12 plugin "github.com/ipfs/go-ipfs/plugin"
13 fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
@@ -83,16 +86,32 @@ type PluginLoader struct {
86 state loaderState
87 plugins map[string]plugin.Plugin
88 started []plugin.Plugin
89 + config config.Plugins
90 + repo string
91 }
92
93 // NewPluginLoader creates new plugin loader
89 -func NewPluginLoader() (*PluginLoader, error) {
90 - loader := &PluginLoader{plugins: make(map[string]plugin.Plugin, len(preloadPlugins))}
94 +func NewPluginLoader(repo string) (*PluginLoader, error) {
95 + loader := &PluginLoader{plugins: make(map[string]plugin.Plugin, len(preloadPlugins)), repo: repo}
96 + if repo != "" {
97 + cfg, err := cserialize.Load(filepath.Join(repo, config.DefaultConfigFile))
98 + switch err {
99 + case cserialize.ErrNotInitialized:
100 + case nil:
101 + loader.config = cfg.Plugins
102 + default:
103 + return nil, err
104 + }
105 + }
106 for _, v := range preloadPlugins {
107 if err := loader.Load(v); err != nil {
108 return nil, err
109 }
110 }
111 +
112 + if err := loader.LoadDirectory(filepath.Join(repo, "plugins")); err != nil {
113 + return nil, err
114 + }
115 return loader, nil
116 }
117
@@ -125,6 +144,10 @@ func (loader *PluginLoader) Load(pl plugin.Plugin) error {
144 "while trying to load dynamically: %s",
145 name, ppl.Version(), pl.Version())
146 }
147 + if loader.config.Plugins[name].Disabled {
148 + log.Infof("not loading disabled plugin %s", name)
149 + return nil
150 + }
151 loader.plugins[name] = pl
152 return nil
153 }
@@ -164,8 +187,11 @@ func (loader *PluginLoader) Initialize() error {
187 if err := loader.transition(loaderLoading, loaderInitializing); err != nil {
188 return err
189 }
167 - for _, p := range loader.plugins {
168 - err := p.Init()
190 + for name, p := range loader.plugins {
191 + err := p.Init(&plugin.Environment{
192 + Repo: loader.repo,
193 + Config: loader.config.Plugins[name].Config,
194 + })
195 if err != nil {
196 loader.state = loaderFailed
197 return err
plugin/plugin.go
+14 -1
@@ -1,12 +1,25 @@
1 package plugin
2
3 +// Environment is the environment passed into the plugin on init.
4 +type Environment struct {
5 + // Path to the IPFS repo.
6 + Repo string
7 +
8 + // The plugin's config, if specified.
9 + Config interface{}
10 +}
11 +
12 // Plugin is base interface for all kinds of go-ipfs plugins
13 // It will be included in interfaces of different Plugins
14 type Plugin interface {
15 // Name should return unique name of the plugin
16 Name() string
17 +
18 // Version returns current version of the plugin
19 Version() string
20 +
21 // Init is called once when the Plugin is being loaded
11 - Init() error
22 + // The plugin is passed an environment containing the path to the
23 + // (possibly uninitialized) IPFS repo and the plugin's config.
24 + Init(env *Environment) error
25 }
plugin/plugins/badgerds/badgerds.go
+1 -1
@@ -30,7 +30,7 @@ func (*badgerdsPlugin) Version() string {
30 return "0.1.0"
31 }
32
33 -func (*badgerdsPlugin) Init() error {
33 +func (*badgerdsPlugin) Init(_ *plugin.Environment) error {
34 return nil
35 }
36
plugin/plugins/flatfs/flatfs.go
+1 -1
@@ -28,7 +28,7 @@ func (*flatfsPlugin) Version() string {
28 return "0.1.0"
29 }
30
31 -func (*flatfsPlugin) Init() error {
31 +func (*flatfsPlugin) Init(_ *plugin.Environment) error {
32 return nil
33 }
34
plugin/plugins/git/git.go
+1 -1
@@ -32,7 +32,7 @@ func (*gitPlugin) Version() string {
32 return "0.0.1"
33 }
34
35 -func (*gitPlugin) Init() error {
35 +func (*gitPlugin) Init(_ *plugin.Environment) error {
36 return nil
37 }
38
plugin/plugins/levelds/levelds.go
+1 -1
@@ -29,7 +29,7 @@ func (*leveldsPlugin) Version() string {
29 return "0.1.0"
30 }
31
32 -func (*leveldsPlugin) Init() error {
32 +func (*leveldsPlugin) Init(_ *plugin.Environment) error {
33 return nil
34 }
35
repo/fsrepo/config_test.go
+1 -1
@@ -75,7 +75,7 @@ var measureConfig = []byte(`{
75 }`)
76
77 func TestDefaultDatastoreConfig(t *testing.T) {
78 - loader, err := loader.NewPluginLoader()
78 + loader, err := loader.NewPluginLoader("")
79 if err != nil {
80 t.Fatal(err)
81 }
test/sharness/t0020-init.sh
+2 -2
@@ -22,9 +22,9 @@ test_expect_success "ipfs init fails" '
22 # Under Windows/Cygwin the error message is different,
23 # so we use the STD_ERR_MSG prereq.
24 if test_have_prereq STD_ERR_MSG; then
25 - init_err_msg="Error: error opening repository at $IPFS_PATH: permission denied"
25 + init_err_msg="Error: error loading plugins: open $IPFS_PATH/config: permission denied"
26 else
27 - init_err_msg="Error: mkdir $IPFS_PATH: The system cannot find the path specified."
27 + init_err_msg="Error: error loading plugins: open $IPFS_PATH/config: The system cannot find the path specified."
28 fi
29
30 test_expect_success "ipfs init output looks good" '
test/sharness/t0280-plugin-data/example.go new
+30
@@ -0,0 +1,30 @@
1 +package main
2 +
3 +import (
4 + "fmt"
5 + "os"
6 +
7 + "github.com/ipfs/go-ipfs/plugin"
8 +)
9 +
10 +var Plugins = []plugin.Plugin{
11 + &testPlugin{},
12 +}
13 +
14 +var _ = Plugins // used
15 +
16 +type testPlugin struct{}
17 +
18 +func (*testPlugin) Name() string {
19 + return "test-plugin"
20 +}
21 +
22 +func (*testPlugin) Version() string {
23 + return "0.1.0"
24 +}
25 +
26 +func (*testPlugin) Init(env *plugin.Environment) error {
27 + fmt.Fprintf(os.Stderr, "testplugin %s\n", env.Repo)
28 + fmt.Fprintf(os.Stderr, "testplugin %v\n", env.Config)
29 + return nil
30 +}
test/sharness/t0280-plugin.sh
+61
@@ -8,6 +8,12 @@ test_description="Test plugin loading"
8
9 . lib/test-lib.sh
10
11 +if ! test_have_prereq PLUGIN; then
12 + skip_all='skipping plugin tests, plugins not available'
13 +
14 + test_done
15 +fi
16 +
17 test_init_ipfs
18
19 test_expect_success "ipfs id succeeds" '
@@ -28,4 +34,59 @@ test_expect_success "cleanup bad plugin" '
34 rm "$IPFS_PATH/plugins/foo.so"
35 '
36
37 +test_expect_success "install test plugin" '
38 + go build \
39 + -asmflags=all="-trimpath=${GOPATH}" -gcflags=all="-trimpath=${GOPATH}" \
40 + -buildmode=plugin -o "$IPFS_PATH/plugins/example.so" ../t0280-plugin-data/example.go &&
41 + chmod +x "$IPFS_PATH/plugins/example.so"
42 +'
43 +
44 +test_plugin() {
45 + local loads="$1"
46 + local repo="$2"
47 + local config="$3"
48 +
49 + rm -f id_raw_output id_output id_output_expected
50 +
51 + test_expect_success "id runs" '
52 + ipfs id 2>id_raw_output >/dev/null
53 + '
54 +
55 + test_expect_success "filter test plugin output" '
56 + sed -ne "s/^testplugin //p" id_raw_output >id_output
57 + '
58 +
59 + if [ "$loads" != "true" ]; then
60 + test_expect_success "plugin doesn't load" '
61 + test_must_be_empty id_output
62 + '
63 + else
64 + test_expect_success "plugin produces the correct output" '
65 + echo "$repo" >id_output_expected &&
66 + echo "$config" >>id_output_expected &&
67 + test_cmp id_output id_output_expected
68 + '
69 + fi
70 +}
71 +
72 +test_plugin true "$IPFS_PATH" "<nil>"
73 +
74 +test_expect_success "disable the plugin" '
75 + ipfs config --json Plugins.Plugins.test-plugin.Disabled true
76 +'
77 +
78 +test_plugin false
79 +
80 +test_expect_success "re-enable the plugin" '
81 + ipfs config --json Plugins.Plugins.test-plugin.Disabled false
82 +'
83 +
84 +test_plugin true "$IPFS_PATH" "<nil>"
85 +
86 +test_expect_success "configure the plugin" '
87 + ipfs config Plugins.Plugins.test-plugin.Config foobar
88 +'
89 +
90 +test_plugin true "$IPFS_PATH" "foobar"
91 +
92 test_done