github.com/vmware/govmomi@v0.51.0/vapi/library/finder/finder.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 finder 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "path" 12 "strings" 13 14 "github.com/vmware/govmomi/vapi/library" 15 ) 16 17 // Finder is a helper object for finding content library objects by their 18 // inventory paths: /LIBRARY/ITEM/FILE. 19 // 20 // Wildcard characters `*` and `?` are both supported. However, the use 21 // of a wildcard character in the search string results in a full listing of 22 // that part of the path's server-side items. 23 // 24 // Path parts that do not use wildcard characters rely on server-side Find 25 // functions to find the path token by its name. Ironically finding one 26 // item with a direct path takes longer than if a wildcard is used because 27 // of the multiple round-trips. Direct paths will be more performant on 28 // systems that have numerous items. 29 type Finder struct { 30 M *library.Manager 31 } 32 33 // NewFinder returns a new Finder. 34 func NewFinder(m *library.Manager) *Finder { 35 return &Finder{m} 36 } 37 38 // Find finds one or more items that match the provided inventory path(s). 39 func (f *Finder) Find( 40 ctx context.Context, ipath ...string) ([]FindResult, error) { 41 42 if len(ipath) == 0 { 43 ipath = []string{""} 44 } 45 var result []FindResult 46 for _, p := range ipath { 47 results, err := f.find(ctx, p) 48 if err != nil { 49 return nil, err 50 } 51 result = append(result, results...) 52 } 53 return result, nil 54 } 55 56 func (f *Finder) find(ctx context.Context, ipath string) ([]FindResult, error) { 57 58 if ipath == "" { 59 ipath = "*" 60 } 61 62 // Get the argument and remove any leading separator characters. 63 ipath = strings.TrimPrefix(ipath, "/") 64 65 // Tokenize the path into its distinct parts. 66 parts := strings.Split(ipath, "/") 67 68 // If there are more than three parts then the file name contains 69 // the "/" character. In that case collapse any additional parts 70 // back into the filename. 71 if len(parts) > 3 { 72 parts = []string{ 73 parts[0], 74 parts[1], 75 strings.Join(parts[2:], "/"), 76 } 77 } 78 79 libs, err := f.findLibraries(ctx, parts[0]) 80 if err != nil { 81 return nil, err 82 } 83 84 // If the path is a single token then the libraries are requested. 85 if len(parts) < 2 { 86 return libs, nil 87 } 88 89 items, err := f.findLibraryItems(ctx, libs, parts[1]) 90 if err != nil { 91 return nil, err 92 } 93 94 // If the path is two tokens then the library items are requested. 95 if len(parts) < 3 { 96 return items, nil 97 } 98 99 // Get the library item files. 100 return f.findLibraryItemFiles(ctx, items, parts[2]) 101 } 102 103 // FindResult is the type of object returned from a Find operation. 104 type FindResult interface { 105 106 // GetParent returns the parent of the find result. If the find result 107 // is a Library then this function will return nil. 108 GetParent() FindResult 109 110 // GetPath returns the inventory path of the find result. 111 GetPath() string 112 113 // GetID returns the ID of the find result. 114 GetID() string 115 116 // GetName returns the name of the find result. 117 GetName() string 118 119 // GetResult gets the underlying library object. 120 GetResult() any 121 } 122 123 type findResult struct { 124 result any 125 parent FindResult 126 } 127 128 func (f findResult) GetResult() any { 129 return f.result 130 } 131 func (f findResult) GetParent() FindResult { 132 return f.parent 133 } 134 func (f findResult) GetPath() string { 135 switch f.result.(type) { 136 case library.Library: 137 return fmt.Sprintf("/%s", f.GetName()) 138 case library.Item, library.File: 139 return fmt.Sprintf("%s/%s", f.parent.GetPath(), f.GetName()) 140 default: 141 return "" 142 } 143 } 144 145 func (f findResult) GetID() string { 146 switch t := f.result.(type) { 147 case library.Library: 148 return t.ID 149 case library.Item: 150 return t.ID 151 default: 152 return "" 153 } 154 } 155 156 func (f findResult) GetName() string { 157 switch t := f.result.(type) { 158 case library.Library: 159 return t.Name 160 case library.Item: 161 return t.Name 162 case library.File: 163 return t.Name 164 default: 165 return "" 166 } 167 } 168 169 func (f findResult) MarshalJSON() ([]byte, error) { 170 return json.Marshal(f.GetResult()) 171 } 172 173 func (f *Finder) findLibraries( 174 ctx context.Context, 175 token string) ([]FindResult, error) { 176 177 if token == "" { 178 token = "*" 179 } 180 181 var result []FindResult 182 183 // If the token does not contain any wildcard characters then perform 184 // a lookup by name using a server side call. 185 if !strings.ContainsAny(token, "*?") { 186 libIDs, err := f.M.FindLibrary(ctx, library.Find{Name: token}) 187 if err != nil { 188 return nil, err 189 } 190 for _, id := range libIDs { 191 lib, err := f.M.GetLibraryByID(ctx, id) 192 if err != nil { 193 return nil, err 194 } 195 result = append(result, findResult{result: *lib}) 196 } 197 if len(result) == 0 { 198 lib, err := f.M.GetLibraryByID(ctx, token) 199 if err == nil { 200 result = append(result, findResult{result: *lib}) 201 } 202 } 203 return result, nil 204 } 205 206 libs, err := f.M.GetLibraries(ctx) 207 if err != nil { 208 return nil, err 209 } 210 for _, lib := range libs { 211 match, err := path.Match(token, lib.Name) 212 if err != nil { 213 return nil, err 214 } 215 if match { 216 result = append(result, findResult{result: lib}) 217 } 218 } 219 return result, nil 220 } 221 222 func (f *Finder) findLibraryItems( 223 ctx context.Context, 224 parents []FindResult, token string) ([]FindResult, error) { 225 226 if token == "" { 227 token = "*" 228 } 229 230 var result []FindResult 231 232 for _, parent := range parents { 233 // If the token does not contain any wildcard characters then perform 234 // a lookup by name using a server side call. 235 if !strings.ContainsAny(token, "*?") { 236 childIDs, err := f.M.FindLibraryItems( 237 ctx, library.FindItem{ 238 Name: token, 239 LibraryID: parent.GetID(), 240 }) 241 if err != nil { 242 return nil, err 243 } 244 for _, id := range childIDs { 245 child, err := f.M.GetLibraryItem(ctx, id) 246 if err != nil { 247 return nil, err 248 } 249 result = append(result, findResult{ 250 result: *child, 251 parent: parent, 252 }) 253 } 254 continue 255 } 256 257 children, err := f.M.GetLibraryItems(ctx, parent.GetID()) 258 if err != nil { 259 return nil, err 260 } 261 for _, child := range children { 262 match, err := path.Match(token, child.Name) 263 if err != nil { 264 return nil, err 265 } 266 if match { 267 result = append( 268 result, findResult{parent: parent, result: child}) 269 } 270 } 271 } 272 return result, nil 273 } 274 275 func (f *Finder) findLibraryItemFiles( 276 ctx context.Context, 277 parents []FindResult, token string) ([]FindResult, error) { 278 279 if token == "" { 280 token = "*" 281 } 282 283 var result []FindResult 284 285 for _, parent := range parents { 286 // If the token does not contain any wildcard characters then perform 287 // a lookup by name using a server side call. 288 if !strings.ContainsAny(token, "*?") { 289 child, err := f.M.GetLibraryItemFile(ctx, parent.GetID(), token) 290 if err != nil { 291 return nil, err 292 } 293 result = append(result, findResult{ 294 result: *child, 295 parent: parent, 296 }) 297 continue 298 } 299 300 children, err := f.M.ListLibraryItemFiles(ctx, parent.GetID()) 301 if err != nil { 302 return nil, err 303 } 304 for _, child := range children { 305 match, err := path.Match(token, child.Name) 306 if err != nil { 307 return nil, err 308 } 309 if match { 310 result = append( 311 result, findResult{parent: parent, result: child}) 312 } 313 } 314 } 315 return result, nil 316 }