Repository: vmware/govmomi
Pull request #3478 (2024-06-28 20:39:16)

api: add ResolveLibraryItemStorage


Patch:
diff --git a/vapi/library/finder/path.go b/vapi/library/finder/path.go
index 213e1ebd..7605627f 100644
--- a/vapi/library/finder/path.go
+++ b/vapi/library/finder/path.go
@@ -21,10 +21,13 @@ import (
 	"fmt"
 	"net/url"
 	"path"
+	"strings"
 
 	"github.com/vmware/govmomi/object"
+	"github.com/vmware/govmomi/property"
 	"github.com/vmware/govmomi/vapi/library"
 	"github.com/vmware/govmomi/vim25"
+	"github.com/vmware/govmomi/vim25/mo"
 	"github.com/vmware/govmomi/vim25/types"
 )
 
@@ -130,3 +133,50 @@ func (f *PathFinder) datastoreName(ctx context.Context, id string) (string, erro
 	f.cache[id] = name
 	return name, nil
 }
+
+// ResolveLibraryItemStorage transforms StorageURIs Datastore url (uuid) to Datastore name.
+func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []library.Storage) error {
+	// TODO:
+	// - reuse PathFinder.cache
+	// - the transform here isn't Content Library specific, but is currently the only known use case
+	backing := map[string]*mo.Datastore{}
+	var ids []types.ManagedObjectReference
+
+	// don't think we can have more than 1 Datastore backing currently, future proof anyhow
+	for _, item := range storage {
+		id := item.StorageBacking.DatastoreID
+		if _, ok := backing[id]; ok {
+			continue
+		}
+		backing[id] = nil
+		ids = append(ids, types.ManagedObjectReference{Type: "Datastore", Value: id})
+	}
+
+	var ds []mo.Datastore
+	pc := property.DefaultCollector(f.c)
+	if err := pc.Retrieve(ctx, ids, []string{"name", "info.url"}, &ds); err != nil {
+		return err
+	}
+
+	for i := range ds {
+		backing[ds[i].Self.Value] = &ds[i]
+	}
+
+	for _, item := range storage {
+		b := backing[item.StorageBacking.DatastoreID]
+		dsurl := b.Info.GetDatastoreInfo().Url
+
+		for i := range item.StorageURIs {
+			u := strings.TrimPrefix(item.StorageURIs[i], dsurl)
+			u = strings.TrimPrefix(u, "/")
+			u = strings.SplitN(u, "?", 2)[0] // strip query, if any
+
+			item.StorageURIs[i] = (&object.DatastorePath{
+				Datastore: b.Name,
+				Path:      u,
+			}).String()
+		}
+	}
+
+	return nil
+}
diff --git a/vim25/mo/type_info.go b/vim25/mo/type_info.go
index 21f59291..003b56c2 100644
--- a/vim25/mo/type_info.go
+++ b/vim25/mo/type_info.go
@@ -35,9 +35,6 @@ type typeInfo struct {
 
 	// Map property names to field indices.
 	props map[string][]int
-
-	// Use base type for interface indices.
-	base bool
 }
 
 var typeInfoLock sync.RWMutex
@@ -68,20 +65,22 @@ func typeInfoForType(tname string) *typeInfo {
 
 func baseType(ftyp reflect.Type) reflect.Type {
 	base := strings.TrimPrefix(ftyp.Name(), "Base")
+	switch base {
+	case "MethodFault":
+		return nil
+	}
 	if kind, ok := types.TypeFunc()(base); ok {
 		return kind
 	}
-	return ftyp
+	return nil
 }
 
-func newTypeInfo(typ reflect.Type, base ...bool) *typeInfo {
+func newTypeInfo(typ reflect.Type) *typeInfo {
 	t := typeInfo{
 		typ:   typ,
 		props: make(map[string][]int),
 	}
-	if len(base) == 1 {
-		t.base = base[0]
-	}
+
 	t.build(typ, "", []int{})
 
 	return &t
@@ -170,13 +169,16 @@ func (t *typeInfo) build(typ reflect.Type, fn string, fi []int) {
 			t.build(ftyp, fnc, fic)
 		}
 
+		// Base type can only access base fields, for example Datastore.Info
+		// is types.BaseDataStore, so we create a new(types.DatastoreInfo)
 		// Indexed property path may traverse into array element fields.
 		// When interface, use the base type to index fields.
 		// For example, BaseVirtualDevice:
 		//   config.hardware.device[4000].deviceInfo.label
-		if t.base && ftyp.Kind() == reflect.Interface {
-			base := baseType(ftyp)
-			t.build(base, fnc, fic)
+		if ftyp.Kind() == reflect.Interface {
+			if base := baseType(ftyp); base != nil {
+				t.build(base, fnc, fic)
+			}
 		}
 	}
 }
@@ -283,7 +285,7 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value, field ...string)
 					item = reflect.New(rt.Elem())
 				}
 
-				field := newTypeInfo(item.Type(), true)
+				field := newTypeInfo(item.Type())
 				if ix, ok := field.props[path]; ok {
 					assignValue(item, ix, pv)
 				}


Test patch:
diff --git a/vim25/mo/retrieve_test.go b/vim25/mo/retrieve_test.go
index 24066dd7..ca52e0c8 100644
--- a/vim25/mo/retrieve_test.go
+++ b/vim25/mo/retrieve_test.go
@@ -211,3 +211,32 @@ func TestEventReferences(t *testing.T) {
 		t.Errorf("%d refs", n)
 	}
 }
+
+func TestDatastoreInfoURL(t *testing.T) {
+	// Datastore.Info is types.BaseDatastoreInfo
+	// LoadObjectContent() should populate Info with the base type (*types.DatastoreInfo) in this case
+	content := []types.ObjectContent{
+		{
+			Obj: types.ManagedObjectReference{Type: "Datastore", Value: "datastore-48", ServerGUID: ""},
+			PropSet: []types.DynamicProperty{
+				{
+					Name: "info.url",
+					Val:  "ds:///vmfs/volumes/666d7a79-cb0d28b2-57c8-0645602e1b58/",
+				},
+			},
+			MissingSet: nil,
+		},
+	}
+
+	var ds Datastore
+
+	if err := LoadObjectContent(content, &ds); err != nil {
+		t.Fatal(err)
+	}
+
+	info := ds.Info.GetDatastoreInfo()
+
+	if info.Url != content[0].PropSet[0].Val.(string) {
+		t.Errorf("info.url=%s", info.Url)
+	}
+}