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 }