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