1/*
2Copyright (c) 2017 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 object
18
19import (
20	"bytes"
21	"context"
22	"flag"
23	"fmt"
24	"io"
25	"strings"
26
27	"github.com/vmware/govmomi/govc/cli"
28	"github.com/vmware/govmomi/govc/flags"
29	"github.com/vmware/govmomi/object"
30	"github.com/vmware/govmomi/property"
31	"github.com/vmware/govmomi/view"
32	"github.com/vmware/govmomi/vim25"
33	"github.com/vmware/govmomi/vim25/types"
34)
35
36type find struct {
37	*flags.DatacenterFlag
38
39	ref      bool
40	kind     kinds
41	name     string
42	maxdepth int
43}
44
45var alias = []struct {
46	name string
47	kind string
48}{
49	{"a", "VirtualApp"},
50	{"c", "ClusterComputeResource"},
51	{"d", "Datacenter"},
52	{"f", "Folder"},
53	{"g", "DistributedVirtualPortgroup"},
54	{"h", "HostSystem"},
55	{"m", "VirtualMachine"},
56	{"n", "Network"},
57	{"o", "OpaqueNetwork"},
58	{"p", "ResourcePool"},
59	{"r", "ComputeResource"},
60	{"s", "Datastore"},
61	{"w", "DistributedVirtualSwitch"},
62}
63
64func aliasHelp() string {
65	var help bytes.Buffer
66
67	for _, a := range alias {
68		fmt.Fprintf(&help, "  %s    %s\n", a.name, a.kind)
69	}
70
71	return help.String()
72}
73
74type kinds []string
75
76func (e *kinds) String() string {
77	return fmt.Sprint(*e)
78}
79
80func (e *kinds) Set(value string) error {
81	*e = append(*e, e.alias(value))
82	return nil
83}
84
85func (e *kinds) alias(value string) string {
86	if len(value) != 1 {
87		return value
88	}
89
90	for _, a := range alias {
91		if a.name == value {
92			return a.kind
93		}
94	}
95
96	return value
97}
98
99func (e *kinds) wanted(kind string) bool {
100	if len(*e) == 0 {
101		return true
102	}
103
104	for _, k := range *e {
105		if kind == k {
106			return true
107		}
108	}
109
110	return false
111}
112
113func init() {
114	cli.Register("find", &find{})
115}
116
117func (cmd *find) Register(ctx context.Context, f *flag.FlagSet) {
118	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
119	cmd.DatacenterFlag.Register(ctx, f)
120
121	f.Var(&cmd.kind, "type", "Resource type")
122	f.StringVar(&cmd.name, "name", "*", "Resource name")
123	f.IntVar(&cmd.maxdepth, "maxdepth", -1, "Max depth")
124	f.BoolVar(&cmd.ref, "i", false, "Print the managed object reference")
125}
126
127func (cmd *find) Usage() string {
128	return "[ROOT] [KEY VAL]..."
129}
130
131func (cmd *find) Description() string {
132	atable := aliasHelp()
133
134	return fmt.Sprintf(`Find managed objects.
135
136ROOT can be an inventory path or ManagedObjectReference.
137ROOT defaults to '.', an alias for the root folder or DC if set.
138
139Optional KEY VAL pairs can be used to filter results against object instance properties.
140Use the govc 'object.collect' command to view possible object property keys.
141
142The '-type' flag value can be a managed entity type or one of the following aliases:
143
144%s
145Examples:
146  govc find
147  govc find /dc1 -type c
148  govc find vm -name my-vm-*
149  govc find . -type n
150  govc find . -type m -runtime.powerState poweredOn
151  govc find . -type m -datastore $(govc find -i datastore -name vsanDatastore)
152  govc find . -type s -summary.type vsan
153  govc find . -type h -hardware.cpuInfo.numCpuCores 16`, atable)
154}
155
156// rootMatch returns true if the root object path should be printed
157func (cmd *find) rootMatch(ctx context.Context, root object.Reference, client *vim25.Client, filter property.Filter) bool {
158	ref := root.Reference()
159
160	if !cmd.kind.wanted(ref.Type) {
161		return false
162	}
163
164	if len(filter) == 1 && filter["name"] == "*" {
165		return true
166	}
167
168	var content []types.ObjectContent
169
170	pc := property.DefaultCollector(client)
171	_ = pc.RetrieveWithFilter(ctx, []types.ManagedObjectReference{ref}, filter.Keys(), &content, filter)
172
173	return content != nil
174}
175
176type findResult []string
177
178func (r findResult) Write(w io.Writer) error {
179	for i := range r {
180		fmt.Fprintln(w, r[i])
181	}
182	return nil
183}
184
185func (r findResult) Dump() interface{} {
186	return []string(r)
187}
188
189func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error {
190	client, err := cmd.Client()
191	if err != nil {
192		return err
193	}
194
195	finder, err := cmd.Finder()
196	if err != nil {
197		return err
198	}
199
200	root := client.ServiceContent.RootFolder
201	rootPath := "/"
202
203	arg := f.Arg(0)
204	props := f.Args()
205
206	if len(props) > 0 {
207		if strings.HasPrefix(arg, "-") {
208			arg = "."
209		} else {
210			props = props[1:]
211		}
212	}
213
214	if len(props)%2 != 0 {
215		return flag.ErrHelp
216	}
217
218	dc, err := cmd.DatacenterIfSpecified()
219	if err != nil {
220		return err
221	}
222
223	switch arg {
224	case rootPath:
225	case "", ".":
226		if dc == nil {
227			arg = rootPath
228		} else {
229			arg = "."
230			root = dc.Reference()
231			rootPath = dc.InventoryPath
232		}
233	default:
234		path := arg
235		if !strings.Contains(arg, "/") {
236			// Force list mode
237			p := "."
238			if dc != nil {
239				p = dc.InventoryPath
240			}
241			path = strings.Join([]string{p, arg}, "/")
242		}
243
244		l, ferr := finder.ManagedObjectList(ctx, path)
245		if ferr != nil {
246			return err
247		}
248
249		switch len(l) {
250		case 0:
251			return fmt.Errorf("%s not found", arg)
252		case 1:
253			root = l[0].Object.Reference()
254			rootPath = l[0].Path
255		default:
256			return fmt.Errorf("%q matches %d objects", arg, len(l))
257		}
258	}
259
260	filter := property.Filter{}
261
262	if len(props)%2 != 0 {
263		return flag.ErrHelp
264	}
265
266	for i := 0; i < len(props); i++ {
267		key := props[i]
268		if !strings.HasPrefix(key, "-") {
269			return flag.ErrHelp
270		}
271
272		key = key[1:]
273		i++
274		val := props[i]
275
276		if xf := f.Lookup(key); xf != nil {
277			// Support use of -flag following the ROOT arg (flag package does not do this)
278			if err = xf.Value.Set(val); err != nil {
279				return err
280			}
281		} else {
282			filter[key] = val
283		}
284	}
285
286	filter["name"] = cmd.name
287	var paths findResult
288
289	printPath := func(o types.ManagedObjectReference, p string) {
290		if cmd.ref {
291			paths = append(paths, o.String())
292			return
293		}
294
295		path := strings.Replace(p, rootPath, arg, 1)
296		paths = append(paths, path)
297	}
298
299	recurse := false
300
301	switch cmd.maxdepth {
302	case -1:
303		recurse = true
304	case 0:
305	case 1:
306	default:
307		return flag.ErrHelp // TODO: ?
308	}
309
310	if cmd.rootMatch(ctx, root, client, filter) {
311		printPath(root, arg)
312	}
313
314	if cmd.maxdepth == 0 {
315		return cmd.WriteResult(paths)
316	}
317
318	m := view.NewManager(client)
319
320	v, err := m.CreateContainerView(ctx, root, cmd.kind, recurse)
321	if err != nil {
322		return err
323	}
324
325	defer v.Destroy(ctx)
326
327	objs, err := v.Find(ctx, cmd.kind, filter)
328	if err != nil {
329		return err
330	}
331
332	for _, o := range objs {
333		var path string
334
335		if !cmd.ref {
336			e, err := finder.Element(ctx, o)
337			if err != nil {
338				return err
339			}
340			path = e.Path
341		}
342
343		printPath(o, path)
344	}
345
346	return cmd.WriteResult(paths)
347}
348