diff --git a/plugin/entrySchema.go b/plugin/entrySchema.go index d858ff5aa..9904785af 100644 --- a/plugin/entrySchema.go +++ b/plugin/entrySchema.go @@ -18,12 +18,31 @@ func TypeID(e Entry) string { pluginName := pluginName(e) rawTypeID := rawTypeID(e) if pluginName == "" { - // e is the plugin registry + // e is the plugin registry or a core entry used by an external plugin return rawTypeID } return namespace(pluginName, rawTypeID) } +// SchemaGraph returns e's schema graph. This is effectively a +// map[string]plugin.EntrySchema object that preserves insertion +// order (where the string is each entry's type ID). +func SchemaGraph(e Entry) (*linkedhashmap.Map, error) { + s, err := schema(e) + if err != nil { + return nil, err + } + if s == nil { + return nil, nil + } + if s.graph == nil { + // e is a core plugin entry so fill its graph + s.graph = linkedhashmap.New() + s.fill(s.graph) + } + return s.graph, nil +} + func schema(e Entry) (*EntrySchema, error) { switch t := e.(type) { case externalPlugin: @@ -361,16 +380,9 @@ func pluginName(e Entry) string { switch e.(type) { case Root: return CName(e) - case *Registry: - return "" default: - // e has no ID. This is possible if e's from the apifs package. For now, - // it is enough to return "__apifs__" here because this is an unlikely - // edge case. - // - // TODO: Panic here once https://github.com/puppetlabs/wash/issues/438 - // is resolved. - return "__apifs__" + // e is an apifs entry or a core plugin entry used by an external plugin + return "" } } segments := strings.SplitN(trimmedID, "/", 2) diff --git a/plugin/external/coreEntries.go b/plugin/external/coreEntries.go index 29c0addb3..2bdee5c2e 100644 --- a/plugin/external/coreEntries.go +++ b/plugin/external/coreEntries.go @@ -10,7 +10,7 @@ import ( type coreEntry interface { createInstance(parent *pluginEntry, decodedEntry decodedExternalPluginEntry) (plugin.Entry, error) - schema() *plugin.EntrySchema + template() plugin.Entry } var coreEntries = map[string]coreEntry{ @@ -31,6 +31,6 @@ func (volumeFS) createInstance(parent *pluginEntry, e decodedExternalPluginEntry return volume.NewFS(e.Name, parent, int(opts.Maxdepth)), nil } -func (volumeFS) schema() *plugin.EntrySchema { - return (&volume.FS{}).Schema() +func (volumeFS) template() plugin.Entry { + return &volume.FS{} } diff --git a/plugin/external/pluginEntry.go b/plugin/external/pluginEntry.go index 3aa1c8e4c..fa90521d1 100644 --- a/plugin/external/pluginEntry.go +++ b/plugin/external/pluginEntry.go @@ -13,7 +13,6 @@ import ( "time" "github.com/getlantern/deepcopy" - "github.com/jinzhu/copier" "github.com/emirpasic/gods/maps/linkedhashmap" "github.com/puppetlabs/wash/activity" @@ -711,17 +710,24 @@ func unmarshalSchemaGraph(pluginName, rawTypeID string, stdout []byte) (*linkedh graph := linkedhashmap.New() putNode := func(rawTypeID string, rawSchema interface{}) error { if coreEnt, ok := coreEntries[rawTypeID]; ok { - pluginSchema := coreEnt.schema() - - // Copy only the public fields so we serialize it as just data. Uses copier because it uses - // reflect to copy public fields, rather than Marshal/UnmarshalJSON which we've overridden. - var schema plugin.EntrySchema - err := copier.Copy(&schema, pluginSchema) - if err != nil { - panic(fmt.Sprintf("should always be able to copy from EntrySchema to EntrySchema: %v", err)) + template := coreEnt.template() + rawTypeID = plugin.TypeID(template) + if populatedTypeIDs[rawTypeID] { + // We've already included this entry's schema-graph + return nil } + coreEntSchemaGraph, _ := plugin.SchemaGraph(template) + coreEntSchemaGraph.Each(func(rawTypeIDV interface{}, schemaV interface{}) { + // Munge the schema's children's type IDs + schema := schemaV.(plugin.EntrySchema) + children := []string{} + for _, child := range schema.Children { + children = append(children, namespace(pluginName, child)) + } + schema.Children = children + graph.Put(namespace(pluginName, rawTypeIDV.(string)), schema) + }) populatedTypeIDs[rawTypeID] = true - graph.Put(namespace(pluginName, rawTypeID), schema) return nil } @@ -764,6 +770,9 @@ func unmarshalSchemaGraph(pluginName, rawTypeID string, stdout []byte) (*linkedh } var namespacedChildren []string for _, child := range node.Children { + if coreEnt, ok := coreEntries[child]; ok { + child = plugin.TypeID(coreEnt.template()) + } requiredTypeIDs[child] = true namespacedChildren = append(namespacedChildren, namespace(pluginName, child)) } diff --git a/plugin/external/pluginEntry_test.go b/plugin/external/pluginEntry_test.go index aa6d16568..c842dc8cc 100644 --- a/plugin/external/pluginEntry_test.go +++ b/plugin/external/pluginEntry_test.go @@ -1385,6 +1385,61 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ValidInput() } } +func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_CoreEntries() { + entry := &pluginEntry{ + rawTypeID: "foo", + } + entry.SetTestID("fooPlugin") + + stdout := []byte(` +{ + "foo":{ + "label": "fooLabel", + "methods": ["list"], + "children": ["__volume::fs__"] + }, + "__volume::fs__": {} +} +`) + + graph, err := unmarshalSchemaGraph(pluginName(entry), rawTypeID(entry), stdout) + if suite.NoError(err) { + assertSchema := func(typeID string, assertFunc func(plugin.EntrySchema)) { + schema, found := graph.Get(typeID) + if !found { + suite.FailNow("expected %v to be present in schema graph", typeID) + } + assertFunc(schema.(plugin.EntrySchema)) + } + + // Ensure that only four nodes exist in schema graph -- "foo", volume::fs, + // volume::dir, and volume::file + suite.Equal(int(4), graph.Size()) + + // Now ensure that the right nodes are set in the graph + volumeFSTemplate := (&volumeFS{}).template() + assertSchema("fooPlugin::foo", func(s plugin.EntrySchema) { + expectedVolumeFSTypeID := fmt.Sprintf("fooPlugin::%v", plugin.TypeID(volumeFSTemplate)) + suite.Equal([]string{expectedVolumeFSTypeID}, s.Children) + }) + // Ensure that volume::fs' schema-graph's been merged with our schema graph + // and that all the type IDs are properly namespaced + volumeFSGraph, _ := plugin.SchemaGraph(volumeFSTemplate) + volumeFSGraph.Each(func(typeIDV interface{}, schemaV interface{}) { + typeID := typeIDV.(string) + schema := schemaV.(plugin.EntrySchema) + expectedChildren := []string{} + for _, child := range schema.Children { + expectedChildren = append(expectedChildren, "fooPlugin::"+child) + } + assertSchema("fooPlugin::"+typeID, func(s plugin.EntrySchema) { + suite.Equal(schema.Label, s.Label) + suite.Equal(expectedChildren, s.Children) + }) + }) + } +} + func TestExternalPluginEntry(t *testing.T) { suite.Run(t, new(ExternalPluginEntryTestSuite)) }