/* Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package vm import ( "context" "flag" "fmt" "github.com/vmware/govmomi/govc/cli" "github.com/vmware/govmomi/govc/flags" "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/property" "github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) type clone struct { *flags.ClientFlag *flags.ClusterFlag *flags.DatacenterFlag *flags.DatastoreFlag *flags.StoragePodFlag *flags.ResourcePoolFlag *flags.HostSystemFlag *flags.NetworkFlag *flags.FolderFlag *flags.VirtualMachineFlag name string memory int cpus int on bool force bool template bool customization string waitForIP bool annotation string snapshot string link bool Client *vim25.Client Cluster *object.ClusterComputeResource Datacenter *object.Datacenter Datastore *object.Datastore StoragePod *object.StoragePod ResourcePool *object.ResourcePool HostSystem *object.HostSystem Folder *object.Folder VirtualMachine *object.VirtualMachine } func init() { cli.Register("vm.clone", &clone{}) } func (cmd *clone) Register(ctx context.Context, f *flag.FlagSet) { cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) cmd.ClientFlag.Register(ctx, f) cmd.ClusterFlag, ctx = flags.NewClusterFlag(ctx) cmd.ClusterFlag.RegisterPlacement(ctx, f) cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) cmd.DatacenterFlag.Register(ctx, f) cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) cmd.DatastoreFlag.Register(ctx, f) cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx) cmd.StoragePodFlag.Register(ctx, f) cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) cmd.ResourcePoolFlag.Register(ctx, f) cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) cmd.HostSystemFlag.Register(ctx, f) cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx) cmd.NetworkFlag.Register(ctx, f) cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) cmd.FolderFlag.Register(ctx, f) cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) cmd.VirtualMachineFlag.Register(ctx, f) f.IntVar(&cmd.memory, "m", 0, "Size in MB of memory") f.IntVar(&cmd.cpus, "c", 0, "Number of CPUs") f.BoolVar(&cmd.on, "on", true, "Power on VM") f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists") f.BoolVar(&cmd.template, "template", false, "Create a Template") f.StringVar(&cmd.customization, "customization", "", "Customization Specification Name") f.BoolVar(&cmd.waitForIP, "waitip", false, "Wait for VM to acquire IP address") f.StringVar(&cmd.annotation, "annotation", "", "VM description") f.StringVar(&cmd.snapshot, "snapshot", "", "Snapshot name to clone from") f.BoolVar(&cmd.link, "link", false, "Creates a linked clone from snapshot or source VM") } func (cmd *clone) Usage() string { return "NAME" } func (cmd *clone) Description() string { return `Clone VM or template to NAME. Examples: govc vm.clone -vm template-vm new-vm govc vm.clone -vm template-vm -link new-vm govc vm.clone -vm template-vm -snapshot s-name new-vm govc vm.clone -vm template-vm -link -snapshot s-name new-vm govc vm.clone -vm template-vm -cluster cluster1 new-vm # use compute cluster placement govc vm.clone -vm template-vm -datastore-cluster dscluster new-vm # use datastore cluster placement govc vm.clone -vm template-vm -snapshot $(govc snapshot.tree -vm template-vm -C) new-vm govc vm.clone -vm template-vm -template new-template # clone a VM template govc vm.clone -vm=/ClusterName/vm/FolderName/VM_templateName -on=true -host=myesxi01 -ds=datastore01 myVM_name` } func (cmd *clone) Process(ctx context.Context) error { if err := cmd.ClientFlag.Process(ctx); err != nil { return err } if err := cmd.ClusterFlag.Process(ctx); err != nil { return err } if err := cmd.DatacenterFlag.Process(ctx); err != nil { return err } if err := cmd.DatastoreFlag.Process(ctx); err != nil { return err } if err := cmd.StoragePodFlag.Process(ctx); err != nil { return err } if err := cmd.ResourcePoolFlag.Process(ctx); err != nil { return err } if err := cmd.HostSystemFlag.Process(ctx); err != nil { return err } if err := cmd.NetworkFlag.Process(ctx); err != nil { return err } if err := cmd.FolderFlag.Process(ctx); err != nil { return err } if err := cmd.VirtualMachineFlag.Process(ctx); err != nil { return err } return nil } func (cmd *clone) Run(ctx context.Context, f *flag.FlagSet) error { var err error if len(f.Args()) != 1 { return flag.ErrHelp } cmd.name = f.Arg(0) if cmd.name == "" { return flag.ErrHelp } cmd.Client, err = cmd.ClientFlag.Client() if err != nil { return err } cmd.Cluster, err = cmd.ClusterFlag.ClusterIfSpecified() if err != nil { return err } cmd.Datacenter, err = cmd.DatacenterFlag.Datacenter() if err != nil { return err } if cmd.StoragePodFlag.Isset() { cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod() if err != nil { return err } } else if cmd.Cluster == nil { cmd.Datastore, err = cmd.DatastoreFlag.Datastore() if err != nil { return err } } cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified() if err != nil { return err } if cmd.HostSystem != nil { if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil { return err } } else { if cmd.Cluster == nil { // -host is optional if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil { return err } } } if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil { return err } if cmd.VirtualMachine, err = cmd.VirtualMachineFlag.VirtualMachine(); err != nil { return err } if cmd.VirtualMachine == nil { return flag.ErrHelp } vm, err := cmd.cloneVM(ctx) if err != nil { return err } if cmd.cpus > 0 || cmd.memory > 0 || cmd.annotation != "" { vmConfigSpec := types.VirtualMachineConfigSpec{} if cmd.cpus > 0 { vmConfigSpec.NumCPUs = int32(cmd.cpus) } if cmd.memory > 0 { vmConfigSpec.MemoryMB = int64(cmd.memory) } vmConfigSpec.Annotation = cmd.annotation task, err := vm.Reconfigure(ctx, vmConfigSpec) if err != nil { return err } _, err = task.WaitForResult(ctx, nil) if err != nil { return err } } if cmd.template { return nil } if cmd.on { task, err := vm.PowerOn(ctx) if err != nil { return err } _, err = task.WaitForResult(ctx, nil) if err != nil { return err } if cmd.waitForIP { _, err = vm.WaitForIP(ctx) if err != nil { return err } } } return nil } func (cmd *clone) cloneVM(ctx context.Context) (*object.VirtualMachine, error) { devices, err := cmd.VirtualMachine.Device(ctx) if err != nil { return nil, err } // prepare virtual device config spec for network card configSpecs := []types.BaseVirtualDeviceConfigSpec{} if cmd.NetworkFlag.IsSet() { op := types.VirtualDeviceConfigSpecOperationAdd card, derr := cmd.NetworkFlag.Device() if derr != nil { return nil, derr } // search for the first network card of the source for _, device := range devices { if _, ok := device.(types.BaseVirtualEthernetCard); ok { op = types.VirtualDeviceConfigSpecOperationEdit // set new backing info cmd.NetworkFlag.Change(device, card) card = device break } } configSpecs = append(configSpecs, &types.VirtualDeviceConfigSpec{ Operation: op, Device: card, }) } folderref := cmd.Folder.Reference() var poolref *types.ManagedObjectReference if cmd.ResourcePool != nil { poolref = types.NewReference(cmd.ResourcePool.Reference()) } relocateSpec := types.VirtualMachineRelocateSpec{ DeviceChange: configSpecs, Folder: &folderref, Pool: poolref, } if cmd.HostSystem != nil { hostref := cmd.HostSystem.Reference() relocateSpec.Host = &hostref } cloneSpec := &types.VirtualMachineCloneSpec{ PowerOn: false, Template: cmd.template, } if cmd.snapshot == "" { if cmd.link { relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndAllowSharing) } } else { if cmd.link { relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking) } ref, ferr := cmd.VirtualMachine.FindSnapshot(ctx, cmd.snapshot) if ferr != nil { return nil, ferr } cloneSpec.Snapshot = ref } cloneSpec.Location = relocateSpec vmref := cmd.VirtualMachine.Reference() // clone to storage pod datastoreref := types.ManagedObjectReference{} if cmd.StoragePod != nil && cmd.Datastore == nil { storagePod := cmd.StoragePod.Reference() // Build pod selection spec from config spec podSelectionSpec := types.StorageDrsPodSelectionSpec{ StoragePod: &storagePod, } // Build the placement spec storagePlacementSpec := types.StoragePlacementSpec{ Folder: &folderref, Vm: &vmref, CloneName: cmd.name, CloneSpec: cloneSpec, PodSelectionSpec: podSelectionSpec, Type: string(types.StoragePlacementSpecPlacementTypeClone), } // Get the storage placement result storageResourceManager := object.NewStorageResourceManager(cmd.Client) result, err := storageResourceManager.RecommendDatastores(ctx, storagePlacementSpec) if err != nil { return nil, err } // Get the recommendations recommendations := result.Recommendations if len(recommendations) == 0 { return nil, fmt.Errorf("no datastore-cluster recommendations") } // Get the first recommendation datastoreref = recommendations[0].Action[0].(*types.StoragePlacementAction).Destination } else if cmd.StoragePod == nil && cmd.Datastore != nil { datastoreref = cmd.Datastore.Reference() } else if cmd.Cluster != nil { spec := types.PlacementSpec{ PlacementType: string(types.PlacementSpecPlacementTypeClone), CloneName: cmd.name, CloneSpec: cloneSpec, RelocateSpec: &cloneSpec.Location, Vm: &vmref, } result, err := cmd.Cluster.PlaceVm(ctx, spec) if err != nil { return nil, err } recs := result.Recommendations if len(recs) == 0 { return nil, fmt.Errorf("no cluster recommendations") } rspec := *recs[0].Action[0].(*types.PlacementAction).RelocateSpec cloneSpec.Location.Host = rspec.Host cloneSpec.Location.Datastore = rspec.Datastore datastoreref = *rspec.Datastore } else { return nil, fmt.Errorf("please provide either a cluster, datastore or datastore-cluster") } // Set the destination datastore cloneSpec.Location.Datastore = &datastoreref // Check if vmx already exists if !cmd.force { vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name) var mds mo.Datastore err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, datastoreref, []string{"name"}, &mds) if err != nil { return nil, err } datastore := object.NewDatastore(cmd.Client, datastoreref) datastore.InventoryPath = mds.Name _, err := datastore.Stat(ctx, vmxPath) if err == nil { dsPath := cmd.Datastore.Path(vmxPath) return nil, fmt.Errorf("file %s already exists", dsPath) } } // check if customization specification requested if len(cmd.customization) > 0 { // get the customization spec manager customizationSpecManager := object.NewCustomizationSpecManager(cmd.Client) // check if customization specification exists exists, err := customizationSpecManager.DoesCustomizationSpecExist(ctx, cmd.customization) if err != nil { return nil, err } if !exists { return nil, fmt.Errorf("customization specification %s does not exists", cmd.customization) } // get the customization specification customSpecItem, err := customizationSpecManager.GetCustomizationSpec(ctx, cmd.customization) if err != nil { return nil, err } customSpec := customSpecItem.Spec // set the customization cloneSpec.Customization = &customSpec } task, err := cmd.VirtualMachine.Clone(ctx, cmd.Folder, cmd.name, *cloneSpec) if err != nil { return nil, err } logger := cmd.ProgressLogger(fmt.Sprintf("Cloning %s to %s...", cmd.VirtualMachine.InventoryPath, cmd.name)) defer logger.Wait() info, err := task.WaitForResult(ctx, logger) if err != nil { return nil, err } return object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)), nil }