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