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