-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
plugin: create plugin API and loader, add ipld-git plugin #4033
Changes from 22 commits
7203c43
285a4d8
d6f280e
d2cc708
2c71cf0
04b26fe
cfad460
d6657ed
f947931
42b0ba3
b067972
c569b0b
e293de1
314d2c3
be5ee0b
b135a1f
6da6e77
fb618a2
36cbfa4
2df6186
b5ea00e
3e8e039
c0ee7d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package coredag | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
|
||
node "gx/ipfs/QmYNyRZJBUYPNrLszFmrBrPJbsBh2vMsefz5gnDpB5M1P6/go-ipld-format" | ||
ipldcbor "gx/ipfs/QmemYymP73eVdTUUMZEiSpiHeZQKNJdT5dP2iuHssZh1sR/go-ipld-cbor" | ||
) | ||
|
||
// DagParser is function used for parsing stream into Node | ||
type DagParser func(r io.Reader) ([]node.Node, error) | ||
|
||
// FormatParsers is used for mapping format descriptors to DagParsers | ||
type FormatParsers map[string]DagParser | ||
|
||
// InputEncParsers is used for mapping input encodings to FormatParsers | ||
type InputEncParsers map[string]FormatParsers | ||
|
||
// DefaultInputEncParsers is InputEncParser that is used everywhere | ||
var DefaultInputEncParsers = InputEncParsers{ | ||
"json": defaultJSONParsers, | ||
"raw": defaultRawParsers, | ||
} | ||
|
||
var defaultJSONParsers = FormatParsers{ | ||
"cbor": cborJSONParser, | ||
"dag-cbor": cborJSONParser, | ||
} | ||
|
||
var defaultRawParsers = FormatParsers{ | ||
"cbor": cborRawParser, | ||
"dag-cbor": cborRawParser, | ||
} | ||
|
||
// ParseInputs uses DefaultInputEncParsers to parse io.Reader described by | ||
// input encoding and format to an instance of ipld Node | ||
func ParseInputs(ienc, format string, r io.Reader) ([]node.Node, error) { | ||
return DefaultInputEncParsers.ParseInputs(ienc, format, r) | ||
} | ||
|
||
// AddParser adds DagParser under give input encoding and format | ||
func (iep InputEncParsers) AddParser(ienv, format string, f DagParser) { | ||
m, ok := iep[ienv] | ||
if !ok { | ||
m = make(FormatParsers) | ||
iep[ienv] = m | ||
} | ||
|
||
m[format] = f | ||
} | ||
|
||
// ParseInputs parses io.Reader described by input encoding and format to | ||
// an instance of ipld Node | ||
func (iep InputEncParsers) ParseInputs(ienc, format string, r io.Reader) ([]node.Node, error) { | ||
pset, ok := iep[ienc] | ||
if !ok { | ||
return nil, fmt.Errorf("no input parser for %q", ienc) | ||
} | ||
|
||
parser, ok := pset[format] | ||
if !ok { | ||
return nil, fmt.Errorf("no parser for format %q using input type %q", format, ienc) | ||
} | ||
|
||
return parser(r) | ||
} | ||
|
||
func cborJSONParser(r io.Reader) ([]node.Node, error) { | ||
nd, err := ipldcbor.FromJson(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return []node.Node{nd}, nil | ||
} | ||
|
||
func cborRawParser(r io.Reader) ([]node.Node, error) { | ||
data, err := ioutil.ReadAll(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
nd, err := ipldcbor.Decode(data) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return []node.Node{nd}, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
include mk/header.mk | ||
|
||
dir := $(d)/loader | ||
include $(dir)/Rules.mk | ||
|
||
dir := $(d)/plugins | ||
include $(dir)/Rules.mk | ||
|
||
include mk/footer.mk |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package plugin | ||
|
||
import ( | ||
"github.com/ipfs/go-ipfs/core/coredag" | ||
|
||
node "gx/ipfs/QmYNyRZJBUYPNrLszFmrBrPJbsBh2vMsefz5gnDpB5M1P6/go-ipld-format" | ||
) | ||
|
||
// PluginIPLD is an interface that can be implemented to add handlers for | ||
// for different IPLD formats | ||
type PluginIPLD interface { | ||
Plugin | ||
|
||
RegisterBlockDecoders(dec node.BlockDecoder) error | ||
RegisterInputEncParsers(iec coredag.InputEncParsers) error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
preload.go |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
include mk/header.mk | ||
|
||
$(d)/preload.go: d:=$(d) | ||
$(d)/preload.go: $(d)/preload_list | ||
$(d)/preload.sh > $@ | ||
go fmt $@ >/dev/null | ||
|
||
DEPS_GO += $(d)/preload.go | ||
|
||
include mk/footer.mk |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package loader | ||
|
||
import ( | ||
"github.com/ipfs/go-ipfs/core/coredag" | ||
"github.com/ipfs/go-ipfs/plugin" | ||
|
||
format "gx/ipfs/QmYNyRZJBUYPNrLszFmrBrPJbsBh2vMsefz5gnDpB5M1P6/go-ipld-format" | ||
) | ||
|
||
func initialize(plugins []plugin.Plugin) error { | ||
for _, p := range plugins { | ||
err := p.Init() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func run(plugins []plugin.Plugin) error { | ||
for _, pl := range plugins { | ||
err := runIPLDPlugin(pl) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func runIPLDPlugin(pl plugin.Plugin) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do a type switch up in the 'run' function, and have this function accept the proper type. Then we can error out if the types don't match correctly instead of silently ignoring the problem (which could easily happen with a mis-compiled plugin) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about it, but a Plugin could in theory implement multiple interfaces, then typeswich isn't really helping us as it will choose just one interface (the first in the switch statement). Miss-compiled plugin will error out or log if: the plugin lib can't be loaded, there is no |
||
ipldpl, ok := pl.(plugin.PluginIPLD) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
var err error | ||
err = ipldpl.RegisterBlockDecoders(format.DefaultBlockDecoder) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just |
||
if err != nil { | ||
return err | ||
} | ||
|
||
err = ipldpl.RegisterInputEncParsers(coredag.DefaultInputEncParsers) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package loader | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/ipfs/go-ipfs/plugin" | ||
|
||
logging "gx/ipfs/QmSpJByNKFX1sCsHBEp3R73FL4NF6FnQTEGyNAXHm2GS52/go-log" | ||
) | ||
|
||
var log = logging.Logger("plugin/loader") | ||
|
||
var loadPluginsFunc = func(string) ([]plugin.Plugin, error) { | ||
return nil, nil | ||
} | ||
|
||
// LoadPlugins loads and initializes plugins. | ||
func LoadPlugins(pluginDir string) ([]plugin.Plugin, error) { | ||
plMap := make(map[string]plugin.Plugin) | ||
for _, v := range preloadPlugins { | ||
plMap[v.Name()] = v | ||
} | ||
|
||
newPls, err := loadDynamicPlugins(pluginDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, pl := range newPls { | ||
if ppl, ok := plMap[pl.Name()]; ok { | ||
// plugin is already preloaded | ||
return nil, fmt.Errorf( | ||
"plugin: %s, is duplicated in version: %s, "+ | ||
"while trying to load dynamically: %s", | ||
ppl.Name(), ppl.Version(), pl.Version()) | ||
} | ||
plMap[pl.Name()] = pl | ||
} | ||
|
||
pls := make([]plugin.Plugin, 0, len(plMap)) | ||
for _, v := range plMap { | ||
pls = append(pls, v) | ||
} | ||
|
||
err = initialize(pls) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = run(pls) | ||
return nil, err | ||
} | ||
|
||
func loadDynamicPlugins(pluginDir string) ([]plugin.Plugin, error) { | ||
_, err := os.Stat(pluginDir) | ||
if os.IsNotExist(err) { | ||
return nil, nil | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return loadPluginsFunc(pluginDir) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if making this global is the right decision.
This will make testing it harder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we dont use a global, registration becomes a much more complicated thing to do. Any ideas on a good way to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we do the same thing we're doing with
BlockDecoder
s (pass it to register)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I am planning that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlockDecoder is doing the same (it is using DefaultBlockDecoder from go-ipld-format).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. The problem isn't really registration, it's using the the decoders (e.g. in
ParseInputs
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Plugin API for IPLD I've sketched out I've made the registration map an argument so at least the plugins can be tested.
We have to figure out how to use the decoders well with non-global states.