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 }