github.com/vmware/govmomi@v0.51.0/ovf/configspec.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 ovf
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"math"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/vmware/govmomi/object"
    15  	"github.com/vmware/govmomi/vim25/types"
    16  )
    17  
    18  const (
    19  	ResourceSubTypeSoundCardSB16          = "vmware.soundcard.sb16"
    20  	ResourceSubTypeSoundCardEnsoniq1371   = "vmware.soundcard.ensoniq1371"
    21  	ResourceSubTypeSoundCardHDAudio       = "vmware.soundcard.hdaudio"
    22  	ResourceSubTypePCIController          = "vmware.pcicontroller"
    23  	ResourceSubTypePS2Controller          = "vmware.ps2controller"
    24  	ResourceSubTypeSIOController          = "vmware.siocontroller"
    25  	ResourceSubTypeKeyboard               = "vmware.keyboard"
    26  	ResourceSubTypePointingDevice         = "vmware.pointingdevice"
    27  	ResourceSubTypeVMCI                   = "vmware.vmci"
    28  	ResourceSubTypeUSBEHCI                = "vmware.usb.ehci" /* USB 2.0 */
    29  	ResourceSubTypeUSBXHCI                = "vmware.usb.xhci" /* USB 3.0 */
    30  	ResourceSubTypeCdromISO               = "vmware.cdrom.iso"
    31  	ResourceSubTypeCDROMRemotePassthrough = "vmware.cdrom.remotepassthrough"
    32  	ResourceSubTypeCDROMRemoteATAPI       = "vmware.cdrom.remoteatapi"
    33  	ResourceSubTypeCDROMPassthrough       = "vmware.cdrom.passthrough"
    34  	ResourceSubTypeCDROMATAPI             = "vmware.cdrom.atapi"
    35  	ResourceSubTypeFloppyDevice           = "vmware.floppy.device"
    36  	ResourceSubTypeFloppyImage            = "vmware.floppy.image"
    37  	ResourceSubTypeFloppyRemoveDevice     = "vmware.floppy.remotedevice"
    38  	ResourceSubTypeSCSIPassthrough        = "vmware.scsi.passthrough"
    39  	ResourceSubTypeParallelPortDevice     = "vmware.parallelport.device"
    40  	ResourceSubTypeParallelPortFile       = "vmware.parallelport.file"
    41  	ResourceSubTypeSerialPortDevice       = "vmware.serialport.device"
    42  	ResourceSubTypeSerialPortFile         = "vmware.serialport.file"
    43  	ResourceSubTypeSerialPortPipe         = "vmware.serialport.pipe"
    44  	ResourceSubTypeSerialPortURI          = "vmware.serialport.uri"
    45  	ResourceSubTypeSerialPortThinPrint    = "vmware.serialport.thinprint"
    46  	ResourceSubTypeSATAAHCI               = "vmware.sata.ahci"
    47  	ResourceSubTypeSATAAHCIAlter          = "ahci"
    48  	ResourceSubTypeNVMEController         = "vmware.nvme.controller"
    49  	ResourceSubTypeNVDIMMController       = "vmware.nvdimm.controller"
    50  	ResourceSubTypeNVDIMMDevice           = "vmware.nvdimm.device"
    51  	ResourceSubTypePCIPassthrough         = "vmware.pci.passthrough"
    52  	ResourceSubTypePCIPassthroughDVX      = "vmware.pci.passthrough-dvx"
    53  	ResourceSubTypePCIPassthroughAH       = "vmware.pci.passthrough-ah"
    54  	ResourceSubTypePCIPassthroughVMIOP    = "vmware.pci.passthrough-vmiop"
    55  	ResourceSubTypePrecisionClock         = "vmware.precisionclock"
    56  	ResourceSubTypeWatchdogTimer          = "vmware.watchdogtimer"
    57  	ResourceSubTypeVTPM                   = "vmware.vtpm"
    58  )
    59  
    60  var errUnsupportedResourceSubtype = errors.New("unsupported resource subtype")
    61  
    62  // ErrUnsupportedItem is returned by Envelope.ToConfigSpec when there is an
    63  // invalid item configuration.
    64  type ErrUnsupportedItem struct {
    65  	Name             string
    66  	Index            int
    67  	InstanceID       string
    68  	ResourceType     CIMResourceType
    69  	ResourceSubType  string
    70  	LocalizedMessage string
    71  }
    72  
    73  func (e ErrUnsupportedItem) Error() string {
    74  	msg := fmt.Sprintf(
    75  		"unsupported item name=%q, index=%d, instanceID=%q",
    76  		e.Name, e.Index, e.InstanceID)
    77  	if e.ResourceType > 0 {
    78  		msg = fmt.Sprintf("%s, resourceType=%d", msg, e.ResourceType)
    79  	}
    80  	if e.ResourceSubType != "" {
    81  		msg = fmt.Sprintf("%s, resourceSubType=%s", msg, e.ResourceSubType)
    82  	}
    83  	if e.LocalizedMessage != "" {
    84  		msg = fmt.Sprintf("%s, msg=%q", msg, e.LocalizedMessage)
    85  	}
    86  	return msg
    87  }
    88  
    89  // AsErrUnsupportedItem returns any possible wrapped ErrUnsupportedItem error
    90  // from the provided error.
    91  func AsErrUnsupportedItem(in error) (ErrUnsupportedItem, bool) {
    92  	var out ErrUnsupportedItem
    93  	if errors.As(in, &out) {
    94  		return out, true
    95  	}
    96  	return ErrUnsupportedItem{}, false
    97  }
    98  
    99  func errUnsupportedItem(
   100  	index int,
   101  	item itemElement,
   102  	inner error,
   103  	args ...any) error {
   104  
   105  	err := ErrUnsupportedItem{
   106  		Name:            item.ElementName,
   107  		InstanceID:      item.InstanceID,
   108  		Index:           index,
   109  		ResourceSubType: item.resourceSubType,
   110  	}
   111  
   112  	if item.ResourceType != nil {
   113  		err.ResourceType = *item.ResourceType
   114  	}
   115  
   116  	if len(args) == 1 {
   117  		err.LocalizedMessage = args[0].(string)
   118  	} else if len(args) > 1 {
   119  		err.LocalizedMessage = fmt.Sprintf(args[0].(string), args[1:]...)
   120  	}
   121  
   122  	if inner != nil {
   123  		return fmt.Errorf("%w, %w", err, inner)
   124  	}
   125  
   126  	return err
   127  }
   128  
   129  type itemElement struct {
   130  	ResourceAllocationSettingData
   131  
   132  	resourceSubType string
   133  }
   134  
   135  type configSpec = types.VirtualMachineConfigSpec
   136  
   137  // ToConfigSpecOptions influence the behavior of the ToConfigSpecWithOptions
   138  // function.
   139  type ToConfigSpecOptions struct {
   140  
   141  	// Strict indicates that an error should be returned on Item elements in
   142  	// a VirtualHardware section that have an unknown ResourceType, i.e. a value
   143  	// that falls outside the range of the enum CIMResourceType.
   144  	Strict bool
   145  }
   146  
   147  // ToConfigSpec calls ToConfigSpecWithOptions with an empty ToConfigSpecOptions
   148  // object.
   149  func (e Envelope) ToConfigSpec() (types.VirtualMachineConfigSpec, error) {
   150  	return e.ToConfigSpecWithOptions(ToConfigSpecOptions{})
   151  }
   152  
   153  // ToConfigSpecWithOptions transforms the envelope into a ConfigSpec that may be
   154  // used to create a new virtual machine.
   155  // Please note, at this time:
   156  //   - Only a single VirtualSystem is supported. The VirtualSystemCollection
   157  //     section is ignored.
   158  //   - Only the first VirtualHardware section is supported.
   159  //   - Only the default deployment option configuration is considered. Elements
   160  //     part of a non-default configuration are ignored.
   161  //   - Disks must specify zero or one HostResource elements.
   162  //   - Many, many more constraints...
   163  func (e Envelope) ToConfigSpecWithOptions(
   164  	opts ToConfigSpecOptions) (types.VirtualMachineConfigSpec, error) {
   165  
   166  	vs := e.VirtualSystem
   167  	if vs == nil {
   168  		return configSpec{}, errors.New("no VirtualSystem")
   169  	}
   170  
   171  	// Determine if there is a default configuration.
   172  	var defaultConfigName string
   173  	if do := e.DeploymentOption; do != nil {
   174  		for _, c := range do.Configuration {
   175  			if d := c.Default; d != nil && *d {
   176  				defaultConfigName = c.ID
   177  				break
   178  			}
   179  		}
   180  	}
   181  
   182  	dst := configSpec{
   183  		Files: &types.VirtualMachineFileInfo{},
   184  		Name:  vs.ID,
   185  	}
   186  
   187  	// Set the guest ID.
   188  	if os := vs.OperatingSystem; os != nil && os.OSType != nil {
   189  		dst.GuestId = *os.OSType
   190  	}
   191  
   192  	// Parse the hardware.
   193  	if err := e.toHardware(&dst, defaultConfigName, vs, opts); err != nil {
   194  		return configSpec{}, err
   195  	}
   196  
   197  	// Parse the vApp config.
   198  	if err := e.toVAppConfig(&dst, defaultConfigName, vs); err != nil {
   199  		return configSpec{}, err
   200  	}
   201  
   202  	return dst, nil
   203  }
   204  
   205  func (e Envelope) toHardware(
   206  	dst *configSpec,
   207  	configName string,
   208  	vs *VirtualSystem,
   209  	opts ToConfigSpecOptions) error {
   210  
   211  	var hw VirtualHardwareSection
   212  	if len(vs.VirtualHardware) == 0 {
   213  		return errors.New("no VirtualHardware")
   214  	}
   215  	hw = vs.VirtualHardware[0]
   216  
   217  	// Set the hardware version.
   218  	if vmx := hw.System.VirtualSystemType; vmx != nil {
   219  		dst.Version = *vmx
   220  	}
   221  
   222  	// Parse the config
   223  	e.toConfig(dst, hw)
   224  
   225  	// Parse the extra config.
   226  	e.toExtraConfig(dst, hw)
   227  
   228  	var (
   229  		devices   object.VirtualDeviceList
   230  		resources = map[string]types.BaseVirtualDevice{}
   231  	)
   232  
   233  	for index := range hw.Item {
   234  		item := itemElement{
   235  			ResourceAllocationSettingData: hw.Item[index],
   236  		}
   237  
   238  		if c := item.Configuration; c != nil {
   239  			if *c != configName {
   240  				// Skip items that do not belong to the provided config.
   241  				continue
   242  			}
   243  		}
   244  
   245  		if item.ResourceType == nil {
   246  			return errUnsupportedItem(index, item, nil, "nil ResourceType")
   247  		}
   248  
   249  		// Get the resource sub type, if any.
   250  		if rst := item.ResourceSubType; rst != nil {
   251  			item.resourceSubType = strings.ToLower(*rst)
   252  		}
   253  
   254  		var (
   255  			d   types.BaseVirtualDevice
   256  			err error
   257  		)
   258  
   259  		switch *item.ResourceType {
   260  
   261  		case Other: // 1
   262  			d, err = e.toOther(item, devices, resources)
   263  
   264  		case ComputerSystem: // 2
   265  			// TODO(akutz)
   266  
   267  		case Processor: // 3
   268  			if item.VirtualQuantity == nil {
   269  				return errUnsupportedItem(
   270  					index, item, nil, "nil VirtualQuantity")
   271  			}
   272  			dst.NumCPUs = int32(*item.VirtualQuantity)
   273  			if cps := item.CoresPerSocket; cps != nil {
   274  				dst.NumCoresPerSocket = cps.Value
   275  			}
   276  
   277  		case Memory: // 4
   278  			if item.VirtualQuantity == nil {
   279  				return errUnsupportedItem(
   280  					index, item, nil, "nil VirtualQuantity")
   281  			}
   282  			dst.MemoryMB = int64(*item.VirtualQuantity)
   283  
   284  		case IdeController: // 5
   285  			d, err = e.toIDEController(item, devices, resources)
   286  
   287  		case ParallelScsiHba: // 6
   288  			d, err = e.toSCSIController(item, devices, resources)
   289  
   290  		case FcHba: // 7
   291  			// TODO(akutz)
   292  
   293  		case IScsiHba: // 8
   294  			// TODO(akutz)
   295  
   296  		case IbHba: // 9
   297  			// TODO(akutz)
   298  
   299  		case EthernetAdapter: // 10
   300  			d, err = e.toNetworkInterface(item, devices, resources)
   301  
   302  		case OtherNetwork: // 11
   303  			// TODO(akutz)
   304  
   305  		case IoSlot: // 12
   306  			// TODO(akutz)
   307  
   308  		case IoDevice: // 13
   309  			// TODO(akutz)
   310  
   311  		case FloppyDrive: // 14
   312  			if devices.PickController((*types.VirtualSIOController)(nil)) == nil {
   313  				c := &types.VirtualSIOController{}
   314  				c.Key = devices.NewKey()
   315  				devices = append(devices, c)
   316  			}
   317  			d, err = e.toFloppyDrive(item, devices, resources)
   318  
   319  		case CdDrive, DvdDrive: // 15, 16
   320  			d, err = e.toCDOrDVDDrive(item, devices, resources)
   321  
   322  		case DiskDrive: // 17
   323  			d, err = e.toVirtualDisk(item, devices, resources)
   324  
   325  		case TapeDrive: // 18
   326  			// TODO(akutz)
   327  
   328  		case StorageExtent: // 19
   329  			// TODO(akutz)
   330  
   331  		case OtherStorage: // 20
   332  			d, err = e.toOtherStorage(item, devices, resources)
   333  
   334  		case SerialPort: // 21
   335  			// TODO(akutz)
   336  
   337  		case ParallelPort: // 22
   338  			// TODO(akutz)
   339  
   340  		case UsbController: // 23
   341  			d, err = e.toUSB(item, devices, resources)
   342  
   343  		case Graphics: // 24
   344  			d, err = e.toVideoCard(item, devices, resources)
   345  
   346  		case Ieee1394: // 25
   347  			// TODO(akutz)
   348  
   349  		case PartitionableUnit: // 26
   350  			// TODO(akutz)
   351  
   352  		case BasePartitionable: // 27
   353  			// TODO(akutz)
   354  
   355  		case PowerSupply: // 28
   356  			// TODO(akutz)
   357  
   358  		case CoolingDevice: // 29
   359  			// TODO(akutz)
   360  
   361  		case EthernetSwitchPort: // 30
   362  			// TODO(akutz)
   363  
   364  		case LogicalDisk: // 31
   365  			// TODO(akutz)
   366  
   367  		case StorageVolume: // 32
   368  			// TODO(akutz)
   369  
   370  		case EthernetConnection: // 33
   371  			// TODO(akutz)
   372  
   373  		default:
   374  			if opts.Strict {
   375  				return errUnsupportedItem(
   376  					index, item, nil, "unsupported resource type")
   377  			}
   378  		}
   379  
   380  		if err != nil {
   381  			if err == errUnsupportedResourceSubtype {
   382  				if !opts.Strict {
   383  					continue
   384  				}
   385  			}
   386  			return errUnsupportedItem(index, item, err)
   387  		}
   388  
   389  		if d != nil {
   390  			setConnectable(d, item)
   391  			if err := e.setUnitNumber(item, d); err != nil {
   392  				return errUnsupportedItem(index, item, err)
   393  			}
   394  			if err := e.setPCISlotNumber(item, d); err != nil {
   395  				return errUnsupportedItem(index, item, err)
   396  			}
   397  			devices = append(devices, d)
   398  		}
   399  	}
   400  
   401  	// Add the devices to the ConfigSpec.
   402  	dst.DeviceChange, _ = devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
   403  
   404  	return nil
   405  }
   406  
   407  func (e Envelope) setUnitNumber(
   408  	item itemElement,
   409  	d types.BaseVirtualDevice) error {
   410  
   411  	if item.AddressOnParent == nil || *item.AddressOnParent == "" {
   412  		return nil
   413  	}
   414  
   415  	unitNumber, err := strconv.ParseInt(*item.AddressOnParent, 10, 32)
   416  	if err != nil {
   417  		return fmt.Errorf("invalid AddressOnParent=%q", *item.AddressOnParent)
   418  	}
   419  
   420  	d.GetVirtualDevice().UnitNumber = types.NewInt32(int32(unitNumber))
   421  	return nil
   422  }
   423  
   424  func (e Envelope) setBusNumber(
   425  	item itemElement,
   426  	d types.BaseVirtualDevice) error {
   427  
   428  	if item.Address == nil || *item.Address == "" {
   429  		return nil
   430  	}
   431  
   432  	c, ok := d.(types.BaseVirtualController)
   433  	if !ok {
   434  		return fmt.Errorf("expectedType=%s, actualType=%T",
   435  			"types.BaseVirtualController", d)
   436  	}
   437  
   438  	busNumber, err := strconv.ParseInt(*item.Address, 10, 32)
   439  	if err != nil {
   440  		return fmt.Errorf("invalid Address=%q", *item.Address)
   441  	}
   442  
   443  	c.GetVirtualController().BusNumber = int32(busNumber)
   444  	return nil
   445  }
   446  
   447  func (e Envelope) setPCISlotNumber(
   448  	item itemElement,
   449  	d types.BaseVirtualDevice) error {
   450  
   451  	var pciSlotNumber int32 = -1
   452  
   453  	for i := range item.Config {
   454  		c := item.Config[i]
   455  		if c.Key == "slotInfo.pciSlotNumber" {
   456  			if c.Value != "" {
   457  				v, err := strconv.ParseInt(c.Value, 10, 32)
   458  				if err != nil {
   459  					return fmt.Errorf("invalid pci slot number %s", c.Value)
   460  				}
   461  				pciSlotNumber = int32(v)
   462  			}
   463  			break
   464  		}
   465  	}
   466  
   467  	if pciSlotNumber >= 0 {
   468  		vd := d.GetVirtualDevice()
   469  		if vd.SlotInfo == nil {
   470  			vd.SlotInfo = &types.VirtualDevicePciBusSlotInfo{}
   471  		}
   472  		si, ok := vd.SlotInfo.(*types.VirtualDevicePciBusSlotInfo)
   473  		if !ok {
   474  			return fmt.Errorf("expectedType=%s, actualType=%T",
   475  				"*types.VirtualDevicePciBusSlotInfo", vd.SlotInfo)
   476  		}
   477  		si.PciSlotNumber = pciSlotNumber
   478  	}
   479  
   480  	return nil
   481  }
   482  
   483  func (e Envelope) ovfDisk(diskID string) *VirtualDiskDesc {
   484  	for _, disk := range e.Disk.Disks {
   485  		if strings.HasSuffix(diskID, disk.DiskID) {
   486  			return &disk
   487  		}
   488  	}
   489  	return nil
   490  }
   491  
   492  func (e Envelope) toVirtualDisk(
   493  	item itemElement,
   494  	devices object.VirtualDeviceList,
   495  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   496  
   497  	if item.Parent == nil {
   498  		return nil, fmt.Errorf("missing Parent")
   499  	}
   500  
   501  	r, ok := resources[*item.Parent]
   502  	if !ok {
   503  		return nil, nil
   504  	}
   505  
   506  	c, ok := r.(types.BaseVirtualController)
   507  	if !ok {
   508  		return nil, fmt.Errorf("expectedType=%s, actualType=%T",
   509  			"types.BaseVirtualController", r)
   510  	}
   511  
   512  	d := devices.CreateDisk(c, types.ManagedObjectReference{}, "")
   513  
   514  	d.VirtualDevice.DeviceInfo = &types.Description{
   515  		Label: item.ElementName,
   516  	}
   517  
   518  	// Find the disk's capacity.
   519  	var capacityInBytes uint64
   520  	switch len(item.HostResource) {
   521  
   522  	case 0:
   523  		var allocUnitsSz string
   524  		if item.AllocationUnits != nil {
   525  			allocUnitsSz = *item.AllocationUnits
   526  		}
   527  		capacityInBytes = uint64(ParseCapacityAllocationUnits(allocUnitsSz))
   528  		if r := item.VirtualQuantity; r != nil {
   529  			capacityInBytes *= uint64(*r)
   530  		}
   531  
   532  	case 1:
   533  		diskID := item.HostResource[0]
   534  		dd := e.ovfDisk(diskID)
   535  		if dd == nil {
   536  			return nil, fmt.Errorf("missing diskID %q", diskID)
   537  		}
   538  
   539  		var allocUnitsSz string
   540  		if dd.CapacityAllocationUnits != nil {
   541  			allocUnitsSz = *dd.CapacityAllocationUnits
   542  		}
   543  		capacityInBytes = uint64(ParseCapacityAllocationUnits(allocUnitsSz))
   544  		if capSz := dd.Capacity; capSz != "" {
   545  			cap, err := strconv.ParseUint(dd.Capacity, 10, 64)
   546  			if err != nil {
   547  				return nil, fmt.Errorf("disk=%s has invalid capacity=%q",
   548  					diskID, capSz)
   549  			}
   550  			capacityInBytes *= cap
   551  		}
   552  
   553  	default:
   554  		return nil, fmt.Errorf("multiple HostResource elements")
   555  	}
   556  
   557  	if capacityInBytes > math.MaxInt64 {
   558  		return nil, fmt.Errorf(
   559  			"capacityInBytes=%d exceeds math.MaxInt64", capacityInBytes)
   560  	}
   561  
   562  	d.CapacityInBytes = int64(capacityInBytes)
   563  
   564  	return d, nil
   565  }
   566  
   567  func (e Envelope) toCDOrDVDDrive(
   568  	item itemElement,
   569  	devices object.VirtualDeviceList,
   570  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   571  
   572  	if item.Parent == nil {
   573  		return nil, fmt.Errorf("missing Parent")
   574  	}
   575  
   576  	r, ok := resources[*item.Parent]
   577  	if !ok {
   578  		return nil, nil // Parent is unsupported
   579  	}
   580  
   581  	c, ok := r.(types.BaseVirtualController)
   582  	if !ok {
   583  		return nil, fmt.Errorf("expectedType=%s, actualType=%T",
   584  			"*types.VirtualIDEController", r)
   585  	}
   586  
   587  	d, err := devices.CreateCdrom(c)
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  
   592  	return d, nil
   593  }
   594  
   595  func (e Envelope) toSCSIController(
   596  	item itemElement,
   597  	devices object.VirtualDeviceList,
   598  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   599  
   600  	d, err := devices.CreateSCSIController(item.resourceSubType)
   601  	if err != nil {
   602  		return nil, err
   603  	}
   604  	if err := e.setBusNumber(item, d); err != nil {
   605  		return nil, err
   606  	}
   607  	resources[item.InstanceID] = d
   608  
   609  	return d, nil
   610  }
   611  
   612  func (e Envelope) toIDEController(
   613  	item itemElement,
   614  	devices object.VirtualDeviceList,
   615  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   616  
   617  	d, err := devices.CreateIDEController()
   618  	if err != nil {
   619  		return nil, err
   620  	}
   621  	if err := e.setBusNumber(item, d); err != nil {
   622  		return nil, err
   623  	}
   624  	resources[item.InstanceID] = d
   625  	return d, nil
   626  }
   627  
   628  func (e Envelope) toOtherStorage(
   629  	item itemElement,
   630  	devices object.VirtualDeviceList,
   631  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   632  
   633  	switch item.resourceSubType {
   634  	case ResourceSubTypeSATAAHCI, ResourceSubTypeSATAAHCIAlter:
   635  		return e.toSATAController(item, devices, resources)
   636  	case ResourceSubTypeNVMEController:
   637  		return e.toNVMEController(item, devices, resources)
   638  	}
   639  	return nil, errUnsupportedResourceSubtype
   640  }
   641  
   642  func (e Envelope) toSATAController(
   643  	item itemElement,
   644  	devices object.VirtualDeviceList,
   645  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   646  
   647  	d, err := devices.CreateSATAController()
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  	if err := e.setBusNumber(item, d); err != nil {
   652  		return nil, err
   653  	}
   654  	resources[item.InstanceID] = d
   655  	return d, nil
   656  }
   657  
   658  func (e Envelope) toNVMEController(
   659  	item itemElement,
   660  	devices object.VirtualDeviceList,
   661  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   662  
   663  	d, err := devices.CreateNVMEController()
   664  	if err != nil {
   665  		return nil, err
   666  	}
   667  	if err := e.setBusNumber(item, d); err != nil {
   668  		return nil, err
   669  	}
   670  	resources[item.InstanceID] = d
   671  	return d, nil
   672  }
   673  
   674  func (e Envelope) toUSB(
   675  	item itemElement,
   676  	devices object.VirtualDeviceList,
   677  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   678  
   679  	var d types.BaseVirtualDevice
   680  
   681  	vc := types.VirtualController{
   682  		VirtualDevice: types.VirtualDevice{
   683  			Key: devices.NewKey(),
   684  		},
   685  	}
   686  
   687  	switch item.resourceSubType {
   688  	case ResourceSubTypeUSBEHCI:
   689  		c := &types.VirtualUSBController{VirtualController: vc}
   690  		for i := range item.Config {
   691  			ic := item.Config[i]
   692  			switch ic.Key {
   693  			case "autoConnectDevices":
   694  				c.AutoConnectDevices = szToBoolPtr(ic.Value)
   695  			case "ehciEnabled":
   696  				c.EhciEnabled = szToBoolPtr(ic.Value)
   697  			}
   698  		}
   699  		d = c
   700  	case ResourceSubTypeUSBXHCI:
   701  		c := &types.VirtualUSBXHCIController{VirtualController: vc}
   702  		for i := range item.Config {
   703  			ic := item.Config[i]
   704  			switch ic.Key {
   705  			case "autoConnectDevices":
   706  				c.AutoConnectDevices = szToBoolPtr(ic.Value)
   707  			}
   708  		}
   709  		d = c
   710  	default:
   711  		return nil, errUnsupportedResourceSubtype
   712  	}
   713  
   714  	if err := e.setBusNumber(item, d); err != nil {
   715  		return nil, err
   716  	}
   717  
   718  	resources[item.InstanceID] = d
   719  	return d, nil
   720  }
   721  
   722  func (e Envelope) toNetworkInterface(
   723  	item itemElement,
   724  	devices object.VirtualDeviceList,
   725  	_ map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   726  
   727  	d, err := devices.CreateEthernetCard(item.resourceSubType, nil)
   728  	if err != nil {
   729  		return nil, err
   730  	}
   731  
   732  	nic := d.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
   733  
   734  	for i := range item.Config {
   735  		c := item.Config[i]
   736  		switch c.Key {
   737  		case "wakeOnLanEnabled":
   738  			nic.WakeOnLanEnabled = szToBoolPtr(c.Value)
   739  		case "uptCompatibilityEnabled":
   740  			nic.UptCompatibilityEnabled = szToBoolPtr(c.Value)
   741  		}
   742  	}
   743  
   744  	return d, nil
   745  }
   746  
   747  func (e Envelope) toFloppyDrive(
   748  	item itemElement,
   749  	devices object.VirtualDeviceList,
   750  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   751  
   752  	d, err := devices.CreateFloppy()
   753  	if err != nil {
   754  		return nil, err
   755  	}
   756  	resources[item.InstanceID] = d
   757  
   758  	return d, nil
   759  }
   760  
   761  func (e Envelope) toVideoCard(
   762  	item itemElement,
   763  	devices object.VirtualDeviceList,
   764  	_ map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   765  
   766  	d := &types.VirtualMachineVideoCard{
   767  		VirtualDevice: types.VirtualDevice{
   768  			Key: devices.NewKey(),
   769  		},
   770  	}
   771  
   772  	for i := range item.Config {
   773  		c := item.Config[i]
   774  		switch c.Key {
   775  		case "enable3DSupport":
   776  			d.Enable3DSupport = szToBoolPtr(c.Value)
   777  		case "graphicsMemorySizeInKB":
   778  			v, err := strconv.ParseInt(c.Value, 10, 64)
   779  			if err != nil {
   780  				return nil, fmt.Errorf("invalid %q=%s", c.Key, c.Value)
   781  			}
   782  			d.GraphicsMemorySizeInKB = v
   783  		case "useAutoDetect":
   784  			d.UseAutoDetect = szToBoolPtr(c.Value)
   785  		case "videoRamSizeInKB":
   786  			v, err := strconv.ParseInt(c.Value, 10, 64)
   787  			if err != nil {
   788  				return nil, fmt.Errorf("invalid %q=%s", c.Key, c.Value)
   789  			}
   790  			d.VideoRamSizeInKB = v
   791  		case "numDisplays":
   792  			v, err := strconv.ParseInt(c.Value, 10, 32)
   793  			if err != nil {
   794  				return nil, fmt.Errorf("invalid %q=%s", c.Key, c.Value)
   795  			}
   796  			d.NumDisplays = int32(v)
   797  		case "use3dRenderer":
   798  			d.Use3dRenderer = c.Value
   799  		}
   800  	}
   801  
   802  	return d, nil
   803  }
   804  
   805  func (e Envelope) toOther(
   806  	item itemElement,
   807  	devices object.VirtualDeviceList,
   808  	resources map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   809  
   810  	switch item.resourceSubType {
   811  	case ResourceSubTypeVMCI:
   812  		return e.toVMCI(item, devices, resources)
   813  	}
   814  	return nil, errUnsupportedResourceSubtype
   815  }
   816  
   817  func (e Envelope) toVMCI(
   818  	item itemElement,
   819  	devices object.VirtualDeviceList,
   820  	_ map[string]types.BaseVirtualDevice) (types.BaseVirtualDevice, error) {
   821  
   822  	d := &types.VirtualMachineVMCIDevice{
   823  		VirtualDevice: types.VirtualDevice{
   824  			Key: devices.NewKey(),
   825  		},
   826  	}
   827  
   828  	for i := range item.Config {
   829  		c := item.Config[i]
   830  		switch c.Key {
   831  		case "allowUnrestrictedCommunication":
   832  			d.AllowUnrestrictedCommunication = szToBoolPtr(c.Value)
   833  		}
   834  	}
   835  
   836  	return d, nil
   837  }
   838  
   839  func (e Envelope) toConfig(
   840  	dst *configSpec,
   841  	hw VirtualHardwareSection) {
   842  
   843  	for i := range hw.Config {
   844  		c := hw.Config[i]
   845  		switch c.Key {
   846  		case "cpuHotAddEnabled":
   847  			dst.CpuHotAddEnabled = szToBoolPtr(c.Value)
   848  		case "cpuHotRemoveEnabled":
   849  			dst.CpuHotRemoveEnabled = szToBoolPtr(c.Value)
   850  		case "bootOptions.efiSecureBootEnabled":
   851  			initBootOptions(dst)
   852  			dst.BootOptions.EfiSecureBootEnabled = szToBoolPtr(c.Value)
   853  		case "firmware":
   854  			dst.Firmware = c.Value
   855  		case "flags.vbsEnabled":
   856  			initFlags(dst)
   857  			dst.Flags.VbsEnabled = szToBoolPtr(c.Value)
   858  		case "flags.vvtdEnabled":
   859  			initFlags(dst)
   860  			dst.Flags.VvtdEnabled = szToBoolPtr(c.Value)
   861  		case "memoryHotAddEnabled":
   862  			dst.MemoryHotAddEnabled = szToBoolPtr(c.Value)
   863  		case "nestedHVEnabled":
   864  			dst.NestedHVEnabled = szToBoolPtr(c.Value)
   865  		case "virtualICH7MPresent":
   866  			dst.VirtualICH7MPresent = szToBoolPtr(c.Value)
   867  		case "virtualSMCPresent":
   868  			dst.VirtualSMCPresent = szToBoolPtr(c.Value)
   869  		case "cpuAllocation.shares.shares":
   870  			initCPUAllocationShares(dst)
   871  			dst.CpuAllocation.Shares.Shares = szToInt32(c.Value)
   872  		case "cpuAllocation.shares.level":
   873  			initCPUAllocationShares(dst)
   874  			dst.CpuAllocation.Shares.Level = types.SharesLevel(c.Value)
   875  		case "simultaneousThreads":
   876  			dst.SimultaneousThreads = szToInt32(c.Value)
   877  		case "tools.syncTimeWithHost":
   878  			initToolsConfig(dst)
   879  			dst.Tools.SyncTimeWithHost = szToBoolPtr(c.Value)
   880  		case "tools.syncTimeWithHostAllowed":
   881  			initToolsConfig(dst)
   882  			dst.Tools.SyncTimeWithHostAllowed = szToBoolPtr(c.Value)
   883  		case "tools.afterPowerOn":
   884  			initToolsConfig(dst)
   885  			dst.Tools.AfterPowerOn = szToBoolPtr(c.Value)
   886  		case "tools.afterResume":
   887  			initToolsConfig(dst)
   888  			dst.Tools.AfterResume = szToBoolPtr(c.Value)
   889  		case "tools.beforeGuestShutdown":
   890  			initToolsConfig(dst)
   891  			dst.Tools.BeforeGuestShutdown = szToBoolPtr(c.Value)
   892  		case "tools.beforeGuestStandby":
   893  			initToolsConfig(dst)
   894  			dst.Tools.BeforeGuestStandby = szToBoolPtr(c.Value)
   895  		case "tools.toolsUpgradePolicy":
   896  			initToolsConfig(dst)
   897  			dst.Tools.ToolsUpgradePolicy = c.Value
   898  		case "powerOpInfo.powerOffType":
   899  			initPowerOpInfo(dst)
   900  			dst.PowerOpInfo.PowerOffType = c.Value
   901  		case "powerOpInfo.resetType":
   902  			initPowerOpInfo(dst)
   903  			dst.PowerOpInfo.ResetType = c.Value
   904  		case "powerOpInfo.suspendType":
   905  			initPowerOpInfo(dst)
   906  			dst.PowerOpInfo.SuspendType = c.Value
   907  		case "powerOpInfo.standbyAction":
   908  			initPowerOpInfo(dst)
   909  			dst.PowerOpInfo.StandbyAction = c.Value
   910  		case "vPMCEnabled":
   911  			dst.VPMCEnabled = szToBoolPtr(c.Value)
   912  		}
   913  	}
   914  }
   915  
   916  func (e Envelope) toExtraConfig(
   917  	dst *configSpec,
   918  	hw VirtualHardwareSection) {
   919  
   920  	var newEC object.OptionValueList
   921  	for i := range hw.ExtraConfig {
   922  		newEC = append(newEC, &types.OptionValue{
   923  			Key:   hw.ExtraConfig[i].Key,
   924  			Value: hw.ExtraConfig[i].Value,
   925  		})
   926  	}
   927  	dst.ExtraConfig = newEC.Join(dst.ExtraConfig...)
   928  }
   929  
   930  func initToolsConfig(dst *configSpec) {
   931  	if dst.Tools == nil {
   932  		dst.Tools = &types.ToolsConfigInfo{}
   933  	}
   934  }
   935  
   936  func initPowerOpInfo(dst *configSpec) {
   937  	if dst.PowerOpInfo == nil {
   938  		dst.PowerOpInfo = &types.VirtualMachineDefaultPowerOpInfo{}
   939  	}
   940  }
   941  
   942  func initCPUAllocation(dst *configSpec) {
   943  	if dst.CpuAllocation == nil {
   944  		dst.CpuAllocation = &types.ResourceAllocationInfo{}
   945  	}
   946  }
   947  
   948  func initCPUAllocationShares(dst *configSpec) {
   949  	initCPUAllocation(dst)
   950  	if dst.CpuAllocation.Shares == nil {
   951  		dst.CpuAllocation.Shares = &types.SharesInfo{}
   952  	}
   953  }
   954  
   955  func initFlags(dst *configSpec) {
   956  	if dst.Flags == nil {
   957  		dst.Flags = &types.VirtualMachineFlagInfo{}
   958  	}
   959  }
   960  
   961  func initBootOptions(dst *configSpec) {
   962  	if dst.BootOptions == nil {
   963  		dst.BootOptions = &types.VirtualMachineBootOptions{}
   964  	}
   965  }
   966  
   967  func setConnectable(dst types.BaseVirtualDevice, src itemElement) {
   968  
   969  	d := dst.GetVirtualDevice()
   970  	for i := range src.Config {
   971  		c := src.Config[i]
   972  		switch c.Key {
   973  		case "connectable.allowGuestControl":
   974  			if d.Connectable == nil {
   975  				d.Connectable = &types.VirtualDeviceConnectInfo{}
   976  			}
   977  			d.Connectable.AllowGuestControl = szToBool(c.Value)
   978  		}
   979  	}
   980  }
   981  
   982  func szToBoolPtr(s string) *bool {
   983  	if s == "" {
   984  		return nil
   985  	}
   986  	b, _ := strconv.ParseBool(s)
   987  	return &b
   988  }
   989  
   990  func szToBool(s string) bool {
   991  	b, _ := strconv.ParseBool(s)
   992  	return b
   993  }
   994  
   995  func szToInt32(s string) int32 {
   996  	v, _ := strconv.ParseInt(s, 10, 32)
   997  	return int32(v)
   998  }
   999  
  1000  func deref[T any](pT *T) T {
  1001  	var t T
  1002  	if pT != nil {
  1003  		t = *pT
  1004  	}
  1005  	return t
  1006  }
  1007  
  1008  func (e Envelope) toVAppConfig(
  1009  	dst *configSpec,
  1010  	configName string,
  1011  	vs *VirtualSystem) error {
  1012  
  1013  	if len(vs.Product) == 0 {
  1014  		return nil
  1015  	}
  1016  
  1017  	vapp := &types.VAppConfigSpec{}
  1018  
  1019  	index := 0
  1020  	for i, product := range vs.Product {
  1021  		vapp.Product = append(vapp.Product, types.VAppProductSpec{
  1022  			ArrayUpdateSpec: types.ArrayUpdateSpec{
  1023  				Operation: types.ArrayUpdateOperationAdd,
  1024  			},
  1025  			Info: &types.VAppProductInfo{
  1026  				Key:         int32(i),
  1027  				ClassId:     deref(product.Class),
  1028  				InstanceId:  deref(product.Instance),
  1029  				Name:        product.Product,
  1030  				Vendor:      product.Vendor,
  1031  				Version:     product.Version,
  1032  				FullVersion: product.FullVersion,
  1033  				ProductUrl:  product.ProductURL,
  1034  				VendorUrl:   product.VendorURL,
  1035  				AppUrl:      product.AppURL,
  1036  			},
  1037  		})
  1038  
  1039  		for _, p := range product.Property {
  1040  			if p.Configuration != nil && *p.Configuration != configName {
  1041  				// Skip properties that are not part of the provided
  1042  				// configuration.
  1043  				continue
  1044  			}
  1045  			vapp.Property = append(vapp.Property, types.VAppPropertySpec{
  1046  				ArrayUpdateSpec: types.ArrayUpdateSpec{
  1047  					Operation: types.ArrayUpdateOperationAdd,
  1048  				},
  1049  				Info: &types.VAppPropertyInfo{
  1050  					Key:              int32(index),
  1051  					ClassId:          deref(product.Class),
  1052  					InstanceId:       deref(product.Instance),
  1053  					Id:               p.Key,
  1054  					Category:         product.Category,
  1055  					Label:            deref(p.Label),
  1056  					Type:             p.Type,
  1057  					UserConfigurable: p.UserConfigurable,
  1058  					DefaultValue:     deref(p.Default),
  1059  					Value:            "",
  1060  					Description:      deref(p.Description),
  1061  				},
  1062  			})
  1063  			index++
  1064  		}
  1065  	}
  1066  
  1067  	dst.VAppConfig = vapp
  1068  	return nil
  1069  }