feat: make it easier to load custom plugins
1. Allow loading from arbitrary and multiple directories. 2. Make it possible to directly load built-in plugins that _aren't_ in the preload list.
Steven Allen committed
Jun 28, 2019 at 08:42 UTC
4badef2a14c6e0c2421fd915540d79060cbf3df3
3 files changed
+136
-40
cmd/ipfs/main.go
+9
-7
@@ -49,18 +49,20 @@ const (
49
func loadPlugins(repoPath string) (*loader.PluginLoader, error) {
50
pluginpath := filepath.Join(repoPath, "plugins")
51
52
+ plugins, err := loader.NewPluginLoader()
53
+ if err != nil {
54
+ return nil, fmt.Errorf("error loading preloaded plugins: %s", err)
55
+ }
56
+
57
// check if repo is accessible before loading plugins
53
- var plugins *loader.PluginLoader
58
ok, err := checkPermissions(repoPath)
59
if err != nil {
60
return nil, err
61
}
58
- if !ok {
59
- pluginpath = ""
60
- }
61
- plugins, err = loader.NewPluginLoader(pluginpath)
62
- if err != nil {
63
- return nil, fmt.Errorf("error loading plugins: %s", err)
62
+ if ok {
63
+ if err := plugins.LoadDirectory(pluginpath); err != nil {
64
+ return nil, err
65
+ }
66
}
67
68
if err := plugins.Initialize(); err != nil {
plugin/loader/loader.go
+126
-32
@@ -21,43 +21,113 @@ var loadPluginsFunc = func(string) ([]plugin.Plugin, error) {
21
return nil, nil
22
}
23
24
+type loaderState int
25
+
26
+const (
27
+ loaderLoading loaderState = iota
28
+ loaderInitializing
29
+ loaderInitialized
30
+ loaderInjecting
31
+ loaderInjected
32
+ loaderStarting
33
+ loaderStarted
34
+ loaderClosing
35
+ loaderClosed
36
+ loaderFailed
37
+)
38
+
39
+func (ls loaderState) String() string {
40
+ switch ls {
41
+ case loaderLoading:
42
+ return "Loading"
43
+ case loaderInitializing:
44
+ return "Initializing"
45
+ case loaderInitialized:
46
+ return "Initialized"
47
+ case loaderInjecting:
48
+ return "Injecting"
49
+ case loaderInjected:
50
+ return "Injected"
51
+ case loaderStarting:
52
+ return "Starting"
53
+ case loaderStarted:
54
+ return "Started"
55
+ case loaderClosing:
56
+ return "Closing"
57
+ case loaderClosed:
58
+ return "Closed"
59
+ case loaderFailed:
60
+ return "Failed"
61
+ default:
62
+ return "Unknown"
63
+ }
64
+}
65
+
66
// PluginLoader keeps track of loaded plugins
67
type PluginLoader struct {
26
- plugins []plugin.Plugin
68
+ state loaderState
69
+ plugins map[string]plugin.Plugin
70
+ started []plugin.Plugin
71
}
72
73
// NewPluginLoader creates new plugin loader
30
-func NewPluginLoader(pluginDir string) (*PluginLoader, error) {
31
- plMap := make(map[string]plugin.Plugin)
74
+func NewPluginLoader() (*PluginLoader, error) {
75
+ loader := &PluginLoader{plugins: make(map[string]plugin.Plugin, len(preloadPlugins))}
76
for _, v := range preloadPlugins {
33
- plMap[v.Name()] = v
34
- }
35
-
36
- if pluginDir != "" {
37
- newPls, err := loadDynamicPlugins(pluginDir)
38
- if err != nil {
77
+ if err := loader.Load(v); err != nil {
78
return nil, err
79
}
80
+ }
81
+ return loader, nil
82
+}
83
42
- for _, pl := range newPls {
43
- if ppl, ok := plMap[pl.Name()]; ok {
44
- // plugin is already preloaded
45
- return nil, fmt.Errorf(
46
- "plugin: %s, is duplicated in version: %s, "+
47
- "while trying to load dynamically: %s",
48
- ppl.Name(), ppl.Version(), pl.Version())
49
- }
50
- plMap[pl.Name()] = pl
51
- }
84
+func (loader *PluginLoader) assertState(state loaderState) error {
85
+ if loader.state != state {
86
+ return fmt.Errorf("loader state must be %s, was %s", state, loader.state)
87
}
88
+ return nil
89
+}
90
54
- loader := &PluginLoader{plugins: make([]plugin.Plugin, 0, len(plMap))}
91
+func (loader *PluginLoader) transition(from, to loaderState) error {
92
+ if err := loader.assertState(from); err != nil {
93
+ return err
94
+ }
95
+ loader.state = to
96
+ return nil
97
+}
98
56
- for _, v := range plMap {
57
- loader.plugins = append(loader.plugins, v)
99
+func (loader *PluginLoader) Load(pl plugin.Plugin) error {
100
+ if err := loader.assertState(loaderLoading); err != nil {
101
+ return err
102
}
103
60
- return loader, nil
104
+ name := pl.Name()
105
+ if ppl, ok := loader.plugins[name]; ok {
106
+ // plugin is already loaded
107
+ return fmt.Errorf(
108
+ "plugin: %s, is duplicated in version: %s, "+
109
+ "while trying to load dynamically: %s",
110
+ name, ppl.Version(), pl.Version())
111
+ }
112
+ loader.plugins[name] = pl
113
+ return nil
114
+}
115
+
116
+func (loader *PluginLoader) LoadDirectory(pluginDir string) error {
117
+ if err := loader.assertState(loaderLoading); err != nil {
118
+ return err
119
+ }
120
+ newPls, err := loadDynamicPlugins(pluginDir)
121
+ if err != nil {
122
+ return err
123
+ }
124
+
125
+ for _, pl := range newPls {
126
+ if err := loader.Load(pl); err != nil {
127
+ return err
128
+ }
129
+ }
130
+ return nil
131
}
132
133
func loadDynamicPlugins(pluginDir string) ([]plugin.Plugin, error) {
@@ -74,63 +144,85 @@ func loadDynamicPlugins(pluginDir string) ([]plugin.Plugin, error) {
144
145
// Initialize initializes all loaded plugins
146
func (loader *PluginLoader) Initialize() error {
147
+ if err := loader.transition(loaderLoading, loaderInitializing); err != nil {
148
+ return err
149
+ }
150
for _, p := range loader.plugins {
151
err := p.Init()
152
if err != nil {
153
+ loader.state = loaderFailed
154
return err
155
}
156
}
157
84
- return nil
158
+ return loader.transition(loaderInitializing, loaderInitialized)
159
}
160
161
// Inject hooks all the plugins into the appropriate subsystems.
162
func (loader *PluginLoader) Inject() error {
163
+ if err := loader.transition(loaderInitialized, loaderInjecting); err != nil {
164
+ return err
165
+ }
166
+
167
for _, pl := range loader.plugins {
168
if pl, ok := pl.(plugin.PluginIPLD); ok {
169
err := injectIPLDPlugin(pl)
170
if err != nil {
171
+ loader.state = loaderFailed
172
return err
173
}
174
}
175
if pl, ok := pl.(plugin.PluginTracer); ok {
176
err := injectTracerPlugin(pl)
177
if err != nil {
178
+ loader.state = loaderFailed
179
return err
180
}
181
}
182
if pl, ok := pl.(plugin.PluginDatastore); ok {
183
err := injectDatastorePlugin(pl)
184
if err != nil {
185
+ loader.state = loaderFailed
186
return err
187
}
188
}
189
}
109
- return nil
190
+
191
+ return loader.transition(loaderInjecting, loaderInjected)
192
}
193
194
// Start starts all long-running plugins.
195
func (loader *PluginLoader) Start(iface coreiface.CoreAPI) error {
114
- for i, pl := range loader.plugins {
196
+ if err := loader.transition(loaderInjected, loaderStarting); err != nil {
197
+ return err
198
+ }
199
+ for _, pl := range loader.plugins {
200
if pl, ok := pl.(plugin.PluginDaemon); ok {
201
err := pl.Start(iface)
202
if err != nil {
118
- _ = closePlugins(loader.plugins[:i])
203
+ _ = loader.Close()
204
return err
205
}
206
+ loader.started = append(loader.started, pl)
207
}
208
}
123
- return nil
209
+
210
+ return loader.transition(loaderStarting, loaderStarted)
211
}
212
213
// StopDaemon stops all long-running plugins.
214
func (loader *PluginLoader) Close() error {
128
- return closePlugins(loader.plugins)
129
-}
215
+ switch loader.state {
216
+ case loaderClosing, loaderFailed, loaderClosed:
217
+ // nothing to do.
218
+ return nil
219
+ }
220
+ loader.state = loaderClosing
221
131
-func closePlugins(plugins []plugin.Plugin) error {
222
var errs []string
133
- for _, pl := range plugins {
223
+ started := loader.started
224
+ loader.started = nil
225
+ for _, pl := range started {
226
if pl, ok := pl.(plugin.PluginDaemon); ok {
227
err := pl.Close()
228
if err != nil {
@@ -143,8 +235,10 @@ func closePlugins(plugins []plugin.Plugin) error {
235
}
236
}
237
if errs != nil {
238
+ loader.state = loaderFailed
239
return fmt.Errorf(strings.Join(errs, "\n"))
240
}
241
+ loader.state = loaderClosed
242
return nil
243
}
244
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
}