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