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

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