1/* 2Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package vm 18 19import ( 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 33type clone struct { 34 *flags.ClientFlag 35 *flags.DatacenterFlag 36 *flags.DatastoreFlag 37 *flags.StoragePodFlag 38 *flags.ResourcePoolFlag 39 *flags.HostSystemFlag 40 *flags.NetworkFlag 41 *flags.FolderFlag 42 *flags.VirtualMachineFlag 43 44 name string 45 memory int 46 cpus int 47 on bool 48 force bool 49 template bool 50 customization string 51 waitForIP bool 52 annotation string 53 snapshot string 54 link bool 55 56 Client *vim25.Client 57 Datacenter *object.Datacenter 58 Datastore *object.Datastore 59 StoragePod *object.StoragePod 60 ResourcePool *object.ResourcePool 61 HostSystem *object.HostSystem 62 Folder *object.Folder 63 VirtualMachine *object.VirtualMachine 64} 65 66func init() { 67 cli.Register("vm.clone", &clone{}) 68} 69 70func (cmd *clone) Register(ctx context.Context, f *flag.FlagSet) { 71 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 72 cmd.ClientFlag.Register(ctx, f) 73 74 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 75 cmd.DatacenterFlag.Register(ctx, f) 76 77 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 78 cmd.DatastoreFlag.Register(ctx, f) 79 80 cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx) 81 cmd.StoragePodFlag.Register(ctx, f) 82 83 cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) 84 cmd.ResourcePoolFlag.Register(ctx, f) 85 86 cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx) 87 cmd.HostSystemFlag.Register(ctx, f) 88 89 cmd.NetworkFlag, ctx = flags.NewNetworkFlag(ctx) 90 cmd.NetworkFlag.Register(ctx, f) 91 92 cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) 93 cmd.FolderFlag.Register(ctx, f) 94 95 cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) 96 cmd.VirtualMachineFlag.Register(ctx, f) 97 98 f.IntVar(&cmd.memory, "m", 0, "Size in MB of memory") 99 f.IntVar(&cmd.cpus, "c", 0, "Number of CPUs") 100 f.BoolVar(&cmd.on, "on", true, "Power on VM") 101 f.BoolVar(&cmd.force, "force", false, "Create VM if vmx already exists") 102 f.BoolVar(&cmd.template, "template", false, "Create a Template") 103 f.StringVar(&cmd.customization, "customization", "", "Customization Specification Name") 104 f.BoolVar(&cmd.waitForIP, "waitip", false, "Wait for VM to acquire IP address") 105 f.StringVar(&cmd.annotation, "annotation", "", "VM description") 106 f.StringVar(&cmd.snapshot, "snapshot", "", "Snapshot name to clone from") 107 f.BoolVar(&cmd.link, "link", false, "Creates a linked clone from snapshot or source VM") 108} 109 110func (cmd *clone) Usage() string { 111 return "NAME" 112} 113 114func (cmd *clone) Description() string { 115 return `Clone VM to NAME. 116 117Examples: 118 govc vm.clone -vm template-vm new-vm 119 govc vm.clone -vm template-vm -link new-vm 120 govc vm.clone -vm template-vm -snapshot s-name new-vm 121 govc vm.clone -vm template-vm -link -snapshot s-name new-vm 122 govc vm.clone -vm template-vm -snapshot $(govc snapshot.tree -vm template-vm -C) new-vm` 123} 124 125func (cmd *clone) Process(ctx context.Context) error { 126 if err := cmd.ClientFlag.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 157func (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.Datacenter, err = cmd.DatacenterFlag.Datacenter() 175 if err != nil { 176 return err 177 } 178 179 if cmd.StoragePodFlag.Isset() { 180 cmd.StoragePod, err = cmd.StoragePodFlag.StoragePod() 181 if err != nil { 182 return err 183 } 184 } else { 185 cmd.Datastore, err = cmd.DatastoreFlag.Datastore() 186 if err != nil { 187 return err 188 } 189 } 190 191 cmd.HostSystem, err = cmd.HostSystemFlag.HostSystemIfSpecified() 192 if err != nil { 193 return err 194 } 195 196 if cmd.HostSystem != nil { 197 if cmd.ResourcePool, err = cmd.HostSystem.ResourcePool(ctx); err != nil { 198 return err 199 } 200 } else { 201 // -host is optional 202 if cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool(); err != nil { 203 return err 204 } 205 } 206 207 if cmd.Folder, err = cmd.FolderFlag.Folder(); err != nil { 208 return err 209 } 210 211 if cmd.VirtualMachine, err = cmd.VirtualMachineFlag.VirtualMachine(); err != nil { 212 return err 213 } 214 215 if cmd.VirtualMachine == nil { 216 return flag.ErrHelp 217 } 218 219 vm, err := cmd.cloneVM(ctx) 220 if err != nil { 221 return err 222 } 223 224 if cmd.cpus > 0 || cmd.memory > 0 { 225 vmConfigSpec := types.VirtualMachineConfigSpec{} 226 if cmd.cpus > 0 { 227 vmConfigSpec.NumCPUs = int32(cmd.cpus) 228 } 229 if cmd.memory > 0 { 230 vmConfigSpec.MemoryMB = int64(cmd.memory) 231 } 232 vmConfigSpec.Annotation = cmd.annotation 233 task, err := vm.Reconfigure(ctx, vmConfigSpec) 234 if err != nil { 235 return err 236 } 237 _, err = task.WaitForResult(ctx, nil) 238 if err != nil { 239 return err 240 } 241 } 242 243 if cmd.on { 244 task, err := vm.PowerOn(ctx) 245 if err != nil { 246 return err 247 } 248 249 _, err = task.WaitForResult(ctx, nil) 250 if err != nil { 251 return err 252 } 253 254 if cmd.waitForIP { 255 _, err = vm.WaitForIP(ctx) 256 if err != nil { 257 return err 258 } 259 } 260 } 261 262 return nil 263} 264 265func (cmd *clone) cloneVM(ctx context.Context) (*object.VirtualMachine, error) { 266 devices, err := cmd.VirtualMachine.Device(ctx) 267 if err != nil { 268 return nil, err 269 } 270 271 // prepare virtual device config spec for network card 272 configSpecs := []types.BaseVirtualDeviceConfigSpec{} 273 274 if cmd.NetworkFlag.IsSet() { 275 op := types.VirtualDeviceConfigSpecOperationAdd 276 card, derr := cmd.NetworkFlag.Device() 277 if derr != nil { 278 return nil, derr 279 } 280 // search for the first network card of the source 281 for _, device := range devices { 282 if _, ok := device.(types.BaseVirtualEthernetCard); ok { 283 op = types.VirtualDeviceConfigSpecOperationEdit 284 // set new backing info 285 cmd.NetworkFlag.Change(device, card) 286 card = device 287 break 288 } 289 } 290 291 configSpecs = append(configSpecs, &types.VirtualDeviceConfigSpec{ 292 Operation: op, 293 Device: card, 294 }) 295 } 296 297 folderref := cmd.Folder.Reference() 298 poolref := cmd.ResourcePool.Reference() 299 300 relocateSpec := types.VirtualMachineRelocateSpec{ 301 DeviceChange: configSpecs, 302 Folder: &folderref, 303 Pool: &poolref, 304 } 305 306 if cmd.HostSystem != nil { 307 hostref := cmd.HostSystem.Reference() 308 relocateSpec.Host = &hostref 309 } 310 311 cloneSpec := &types.VirtualMachineCloneSpec{ 312 PowerOn: false, 313 Template: cmd.template, 314 } 315 316 if cmd.snapshot == "" { 317 if cmd.link { 318 relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsMoveAllDiskBackingsAndAllowSharing) 319 } 320 } else { 321 if cmd.link { 322 relocateSpec.DiskMoveType = string(types.VirtualMachineRelocateDiskMoveOptionsCreateNewChildDiskBacking) 323 } 324 325 ref, ferr := cmd.VirtualMachine.FindSnapshot(ctx, cmd.snapshot) 326 if ferr != nil { 327 return nil, ferr 328 } 329 330 cloneSpec.Snapshot = ref 331 } 332 333 cloneSpec.Location = relocateSpec 334 335 // clone to storage pod 336 datastoreref := types.ManagedObjectReference{} 337 if cmd.StoragePod != nil && cmd.Datastore == nil { 338 storagePod := cmd.StoragePod.Reference() 339 340 // Build pod selection spec from config spec 341 podSelectionSpec := types.StorageDrsPodSelectionSpec{ 342 StoragePod: &storagePod, 343 } 344 345 // Get the virtual machine reference 346 vmref := cmd.VirtualMachine.Reference() 347 348 // Build the placement spec 349 storagePlacementSpec := types.StoragePlacementSpec{ 350 Folder: &folderref, 351 Vm: &vmref, 352 CloneName: cmd.name, 353 CloneSpec: cloneSpec, 354 PodSelectionSpec: podSelectionSpec, 355 Type: string(types.StoragePlacementSpecPlacementTypeClone), 356 } 357 358 // Get the storage placement result 359 storageResourceManager := object.NewStorageResourceManager(cmd.Client) 360 result, err := storageResourceManager.RecommendDatastores(ctx, storagePlacementSpec) 361 if err != nil { 362 return nil, err 363 } 364 365 // Get the recommendations 366 recommendations := result.Recommendations 367 if len(recommendations) == 0 { 368 return nil, fmt.Errorf("no recommendations") 369 } 370 371 // Get the first recommendation 372 datastoreref = recommendations[0].Action[0].(*types.StoragePlacementAction).Destination 373 } else if cmd.StoragePod == nil && cmd.Datastore != nil { 374 datastoreref = cmd.Datastore.Reference() 375 } else { 376 return nil, fmt.Errorf("Please provide either a datastore or a storagepod") 377 } 378 379 // Set the destination datastore 380 cloneSpec.Location.Datastore = &datastoreref 381 382 // Check if vmx already exists 383 if !cmd.force { 384 vmxPath := fmt.Sprintf("%s/%s.vmx", cmd.name, cmd.name) 385 386 var mds mo.Datastore 387 err = property.DefaultCollector(cmd.Client).RetrieveOne(ctx, datastoreref, []string{"name"}, &mds) 388 if err != nil { 389 return nil, err 390 } 391 392 datastore := object.NewDatastore(cmd.Client, datastoreref) 393 datastore.InventoryPath = mds.Name 394 395 _, err := datastore.Stat(ctx, vmxPath) 396 if err == nil { 397 dsPath := cmd.Datastore.Path(vmxPath) 398 return nil, fmt.Errorf("File %s already exists", dsPath) 399 } 400 } 401 402 // check if customization specification requested 403 if len(cmd.customization) > 0 { 404 // get the customization spec manager 405 customizationSpecManager := object.NewCustomizationSpecManager(cmd.Client) 406 // check if customization specification exists 407 exists, err := customizationSpecManager.DoesCustomizationSpecExist(ctx, cmd.customization) 408 if err != nil { 409 return nil, err 410 } 411 if exists == false { 412 return nil, fmt.Errorf("Customization specification %s does not exists.", cmd.customization) 413 } 414 // get the customization specification 415 customSpecItem, err := customizationSpecManager.GetCustomizationSpec(ctx, cmd.customization) 416 if err != nil { 417 return nil, err 418 } 419 customSpec := customSpecItem.Spec 420 // set the customization 421 cloneSpec.Customization = &customSpec 422 } 423 424 task, err := cmd.VirtualMachine.Clone(ctx, cmd.Folder, cmd.name, *cloneSpec) 425 if err != nil { 426 return nil, err 427 } 428 429 logger := cmd.ProgressLogger(fmt.Sprintf("Cloning %s to %s...", cmd.VirtualMachine.InventoryPath, cmd.name)) 430 defer logger.Wait() 431 432 info, err := task.WaitForResult(ctx, logger) 433 if err != nil { 434 return nil, err 435 } 436 437 return object.NewVirtualMachine(cmd.Client, info.Result.(types.ManagedObjectReference)), nil 438} 439