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 }