github.com/vmware/govmomi@v0.51.0/vim25/mo/retrieve.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package mo
     6  
     7  import (
     8  	"context"
     9  	"reflect"
    10  
    11  	"github.com/vmware/govmomi/vim25/methods"
    12  	"github.com/vmware/govmomi/vim25/soap"
    13  	"github.com/vmware/govmomi/vim25/types"
    14  )
    15  
    16  func ignoreMissingProperty(ref types.ManagedObjectReference, p types.MissingProperty) bool {
    17  	switch ref.Type {
    18  	case "VirtualMachine":
    19  		switch p.Path {
    20  		case "environmentBrowser":
    21  			// See https://github.com/vmware/govmomi/pull/242
    22  			return true
    23  		case "alarmActionsEnabled":
    24  			// Seen with vApp child VM
    25  			return true
    26  		}
    27  	case "ResourcePool":
    28  		switch p.Path {
    29  		case "resourceConfigSpecDetailed":
    30  			return true
    31  		}
    32  	}
    33  
    34  	return false
    35  }
    36  
    37  // ObjectContentToType loads an ObjectContent value into the value it
    38  // represents. If the ObjectContent value has a non-empty 'MissingSet' field,
    39  // it returns the first fault it finds there as error. If the 'MissingSet'
    40  // field is empty, it returns a pointer to a reflect.Value. It handles contain
    41  // nested properties, such as 'guest.ipAddress' or 'config.hardware'.
    42  func ObjectContentToType(o types.ObjectContent, ptr ...bool) (any, error) {
    43  	// Expect no properties in the missing set
    44  	for _, p := range o.MissingSet {
    45  		if ignoreMissingProperty(o.Obj, p) {
    46  			continue
    47  		}
    48  
    49  		return nil, soap.WrapVimFault(p.Fault.Fault)
    50  	}
    51  
    52  	ti := typeInfoForType(o.Obj.Type)
    53  	v, err := ti.LoadFromObjectContent(o)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	if len(ptr) == 1 && ptr[0] {
    59  		return v.Interface(), nil
    60  	}
    61  	return v.Elem().Interface(), nil
    62  }
    63  
    64  // ApplyPropertyChange converts the response of a call to WaitForUpdates
    65  // and applies it to the given managed object.
    66  func ApplyPropertyChange(obj Reference, changes []types.PropertyChange) {
    67  	t := typeInfoForType(obj.Reference().Type)
    68  	v := reflect.ValueOf(obj)
    69  
    70  	for _, p := range changes {
    71  		var field Field
    72  		if !field.FromString(p.Name) {
    73  			panic(p.Name + ": invalid property path")
    74  		}
    75  
    76  		rv, ok := t.props[field.Path]
    77  		if !ok {
    78  			panic(field.Path + ": property not found")
    79  		}
    80  
    81  		if field.Key == nil { // Key is only used for notifications
    82  			assignValue(v, rv, reflect.ValueOf(p.Val))
    83  		}
    84  	}
    85  }
    86  
    87  // LoadObjectContent converts the response of a call to
    88  // RetrieveProperties{Ex} to one or more managed objects.
    89  func LoadObjectContent(content []types.ObjectContent, dst any) error {
    90  	rt := reflect.TypeOf(dst)
    91  	if rt == nil || rt.Kind() != reflect.Ptr {
    92  		panic("need pointer")
    93  	}
    94  
    95  	rv := reflect.ValueOf(dst).Elem()
    96  	if !rv.CanSet() {
    97  		panic("cannot set dst")
    98  	}
    99  
   100  	isSlice := false
   101  	switch rt.Elem().Kind() {
   102  	case reflect.Struct:
   103  	case reflect.Slice:
   104  		isSlice = true
   105  	default:
   106  		panic("unexpected type")
   107  	}
   108  
   109  	if isSlice {
   110  		for _, p := range content {
   111  			v, err := ObjectContentToType(p)
   112  			if err != nil {
   113  				return err
   114  			}
   115  
   116  			vt := reflect.TypeOf(v)
   117  
   118  			if !rv.Type().AssignableTo(vt) {
   119  				// For example: dst is []ManagedEntity, res is []HostSystem
   120  				if field, ok := vt.FieldByName(rt.Elem().Elem().Name()); ok && field.Anonymous {
   121  					rv.Set(reflect.Append(rv, reflect.ValueOf(v).FieldByIndex(field.Index)))
   122  					continue
   123  				}
   124  			}
   125  
   126  			rv.Set(reflect.Append(rv, reflect.ValueOf(v)))
   127  		}
   128  	} else {
   129  		switch len(content) {
   130  		case 0:
   131  		case 1:
   132  			v, err := ObjectContentToType(content[0])
   133  			if err != nil {
   134  				return err
   135  			}
   136  
   137  			vt := reflect.TypeOf(v)
   138  
   139  			if !rv.Type().AssignableTo(vt) {
   140  				// For example: dst is ComputeResource, res is ClusterComputeResource
   141  				if field, ok := vt.FieldByName(rt.Elem().Name()); ok && field.Anonymous {
   142  					rv.Set(reflect.ValueOf(v).FieldByIndex(field.Index))
   143  					return nil
   144  				}
   145  			}
   146  
   147  			rv.Set(reflect.ValueOf(v))
   148  		default:
   149  			// If dst is not a slice, expect to receive 0 or 1 results
   150  			panic("more than 1 result")
   151  		}
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // RetrievePropertiesEx wraps RetrievePropertiesEx and ContinueRetrievePropertiesEx to collect properties in batches.
   158  func RetrievePropertiesEx(ctx context.Context, r soap.RoundTripper, req types.RetrievePropertiesEx) ([]types.ObjectContent, error) {
   159  	rx, err := methods.RetrievePropertiesEx(ctx, r, &req)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	if rx.Returnval == nil {
   165  		return nil, nil
   166  	}
   167  
   168  	objects := rx.Returnval.Objects
   169  	token := rx.Returnval.Token
   170  
   171  	for token != "" {
   172  		cx, err := methods.ContinueRetrievePropertiesEx(ctx, r, &types.ContinueRetrievePropertiesEx{
   173  			This:  req.This,
   174  			Token: token,
   175  		})
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		token = cx.Returnval.Token
   181  		objects = append(objects, cx.Returnval.Objects...)
   182  	}
   183  
   184  	return objects, nil
   185  }
   186  
   187  // RetrievePropertiesForRequest calls the RetrieveProperties method with the
   188  // specified request and decodes the response struct into the value pointed to
   189  // by dst.
   190  func RetrievePropertiesForRequest(ctx context.Context, r soap.RoundTripper, req types.RetrieveProperties, dst any) error {
   191  	objects, err := RetrievePropertiesEx(ctx, r, types.RetrievePropertiesEx{
   192  		This:    req.This,
   193  		SpecSet: req.SpecSet,
   194  	})
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	return LoadObjectContent(objects, dst)
   200  }
   201  
   202  // RetrieveProperties retrieves the properties of the managed object specified
   203  // as obj and decodes the response struct into the value pointed to by dst.
   204  func RetrieveProperties(ctx context.Context, r soap.RoundTripper, pc, obj types.ManagedObjectReference, dst any) error {
   205  	req := types.RetrieveProperties{
   206  		This: pc,
   207  		SpecSet: []types.PropertyFilterSpec{
   208  			{
   209  				ObjectSet: []types.ObjectSpec{
   210  					{
   211  						Obj:  obj,
   212  						Skip: types.NewBool(false),
   213  					},
   214  				},
   215  				PropSet: []types.PropertySpec{
   216  					{
   217  						All:  types.NewBool(true),
   218  						Type: obj.Type,
   219  					},
   220  				},
   221  			},
   222  		},
   223  	}
   224  
   225  	return RetrievePropertiesForRequest(ctx, r, req, dst)
   226  }
   227  
   228  var morType = reflect.TypeOf((*types.ManagedObjectReference)(nil)).Elem()
   229  
   230  // References returns all non-nil moref field values in the given struct.
   231  // Only Anonymous struct fields are followed by default. The optional follow
   232  // param will follow any struct fields when true.
   233  func References(s any, follow ...bool) []types.ManagedObjectReference {
   234  	var refs []types.ManagedObjectReference
   235  	rval := reflect.ValueOf(s)
   236  	rtype := rval.Type()
   237  
   238  	if rval.Kind() == reflect.Ptr {
   239  		rval = rval.Elem()
   240  		rtype = rval.Type()
   241  	}
   242  
   243  	for i := 0; i < rval.NumField(); i++ {
   244  		val := rval.Field(i)
   245  		finfo := rtype.Field(i)
   246  
   247  		if finfo.Anonymous {
   248  			refs = append(refs, References(val.Interface(), follow...)...)
   249  			continue
   250  		}
   251  		if finfo.Name == "Self" {
   252  			continue
   253  		}
   254  
   255  		ftype := val.Type()
   256  
   257  		if ftype.Kind() == reflect.Slice {
   258  			if ftype.Elem() == morType {
   259  				s := val.Interface().([]types.ManagedObjectReference)
   260  				for i := range s {
   261  					refs = append(refs, s[i])
   262  				}
   263  			}
   264  			continue
   265  		}
   266  
   267  		if ftype.Kind() == reflect.Ptr {
   268  			if val.IsNil() {
   269  				continue
   270  			}
   271  			val = val.Elem()
   272  			ftype = val.Type()
   273  		}
   274  
   275  		if ftype == morType {
   276  			refs = append(refs, val.Interface().(types.ManagedObjectReference))
   277  			continue
   278  		}
   279  
   280  		if len(follow) != 0 && follow[0] {
   281  			if ftype.Kind() == reflect.Struct && val.CanSet() {
   282  				refs = append(refs, References(val.Interface(), follow...)...)
   283  			}
   284  		}
   285  	}
   286  
   287  	return refs
   288  }