github.com/vmware/govmomi@v0.51.0/cli/vm/clone.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 vm
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  
    12  	"github.com/vmware/govmomi/cli"
    13  	"github.com/vmware/govmomi/cli/flags"
    14  	"github.com/vmware/govmomi/object"
    15  	"github.com/vmware/govmomi/property"
    16  	"github.com/vmware/govmomi/vim25"
    17  	"github.com/vmware/govmomi/vim25/mo"
    18  	"github.com/vmware/govmomi/vim25/types"
    19  )
    20  
    21  type clone struct {
    22  	*flags.ClientFlag
    23  	*flags.ClusterFlag
    24  	*flags.DatacenterFlag
    25  	*flags.DatastoreFlag
    26  	*flags.StoragePodFlag
    27  	*flags.ResourcePoolFlag
    28  	*flags.HostSystemFlag
    29  	*flags.NetworkFlag
    30  	*flags.FolderFlag
    31  	*flags.VirtualMachineFlag
    32  
    33  	name          string
    34  	memory        int
    35  	cpus          int
    36  	on            bool
    37  	force         bool
    38  	template      bool
    39  	customization string
    40  	waitForIP     bool
    41  	annotation    string
    42  	snapshot      string
    43  	link          bool
    44  
    45  	Client         *vim25.Client
    46  	Cluster        *object.ClusterComputeResource
    47  	Datacenter     *object.Datacenter
    48  	Datastore      *object.Datastore
    49  	StoragePod     *object.StoragePod
    50  	ResourcePool   *object.ResourcePool
    51  	HostSystem     *object.HostSystem
    52  	Folder         *object.Folder
    53  	VirtualMachine *object.VirtualMachine
    54  }
    55  
    56  func init() {
    57  	cli.Register("vm.clone", &clone{})
    58  }
    59  
    60  func (cmd *clone) Register(ctx context.Context, f *flag.FlagSet) {
    61  	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
    62  	cmd.ClientFlag.Register(ctx, f)
    63  
    64  	cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx)
    65  	cmd.ClusterFlag.RegisterPlacement(ctx, f)
    66  
    67  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
    68  	cmd.DatacenterFlag.Register(ctx, f)
    69  
    70  	cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
    71  	cmd.DatastoreFlag.Register(ctx, f)
    72  
    73  	cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx)
    74  	cmd.StoragePodFlag.Register(ctx, f)
    75  
    76  	cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx)
    77  	cmd.ResourcePoolFlag.Register(ctx, f)
    78  
    79  	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
    80  	cmd.HostSystemFlag.Register(ctx, f)
    81  
    82  	cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx)
    83  	cmd.NetworkFlag.Register(ctx, f)
    84  
    85  	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
    86  	cmd.FolderFlag.Register(ctx, f)
    87  
    88  	cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx)
    89  	cmd.VirtualMachineFlag.Register(ctx, f)
    90  
    91  	f.IntVar(&cmd.memory, "m", 0, "Size in MB of memory")
    92  	f.IntVar(&cmd.cpus, "c", 0, "Number of CPUs")
    93  	f.BoolVar(&cmd.on, "on", true, "Power on VM")
    94  	f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists")
    95  	f.BoolVar(&cmd.template, "template", false, "Create a Template")
    96  	f.StringVar(&cmd.customization, "customization", "", "Customization Specification Name")
    97  	f.BoolVar(&cmd.waitForIP, "waitip", false, "Wait for VM to acquire IP address")
    98  	f.StringVar(&cmd.annotation, "annotation", "", "VM description")
    99  	f.StringVar(&cmd.snapshot, "snapshot", "", "Snapshot name to clone from")
   100  	f.BoolVar(&cmd.link, "link", false, "Creates a linked clone from snapshot or source VM")
   101  }
   102  
   103  func (cmd *clone) Usage() string {
   104  	return "NAME"
   105  }
   106  
   107  func (cmd *clone) Description() string {
   108  	return `Clone VM or template to NAME.
   109  
   110  Examples:
   111    govc vm.clone -vm template-vm new-vm
   112    govc vm.clone -vm template-vm -link new-vm
   113    govc vm.clone -vm template-vm -snapshot s-name new-vm
   114    govc vm.clone -vm template-vm -link -snapshot s-name new-vm
   115    govc vm.clone -vm template-vm -cluster cluster1 new-vm # use compute cluster placement
   116    govc vm.clone -vm template-vm -datastore-cluster dscluster new-vm # use datastore cluster placement
   117    govc vm.clone -vm template-vm -snapshot $(govc snapshot.tree -vm template-vm -C) new-vm
   118    govc vm.clone -vm template-vm -template new-template # clone a VM template
   119    govc vm.clone -vm=/ClusterName/vm/FolderName/VM_templateName -on=true -host=myesxi01 -ds=datastore01 myVM_name`
   120  }
   121  
   122  func (cmd *clone) Process(ctx context.Context) error {
   123  	if err := cmd.ClientFlag.Process(ctx); err != nil {
   124  		return err
   125  	}
   126  	if err := cmd.ClusterFlag.Process(ctx); err != nil {
   127  		return err
   128  	}
   129  	if err := cmd.DatacenterFlag.Process(ctx); err != nil {
   130  		return err
   131  	}
   132  	if err := cmd.DatastoreFlag.Process(ctx); err != nil {
   133  		return err
   134  	}
   135  	if err := cmd.StoragePodFlag.Process(ctx); err != nil {
   136  		return err
   137  	}
   138  	if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
   139  		return err
   140  	}
   141  	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
   142  		return err
   143  	}
   144  	if err := cmd.NetworkFlag.Process(ctx); err != nil {
   145  		return err
   146  	}
   147  	if err := cmd.FolderFlag.Process(ctx); err != nil {
   148  		return err
   149  	}
   150  	if err := cmd.VirtualMachineFlag.Process(ctx); err != nil {
   151  		return err
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  func (cmd *clone) Run(ctx context.Context, f *flag.FlagSet) error {
   158  	var err error
   159  
   160  	if len(f.Args()) != 1 {
   161  		return flag.ErrHelp
   162  	}
   163  
   164  	cmd.name = f.Arg(0)
   165  	if cmd.name == "" {
   166  		return flag.ErrHelp
   167  	}
   168  
   169  	cmd.Client, err = cmd.ClientFlag.Client()
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	cmd.Cluster, err = cmd.ClusterFlag.ClusterIfSpecified()
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter()
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	if cmd.StoragePodFlag.Isset() {
   185  		cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod()
   186  		if err != nil {
   187  			return err
   188  		}
   189  	} else if cmd.Cluster == nil {
   190  		cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
   191  		if err != nil {
   192  			return err
   193  		}
   194  	}
   195  
   196  	cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified()
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	if cmd.HostSystem != nil {
   202  		if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil {
   203  			return err
   204  		}
   205  	} else {
   206  		if cmd.Cluster == nil {
   207  			// -host is optional
   208  			if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil {
   209  				return err
   210  			}
   211  		} else {
   212  			if cmd.ResourcePool, err = cmd.Cluster.ResourcePool(ctx); err != nil {
   213  				return err
   214  			}
   215  		}
   216  	}
   217  
   218  	if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil {
   219  		return err
   220  	}
   221  
   222  	if cmd.VirtualMachine, err = cmd.VirtualMachineFlag.VirtualMachine(); err != nil {
   223  		return err
   224  	}
   225  
   226  	if cmd.VirtualMachine == nil {
   227  		return flag.ErrHelp
   228  	}
   229  
   230  	vm, err := cmd.cloneVM(ctx)
   231  	if err != nil {
   232  		return err
   233  	}
   234  	if cmd.Spec {
   235  		return nil
   236  	}
   237  
   238  	if cmd.cpus > 0 || cmd.memory > 0 || cmd.annotation != "" {
   239  		vmConfigSpec := types.VirtualMachineConfigSpec{}
   240  		if cmd.cpus > 0 {
   241  			vmConfigSpec.NumCPUs = int32(cmd.cpus)
   242  		}
   243  		if cmd.memory > 0 {
   244  			vmConfigSpec.MemoryMB = int64(cmd.memory)
   245  		}
   246  		vmConfigSpec.Annotation = cmd.annotation
   247  		task, err := vm.Reconfigure(ctx, vmConfigSpec)
   248  		if err != nil {
   249  			return err
   250  		}
   251  		_, err = task.WaitForResult(ctx, nil)
   252  		if err != nil {
   253  			return err
   254  		}
   255  	}
   256  
   257  	if cmd.template {
   258  		return nil
   259  	}
   260  
   261  	if cmd.on {
   262  		task, err := vm.PowerOn(ctx)
   263  		if err != nil {
   264  			return err
   265  		}
   266  
   267  		_, err = task.WaitForResult(ctx, nil)
   268  		if err != nil {
   269  			return err
   270  		}
   271  
   272  		if cmd.waitForIP {
   273  			_, err = vm.WaitForIP(ctx)
   274  			if err != nil {
   275  				return err
   276  			}
   277  		}
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func (cmd *clone) cloneVM(ctx context.Context) (*object.VirtualMachine, error) {
   284  	devices, err := cmd.VirtualMachine.Device(ctx)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	// prepare virtual device config spec for network card
   290  	configSpecs := []types.BaseVirtualDeviceConfigSpec{}
   291  
   292  	if cmd.NetworkFlag.IsSet() {
   293  		op := types.VirtualDeviceConfigSpecOperationAdd
   294  		card, derr := cmd.NetworkFlag.Device()
   295  		if derr != nil {
   296  			return nil, derr
   297  		}
   298  		// search for the first network card of the source
   299  		for _, device := range devices {
   300  			if _, ok := device.(types.BaseVirtualEthernetCard); ok {
   301  				op = types.VirtualDeviceConfigSpecOperationEdit
   302  				// set new backing info
   303  				cmd.NetworkFlag.Change(device, card)
   304  				card = device
   305  				break
   306  			}
   307  		}
   308  
   309  		configSpecs = append(configSpecs, &types.VirtualDeviceConfigSpec{
   310  			Operation: op,
   311  			Device:    card,
   312  		})
   313  	}
   314  
   315  	folderref := cmd.Folder.Reference()
   316  	var poolref *types.ManagedObjectReference
   317  	if cmd.ResourcePool != nil {
   318  		poolref = types.NewReference(cmd.ResourcePool.Reference())
   319  	}
   320  
   321  	relocateSpec := types.VirtualMachineRelocateSpec{
   322  		DeviceChange: configSpecs,
   323  		Folder:       &folderref,
   324  		Pool:         poolref,
   325  	}
   326  
   327  	if cmd.HostSystem != nil {
   328  		hostref := cmd.HostSystem.Reference()
   329  		relocateSpec.Host = &hostref
   330  	}
   331  
   332  	cloneSpec := &types.VirtualMachineCloneSpec{
   333  		PowerOn:  false,
   334  		Template: cmd.template,
   335  	}
   336  
   337  	if cmd.snapshot == "" {
   338  		if cmd.link {
   339  			relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndAllowSharing)
   340  		}
   341  	} else {
   342  		if cmd.link {
   343  			relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking)
   344  		}
   345  
   346  		ref, ferr := cmd.VirtualMachine.FindSnapshot(ctx, cmd.snapshot)
   347  		if ferr != nil {
   348  			return nil, ferr
   349  		}
   350  
   351  		cloneSpec.Snapshot = ref
   352  	}
   353  
   354  	cloneSpec.Location = relocateSpec
   355  	vmref := cmd.VirtualMachine.Reference()
   356  
   357  	// clone to storage pod
   358  	datastoreref := types.ManagedObjectReference{}
   359  	if cmd.StoragePod != nil && cmd.Datastore == nil {
   360  		storagePod := cmd.StoragePod.Reference()
   361  
   362  		// Build pod selection spec from config spec
   363  		podSelectionSpec := types.StorageDrsPodSelectionSpec{
   364  			StoragePod: &storagePod,
   365  		}
   366  
   367  		// Build the placement spec
   368  		storagePlacementSpec := types.StoragePlacementSpec{
   369  			Folder:           &folderref,
   370  			Vm:               &vmref,
   371  			CloneName:        cmd.name,
   372  			CloneSpec:        cloneSpec,
   373  			PodSelectionSpec: podSelectionSpec,
   374  			Type:             string(types.StoragePlacementSpecPlacementTypeClone),
   375  		}
   376  
   377  		// Get the storage placement result
   378  		storageResourceManager := object.NewStorageResourceManager(cmd.Client)
   379  		result, err := storageResourceManager.RecommendDatastores(ctx, storagePlacementSpec)
   380  		if err != nil {
   381  			return nil, err
   382  		}
   383  
   384  		// Get the recommendations
   385  		recommendations := result.Recommendations
   386  		if len(recommendations) == 0 {
   387  			return nil, fmt.Errorf("no datastore-cluster recommendations")
   388  		}
   389  
   390  		// Get the first recommendation
   391  		datastoreref = recommendations[0].Action[0].(*types.StoragePlacementAction).Destination
   392  	} else if cmd.StoragePod == nil && cmd.Datastore != nil {
   393  		datastoreref = cmd.Datastore.Reference()
   394  	} else if cmd.Cluster != nil {
   395  		spec := types.PlacementSpec{
   396  			PlacementType: string(types.PlacementSpecPlacementTypeClone),
   397  			CloneName:     cmd.name,
   398  			CloneSpec:     cloneSpec,
   399  			RelocateSpec:  &cloneSpec.Location,
   400  			Vm:            &vmref,
   401  		}
   402  		result, err := cmd.Cluster.PlaceVm(ctx, spec)
   403  		if err != nil {
   404  			return nil, err
   405  		}
   406  
   407  		recs := result.Recommendations
   408  		if len(recs) == 0 {
   409  			return nil, fmt.Errorf("no cluster recommendations")
   410  		}
   411  
   412  		rspec := *recs[0].Action[0].(*types.PlacementAction).RelocateSpec
   413  		cloneSpec.Location.Host = rspec.Host
   414  		cloneSpec.Location.Datastore = rspec.Datastore
   415  		datastoreref = *rspec.Datastore
   416  	} else {
   417  		return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster")
   418  	}
   419  
   420  	// Set the destination datastore
   421  	cloneSpec.Location.Datastore = &datastoreref
   422  
   423  	// Check if vmx already exists
   424  	if !cmd.force {
   425  		vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name)
   426  
   427  		var mds mo.Datastore
   428  		err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, datastoreref, []string{"name"}, &mds)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  
   433  		datastore := object.NewDatastore(cmd.Client, datastoreref)
   434  		datastore.InventoryPath = mds.Name
   435  
   436  		_, err := datastore.Stat(ctx, vmxPath)
   437  		if err == nil {
   438  			dsPath := datastore.Path(vmxPath)
   439  			return nil, fmt.Errorf("file %s already exists", dsPath)
   440  		}
   441  	}
   442  
   443  	// check if customization specification requested
   444  	if len(cmd.customization) > 0 {
   445  		// get the customization spec manager
   446  		customizationSpecManager := object.NewCustomizationSpecManager(cmd.Client)
   447  		// check if customization specification exists
   448  		exists, err := customizationSpecManager.DoesCustomizationSpecExist(ctx, cmd.customization)
   449  		if err != nil {
   450  			return nil, err
   451  		}
   452  		if !exists {
   453  			return nil, fmt.Errorf("customization specification %s does not exists", cmd.customization)
   454  		}
   455  		// get the customization specification
   456  		customSpecItem, err := customizationSpecManager.GetCustomizationSpec(ctx, cmd.customization)
   457  		if err != nil {
   458  			return nil, err
   459  		}
   460  		customSpec := customSpecItem.Spec
   461  		// set the customization
   462  		cloneSpec.Customization = &customSpec
   463  	}
   464  
   465  	if cmd.Spec {
   466  		return nil, cmd.WriteAny(cloneSpec)
   467  	}
   468  
   469  	task, err := cmd.VirtualMachine.Clone(ctx, cmd.Folder, cmd.name, *cloneSpec)
   470  	if err != nil {
   471  		return nil, err
   472  	}
   473  
   474  	logger := cmd.ProgressLogger(fmt.Sprintf("Cloning %s to %s...", cmd.VirtualMachine.InventoryPath, cmd.name))
   475  	defer logger.Wait()
   476  
   477  	info, err := task.WaitForResult(ctx, logger)
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  
   482  	return object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)), nil
   483  }