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