1/*
2Copyright (c) 2014-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 find
18
19import (
20	"context"
21	"os"
22	"path"
23	"strings"
24
25	"github.com/vmware/govmomi/list"
26	"github.com/vmware/govmomi/object"
27	"github.com/vmware/govmomi/property"
28	"github.com/vmware/govmomi/vim25/mo"
29)
30
31// spec is used to specify per-search configuration, independent of the Finder instance.
32type spec struct {
33	// Relative returns the root object to resolve Relative paths (starts with ".")
34	Relative func(ctx context.Context) (object.Reference, error)
35
36	// ListMode can be used to optionally force "ls" behavior, rather than "find" behavior
37	ListMode *bool
38
39	// Contents configures the Recurser to list the Contents of traversable leaf nodes.
40	// This is typically set to true when used from the ls command, where listing
41	// a folder means listing its Contents. This is typically set to false for
42	// commands that take managed entities that are not folders as input.
43	Contents bool
44
45	// Parents specifies the types which can contain the child types being searched for.
46	// for example, when searching for a HostSystem, parent types can be
47	// "ComputeResource" or "ClusterComputeResource".
48	Parents []string
49
50	// Include specifies which types to be included in the results, used only in "find" mode.
51	Include []string
52
53	// Nested should be set to types that can be Nested, used only in "find" mode.
54	Nested []string
55
56	// ChildType avoids traversing into folders that can't contain the Include types, used only in "find" mode.
57	ChildType []string
58}
59
60func (s *spec) traversable(o mo.Reference) bool {
61	ref := o.Reference()
62
63	switch ref.Type {
64	case "Datacenter":
65		if len(s.Include) == 1 && s.Include[0] == "Datacenter" {
66			// No point in traversing deeper as Datacenters cannot be nested
67			return false
68		}
69		return true
70	case "Folder":
71		if f, ok := o.(mo.Folder); ok {
72			// TODO: Not making use of this yet, but here we can optimize when searching the entire
73			// inventory across Datacenters for specific types, for example: 'govc ls -t VirtualMachine /**'
74			// should not traverse into a Datacenter's host, network or datatore folders.
75			if !s.traversableChildType(f.ChildType) {
76				return false
77			}
78		}
79
80		return true
81	}
82
83	for _, kind := range s.Parents {
84		if kind == ref.Type {
85			return true
86		}
87	}
88
89	return false
90}
91
92func (s *spec) traversableChildType(ctypes []string) bool {
93	if len(s.ChildType) == 0 {
94		return true
95	}
96
97	for _, t := range ctypes {
98		for _, c := range s.ChildType {
99			if t == c {
100				return true
101			}
102		}
103	}
104
105	return false
106}
107
108func (s *spec) wanted(e list.Element) bool {
109	if len(s.Include) == 0 {
110		return true
111	}
112
113	w := e.Object.Reference().Type
114
115	for _, kind := range s.Include {
116		if w == kind {
117			return true
118		}
119	}
120
121	return false
122}
123
124// listMode is a global option to revert to the original Finder behavior,
125// disabling the newer "find" mode.
126var listMode = os.Getenv("GOVMOMI_FINDER_LIST_MODE") == "true"
127
128func (s *spec) listMode(isPath bool) bool {
129	if listMode {
130		return true
131	}
132
133	if s.ListMode != nil {
134		return *s.ListMode
135	}
136
137	return isPath
138}
139
140type recurser struct {
141	Collector *property.Collector
142
143	// All configures the recurses to fetch complete objects for leaf nodes.
144	All bool
145}
146
147func (r recurser) List(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
148	if len(parts) == 0 {
149		// Include non-traversable leaf elements in result. For example, consider
150		// the pattern "./vm/my-vm-*", where the pattern should match the VMs and
151		// not try to traverse them.
152		//
153		// Include traversable leaf elements in result, if the contents
154		// field is set to false.
155		//
156		if !s.Contents || !s.traversable(root.Object.Reference()) {
157			return []list.Element{root}, nil
158		}
159	}
160
161	k := list.Lister{
162		Collector: r.Collector,
163		Reference: root.Object.Reference(),
164		Prefix:    root.Path,
165	}
166
167	if r.All && len(parts) < 2 {
168		k.All = true
169	}
170
171	in, err := k.List(ctx)
172	if err != nil {
173		return nil, err
174	}
175
176	// This folder is a leaf as far as the glob goes.
177	if len(parts) == 0 {
178		return in, nil
179	}
180
181	all := parts
182	pattern := parts[0]
183	parts = parts[1:]
184
185	var out []list.Element
186	for _, e := range in {
187		matched, err := path.Match(pattern, path.Base(e.Path))
188		if err != nil {
189			return nil, err
190		}
191
192		if !matched {
193			matched = strings.HasSuffix(e.Path, "/"+path.Join(all...))
194			if matched {
195				// name contains a '/'
196				out = append(out, e)
197			}
198
199			continue
200		}
201
202		nres, err := r.List(ctx, s, e, parts)
203		if err != nil {
204			return nil, err
205		}
206
207		out = append(out, nres...)
208	}
209
210	return out, nil
211}
212
213func (r recurser) Find(ctx context.Context, s *spec, root list.Element, parts []string) ([]list.Element, error) {
214	var out []list.Element
215
216	if len(parts) > 0 {
217		pattern := parts[0]
218		matched, err := path.Match(pattern, path.Base(root.Path))
219		if err != nil {
220			return nil, err
221		}
222
223		if matched && s.wanted(root) {
224			out = append(out, root)
225		}
226	}
227
228	if !s.traversable(root.Object) {
229		return out, nil
230	}
231
232	k := list.Lister{
233		Collector: r.Collector,
234		Reference: root.Object.Reference(),
235		Prefix:    root.Path,
236	}
237
238	in, err := k.List(ctx)
239	if err != nil {
240		return nil, err
241	}
242
243	for _, e := range in {
244		nres, err := r.Find(ctx, s, e, parts)
245		if err != nil {
246			return nil, err
247		}
248
249		out = append(out, nres...)
250	}
251
252	return out, nil
253}
254