1/*
2Copyright (c) 2014-2015 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 importx
18
19import (
20	"context"
21	"errors"
22	"flag"
23	"fmt"
24	"path"
25
26	"github.com/vmware/govmomi/govc/cli"
27	"github.com/vmware/govmomi/govc/flags"
28	"github.com/vmware/govmomi/nfc"
29	"github.com/vmware/govmomi/object"
30	"github.com/vmware/govmomi/ovf"
31	"github.com/vmware/govmomi/vim25"
32	"github.com/vmware/govmomi/vim25/soap"
33	"github.com/vmware/govmomi/vim25/types"
34)
35
36type ovfx struct {
37	*flags.DatastoreFlag
38	*flags.HostSystemFlag
39	*flags.OutputFlag
40	*flags.ResourcePoolFlag
41	*flags.FolderFlag
42
43	*ArchiveFlag
44	*OptionsFlag
45
46	Name string
47
48	Client       *vim25.Client
49	Datacenter   *object.Datacenter
50	Datastore    *object.Datastore
51	ResourcePool *object.ResourcePool
52}
53
54func init() {
55	cli.Register("import.ovf", &ovfx{})
56}
57
58func (cmd *ovfx) Register(ctx context.Context, f *flag.FlagSet) {
59	cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx)
60	cmd.DatastoreFlag.Register(ctx, f)
61	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
62	cmd.HostSystemFlag.Register(ctx, f)
63	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
64	cmd.OutputFlag.Register(ctx, f)
65	cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx)
66	cmd.ResourcePoolFlag.Register(ctx, f)
67	cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx)
68	cmd.FolderFlag.Register(ctx, f)
69
70	cmd.ArchiveFlag, ctx = newArchiveFlag(ctx)
71	cmd.ArchiveFlag.Register(ctx, f)
72	cmd.OptionsFlag, ctx = newOptionsFlag(ctx)
73	cmd.OptionsFlag.Register(ctx, f)
74
75	f.StringVar(&cmd.Name, "name", "", "Name to use for new entity")
76}
77
78func (cmd *ovfx) Process(ctx context.Context) error {
79	if err := cmd.DatastoreFlag.Process(ctx); err != nil {
80		return err
81	}
82	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
83		return err
84	}
85	if err := cmd.OutputFlag.Process(ctx); err != nil {
86		return err
87	}
88	if err := cmd.ResourcePoolFlag.Process(ctx); err != nil {
89		return err
90	}
91	if err := cmd.ArchiveFlag.Process(ctx); err != nil {
92		return err
93	}
94	if err := cmd.OptionsFlag.Process(ctx); err != nil {
95		return err
96	}
97	if err := cmd.FolderFlag.Process(ctx); err != nil {
98		return err
99	}
100	return nil
101}
102
103func (cmd *ovfx) Usage() string {
104	return "PATH_TO_OVF"
105}
106
107func (cmd *ovfx) Run(ctx context.Context, f *flag.FlagSet) error {
108	fpath, err := cmd.Prepare(f)
109	if err != nil {
110		return err
111	}
112
113	archive := &FileArchive{path: fpath}
114	archive.Client = cmd.Client
115
116	cmd.Archive = archive
117
118	moref, err := cmd.Import(fpath)
119	if err != nil {
120		return err
121	}
122
123	vm := object.NewVirtualMachine(cmd.Client, *moref)
124	return cmd.Deploy(vm)
125}
126
127func (cmd *ovfx) Prepare(f *flag.FlagSet) (string, error) {
128	var err error
129
130	args := f.Args()
131	if len(args) != 1 {
132		return "", errors.New("no file specified")
133	}
134
135	cmd.Client, err = cmd.DatastoreFlag.Client()
136	if err != nil {
137		return "", err
138	}
139
140	cmd.Datacenter, err = cmd.DatastoreFlag.Datacenter()
141	if err != nil {
142		return "", err
143	}
144
145	cmd.Datastore, err = cmd.DatastoreFlag.Datastore()
146	if err != nil {
147		return "", err
148	}
149
150	cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePoolIfSpecified()
151	if err != nil {
152		return "", err
153	}
154
155	return f.Arg(0), nil
156}
157
158func (cmd *ovfx) Deploy(vm *object.VirtualMachine) error {
159	if err := cmd.InjectOvfEnv(vm); err != nil {
160		return err
161	}
162
163	if err := cmd.PowerOn(vm); err != nil {
164		return err
165	}
166
167	if err := cmd.WaitForIP(vm); err != nil {
168		return err
169	}
170
171	return nil
172}
173
174func (cmd *ovfx) Map(op []Property) (p []types.KeyValue) {
175	for _, v := range op {
176		p = append(p, v.KeyValue)
177	}
178
179	return
180}
181
182func (cmd *ovfx) NetworkMap(e *ovf.Envelope) (p []types.OvfNetworkMapping) {
183	ctx := context.TODO()
184	finder, err := cmd.DatastoreFlag.Finder()
185	if err != nil {
186		return
187	}
188
189	networks := map[string]string{}
190
191	if e.Network != nil {
192		for _, net := range e.Network.Networks {
193			networks[net.Name] = net.Name
194		}
195	}
196
197	for _, net := range cmd.Options.NetworkMapping {
198		networks[net.Name] = net.Network
199	}
200
201	for src, dst := range networks {
202		if net, err := finder.Network(ctx, dst); err == nil {
203			p = append(p, types.OvfNetworkMapping{
204				Name:    src,
205				Network: net.Reference(),
206			})
207		}
208	}
209	return
210}
211
212func (cmd *ovfx) Import(fpath string) (*types.ManagedObjectReference, error) {
213	ctx := context.TODO()
214	o, err := cmd.ReadOvf(fpath)
215	if err != nil {
216		return nil, err
217	}
218
219	e, err := cmd.ReadEnvelope(o)
220	if err != nil {
221		return nil, fmt.Errorf("failed to parse ovf: %s", err.Error())
222	}
223
224	name := "Govc Virtual Appliance"
225	if e.VirtualSystem != nil {
226		name = e.VirtualSystem.ID
227		if e.VirtualSystem.Name != nil {
228			name = *e.VirtualSystem.Name
229		}
230	}
231
232	// Override name from options if specified
233	if cmd.Options.Name != nil {
234		name = *cmd.Options.Name
235	}
236
237	// Override name from arguments if specified
238	if cmd.Name != "" {
239		name = cmd.Name
240	}
241
242	cisp := types.OvfCreateImportSpecParams{
243		DiskProvisioning:   cmd.Options.DiskProvisioning,
244		EntityName:         name,
245		IpAllocationPolicy: cmd.Options.IPAllocationPolicy,
246		IpProtocol:         cmd.Options.IPProtocol,
247		OvfManagerCommonParams: types.OvfManagerCommonParams{
248			DeploymentOption: cmd.Options.Deployment,
249			Locale:           "US"},
250		PropertyMapping: cmd.Map(cmd.Options.PropertyMapping),
251		NetworkMapping:  cmd.NetworkMap(e),
252	}
253
254	host, err := cmd.HostSystemIfSpecified()
255	if err != nil {
256		return nil, err
257	}
258
259	if cmd.ResourcePool == nil {
260		if host == nil {
261			cmd.ResourcePool, err = cmd.ResourcePoolFlag.ResourcePool()
262		} else {
263			cmd.ResourcePool, err = host.ResourcePool(ctx)
264		}
265		if err != nil {
266			return nil, err
267		}
268	}
269
270	m := ovf.NewManager(cmd.Client)
271	spec, err := m.CreateImportSpec(ctx, string(o), cmd.ResourcePool, cmd.Datastore, cisp)
272	if err != nil {
273		return nil, err
274	}
275	if spec.Error != nil {
276		return nil, errors.New(spec.Error[0].LocalizedMessage)
277	}
278	if spec.Warning != nil {
279		for _, w := range spec.Warning {
280			_, _ = cmd.Log(fmt.Sprintf("Warning: %s\n", w.LocalizedMessage))
281		}
282	}
283
284	if cmd.Options.Annotation != "" {
285		switch s := spec.ImportSpec.(type) {
286		case *types.VirtualMachineImportSpec:
287			s.ConfigSpec.Annotation = cmd.Options.Annotation
288		case *types.VirtualAppImportSpec:
289			s.VAppConfigSpec.Annotation = cmd.Options.Annotation
290		}
291	}
292
293	folder, err := cmd.FolderOrDefault("vm")
294	if err != nil {
295		return nil, err
296	}
297
298	lease, err := cmd.ResourcePool.ImportVApp(ctx, spec.ImportSpec, folder, host)
299	if err != nil {
300		return nil, err
301	}
302
303	info, err := lease.Wait(ctx, spec.FileItem)
304	if err != nil {
305		return nil, err
306	}
307
308	u := lease.StartUpdater(ctx, info)
309	defer u.Done()
310
311	for _, i := range info.Items {
312		err = cmd.Upload(ctx, lease, i)
313		if err != nil {
314			return nil, err
315		}
316	}
317
318	return &info.Entity, lease.Complete(ctx)
319}
320
321func (cmd *ovfx) Upload(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error {
322	file := item.Path
323
324	f, size, err := cmd.Open(file)
325	if err != nil {
326		return err
327	}
328	defer f.Close()
329
330	logger := cmd.ProgressLogger(fmt.Sprintf("Uploading %s... ", path.Base(file)))
331	defer logger.Wait()
332
333	opts := soap.Upload{
334		ContentLength: size,
335		Progress:      logger,
336	}
337
338	return lease.Upload(ctx, item, f, opts)
339}
340
341func (cmd *ovfx) PowerOn(vm *object.VirtualMachine) error {
342	ctx := context.TODO()
343	if !cmd.Options.PowerOn {
344		return nil
345	}
346
347	cmd.Log("Powering on VM...\n")
348
349	task, err := vm.PowerOn(ctx)
350	if err != nil {
351		return err
352	}
353
354	if _, err = task.WaitForResult(ctx, nil); err != nil {
355		return err
356	}
357
358	return nil
359}
360
361func (cmd *ovfx) InjectOvfEnv(vm *object.VirtualMachine) error {
362	if !cmd.Options.InjectOvfEnv {
363		return nil
364	}
365
366	cmd.Log("Injecting OVF environment...\n")
367
368	var opts []types.BaseOptionValue
369
370	a := cmd.Client.ServiceContent.About
371
372	// build up Environment in order to marshal to xml
373	var props []ovf.EnvProperty
374	for _, p := range cmd.Options.PropertyMapping {
375		props = append(props, ovf.EnvProperty{
376			Key:   p.Key,
377			Value: p.Value,
378		})
379	}
380
381	env := ovf.Env{
382		EsxID: vm.Reference().Value,
383		Platform: &ovf.PlatformSection{
384			Kind:    a.Name,
385			Version: a.Version,
386			Vendor:  a.Vendor,
387			Locale:  "US",
388		},
389		Property: &ovf.PropertySection{
390			Properties: props,
391		},
392	}
393
394	opts = append(opts, &types.OptionValue{
395		Key:   "guestinfo.ovfEnv",
396		Value: env.MarshalManual(),
397	})
398
399	ctx := context.Background()
400
401	task, err := vm.Reconfigure(ctx, types.VirtualMachineConfigSpec{
402		ExtraConfig: opts,
403	})
404
405	if err != nil {
406		return err
407	}
408
409	return task.Wait(ctx)
410}
411
412func (cmd *ovfx) WaitForIP(vm *object.VirtualMachine) error {
413	ctx := context.TODO()
414	if !cmd.Options.PowerOn || !cmd.Options.WaitForIP {
415		return nil
416	}
417
418	cmd.Log("Waiting for IP address...\n")
419	ip, err := vm.WaitForIP(ctx)
420	if err != nil {
421		return err
422	}
423
424	cmd.Log(fmt.Sprintf("Received IP address: %s\n", ip))
425	return nil
426}
427