github.com/vmware/govmomi@v0.37.2/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  }