Repository: puppetlabs/wash
Pull request #419 (2019-07-22 18:55:59)

Use plugin.Schema() in `wash validate`
Signed-off-by: Enis Inan <enis.inan@puppet.com>

Contributions to this project require sign-off consistent with the [Developers Certificate of Origin](https://developercertificate.org). This can be as simple as using `git commit -s` on each commit.


Patch:
diff --git a/cmd/validate.go b/cmd/validate.go
index 69ed414..3b469a5 100644
--- a/cmd/validate.go
+++ b/cmd/validate.go
@@ -179,11 +179,7 @@ type criteria struct {
 
 func newCriteria(entry plugin.Entry) criteria {
 	var crit criteria
-	if schema := entry.Schema(); schema != nil {
-		crit.label = schema.Label
-		crit.typeID = schema.TypeID
-		crit.singleton = schema.Singleton
-	}
+	crit.typeID = plugin.TypeID(entry)
 	crit.list = plugin.ListAction().IsSupportedOn(entry)
 	crit.read = plugin.ReadAction().IsSupportedOn(entry)
 	crit.stream = plugin.StreamAction().IsSupportedOn(entry)
@@ -255,6 +251,23 @@ func processEntry(ctx context.Context, pw progress.Writer, wp cmdutil.Pool, e pl
 	defer wp.Done()
 	name := plugin.ID(e)
 	crit := newCriteria(e)
+	schema, err := plugin.Schema(e)
+	if err != nil {
+		errs <- err
+		return
+	}
+	if schema != nil {
+		if schema.TypeID != crit.typeID {
+			errs <- fmt.Errorf(
+				"type ID mismatch: schema type ID is %v; entry type ID is %v",
+				schema.TypeID,
+				crit.typeID,
+			)
+			return
+		}
+		crit.label = schema.Label
+		crit.singleton = schema.Singleton
+	}
 	tracker := progress.Tracker{Message: fmt.Sprintf("Testing %s %s", crit, name), Total: 4}
 	pw.AppendTracker(&tracker)
 
@@ -292,7 +305,7 @@ func processEntry(ctx context.Context, pw progress.Writer, wp cmdutil.Pool, e pl
 				ccrit := newCriteria(entry)
 				// If we have a TypeID, only explore children if they are different from the parent.
 				// This prevents simple recursion like volume directories containing more dirs.
-				if ccrit.typeID == "" || ccrit != crit {
+				if ccrit.typeID == "" || ccrit.typeID != crit.typeID {
 					groups[ccrit] = append(groups[ccrit], entry)
 				}
 			}
diff --git a/plugin/entrySchema.go b/plugin/entrySchema.go
index d2308d6..53bd1fe 100644
--- a/plugin/entrySchema.go
+++ b/plugin/entrySchema.go
@@ -47,7 +47,9 @@ func Schema(e Entry) (*EntrySchema, error) {
 				// Create a schema for root so that `stree <mountpoint>` can still display
 				// it.
 				childSchema = NewEntrySchema(root, CName(root))
-				childSchema.TypeID = namespace(root.name(), "Root")
+				// TODO: Namespace these to something like <root_name>::Root once
+				// https://github.com/puppetlabs/wash/issues/396 is resolved.
+				childSchema.TypeID = "__" + root.name() + "__"
 			}
 			childSchema.IsSingleton()
 			schema.Children = append(schema.Children, childSchema.TypeID)
diff --git a/plugin/externalPluginEntry.go b/plugin/externalPluginEntry.go
index 8ee8247..5a3da37 100644
--- a/plugin/externalPluginEntry.go
+++ b/plugin/externalPluginEntry.go
@@ -30,6 +30,7 @@ type externalPlugin interface {
 	// error-prone version of schema here, in the externalPlugin
 	// interface.
 	schema() (*EntrySchema, error)
+	// Capitalize this so it doesn't conflict with the "typeID" field.
 	TypeID() string
 }
 
@@ -74,7 +75,7 @@ func mungeToMethods(input []interface{}) (map[string]interface{}, error) {
 	return methods, nil
 }
 
-func (e decodedExternalPluginEntry) toExternalPluginEntry(pluginName string, schemaKnown bool, isRoot bool) (*externalPluginEntry, error) {
+func (e decodedExternalPluginEntry) toExternalPluginEntry(schemaKnown bool, isRoot bool) (*externalPluginEntry, error) {
 	if len(e.Name) <= 0 {
 		return nil, fmt.Errorf("the entry name must be provided")
 	}
@@ -127,7 +128,7 @@ func (e decodedExternalPluginEntry) toExternalPluginEntry(pluginName string, sch
 		methods:     methods,
 		state:       e.State,
 		schemaKnown: schemaKnown,
-		typeID:      namespace(pluginName, e.TypeID),
+		typeID:      e.TypeID,
 	}
 	entry.SetAttributes(e.Attributes)
 	entry.setCacheTTLs(e.CacheTTLs)
@@ -224,12 +225,12 @@ func (e *externalPluginEntry) schema() (*EntrySchema, error) {
 	}
 	var graph *linkedhashmap.Map
 	if e.schemaGraphs != nil {
-		g, ok := e.schemaGraphs[e.TypeID()]
+		g, ok := e.schemaGraphs[e.typeID]
 		if !ok {
 			msg := fmt.Errorf(
 				"e.Schema(): entry schemas were prefetched, but no schema graph was provided for %v (%v)",
 				ID(e),
-				e.rawTypeID(),
+				e.typeID,
 			)
 			panic(msg)
 		}
@@ -237,7 +238,7 @@ func (e *externalPluginEntry) schema() (*EntrySchema, error) {
 		// As a sanity check, ensure that the methods specified in the entry's schema
 		// match the methods specified in the entry instance. Return an error if there
 		// is a mismatch. Hopefully this should never happen.
-		es, _ := graph.Get(e.TypeID())
+		es, _ := graph.Get(e.typeID)
 		schemaMethods := es.(entrySchema).Actions
 		instanceMethods := e.supportedMethods()
 		sort.Strings(schemaMethods)
@@ -245,7 +246,7 @@ func (e *externalPluginEntry) schema() (*EntrySchema, error) {
 		mismatchErr := fmt.Errorf(
 			"%v (%v): the schema's supported methods (%v) don't match the instance's supported methods (%v)",
 			ID(e),
-			e.rawTypeID(),
+			e.typeID,
 			strings.Join(schemaMethods, ", "),
 			strings.Join(instanceMethods, ", "),
 		)
@@ -271,7 +272,7 @@ func (e *externalPluginEntry) schema() (*EntrySchema, error) {
 			err := fmt.Errorf(
 				"%v (%v): failed to retrieve the entry's schema: %v",
 				ID(e),
-				e.rawTypeID(),
+				e.typeID,
 				err,
 			)
 			return nil, err
@@ -281,7 +282,7 @@ func (e *externalPluginEntry) schema() (*EntrySchema, error) {
 			err := fmt.Errorf(
 				"%v (%v): could not decode schema from stdout: %v\nreceived:\n%v\nexpected something like:\n%v",
 				ID(e),
-				e.rawTypeID(),
+				e.typeID,
 				err,
 				strings.TrimSpace(inv.stdout.String()),
 				schemaFormat,
@@ -318,10 +319,9 @@ func (e *externalPluginEntry) List(ctx context.Context) ([]Entry, error) {
 		}
 	}
 
-	pluginName := e.pluginName()
 	entries := make([]Entry, len(decodedEntries))
 	for i, decodedExternalPluginEntry := range decodedEntries {
-		entry, err := decodedExternalPluginEntry.toExternalPluginEntry(pluginName, e.schemaKnown, false)
+		entry, err := decodedExternalPluginEntry.toExternalPluginEntry(e.schemaKnown, false)
 		if err != nil {
 			return nil, err
 		}
@@ -516,50 +516,12 @@ func newStdoutDecodeErr(ctx context.Context, decodedThing string, reason error,
 	return newInvokeError(fmt.Sprintf("could not decode %v from stdout: %v", decodedThing, reason), inv)
 }
 
-func (e *externalPluginEntry) pluginName() string {
-	pluginName, _ := e.splitTypeID()
-	return pluginName
-}
-
-func (e *externalPluginEntry) rawTypeID() string {
-	_, rawTypeID := e.splitTypeID()
-	return rawTypeID
-}
-
-// splits the type ID into a tuple (<plugin_name>, <raw_type_id>)
-func (e *externalPluginEntry) splitTypeID() (string, string) {
-	substrings := strings.SplitN(e.TypeID(), "::", 2)
-	if len(substrings) < 2 {
-		// We can hit this case if only some of the entries specify type IDs.
-		// This is impossible in the (common) entry schema scenario because
-		// entry schema support always requires type IDs. So, it is not an
-		// error.
-		//
-		// If we hit this case, then the concept of a type ID doesn't make
-		// sense so we return empty here. Note that returning substrings[0]
-		// is not a good idea b/c the plugin author could namespace their
-		// type IDs with "::", so substrings[0] would contain that initial
-		// chunk.
-		return "__unknown__", ""
-	}
-	return substrings[0], substrings[1]
-}
-
-func namespace(pluginName string, typeID string) string {
-	if len(typeID) == 0 {
-		typeID = "unknown"
-	}
-	return pluginName + "::" + typeID
-}
-
 func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashmap.Map, error) {
-	pluginName, rawTypeID := e.splitTypeID()
-
 	// Since we know e's type ID, it is OK if the serialized schema's keys are
 	// out of order. However, the entry schema returned by the Wash API always
 	// ensures that the first key is the entry's type ID. Thus, we unmarshal the
 	// schema as a map[string]interface{} object, then convert it to a linkedhashmap
-	// object so that we ensure the "first key == e.TypeID()" requirement of the API.
+	// object so that we ensure the "first key == e.typeID" requirement of the API.
 	// We'll also validate the unmarshalled schema in the latter conversion.
 	var rawGraph map[string]interface{}
 	if err := json.Unmarshal(stdout, &rawGraph); err != nil {
@@ -568,8 +530,8 @@ func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashma
 	if len(rawGraph) <= 0 {
 		return nil, fmt.Errorf("expected a non-empty JSON object")
 	}
-	if rawGraph[rawTypeID] == nil {
-		return nil, fmt.Errorf("%v's schema is missing", rawTypeID)
+	if rawGraph[e.typeID] == nil {
+		return nil, fmt.Errorf("%v's schema is missing", e.typeID)
 	}
 
 	// Convert each node in the rawGraph to an EntrySchema object. This step
@@ -580,7 +542,7 @@ func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashma
 	// populatedTypeIDs/requiredTypeIDs variables.
 	populatedTypeIDs := make(map[string]bool)
 	requiredTypeIDs := map[string]bool{
-		rawTypeID: true,
+		e.typeID: true,
 	}
 	type decodedEntrySchema struct {
 		entrySchema
@@ -600,8 +562,8 @@ func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashma
 		}
 
 		// Ensure that all required fields are present
-		populatedTypeIDs[typeID] = true
-		node.TypeID = namespace(pluginName, typeID)
+		node.TypeID = typeID
+		populatedTypeIDs[node.TypeID] = true
 		if len(node.Label) <= 0 {
 			return fmt.Errorf("a label must be provided")
 		}
@@ -622,12 +584,9 @@ func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashma
 			if len(node.Children) <= 0 {
 				return fmt.Errorf("parent entries must specify their children's type IDs")
 			}
-			var namespacedChildren []string
 			for _, child := range node.Children {
 				requiredTypeIDs[child] = true
-				namespacedChildren = append(namespacedChildren, namespace(pluginName, child))
 			}
-			node.Children = namespacedChildren
 		}
 		if node.MetaAttributeSchema != nil && node.MetaAttributeSchema.Type.Type != "object" {
 			return fmt.Errorf("invalid value for the meta attribute schema: expected a JSON object schema but got %v", node.MetaAttributeSchema.Type.Type)
@@ -640,13 +599,13 @@ func (e *externalPluginEntry) unmarshalSchemaGraph(stdout []byte) (*linkedhashma
 		// We don't put node itself in because doing so would marshal its "Methods"
 		// field.
 		node.Actions = node.Methods
-		graph.Put(node.TypeID, node.entrySchema)
+		graph.Put(typeID, node.entrySchema)
 		return nil
 	}
-	if err := putNode(rawTypeID, rawGraph[rawTypeID]); err != nil {
+	if err := putNode(e.typeID, rawGraph[e.typeID]); err != nil {
 		return nil, err
 	}
-	delete(rawGraph, rawTypeID)
+	delete(rawGraph, e.typeID)
 	for typeID, schema := range rawGraph {
 		if err := putNode(typeID, schema); err != nil {
 			return nil, err
diff --git a/plugin/externalPluginRoot.go b/plugin/externalPluginRoot.go
index d6be9e5..c3575c6 100644
--- a/plugin/externalPluginRoot.go
+++ b/plugin/externalPluginRoot.go
@@ -17,9 +17,6 @@ type externalPluginRoot struct {
 
 // Init initializes the external plugin root
 func (r *externalPluginRoot) Init(cfg map[string]interface{}) error {
-	if cfg == nil {
-		cfg = make(map[string]interface{})
-	}
 	cfgJSON, err := json.Marshal(cfg)
 	if err != nil {
 		return fmt.Errorf("could not marshal plugin config %v into JSON: %v", cfg, err)
@@ -59,7 +56,7 @@ it's safe to omit name from the response to 'init'`, r.script.Path()))
 	if decodedRoot.Methods == nil {
 		decodedRoot.Methods = []interface{}{"list"}
 	}
-	entry, err := decodedRoot.toExternalPluginEntry(r.Name(), false, true)
+	entry, err := decodedRoot.toExternalPluginEntry(false, true)
 	if err != nil {
 		return err
 	}
@@ -85,7 +82,7 @@ it's safe to omit name from the response to 'init'`, r.script.Path()))
 				schemaFormat,
 			)
 		}
-		r.schemaGraphs = r.partitionSchemaGraph(graph)
+		r.schemaGraphs = partitionSchemaGraph(graph)
 	}
 
 	return nil
@@ -98,7 +95,7 @@ func (r *externalPluginRoot) WrappedTypes() SchemaMap {
 }
 
 // partitionSchemaGraph partitions graph into a map of <type_id> => <schema_graph>
-func (r *externalPluginRoot) partitionSchemaGraph(graph *linkedhashmap.Map) map[string]*linkedhashmap.Map {
+func partitionSchemaGraph(graph *linkedhashmap.Map) map[string]*linkedhashmap.Map {
 	var populate func(*linkedhashmap.Map, entrySchema, map[string]bool)
 	populate = func(g *linkedhashmap.Map, node entrySchema, visited map[string]bool) {
 		if visited[node.TypeID] {


Test patch:
diff --git a/plugin/externalPluginEntry_test.go b/plugin/externalPluginEntry_test.go
index 320e2f6..f8f8094 100644
--- a/plugin/externalPluginEntry_test.go
+++ b/plugin/externalPluginEntry_test.go
@@ -67,15 +67,15 @@ type ExternalPluginEntryTestSuite struct {
 func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryRequiredFields() {
 	decodedEntry := decodedExternalPluginEntry{}
 
-	_, err := decodedEntry.toExternalPluginEntry("", false, false)
+	_, err := decodedEntry.toExternalPluginEntry(false, false)
 	suite.Regexp("name", err)
 	decodedEntry.Name = "decodedEntry"
 
-	_, err = decodedEntry.toExternalPluginEntry("", false, false)
+	_, err = decodedEntry.toExternalPluginEntry(false, false)
 	suite.Regexp("methods", err)
 	decodedEntry.Methods = []interface{}{"list"}
 
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(decodedEntry.Name, entry.name())
 		suite.Equal(1, len(entry.methods))
@@ -90,7 +90,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryExtraFie
 		Methods: []interface{}{"list", "stream"},
 	}
 
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(decodedEntry.Name, entry.name())
 		suite.Contains(entry.methods, "list")
@@ -112,7 +112,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntry_Support
 		Methods: []interface{}{},
 	}
 
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(decodedEntry.Name, entry.name())
 	}
@@ -125,7 +125,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithMeth
 		Methods: []interface{}{[]interface{}{"list", []interface{}{childEntry}}, "read"},
 	}
 
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(decodedEntry.Name, entry.name())
 		suite.Contains(entry.methods, "list")
@@ -146,7 +146,7 @@ func newMockDecodedEntry(name string) decodedExternalPluginEntry {
 func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithState() {
 	decodedEntry := newMockDecodedEntry("name")
 	decodedEntry.State = "some state"
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(decodedEntry.State, entry.state)
 	}
@@ -155,7 +155,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithStat
 func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithCacheTTLs() {
 	decodedEntry := newMockDecodedEntry("name")
 	decodedEntry.CacheTTLs = decodedCacheTTLs{List: 1}
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		expectedTTLs := NewEntry("foo").ttl
 		expectedTTLs[ListOp] = decodedEntry.CacheTTLs.List * time.Second
@@ -167,11 +167,11 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSlas
 	decodedEntry := newMockDecodedEntry("name")
 	decodedEntry.SlashReplacer = "a string"
 	suite.Panics(
-		func() { _, _ = decodedEntry.toExternalPluginEntry("", false, false) },
+		func() { _, _ = decodedEntry.toExternalPluginEntry(false, false) },
 		"e.SlashReplacer: received string a string instead of a character",
 	)
 	decodedEntry.SlashReplacer = ":"
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.Equal(':', entry.slashReplacer())
 	}
@@ -181,7 +181,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithAttr
 	decodedEntry := newMockDecodedEntry("name")
 	t := time.Now()
 	decodedEntry.Attributes.SetCtime(t)
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		expectedAttr := EntryAttributes{}
 		expectedAttr.SetCtime(t)
@@ -194,7 +194,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Name:    "decodedEntry",
 		Methods: []interface{}{"list"},
 	}
-	entry, err := decodedEntry.toExternalPluginEntry("", false, false)
+	entry, err := decodedEntry.toExternalPluginEntry(false, false)
 	if suite.NoError(err) {
 		suite.False(entry.schemaKnown)
 	}
@@ -205,7 +205,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Name:    "decodedEntry",
 		Methods: []interface{}{"list", "schema"},
 	}
-	_, err := decodedEntry.toExternalPluginEntry("", false, false)
+	_, err := decodedEntry.toExternalPluginEntry(false, false)
 	suite.Regexp("decodedEntry.*implements.*schema.*no.*type.*ID", err)
 }
 
@@ -215,7 +215,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Methods: []interface{}{"list", "schema"},
 		TypeID:  "foo",
 	}
-	_, err := decodedEntry.toExternalPluginEntry("", false, false)
+	_, err := decodedEntry.toExternalPluginEntry(false, false)
 	suite.Regexp("decodedEntry.*foo.*implements.*schema.*root", err)
 }
 
@@ -225,7 +225,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Methods: []interface{}{"list"},
 		TypeID:  "foo",
 	}
-	_, err := decodedEntry.toExternalPluginEntry("", true, false)
+	_, err := decodedEntry.toExternalPluginEntry(true, false)
 	suite.Regexp("decodedEntry.*foo.*must.*implement.*schema", err)
 }
 
@@ -234,7 +234,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Name:    "decodedEntry",
 		Methods: []interface{}{"list", "schema"},
 	}
-	_, err := decodedEntry.toExternalPluginEntry("", true, false)
+	_, err := decodedEntry.toExternalPluginEntry(true, false)
 	suite.Regexp("decodedEntry.*implements.*schema.*no.*type.*ID", err)
 }
 
@@ -247,7 +247,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		},
 		TypeID: "foo",
 	}
-	_, err := decodedEntry.toExternalPluginEntry("", true, false)
+	_, err := decodedEntry.toExternalPluginEntry(true, false)
 	suite.Regexp("decodedEntry.*foo.*plugin.*roots.*support.*prefetching", err)
 }
 
@@ -257,10 +257,9 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSche
 		Methods: []interface{}{"list", "schema"},
 		TypeID:  "foo",
 	}
-	entry, err := decodedEntry.toExternalPluginEntry("fooPlugin", true, false)
+	entry, err := decodedEntry.toExternalPluginEntry(true, false)
 	if suite.NoError(err) {
 		suite.True(entry.schemaKnown)
-		suite.Equal("fooPlugin::foo", entry.typeID)
 	}
 }
 
@@ -299,7 +298,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_DoesNotImplementSchema_Ret
 func (suite *ExternalPluginEntryTestSuite) TestSchema_Prefetched_PanicsIfNoSchemaGraphWasProvided() {
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 		},
@@ -317,7 +316,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_Prefetched_ReturnsTheSchem
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 		},
@@ -327,16 +326,16 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_Prefetched_ReturnsTheSchem
 	entry.SetTestID("/foo")
 	graph := linkedhashmap.New()
 	graph.Put(
-		entry.TypeID(),
+		entry.typeID,
 		entrySchema{
 			Actions: []string{"schema"},
 		},
 	)
-	entry.schemaGraphs[entry.TypeID()] = graph
+	entry.schemaGraphs[entry.typeID] = graph
 
 	s, err := entry.schema()
 	if suite.NoError(err) {
-		suite.Equal(entry.schemaGraphs[entry.TypeID()], s.graph)
+		suite.Equal(entry.schemaGraphs[entry.typeID], s.graph)
 		// Make sure that Wash did not shell out to the plugin script
 		mockScript.AssertNotCalled(suite.T(), "InvokeAndWait")
 	}
@@ -346,7 +345,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_Prefetched_ReturnsErrorIfS
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 			"read":   nil,
@@ -357,12 +356,12 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_Prefetched_ReturnsErrorIfS
 	entry.SetTestID("/foo")
 	graph := linkedhashmap.New()
 	graph.Put(
-		entry.TypeID(),
+		entry.typeID,
 		entrySchema{
 			Actions: []string{"list", "exec"},
 		},
 	)
-	entry.schemaGraphs[entry.TypeID()] = graph
+	entry.schemaGraphs[entry.typeID] = graph
 
 	_, err := entry.schema()
 	suite.Regexp("schema.*methods.*exec.*list.*instance.*methods.*read.*schema", err)
@@ -372,7 +371,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_ReturnsError
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 		},
@@ -390,7 +389,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_ReturnsError
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 		},
@@ -408,7 +407,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_SuccessfulIn
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	entry := &externalPluginEntry{
 		EntryBase: NewEntry("foo"),
-		typeID:    "fooPlugin::baz.fooTypeID",
+		typeID:    "fooTypeID",
 		methods: map[string]interface{}{
 			"schema": nil,
 		},
@@ -418,20 +417,20 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_SuccessfulIn
 
 	stdout := `
 {
-	"baz.fooTypeID": {
+	"fooTypeID": {
 		"label": "fooEntry",
 		"methods": ["list"],
-		"children": ["baz.barTypeID"],
+		"children": ["barTypeID"],
 		"singleton": true,
 		"meta_attribute_schema": {
 			"type": "object"
 		},
 		"metadata_schema": null
 	},
-	"baz.barTypeID": {
+	"barTypeID": {
 		"label": "barEntry",
 		"methods": ["list"],
-		"children": ["baz.barTypeID"],
+		"children": ["barTypeID"],
 		"singleton": false,
 		"meta_attribute_schema": {
 			"type": "object",
@@ -451,7 +450,6 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_SuccessfulIn
 		schemaJSON, err := json.Marshal(schema)
 		if suite.NoError(err) {
 			stdout = strings.ReplaceAll(stdout, "methods", "actions")
-			stdout = strings.ReplaceAll(stdout, "baz.", "fooPlugin::baz.")
 			suite.JSONEq(stdout, string(schemaJSON))
 		}
 	}
@@ -465,7 +463,6 @@ func (suite *ExternalPluginEntryTestSuite) TestList() {
 		schemaGraphs: map[string]*linkedhashmap.Map{
 			"foo": linkedhashmap.New(),
 		},
-		typeID: "fooPlugin::foo_type",
 	}
 	entry.SetTestID("/foo")
 
@@ -488,7 +485,7 @@ func (suite *ExternalPluginEntryTestSuite) TestList() {
 
 	// Test that List properly decodes the entries from stdout
 	stdout := "[" +
-		"{\"name\":\"foo\",\"methods\":[\"list\"],\"type_id\":\"bar\"}" +
+		"{\"name\":\"foo\",\"methods\":[\"list\"]}" +
 		"]"
 	mockInvokeAndWait([]byte(stdout), nil)
 	entries, err := entry.List(ctx)
@@ -500,7 +497,6 @@ func (suite *ExternalPluginEntryTestSuite) TestList() {
 				methods:      map[string]interface{}{"list": nil},
 				script:       entry.script,
 				schemaGraphs: entry.schemaGraphs,
-				typeID:       "fooPlugin::bar",
 			},
 		}
 
@@ -694,7 +690,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfAnEm
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfTypeIDNotPresent() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 	stdout := []byte(`
 {
@@ -706,7 +702,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfType
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnMalformedSchema() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	// Error should indicate that foo's schema is not a JSON object.
@@ -746,7 +742,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnMalf
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfLabelNotProvided() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -760,7 +756,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfLabe
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfMethodsNotProvided() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -776,7 +772,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfMeth
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfNotParentAndChildrenProvided() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -794,7 +790,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfNotP
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfParentAndChildrenNotProvided() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -812,7 +808,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfPare
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfMissingChildSchema() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -830,7 +826,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfMiss
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfSchemaIncludesDanglingTypeIDs() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -860,7 +856,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfSche
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInvalidMetaAttributeSchema() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -880,7 +876,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInva
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInvalidMetadataSchema() {
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::foo",
+		typeID: "foo",
 	}
 
 	stdout := []byte(`
@@ -905,18 +901,17 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ValidInput()
 	stdout := readSchemaFixture(suite.Suite, "externalPluginSchema")
 
 	entry := &externalPluginEntry{
-		typeID: "fooPlugin::aws.Root",
+		typeID: "aws.Root",
 	}
 	actualGraph, err := entry.unmarshalSchemaGraph(stdout)
 	if suite.NoError(err) {
 		// Check that the first key is aws.Root
 		it := actualGraph.Iterator()
 		it.First()
-		suite.Equal(it.Key(), entry.TypeID())
+		suite.Equal(it.Key(), entry.typeID)
 
 		// Now check that all of stdout was successfully unmarshalled.
 		stdout = bytes.ReplaceAll(stdout, []byte("methods"), []byte("actions"))
-		stdout = bytes.ReplaceAll(stdout, []byte("aws."), []byte("fooPlugin::aws."))
 		actualGraphJSON, err := actualGraph.ToJSON()
 		if suite.NoError(err) {
 			suite.JSONEq(string(stdout), string(actualGraphJSON))
diff --git a/plugin/externalPluginRoot_test.go b/plugin/externalPluginRoot_test.go
index 872596e..c794f36 100644
--- a/plugin/externalPluginRoot_test.go
+++ b/plugin/externalPluginRoot_test.go
@@ -28,7 +28,7 @@ func (suite *ExternalPluginRootTestSuite) TestInit() {
 			mock.Anything,
 			"init",
 			nil,
-			"{}",
+			"null",
 		).Return(mockInvocation(stdout), err).Once()
 	}
 
@@ -45,7 +45,7 @@ func (suite *ExternalPluginRootTestSuite) TestInit() {
 	suite.Regexp(regexp.MustCompile("stdout"), err)
 
 	// Test that Init properly decodes the root from stdout
-	stdout := "{\"type_id\":\"foo_type\"}"
+	stdout := "{}"
 	mockInvokeAndWait([]byte(stdout), nil)
 	err = root.Init(nil)
 	if suite.NoError(err) {
@@ -54,7 +54,6 @@ func (suite *ExternalPluginRootTestSuite) TestInit() {
 				EntryBase: NewEntry("foo"),
 				methods:   map[string]interface{}{"list": nil},
 				script:    root.script,
-				typeID:    "foo::foo_type",
 			},
 		}
 
@@ -90,7 +89,7 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_SetsSchemaKnownVari
 		mock.Anything,
 		"init",
 		nil,
-		"{}",
+		"null",
 	).Return(mockInvocation([]byte("{\"type_id\":\"root\",\"methods\":[\"schema\",\"list\"]}")), nil).Once()
 
 	suite.NoError(root.Init(nil))
@@ -108,7 +107,7 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_Re
 		mock.Anything,
 		"init",
 		nil,
-		"{}",
+		"null",
 	).Return(mockInvocation([]byte("{\"type_id\":\"root\",\"methods\":[[\"schema\", \"foo\"],\"list\"]}")), nil).Once()
 
 	err := root.Init(nil)
@@ -121,7 +120,7 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_Re
 func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_PartitionsSchemaGraph() {
 	mockScript := &mockExternalPluginScript{path: "plugin_script"}
 	root := &externalPluginRoot{&externalPluginEntry{
-		EntryBase: NewEntry("fooPlugin"),
+		EntryBase: NewEntry("foo"),
 		script:    mockScript,
 	}}
 
@@ -150,7 +149,7 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_Pa
 		mock.Anything,
 		"init",
 		nil,
-		"{}",
+		"null",
 	).Return(mockInvocation(stdout), nil).Once()
 
 	// Perform the test
@@ -158,12 +157,11 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_Pa
 	if err := root.Init(nil); suite.NoError(err) && suite.NotNil(root.schemaGraphs) {
 		// Ensure that the graph of root.schemaGraphs[type_id] has "type_id" as its
 		// first item. We pick an arbitrary type ID here
-		typeID := namespace(root.name(), "aws.profile")
-		graph := root.schemaGraphs[typeID]
+		graph := root.schemaGraphs["aws.profile"]
 		if suite.NotNil(graph) {
 			it := graph.Iterator()
 			it.First()
-			suite.Equal(typeID, it.Key())
+			suite.Equal("aws.profile", it.Key())
 		}
 
 		// Now ensure that the rest of root.schemaGraphs is what we expect. We already
diff --git a/plugin/testdata/externalPluginSchema.json b/plugin/testdata/externalPluginSchema.json
index 0e60cfc..15b8f59 100644
--- a/plugin/testdata/externalPluginSchema.json
+++ b/plugin/testdata/externalPluginSchema.json
@@ -1134,7 +1134,7 @@
         "children": [
             "aws.ec2InstanceConsoleOutput",
             "aws.ec2InstanceMetadataJSON",
-            "aws.volume.FS"
+            "volume.FS"
         ]
     },
     "aws.ec2InstanceConsoleOutput": {
@@ -1175,7 +1175,7 @@
         "metadata_schema": null,
         "children": null
     },
-    "aws.volume.FS": {
+    "volume.FS": {
         "label": "fs",
         "singleton": false,
         "methods": [
@@ -1194,11 +1194,11 @@
         },
         "metadata_schema": null,
         "children": [
-            "aws.volume.dir",
-            "aws.volume.file"
+            "volume.dir",
+            "volume.file"
         ]
     },
-    "aws.volume.dir": {
+    "volume.dir": {
         "label": "dir",
         "singleton": false,
         "methods": [
@@ -1217,11 +1217,11 @@
         },
         "metadata_schema": null,
         "children": [
-            "aws.volume.dir",
-            "aws.volume.file"
+            "volume.dir",
+            "volume.file"
         ]
     },
-    "aws.volume.file": {
+    "volume.file": {
         "label": "file",
         "singleton": false,
         "methods": [
diff --git a/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json b/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
index 2e14150..5da382b 100644
--- a/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
+++ b/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
@@ -1,6 +1,6 @@
 {
-    "fooPlugin::aws.Root": {
-        "fooPlugin::aws.Root": {
+    "aws.Root": {
+        "aws.Root": {
             "label": "aws",
             "singleton": true,
             "actions": [
@@ -9,10 +9,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.profile"
+                "aws.profile"
             ]
         },
-        "fooPlugin::aws.profile": {
+        "aws.profile": {
             "label": "profile",
             "singleton": false,
             "actions": [
@@ -21,10 +21,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.resourcesDir"
+                "aws.resourcesDir"
             ]
         },
-        "fooPlugin::aws.resourcesDir": {
+        "aws.resourcesDir": {
             "label": "resources",
             "singleton": true,
             "actions": [
@@ -33,11 +33,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Dir",
-                "fooPlugin::aws.ec2Dir"
+                "aws.s3Dir",
+                "aws.ec2Dir"
             ]
         },
-        "fooPlugin::aws.s3Dir": {
+        "aws.s3Dir": {
             "label": "s3",
             "singleton": true,
             "actions": [
@@ -46,10 +46,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Bucket"
+                "aws.s3Bucket"
             ]
         },
-        "fooPlugin::aws.s3Bucket": {
+        "aws.s3Bucket": {
             "label": "bucket",
             "singleton": false,
             "actions": [
@@ -58,11 +58,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -71,11 +71,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -86,7 +86,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2Dir": {
+        "aws.ec2Dir": {
             "label": "ec2",
             "singleton": true,
             "actions": [
@@ -95,10 +95,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstancesDir"
+                "aws.ec2InstancesDir"
             ]
         },
-        "fooPlugin::aws.ec2InstancesDir": {
+        "aws.ec2InstancesDir": {
             "label": "instances",
             "singleton": true,
             "actions": [
@@ -107,10 +107,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2Instance"
+                "aws.ec2Instance"
             ]
         },
-        "fooPlugin::aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -120,12 +120,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -135,7 +135,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -145,7 +145,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -154,11 +154,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -167,11 +167,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -183,8 +183,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.ec2Dir": {
-        "fooPlugin::aws.ec2Dir": {
+    "aws.ec2Dir": {
+        "aws.ec2Dir": {
             "label": "ec2",
             "singleton": true,
             "actions": [
@@ -193,10 +193,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstancesDir"
+                "aws.ec2InstancesDir"
             ]
         },
-        "fooPlugin::aws.ec2InstancesDir": {
+        "aws.ec2InstancesDir": {
             "label": "instances",
             "singleton": true,
             "actions": [
@@ -205,10 +205,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2Instance"
+                "aws.ec2Instance"
             ]
         },
-        "fooPlugin::aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -218,12 +218,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -233,7 +233,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -243,7 +243,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -252,11 +252,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -265,11 +265,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -281,8 +281,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.ec2Instance": {
-        "fooPlugin::aws.ec2Instance": {
+    "aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -292,12 +292,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -307,7 +307,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -317,7 +317,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -326,11 +326,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -339,11 +339,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -355,8 +355,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.ec2InstanceConsoleOutput": {
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+    "aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -367,8 +367,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.ec2InstanceMetadataJSON": {
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+    "aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -379,8 +379,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.ec2InstancesDir": {
-        "fooPlugin::aws.ec2InstancesDir": {
+    "aws.ec2InstancesDir": {
+        "aws.ec2InstancesDir": {
             "label": "instances",
             "singleton": true,
             "actions": [
@@ -389,10 +389,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2Instance"
+                "aws.ec2Instance"
             ]
         },
-        "fooPlugin::aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -402,12 +402,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -417,7 +417,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -427,7 +427,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -436,11 +436,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -449,11 +449,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -465,8 +465,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.profile": {
-        "fooPlugin::aws.profile": {
+    "aws.profile": {
+        "aws.profile": {
             "label": "profile",
             "singleton": false,
             "actions": [
@@ -475,10 +475,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.resourcesDir"
+                "aws.resourcesDir"
             ]
         },
-        "fooPlugin::aws.resourcesDir": {
+        "aws.resourcesDir": {
             "label": "resources",
             "singleton": true,
             "actions": [
@@ -487,11 +487,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Dir",
-                "fooPlugin::aws.ec2Dir"
+                "aws.s3Dir",
+                "aws.ec2Dir"
             ]
         },
-        "fooPlugin::aws.s3Dir": {
+        "aws.s3Dir": {
             "label": "s3",
             "singleton": true,
             "actions": [
@@ -500,10 +500,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Bucket"
+                "aws.s3Bucket"
             ]
         },
-        "fooPlugin::aws.s3Bucket": {
+        "aws.s3Bucket": {
             "label": "bucket",
             "singleton": false,
             "actions": [
@@ -512,11 +512,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -525,11 +525,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -540,7 +540,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2Dir": {
+        "aws.ec2Dir": {
             "label": "ec2",
             "singleton": true,
             "actions": [
@@ -549,10 +549,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstancesDir"
+                "aws.ec2InstancesDir"
             ]
         },
-        "fooPlugin::aws.ec2InstancesDir": {
+        "aws.ec2InstancesDir": {
             "label": "instances",
             "singleton": true,
             "actions": [
@@ -561,10 +561,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2Instance"
+                "aws.ec2Instance"
             ]
         },
-        "fooPlugin::aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -574,12 +574,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -589,7 +589,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -599,7 +599,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -608,11 +608,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -621,11 +621,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -637,8 +637,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.resourcesDir": {
-        "fooPlugin::aws.resourcesDir": {
+    "aws.resourcesDir": {
+        "aws.resourcesDir": {
             "label": "resources",
             "singleton": true,
             "actions": [
@@ -647,11 +647,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Dir",
-                "fooPlugin::aws.ec2Dir"
+                "aws.s3Dir",
+                "aws.ec2Dir"
             ]
         },
-        "fooPlugin::aws.s3Dir": {
+        "aws.s3Dir": {
             "label": "s3",
             "singleton": true,
             "actions": [
@@ -660,10 +660,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Bucket"
+                "aws.s3Bucket"
             ]
         },
-        "fooPlugin::aws.s3Bucket": {
+        "aws.s3Bucket": {
             "label": "bucket",
             "singleton": false,
             "actions": [
@@ -672,11 +672,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -685,11 +685,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -700,7 +700,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2Dir": {
+        "aws.ec2Dir": {
             "label": "ec2",
             "singleton": true,
             "actions": [
@@ -709,10 +709,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstancesDir"
+                "aws.ec2InstancesDir"
             ]
         },
-        "fooPlugin::aws.ec2InstancesDir": {
+        "aws.ec2InstancesDir": {
             "label": "instances",
             "singleton": true,
             "actions": [
@@ -721,10 +721,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2Instance"
+                "aws.ec2Instance"
             ]
         },
-        "fooPlugin::aws.ec2Instance": {
+        "aws.ec2Instance": {
             "label": "instance",
             "singleton": false,
             "actions": [
@@ -734,12 +734,12 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.ec2InstanceConsoleOutput",
-                "fooPlugin::aws.ec2InstanceMetadataJSON",
-                "fooPlugin::aws.volume.FS"
+                "aws.ec2InstanceConsoleOutput",
+                "aws.ec2InstanceMetadataJSON",
+                "volume.FS"
             ]
         },
-        "fooPlugin::aws.ec2InstanceConsoleOutput": {
+        "aws.ec2InstanceConsoleOutput": {
             "label": "console.out",
             "singleton": false,
             "actions": [
@@ -749,7 +749,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.ec2InstanceMetadataJSON": {
+        "aws.ec2InstanceMetadataJSON": {
             "label": "metadata.json",
             "singleton": true,
             "actions": [
@@ -759,7 +759,7 @@
             "metadata_schema": null,
             "children": null
         },
-        "fooPlugin::aws.volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -768,11 +768,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -781,11 +781,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -797,8 +797,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.s3Bucket": {
-        "fooPlugin::aws.s3Bucket": {
+    "aws.s3Bucket": {
+        "aws.s3Bucket": {
             "label": "bucket",
             "singleton": false,
             "actions": [
@@ -807,11 +807,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -820,11 +820,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -836,8 +836,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.s3Dir": {
-        "fooPlugin::aws.s3Dir": {
+    "aws.s3Dir": {
+        "aws.s3Dir": {
             "label": "s3",
             "singleton": true,
             "actions": [
@@ -846,10 +846,10 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3Bucket"
+                "aws.s3Bucket"
             ]
         },
-        "fooPlugin::aws.s3Bucket": {
+        "aws.s3Bucket": {
             "label": "bucket",
             "singleton": false,
             "actions": [
@@ -858,11 +858,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -871,11 +871,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -887,8 +887,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.s3Object": {
-        "fooPlugin::aws.s3Object": {
+    "aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -900,8 +900,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.s3ObjectPrefix": {
-        "fooPlugin::aws.s3ObjectPrefix": {
+    "aws.s3ObjectPrefix": {
+        "aws.s3ObjectPrefix": {
             "label": "prefix",
             "singleton": false,
             "actions": [
@@ -910,11 +910,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.s3ObjectPrefix",
-                "fooPlugin::aws.s3Object"
+                "aws.s3ObjectPrefix",
+                "aws.s3Object"
             ]
         },
-        "fooPlugin::aws.s3Object": {
+        "aws.s3Object": {
             "label": "object",
             "singleton": false,
             "actions": [
@@ -926,8 +926,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.volume.FS": {
-        "fooPlugin::aws.volume.FS": {
+    "volume.FS": {
+        "volume.FS": {
             "label": "fs",
             "singleton": false,
             "actions": [
@@ -936,11 +936,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -949,11 +949,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -965,8 +965,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.volume.dir": {
-        "fooPlugin::aws.volume.dir": {
+    "volume.dir": {
+        "volume.dir": {
             "label": "dir",
             "singleton": false,
             "actions": [
@@ -975,11 +975,11 @@
             "meta_attribute_schema": null,
             "metadata_schema": null,
             "children": [
-                "fooPlugin::aws.volume.dir",
-                "fooPlugin::aws.volume.file"
+                "volume.dir",
+                "volume.file"
             ]
         },
-        "fooPlugin::aws.volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
@@ -991,8 +991,8 @@
             "children": null
         }
     },
-    "fooPlugin::aws.volume.file": {
-        "fooPlugin::aws.volume.file": {
+    "volume.file": {
+        "volume.file": {
             "label": "file",
             "singleton": false,
             "actions": [
diff --git a/plugin/testdata/externalPluginSchema_SchemaGraph.json b/plugin/testdata/externalPluginSchema_SchemaGraph.json
index 6a09092..76d372d 100644
--- a/plugin/testdata/externalPluginSchema_SchemaGraph.json
+++ b/plugin/testdata/externalPluginSchema_SchemaGraph.json
@@ -101,7 +101,7 @@
         "children": [
             "aws.ec2InstanceConsoleOutput",
             "aws.ec2InstanceMetadataJSON",
-            "aws.volume.FS"
+            "volume.FS"
         ]
     },
     "aws.ec2InstanceConsoleOutput": {
@@ -120,29 +120,29 @@
         ],
         "children": null
     },
-    "aws.volume.FS": {
+    "volume.FS": {
         "label": "fs",
         "singleton": false,
         "methods": [
             "list"
         ],
         "children": [
-            "aws.volume.dir",
-            "aws.volume.file"
+            "volume.dir",
+            "volume.file"
         ]
     },
-    "aws.volume.dir": {
+    "volume.dir": {
         "label": "dir",
         "singleton": false,
         "methods": [
             "list"
         ],
         "children": [
-            "aws.volume.dir",
-            "aws.volume.file"
+            "volume.dir",
+            "volume.file"
         ]
     },
-    "aws.volume.file": {
+    "volume.file": {
         "label": "file",
         "singleton": false,
         "methods": [