@cryptotaxi247 / kubo / commits / 4badef2a1

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 }