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.
139
140The '-type' flag value can be a managed entity type or one of the following aliases:
141
142%s
143Examples:
144  govc find
145  govc find /dc1 -type c
146  govc find vm -name my-vm-*
147  govc find . -type n
148  govc find . -type m -runtime.powerState poweredOn
149  govc find . -type m -datastore $(govc find -i datastore -name vsanDatastore)
150  govc find . -type s -summary.type vsan
151  govc find . -type h -hardware.cpuInfo.numCpuCores 16`, atable)
152}
153
154func (cmd *find) Process(ctx context.Context) error {
155	if err := cmd.DatacenterFlag.Process(ctx); err != nil {
156		return err
157	}
158	return nil
159}
160
161// rootMatch returns true if the root object path should be printed
162func (cmd *find) rootMatch(ctx context.Context, root object.Reference, client *vim25.Client, filter property.Filter) bool {
163	ref := root.Reference()
164
165	if !cmd.kind.wanted(ref.Type) {
166		return false
167	}
168
169	if len(filter) == 1 && filter["name"] == "*" {
170		return true
171	}
172
173	var content []types.ObjectContent
174
175	pc := property.DefaultCollector(client)
176	_ = pc.RetrieveWithFilter(ctx, []types.ManagedObjectReference{ref}, filter.Keys(), &content, filter)
177
178	return content != nil
179}
180
181func (cmd *find) Run(ctx context.Context, f *flag.FlagSet) error {
182	client, err := cmd.Client()
183	if err != nil {
184		return err
185	}
186
187	finder, err := cmd.Finder()
188	if err != nil {
189		return err
190	}
191
192	root := client.ServiceContent.RootFolder
193	rootPath := "/"
194
195	arg := f.Arg(0)
196	props := f.Args()
197
198	if len(props) > 0 {
199		if strings.HasPrefix(arg, "-") {
200			arg = "."
201		} else {
202			props = props[1:]
203		}
204	}
205
206	if len(props)%2 != 0 {
207		return flag.ErrHelp
208	}
209
210	switch arg {
211	case rootPath:
212	case "", ".":
213		dc, _ := cmd.DatacenterIfSpecified()
214		if dc == nil {
215			arg = rootPath
216		} else {
217			arg = "."
218			root = dc.Reference()
219			rootPath = dc.InventoryPath
220		}
221	default:
222		l, ferr := finder.ManagedObjectList(ctx, arg)
223		if ferr != nil {
224			return err
225		}
226
227		switch len(l) {
228		case 0:
229			return fmt.Errorf("%s not found", arg)
230		case 1:
231			root = l[0].Object.Reference()
232			rootPath = l[0].Path
233		default:
234			return flag.ErrHelp
235		}
236	}
237
238	filter := property.Filter{}
239
240	if len(props)%2 != 0 {
241		return flag.ErrHelp
242	}
243
244	for i := 0; i < len(props); i++ {
245		key := props[i]
246		if !strings.HasPrefix(key, "-") {
247			return flag.ErrHelp
248		}
249
250		key = key[1:]
251		i++
252		val := props[i]
253
254		if xf := f.Lookup(key); xf != nil {
255			// Support use of -flag following the ROOT arg (flag package does not do this)
256			if err = xf.Value.Set(val); err != nil {
257				return err
258			}
259		} else {
260			filter[key] = val
261		}
262	}
263
264	filter["name"] = cmd.name
265
266	printPath := func(o types.ManagedObjectReference, p string) {
267		if cmd.ref {
268			fmt.Fprintln(cmd.Out, o)
269			return
270		}
271
272		path := strings.Replace(p, rootPath, arg, 1)
273		fmt.Fprintln(cmd.Out, path)
274	}
275
276	recurse := false
277
278	switch cmd.maxdepth {
279	case -1:
280		recurse = true
281	case 0:
282	case 1:
283	default:
284		return flag.ErrHelp // TODO: ?
285	}
286
287	if cmd.rootMatch(ctx, root, client, filter) {
288		printPath(root, arg)
289	}
290
291	if cmd.maxdepth == 0 {
292		return nil
293	}
294
295	m := view.NewManager(client)
296
297	v, err := m.CreateContainerView(ctx, root, cmd.kind, recurse)
298	if err != nil {
299		return err
300	}
301
302	defer v.Destroy(ctx)
303
304	objs, err := v.Find(ctx, cmd.kind, filter)
305	if err != nil {
306		return err
307	}
308
309	for _, o := range objs {
310		var path string
311
312		if !cmd.ref {
313			e, err := finder.Element(ctx, o)
314			if err != nil {
315				return err
316			}
317			path = e.Path
318		}
319
320		printPath(o, path)
321	}
322
323	return nil
324}
325