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  }