253 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| package vsphere
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/vmware/govmomi/property"
 | |
| 	"github.com/vmware/govmomi/view"
 | |
| 	"github.com/vmware/govmomi/vim25/mo"
 | |
| 	"github.com/vmware/govmomi/vim25/types"
 | |
| )
 | |
| 
 | |
| var childTypes map[string][]string
 | |
| 
 | |
| var addFields map[string][]string
 | |
| 
 | |
| var containers map[string]interface{}
 | |
| 
 | |
| // Finder allows callers to find resources in vCenter given a query string.
 | |
| type Finder struct {
 | |
| 	client *Client
 | |
| }
 | |
| 
 | |
| // ResourceFilter is a convenience class holding a finder and a set of paths. It is useful when you need a
 | |
| // self contained object capable of returning a certain set of resources.
 | |
| type ResourceFilter struct {
 | |
| 	finder  *Finder
 | |
| 	resType string
 | |
| 	paths   []string
 | |
| }
 | |
| 
 | |
| // FindAll returns the union of resources found given the supplied resource type and paths.
 | |
| func (f *Finder) FindAll(ctx context.Context, resType string, paths []string, dst interface{}) error {
 | |
| 	for _, p := range paths {
 | |
| 		if err := f.Find(ctx, resType, p, dst); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Find returns the resources matching the specified path.
 | |
| func (f *Finder) Find(ctx context.Context, resType, path string, dst interface{}) error {
 | |
| 	p := strings.Split(path, "/")
 | |
| 	flt := make([]property.Filter, len(p)-1)
 | |
| 	for i := 1; i < len(p); i++ {
 | |
| 		flt[i-1] = property.Filter{"name": p[i]}
 | |
| 	}
 | |
| 	objs := make(map[string]types.ObjectContent)
 | |
| 	err := f.descend(ctx, f.client.Client.ServiceContent.RootFolder, resType, flt, 0, objs)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	objectContentToTypedArray(objs, dst)
 | |
| 	f.client.log.Debugf("Find(%s, %s) returned %d objects", resType, path, len(objs))
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (f *Finder) descend(ctx context.Context, root types.ManagedObjectReference, resType string,
 | |
| 	tokens []property.Filter, pos int, objs map[string]types.ObjectContent) error {
 | |
| 	isLeaf := pos == len(tokens)-1
 | |
| 
 | |
| 	// No more tokens to match?
 | |
| 	if pos >= len(tokens) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Determine child types
 | |
| 
 | |
| 	ct, ok := childTypes[root.Reference().Type]
 | |
| 	if !ok {
 | |
| 		// We don't know how to handle children of this type. Stop descending.
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	m := view.NewManager(f.client.Client.Client)
 | |
| 	v, err := m.CreateContainerView(ctx, root, ct, false)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer v.Destroy(ctx)
 | |
| 	var content []types.ObjectContent
 | |
| 
 | |
| 	fields := []string{"name"}
 | |
| 	recurse := tokens[pos]["name"] == "**"
 | |
| 
 | |
| 	types := ct
 | |
| 	if isLeaf {
 | |
| 		if af, ok := addFields[resType]; ok {
 | |
| 			fields = append(fields, af...)
 | |
| 		}
 | |
| 		if recurse {
 | |
| 			// Special case: The last token is a recursive wildcard, so we can grab everything
 | |
| 			// recursively in a single call.
 | |
| 			v2, err := m.CreateContainerView(ctx, root, []string{resType}, true)
 | |
| 			defer v2.Destroy(ctx)
 | |
| 			err = v2.Retrieve(ctx, []string{resType}, fields, &content)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			for _, c := range content {
 | |
| 				objs[c.Obj.String()] = c
 | |
| 			}
 | |
| 			return nil
 | |
| 		}
 | |
| 		types = []string{resType} // Only load wanted object type at leaf level
 | |
| 	}
 | |
| 	err = v.Retrieve(ctx, types, fields, &content)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	rerunAsLeaf := false
 | |
| 	for _, c := range content {
 | |
| 		if !matchName(tokens[pos], c.PropSet) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Already been here through another path? Skip!
 | |
| 		if _, ok := objs[root.Reference().String()]; ok {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if c.Obj.Type == resType && isLeaf {
 | |
| 			// We found what we're looking for. Consider it a leaf and stop descending
 | |
| 			objs[c.Obj.String()] = c
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Deal with recursive wildcards (**)
 | |
| 		var inc int
 | |
| 		if recurse {
 | |
| 			inc = 0 // By default, we stay on this token
 | |
| 			if !isLeaf {
 | |
| 				// Lookahead to next token.
 | |
| 				if matchName(tokens[pos+1], c.PropSet) {
 | |
| 					// Are we looking ahead at a leaf node that has the wanted type?
 | |
| 					// Rerun the entire level as a leaf. This is needed since all properties aren't loaded
 | |
| 					// when we're processing non-leaf nodes.
 | |
| 					if pos == len(tokens)-2 {
 | |
| 						if c.Obj.Type == resType {
 | |
| 							rerunAsLeaf = true
 | |
| 							continue
 | |
| 						}
 | |
| 					} else if _, ok := containers[c.Obj.Type]; ok {
 | |
| 						// Tokens match and we're looking ahead at a container type that's not a leaf
 | |
| 						// Consume this token and the next.
 | |
| 						inc = 2
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			// The normal case: Advance to next token before descending
 | |
| 			inc = 1
 | |
| 		}
 | |
| 		err := f.descend(ctx, c.Obj, resType, tokens, pos+inc, objs)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if rerunAsLeaf {
 | |
| 		// We're at a "pseudo leaf", i.e. we looked ahead a token and found that this level contains leaf nodes.
 | |
| 		// Rerun the entire level as a leaf to get those nodes. This will only be executed when pos is one token
 | |
| 		// before the last, to pos+1 will always point to a leaf token.
 | |
| 		return f.descend(ctx, root, resType, tokens, pos+1, objs)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func objectContentToTypedArray(objs map[string]types.ObjectContent, dst interface{}) error {
 | |
| 	rt := reflect.TypeOf(dst)
 | |
| 	if rt == nil || rt.Kind() != reflect.Ptr {
 | |
| 		panic("need pointer")
 | |
| 	}
 | |
| 
 | |
| 	rv := reflect.ValueOf(dst).Elem()
 | |
| 	if !rv.CanSet() {
 | |
| 		panic("cannot set dst")
 | |
| 	}
 | |
| 	for _, p := range objs {
 | |
| 		v, err := mo.ObjectContentToType(p)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		vt := reflect.TypeOf(v)
 | |
| 
 | |
| 		if !rv.Type().AssignableTo(vt) {
 | |
| 			// For example: dst is []ManagedEntity, res is []HostSystem
 | |
| 			if field, ok := vt.FieldByName(rt.Elem().Elem().Name()); ok && field.Anonymous {
 | |
| 				rv.Set(reflect.Append(rv, reflect.ValueOf(v).FieldByIndex(field.Index)))
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		rv.Set(reflect.Append(rv, reflect.ValueOf(v)))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // FindAll finds all resources matching the paths that were specified upon creation of
 | |
| // the ResourceFilter.
 | |
| func (r *ResourceFilter) FindAll(ctx context.Context, dst interface{}) error {
 | |
| 	return r.finder.FindAll(ctx, r.resType, r.paths, dst)
 | |
| }
 | |
| 
 | |
| func matchName(f property.Filter, props []types.DynamicProperty) bool {
 | |
| 	for _, prop := range props {
 | |
| 		if prop.Name == "name" {
 | |
| 			return f.MatchProperty(prop)
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	childTypes = map[string][]string{
 | |
| 		"HostSystem":             {"VirtualMachine"},
 | |
| 		"ComputeResource":        {"HostSystem", "ResourcePool", "VirtualApp"},
 | |
| 		"ClusterComputeResource": {"HostSystem", "ResourcePool", "VirtualApp"},
 | |
| 		"Datacenter":             {"Folder"},
 | |
| 		"Folder": {
 | |
| 			"Folder",
 | |
| 			"Datacenter",
 | |
| 			"VirtualMachine",
 | |
| 			"ComputeResource",
 | |
| 			"ClusterComputeResource",
 | |
| 			"Datastore",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	addFields = map[string][]string{
 | |
| 		"HostSystem": {"parent"},
 | |
| 		"VirtualMachine": {"runtime.host", "config.guestId", "config.uuid", "runtime.powerState",
 | |
| 			"summary.customValue", "guest.net", "guest.hostName"},
 | |
| 		"Datastore":              {"parent", "info"},
 | |
| 		"ClusterComputeResource": {"parent"},
 | |
| 		"Datacenter":             {"parent"},
 | |
| 	}
 | |
| 
 | |
| 	containers = map[string]interface{}{
 | |
| 		"HostSystem":      nil,
 | |
| 		"ComputeResource": nil,
 | |
| 		"Datacenter":      nil,
 | |
| 		"ResourcePool":    nil,
 | |
| 		"Folder":          nil,
 | |
| 		"VirtualApp":      nil,
 | |
| 	}
 | |
| }
 |