github.com/vmware/govmomi@v0.43.0/govc/vm/create.go (about)

     1  /*
     2  Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vm
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"io"
    24  	"strings"
    25  	"text/tabwriter"
    26  
    27  	"github.com/vmware/govmomi/find"
    28  	"github.com/vmware/govmomi/govc/cli"
    29  	"github.com/vmware/govmomi/govc/flags"
    30  	"github.com/vmware/govmomi/object"
    31  	"github.com/vmware/govmomi/property"
    32  	"github.com/vmware/govmomi/units"
    33  	"github.com/vmware/govmomi/vim25"
    34  	"github.com/vmware/govmomi/vim25/mo"
    35  	"github.com/vmware/govmomi/vim25/types"
    36  )
    37  
    38  var (
    39  	FirmwareTypes = types.GuestOsDescriptorFirmwareType("").Strings()
    40  
    41  	FirmwareUsage = fmt.Sprintf("Firmware type [%s]", strings.Join(FirmwareTypes, "|"))
    42  )
    43  
    44  type create struct {
    45  	*flags.ClientFlag
    46  	*flags.ClusterFlag
    47  	*flags.DatacenterFlag
    48  	*flags.DatastoreFlag
    49  	*flags.StoragePodFlag
    50  	*flags.ResourcePoolFlag
    51  	*flags.HostSystemFlag
    52  	*flags.NetworkFlag
    53  	*flags.FolderFlag
    54  	*flags.StorageProfileFlag
    55  
    56  	name       string
    57  	memory     int
    58  	cpus       int
    59  	guestID    string
    60  	link       bool
    61  	on         bool
    62  	force      bool
    63  	controller string
    64  	eager      bool
    65  	thick      bool
    66  	annotation string
    67  	firmware   string
    68  	version    string
    69  	place      bool
    70  
    71  	iso              string
    72  	isoDatastoreFlag *flags.DatastoreFlag
    73  
    74  	disk              string
    75  	diskDatastoreFlag *flags.DatastoreFlag
    76  	diskDatastore     *object.Datastore
    77  
    78  	// Only set if the disk argument is a byte size, which means the disk
    79  	// doesn't exist yet and should be created
    80  	diskByteSize int64
    81  
    82  	Client       *vim25.Client
    83  	Cluster      *object.ClusterComputeResource
    84  	Datacenter   *object.Datacenter
    85  	Datastore    *object.Datastore
    86  	StoragePod   *object.StoragePod
    87  	ResourcePool *object.ResourcePool
    88  	HostSystem   *object.HostSystem
    89  	Folder       *object.Folder
    90  }
    91  
    92  func init() {
    93  	cli.Register("vm.create", &create{})
    94  }
    95  
    96  func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) {
    97  	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
    98  	cmd.ClientFlag.Register(ctx, f)
    99  
   100  	cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx)
   101  	cmd.ClusterFlag.RegisterPlacement(ctx, f)
   102  
   103  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
   104  	cmd.DatacenterFlag.Register(ctx, f)
   105  
   106  	cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
   107  	cmd.DatastoreFlag.Register(ctx, f)
   108  
   109  	cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx)
   110  	cmd.StoragePodFlag.Register(ctx, f)
   111  
   112  	cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx)
   113  	cmd.ResourcePoolFlag.Register(ctx, f)
   114  
   115  	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
   116  	cmd.HostSystemFlag.Register(ctx, f)
   117  
   118  	cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx)
   119  	cmd.NetworkFlag.Register(ctx, f)
   120  
   121  	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
   122  	cmd.FolderFlag.Register(ctx, f)
   123  
   124  	cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx)
   125  	cmd.StorageProfileFlag.Register(ctx, f)
   126  
   127  	f.IntVar(&cmd.memory, "m", 1024, "Size in MB of memory")
   128  	f.IntVar(&cmd.cpus, "c", 1, "Number of CPUs")
   129  	f.StringVar(&cmd.guestID, "g", "otherGuest", "Guest OS ID")
   130  	f.BoolVar(&cmd.link, "link", true, "Link specified disk")
   131  	f.BoolVar(&cmd.on, "on", true, "Power on VM")
   132  	f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists")
   133  	f.StringVar(&cmd.controller, "disk.controller", "scsi", "Disk controller type")
   134  	f.BoolVar(&cmd.eager, "disk.eager", false, "Eagerly scrub new disk")
   135  	f.BoolVar(&cmd.thick, "disk.thick", false, "Thick provision new disk")
   136  	f.StringVar(&cmd.annotation, "annotation", "", "VM description")
   137  	f.StringVar(&cmd.firmware, "firmware", FirmwareTypes[0], FirmwareUsage)
   138  	if cli.ShowUnreleased() {
   139  		f.BoolVar(&cmd.place, "place", false, "Place VM without creating")
   140  	}
   141  
   142  	esxiVersions := types.GetESXiVersions()
   143  	esxiVersionStrings := make([]string, len(esxiVersions))
   144  	for i := range esxiVersions {
   145  		esxiVersionStrings[i] = esxiVersions[i].String()
   146  	}
   147  	f.StringVar(&cmd.version, "version", "",
   148  		fmt.Sprintf("ESXi hardware version [%s]", strings.Join(esxiVersionStrings, "|")))
   149  
   150  	f.StringVar(&cmd.iso, "iso", "", "ISO path")
   151  	cmd.isoDatastoreFlag, ctx = flags.NewCustomDatastoreFlag(ctx)
   152  	f.StringVar(&cmd.isoDatastoreFlag.Name, "iso-datastore", "", "Datastore for ISO file")
   153  
   154  	f.StringVar(&cmd.disk, "disk", "", "Disk path (to use existing) OR size (to create new, e.g. 20GB)")
   155  	cmd.diskDatastoreFlag, _ = flags.NewCustomDatastoreFlag(ctx)
   156  	f.StringVar(&cmd.diskDatastoreFlag.Name, "disk-datastore", "", "Datastore for disk file")
   157  }
   158  
   159  func (cmd *create) Process(ctx context.Context) error {
   160  	if err := cmd.ClientFlag.Process(ctx); err != nil {
   161  		return err
   162  	}
   163  	if err := cmd.ClusterFlag.Process(ctx); err != nil {
   164  		return err
   165  	}
   166  	if err := cmd.DatacenterFlag.Process(ctx); err != nil {
   167  		return err
   168  	}
   169  	if err := cmd.DatastoreFlag.Process(ctx); err != nil {
   170  		return err
   171  	}
   172  	if err := cmd.StoragePodFlag.Process(ctx); err != nil {
   173  		return err
   174  	}
   175  	if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
   176  		return err
   177  	}
   178  	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
   179  		return err
   180  	}
   181  	if err := cmd.NetworkFlag.Process(ctx); err != nil {
   182  		return err
   183  	}
   184  	if err := cmd.FolderFlag.Process(ctx); err != nil {
   185  		return err
   186  	}
   187  	if err := cmd.StorageProfileFlag.Process(ctx); err != nil {
   188  		return err
   189  	}
   190  
   191  	// Default iso/disk datastores to the VM's datastore
   192  	if cmd.isoDatastoreFlag.Name == "" {
   193  		cmd.isoDatastoreFlag = cmd.DatastoreFlag
   194  	}
   195  	if cmd.diskDatastoreFlag.Name == "" {
   196  		cmd.diskDatastoreFlag = cmd.DatastoreFlag
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func (cmd *create) Usage() string {
   203  	return "NAME"
   204  }
   205  
   206  func (cmd *create) Description() string {
   207  	return `Create VM.
   208  
   209  For a list of possible '-g' IDs, use 'govc vm.option.info' or see:
   210  https://code.vmware.com/apis/358/vsphere/doc/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html
   211  
   212  Examples:
   213    govc vm.create -on=false vm-name
   214    govc vm.create -iso library:/boot/linux/ubuntu.iso vm-name # Content Library ISO
   215    govc vm.create -cluster cluster1 vm-name # use compute cluster placement
   216    govc vm.create -datastore-cluster dscluster vm-name # use datastore cluster placement
   217    govc vm.create -m 2048 -c 2 -g freebsd64Guest -net.adapter vmxnet3 -disk.controller pvscsi vm-name`
   218  }
   219  
   220  func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
   221  	var err error
   222  
   223  	if len(f.Args()) != 1 {
   224  		return flag.ErrHelp
   225  	}
   226  
   227  	cmd.name = f.Arg(0)
   228  	if cmd.name == "" {
   229  		return flag.ErrHelp
   230  	}
   231  
   232  	cmd.Client, err = cmd.ClientFlag.Client()
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	cmd.Cluster, err = cmd.ClusterFlag.ClusterIfSpecified()
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	if cmd.StoragePodFlag.Isset() {
   248  		cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod()
   249  		if err != nil {
   250  			return err
   251  		}
   252  	} else if cmd.Cluster == nil {
   253  		cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
   254  		if err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified()
   260  	if err != nil {
   261  		return err
   262  	}
   263  
   264  	if cmd.HostSystem != nil {
   265  		if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil {
   266  			return err
   267  		}
   268  	} else {
   269  		if cmd.Cluster == nil {
   270  			// -host is optional
   271  			if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil {
   272  				return err
   273  			}
   274  		} else {
   275  			if cmd.ResourcePool, err = cmd.Cluster.ResourcePool(ctx); err != nil {
   276  				return err
   277  			}
   278  		}
   279  	}
   280  
   281  	if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil {
   282  		return err
   283  	}
   284  
   285  	// Verify ISO exists
   286  	if cmd.iso != "" {
   287  		iso, err := cmd.isoDatastoreFlag.FileBacking(ctx, cmd.iso, true)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		cmd.iso = iso
   292  	}
   293  
   294  	// Verify disk exists
   295  	if cmd.disk != "" {
   296  		var b units.ByteSize
   297  
   298  		// If disk can be parsed as byte units, don't stat
   299  		err = b.Set(cmd.disk)
   300  		if err == nil {
   301  			cmd.diskByteSize = int64(b)
   302  		} else {
   303  			_, err = cmd.diskDatastoreFlag.Stat(ctx, cmd.disk)
   304  			if err != nil {
   305  				return err
   306  			}
   307  
   308  			cmd.diskDatastore, err = cmd.diskDatastoreFlag.Datastore()
   309  			if err != nil {
   310  				return err
   311  			}
   312  		}
   313  	}
   314  
   315  	task, err := cmd.createVM(ctx)
   316  	if err != nil {
   317  		return err
   318  	}
   319  	if cmd.place || cmd.Spec {
   320  		return nil
   321  	}
   322  	info, err := task.WaitForResult(ctx, nil)
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	vm := object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference))
   328  
   329  	if cmd.on {
   330  		task, err := vm.PowerOn(ctx)
   331  		if err != nil {
   332  			return err
   333  		}
   334  
   335  		_, err = task.WaitForResult(ctx, nil)
   336  		if err != nil {
   337  			return err
   338  		}
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  type place struct {
   345  	Spec            types.PlacementSpec           `json:"spec"`
   346  	Recommendations []types.ClusterRecommendation `json:"recommendations"`
   347  
   348  	ctx context.Context
   349  	cmd *create
   350  }
   351  
   352  func (p *place) Dump() interface{} {
   353  	return p.Recommendations
   354  }
   355  
   356  func (p *place) action(w io.Writer, r types.ClusterRecommendation, a *types.PlacementAction) error {
   357  	spec := a.RelocateSpec
   358  	if spec == nil {
   359  		return nil
   360  	}
   361  
   362  	fields := []struct {
   363  		name string
   364  		moid *types.ManagedObjectReference
   365  	}{
   366  		{"Target", r.Target},
   367  		{"  Folder", spec.Folder},
   368  		{"  Datastore", spec.Datastore},
   369  		{"  Pool", spec.Pool},
   370  		{"  Host", spec.Host},
   371  	}
   372  
   373  	for _, f := range fields {
   374  		if f.moid == nil {
   375  			continue
   376  		}
   377  		path, err := find.InventoryPath(p.ctx, p.cmd.Client, *f.moid)
   378  		if err != nil {
   379  			return err
   380  		}
   381  		fmt.Fprintf(w, "%s:\t%s\n", f.name, path)
   382  	}
   383  
   384  	return nil
   385  }
   386  
   387  func (p *place) Write(w io.Writer) error {
   388  	tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
   389  
   390  	for _, r := range p.Recommendations {
   391  		for _, a := range r.Action {
   392  			p.action(tw, r, a.(*types.PlacementAction))
   393  		}
   394  	}
   395  
   396  	return tw.Flush()
   397  }
   398  
   399  func (cmd *create) createVM(ctx context.Context) (*object.Task, error) {
   400  	var devices object.VirtualDeviceList
   401  	var err error
   402  
   403  	if cmd.version != "" {
   404  		if v, _ := types.ParseESXiVersion(cmd.version); v.IsValid() {
   405  			cmd.version = v.HardwareVersion().String()
   406  		} else if v, _ := types.ParseHardwareVersion(cmd.version); v.IsValid() {
   407  			cmd.version = v.String()
   408  		} else {
   409  			return nil, fmt.Errorf("invalid version: %s", cmd.version)
   410  		}
   411  	}
   412  
   413  	spec := &types.VirtualMachineConfigSpec{
   414  		Name:       cmd.name,
   415  		GuestId:    cmd.guestID,
   416  		NumCPUs:    int32(cmd.cpus),
   417  		MemoryMB:   int64(cmd.memory),
   418  		Annotation: cmd.annotation,
   419  		Firmware:   cmd.firmware,
   420  		Version:    cmd.version,
   421  	}
   422  
   423  	spec.VmProfile, err = cmd.StorageProfileSpec(ctx)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	devices, err = cmd.addStorage(nil)
   429  	if err != nil {
   430  		return nil, err
   431  	}
   432  
   433  	devices, err = cmd.addNetwork(devices)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  
   438  	deviceChange, err := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	spec.DeviceChange = deviceChange
   444  
   445  	var datastore *object.Datastore
   446  
   447  	// If storage pod is specified, collect placement recommendations
   448  	if cmd.StoragePod != nil {
   449  		datastore, err = cmd.recommendDatastore(ctx, spec)
   450  		if err != nil {
   451  			return nil, err
   452  		}
   453  	} else if cmd.Datastore != nil {
   454  		datastore = cmd.Datastore
   455  	} else if cmd.Cluster != nil {
   456  		pspec := types.PlacementSpec{
   457  			PlacementType: string(types.PlacementSpecPlacementTypeCreate),
   458  			ConfigSpec:    spec,
   459  		}
   460  		result, err := cmd.Cluster.PlaceVm(ctx, pspec)
   461  		if err != nil {
   462  			return nil, err
   463  		}
   464  
   465  		recs := result.Recommendations
   466  		if cmd.place {
   467  			return nil, cmd.WriteResult(&place{pspec, recs, ctx, cmd})
   468  		}
   469  		if len(recs) == 0 {
   470  			return nil, fmt.Errorf("no cluster recommendations")
   471  		}
   472  
   473  		rspec := *recs[0].Action[0].(*types.PlacementAction).RelocateSpec
   474  		if rspec.Datastore != nil {
   475  			datastore = object.NewDatastore(cmd.Client, *rspec.Datastore)
   476  			datastore.InventoryPath, _ = datastore.ObjectName(ctx)
   477  			cmd.Datastore = datastore
   478  		}
   479  		if rspec.Host != nil {
   480  			cmd.HostSystem = object.NewHostSystem(cmd.Client, *rspec.Host)
   481  		}
   482  		if rspec.Pool != nil {
   483  			cmd.ResourcePool = object.NewResourcePool(cmd.Client, *rspec.Pool)
   484  		}
   485  	} else {
   486  		return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster")
   487  	}
   488  
   489  	if !cmd.force && !cmd.Spec {
   490  		vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name)
   491  
   492  		_, err := datastore.Stat(ctx, vmxPath)
   493  		if err == nil {
   494  			dsPath := cmd.Datastore.Path(vmxPath)
   495  			return nil, fmt.Errorf("file %s already exists", dsPath)
   496  		}
   497  	}
   498  
   499  	folder := cmd.Folder
   500  
   501  	spec.Files = &types.VirtualMachineFileInfo{
   502  		VmPathName: fmt.Sprintf("[%s]", datastore.Name()),
   503  	}
   504  
   505  	if cmd.Spec {
   506  		return nil, cmd.WriteAny(spec)
   507  	}
   508  
   509  	return folder.CreateVM(ctx, *spec, cmd.ResourcePool, cmd.HostSystem)
   510  }
   511  
   512  func (cmd *create) addStorage(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) {
   513  	if cmd.controller != "ide" {
   514  		if cmd.controller == "nvme" {
   515  			nvme, err := devices.CreateNVMEController()
   516  			if err != nil {
   517  				return nil, err
   518  			}
   519  
   520  			devices = append(devices, nvme)
   521  			cmd.controller = devices.Name(nvme)
   522  		} else if cmd.controller == "sata" {
   523  			sata, err := devices.CreateSATAController()
   524  			if err != nil {
   525  				return nil, err
   526  			}
   527  
   528  			devices = append(devices, sata)
   529  			cmd.controller = devices.Name(sata)
   530  		} else {
   531  			scsi, err := devices.CreateSCSIController(cmd.controller)
   532  			if err != nil {
   533  				return nil, err
   534  			}
   535  
   536  			devices = append(devices, scsi)
   537  			cmd.controller = devices.Name(scsi)
   538  		}
   539  	}
   540  
   541  	// If controller is specified to be IDE or if an ISO is specified, add IDE controller.
   542  	if cmd.controller == "ide" || cmd.iso != "" {
   543  		ide, err := devices.CreateIDEController()
   544  		if err != nil {
   545  			return nil, err
   546  		}
   547  
   548  		devices = append(devices, ide)
   549  	}
   550  
   551  	if cmd.diskByteSize != 0 {
   552  		controller, err := devices.FindDiskController(cmd.controller)
   553  		if err != nil {
   554  			return nil, err
   555  		}
   556  
   557  		backing := &types.VirtualDiskFlatVer2BackingInfo{
   558  			DiskMode:        string(types.VirtualDiskModePersistent),
   559  			ThinProvisioned: types.NewBool(!cmd.thick),
   560  		}
   561  		if cmd.thick {
   562  			backing.EagerlyScrub = &cmd.eager
   563  		}
   564  		disk := &types.VirtualDisk{
   565  			VirtualDevice: types.VirtualDevice{
   566  				Key:     devices.NewKey(),
   567  				Backing: backing,
   568  			},
   569  			CapacityInKB: cmd.diskByteSize / 1024,
   570  		}
   571  
   572  		devices.AssignController(disk, controller)
   573  		devices = append(devices, disk)
   574  	} else if cmd.disk != "" {
   575  		controller, err := devices.FindDiskController(cmd.controller)
   576  		if err != nil {
   577  			return nil, err
   578  		}
   579  
   580  		ds := cmd.diskDatastore.Reference()
   581  		path := cmd.diskDatastore.Path(cmd.disk)
   582  		disk := devices.CreateDisk(controller, ds, path)
   583  
   584  		if cmd.link {
   585  			disk = devices.ChildDisk(disk)
   586  		}
   587  
   588  		devices = append(devices, disk)
   589  	}
   590  
   591  	if cmd.iso != "" {
   592  		ide, err := devices.FindIDEController("")
   593  		if err != nil {
   594  			return nil, err
   595  		}
   596  
   597  		cdrom, err := devices.CreateCdrom(ide)
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  
   602  		cdrom = devices.InsertIso(cdrom, cmd.iso)
   603  		devices = append(devices, cdrom)
   604  	}
   605  
   606  	return devices, nil
   607  }
   608  
   609  func (cmd *create) addNetwork(devices object.VirtualDeviceList) (object.VirtualDeviceList, error) {
   610  	netdev, err := cmd.NetworkFlag.Device()
   611  	if err != nil {
   612  		return nil, err
   613  	}
   614  
   615  	devices = append(devices, netdev)
   616  	return devices, nil
   617  }
   618  
   619  func (cmd *create) recommendDatastore(ctx context.Context, spec *types.VirtualMachineConfigSpec) (*object.Datastore, error) {
   620  	sp := cmd.StoragePod.Reference()
   621  
   622  	// Build pod selection spec from config spec
   623  	podSelectionSpec := types.StorageDrsPodSelectionSpec{
   624  		StoragePod: &sp,
   625  	}
   626  
   627  	// Keep list of disks that need to be placed
   628  	var disks []*types.VirtualDisk
   629  
   630  	// Collect disks eligible for placement
   631  	for _, deviceConfigSpec := range spec.DeviceChange {
   632  		s := deviceConfigSpec.GetVirtualDeviceConfigSpec()
   633  		if s.Operation != types.VirtualDeviceConfigSpecOperationAdd {
   634  			continue
   635  		}
   636  
   637  		if s.FileOperation != types.VirtualDeviceConfigSpecFileOperationCreate {
   638  			continue
   639  		}
   640  
   641  		d, ok := s.Device.(*types.VirtualDisk)
   642  		if !ok {
   643  			continue
   644  		}
   645  
   646  		podConfigForPlacement := types.VmPodConfigForPlacement{
   647  			StoragePod: sp,
   648  			Disk: []types.PodDiskLocator{
   649  				{
   650  					DiskId:          d.Key,
   651  					DiskBackingInfo: d.Backing,
   652  				},
   653  			},
   654  		}
   655  
   656  		podSelectionSpec.InitialVmConfig = append(podSelectionSpec.InitialVmConfig, podConfigForPlacement)
   657  		disks = append(disks, d)
   658  	}
   659  
   660  	sps := types.StoragePlacementSpec{
   661  		Type:             string(types.StoragePlacementSpecPlacementTypeCreate),
   662  		ResourcePool:     types.NewReference(cmd.ResourcePool.Reference()),
   663  		PodSelectionSpec: podSelectionSpec,
   664  		ConfigSpec:       spec,
   665  	}
   666  
   667  	srm := object.NewStorageResourceManager(cmd.Client)
   668  	result, err := srm.RecommendDatastores(ctx, sps)
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  
   673  	// Use result to pin disks to recommended datastores
   674  	recs := result.Recommendations
   675  	if len(recs) == 0 {
   676  		return nil, fmt.Errorf("no datastore-cluster recommendations")
   677  	}
   678  
   679  	ds := recs[0].Action[0].(*types.StoragePlacementAction).Destination
   680  
   681  	var mds mo.Datastore
   682  	err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, ds, []string{"name"}, &mds)
   683  	if err != nil {
   684  		return nil, err
   685  	}
   686  
   687  	datastore := object.NewDatastore(cmd.Client, ds)
   688  	datastore.InventoryPath = mds.Name
   689  
   690  	// Apply recommendation to eligible disks
   691  	for _, disk := range disks {
   692  		backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
   693  		backing.Datastore = &ds
   694  	}
   695  
   696  	return datastore, nil
   697  }