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

     1  /*
     2  Copyright (c) 2014-2016 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  
   247  	if cmd.cpus > 0 || cmd.memory > 0 || cmd.annotation != "" {
   248  		vmConfigSpec := types.VirtualMachineConfigSpec{}
   249  		if cmd.cpus > 0 {
   250  			vmConfigSpec.NumCPUs = int32(cmd.cpus)
   251  		}
   252  		if cmd.memory > 0 {
   253  			vmConfigSpec.MemoryMB = int64(cmd.memory)
   254  		}
   255  		vmConfigSpec.Annotation = cmd.annotation
   256  		task, err := vm.Reconfigure(ctx, vmConfigSpec)
   257  		if err != nil {
   258  			return err
   259  		}
   260  		_, err = task.WaitForResult(ctx, nil)
   261  		if err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	if cmd.template {
   267  		return nil
   268  	}
   269  
   270  	if cmd.on {
   271  		task, err := vm.PowerOn(ctx)
   272  		if err != nil {
   273  			return err
   274  		}
   275  
   276  		_, err = task.WaitForResult(ctx, nil)
   277  		if err != nil {
   278  			return err
   279  		}
   280  
   281  		if cmd.waitForIP {
   282  			_, err = vm.WaitForIP(ctx)
   283  			if err != nil {
   284  				return err
   285  			}
   286  		}
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  func (cmd *clone) cloneVM(ctx context.Context) (*object.VirtualMachine, error) {
   293  	devices, err := cmd.VirtualMachine.Device(ctx)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  
   298  	// prepare virtual device config spec for network card
   299  	configSpecs := []types.BaseVirtualDeviceConfigSpec{}
   300  
   301  	if cmd.NetworkFlag.IsSet() {
   302  		op := types.VirtualDeviceConfigSpecOperationAdd
   303  		card, derr := cmd.NetworkFlag.Device()
   304  		if derr != nil {
   305  			return nil, derr
   306  		}
   307  		// search for the first network card of the source
   308  		for _, device := range devices {
   309  			if _, ok := device.(types.BaseVirtualEthernetCard); ok {
   310  				op = types.VirtualDeviceConfigSpecOperationEdit
   311  				// set new backing info
   312  				cmd.NetworkFlag.Change(device, card)
   313  				card = device
   314  				break
   315  			}
   316  		}
   317  
   318  		configSpecs = append(configSpecs, &types.VirtualDeviceConfigSpec{
   319  			Operation: op,
   320  			Device:    card,
   321  		})
   322  	}
   323  
   324  	folderref := cmd.Folder.Reference()
   325  	var poolref *types.ManagedObjectReference
   326  	if cmd.ResourcePool != nil {
   327  		poolref = types.NewReference(cmd.ResourcePool.Reference())
   328  	}
   329  
   330  	relocateSpec := types.VirtualMachineRelocateSpec{
   331  		DeviceChange: configSpecs,
   332  		Folder:       &folderref,
   333  		Pool:         poolref,
   334  	}
   335  
   336  	if cmd.HostSystem != nil {
   337  		hostref := cmd.HostSystem.Reference()
   338  		relocateSpec.Host = &hostref
   339  	}
   340  
   341  	cloneSpec := &types.VirtualMachineCloneSpec{
   342  		PowerOn:  false,
   343  		Template: cmd.template,
   344  	}
   345  
   346  	if cmd.snapshot == "" {
   347  		if cmd.link {
   348  			relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndAllowSharing)
   349  		}
   350  	} else {
   351  		if cmd.link {
   352  			relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking)
   353  		}
   354  
   355  		ref, ferr := cmd.VirtualMachine.FindSnapshot(ctx, cmd.snapshot)
   356  		if ferr != nil {
   357  			return nil, ferr
   358  		}
   359  
   360  		cloneSpec.Snapshot = ref
   361  	}
   362  
   363  	cloneSpec.Location = relocateSpec
   364  	vmref := cmd.VirtualMachine.Reference()
   365  
   366  	// clone to storage pod
   367  	datastoreref := types.ManagedObjectReference{}
   368  	if cmd.StoragePod != nil && cmd.Datastore == nil {
   369  		storagePod := cmd.StoragePod.Reference()
   370  
   371  		// Build pod selection spec from config spec
   372  		podSelectionSpec := types.StorageDrsPodSelectionSpec{
   373  			StoragePod: &storagePod,
   374  		}
   375  
   376  		// Build the placement spec
   377  		storagePlacementSpec := types.StoragePlacementSpec{
   378  			Folder:           &folderref,
   379  			Vm:               &vmref,
   380  			CloneName:        cmd.name,
   381  			CloneSpec:        cloneSpec,
   382  			PodSelectionSpec: podSelectionSpec,
   383  			Type:             string(types.StoragePlacementSpecPlacementTypeClone),
   384  		}
   385  
   386  		// Get the storage placement result
   387  		storageResourceManager := object.NewStorageResourceManager(cmd.Client)
   388  		result, err := storageResourceManager.RecommendDatastores(ctx, storagePlacementSpec)
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  
   393  		// Get the recommendations
   394  		recommendations := result.Recommendations
   395  		if len(recommendations) == 0 {
   396  			return nil, fmt.Errorf("no datastore-cluster recommendations")
   397  		}
   398  
   399  		// Get the first recommendation
   400  		datastoreref = recommendations[0].Action[0].(*types.StoragePlacementAction).Destination
   401  	} else if cmd.StoragePod == nil && cmd.Datastore != nil {
   402  		datastoreref = cmd.Datastore.Reference()
   403  	} else if cmd.Cluster != nil {
   404  		spec := types.PlacementSpec{
   405  			PlacementType: string(types.PlacementSpecPlacementTypeClone),
   406  			CloneName:     cmd.name,
   407  			CloneSpec:     cloneSpec,
   408  			RelocateSpec:  &cloneSpec.Location,
   409  			Vm:            &vmref,
   410  		}
   411  		result, err := cmd.Cluster.PlaceVm(ctx, spec)
   412  		if err != nil {
   413  			return nil, err
   414  		}
   415  
   416  		recs := result.Recommendations
   417  		if len(recs) == 0 {
   418  			return nil, fmt.Errorf("no cluster recommendations")
   419  		}
   420  
   421  		rspec := *recs[0].Action[0].(*types.PlacementAction).RelocateSpec
   422  		cloneSpec.Location.Host = rspec.Host
   423  		cloneSpec.Location.Datastore = rspec.Datastore
   424  		datastoreref = *rspec.Datastore
   425  	} else {
   426  		return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster")
   427  	}
   428  
   429  	// Set the destination datastore
   430  	cloneSpec.Location.Datastore = &datastoreref
   431  
   432  	// Check if vmx already exists
   433  	if !cmd.force {
   434  		vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name)
   435  
   436  		var mds mo.Datastore
   437  		err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, datastoreref, []string{"name"}, &mds)
   438  		if err != nil {
   439  			return nil, err
   440  		}
   441  
   442  		datastore := object.NewDatastore(cmd.Client, datastoreref)
   443  		datastore.InventoryPath = mds.Name
   444  
   445  		_, err := datastore.Stat(ctx, vmxPath)
   446  		if err == nil {
   447  			dsPath := datastore.Path(vmxPath)
   448  			return nil, fmt.Errorf("file %s already exists", dsPath)
   449  		}
   450  	}
   451  
   452  	// check if customization specification requested
   453  	if len(cmd.customization) > 0 {
   454  		// get the customization spec manager
   455  		customizationSpecManager := object.NewCustomizationSpecManager(cmd.Client)
   456  		// check if customization specification exists
   457  		exists, err := customizationSpecManager.DoesCustomizationSpecExist(ctx, cmd.customization)
   458  		if err != nil {
   459  			return nil, err
   460  		}
   461  		if !exists {
   462  			return nil, fmt.Errorf("customization specification %s does not exists", cmd.customization)
   463  		}
   464  		// get the customization specification
   465  		customSpecItem, err := customizationSpecManager.GetCustomizationSpec(ctx, cmd.customization)
   466  		if err != nil {
   467  			return nil, err
   468  		}
   469  		customSpec := customSpecItem.Spec
   470  		// set the customization
   471  		cloneSpec.Customization = &customSpec
   472  	}
   473  
   474  	task, err := cmd.VirtualMachine.Clone(ctx, cmd.Folder, cmd.name, *cloneSpec)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  
   479  	logger := cmd.ProgressLogger(fmt.Sprintf("Cloning %s to %s...", cmd.VirtualMachine.InventoryPath, cmd.name))
   480  	defer logger.Wait()
   481  
   482  	info, err := task.WaitForResult(ctx, logger)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  
   487  	return object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)), nil
   488  }