github.com/vmware/govmomi@v0.51.0/vapi/library/finder/path.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  	"errors"
    10  	"fmt"
    11  	"net/url"
    12  	"path"
    13  	"strings"
    14  
    15  	"github.com/vmware/govmomi/object"
    16  	"github.com/vmware/govmomi/property"
    17  	"github.com/vmware/govmomi/vapi/library"
    18  	"github.com/vmware/govmomi/vim25"
    19  	"github.com/vmware/govmomi/vim25/mo"
    20  	"github.com/vmware/govmomi/vim25/types"
    21  )
    22  
    23  // PathFinder is used to find the Datastore path of a library.Library, library.Item or library.File.
    24  type PathFinder struct {
    25  	m     *library.Manager
    26  	c     *vim25.Client
    27  	cache map[string]string
    28  }
    29  
    30  // NewPathFinder creates a new PathFinder instance.
    31  func NewPathFinder(m *library.Manager, c *vim25.Client) *PathFinder {
    32  	return &PathFinder{
    33  		m:     m,
    34  		c:     c,
    35  		cache: make(map[string]string),
    36  	}
    37  }
    38  
    39  // Path returns the absolute datastore path for a Library, Item or File.
    40  // The cache is used by DatastoreName().
    41  func (f *PathFinder) Path(ctx context.Context, r FindResult) (string, error) {
    42  	switch l := r.GetResult().(type) {
    43  	case library.Library:
    44  		id := ""
    45  		if len(l.Storage) != 0 {
    46  			var err error
    47  			id, err = f.datastoreName(ctx, l.Storage[0].DatastoreID)
    48  			if err != nil {
    49  				return "", err
    50  			}
    51  		}
    52  		return fmt.Sprintf("[%s] contentlib-%s", id, l.ID), nil
    53  	case library.Item:
    54  		p, err := f.Path(ctx, r.GetParent())
    55  		if err != nil {
    56  			return "", err
    57  		}
    58  		return fmt.Sprintf("%s/%s", p, l.ID), nil
    59  	case library.File:
    60  		return f.getFileItemPath(ctx, r)
    61  	default:
    62  		return "", fmt.Errorf("unsupported type=%T", l)
    63  	}
    64  }
    65  
    66  // getFileItemPath returns the absolute datastore path for a library.File
    67  func (f *PathFinder) getFileItemPath(ctx context.Context, r FindResult) (string, error) {
    68  	name := r.GetName()
    69  
    70  	dir, err := f.Path(ctx, r.GetParent())
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	p := path.Join(dir, name)
    76  
    77  	lib := r.GetParent().GetParent().GetResult().(library.Library)
    78  	if len(lib.Storage) == 0 {
    79  		return p, nil
    80  	}
    81  
    82  	// storage file name includes a uuid, for example:
    83  	//  "ubuntu-14.04.6-server-amd64.iso" -> "ubuntu-14.04.6-server-amd64_0653e3f3-b4f4-41fb-9b72-c4102450e3dc.iso"
    84  	s, err := f.m.GetLibraryItemStorage(ctx, r.GetParent().GetID(), name)
    85  	if err != nil {
    86  		return p, err
    87  	}
    88  	// Currently there can only be 1 storage URI
    89  	if len(s) == 0 {
    90  		return p, nil
    91  	}
    92  
    93  	uris := s[0].StorageURIs
    94  	if len(uris) == 0 {
    95  		return p, nil
    96  	}
    97  	u, err := url.Parse(uris[0])
    98  	if err != nil {
    99  		return p, err
   100  	}
   101  
   102  	return path.Join(dir, path.Base(u.Path)), nil
   103  }
   104  
   105  // datastoreName returns the Datastore.Name for the given id.
   106  func (f *PathFinder) datastoreName(ctx context.Context, id string) (string, error) {
   107  	if name, ok := f.cache[id]; ok {
   108  		return name, nil
   109  	}
   110  
   111  	obj := types.ManagedObjectReference{
   112  		Type:  "Datastore",
   113  		Value: id,
   114  	}
   115  
   116  	ds := object.NewDatastore(f.c, obj)
   117  	name, err := ds.ObjectName(ctx)
   118  	if err != nil {
   119  		return "", err
   120  	}
   121  
   122  	f.cache[id] = name
   123  	return name, nil
   124  }
   125  
   126  func (f *PathFinder) convertPath(
   127  	ctx context.Context,
   128  	dc *object.Datacenter,
   129  	ds mo.Datastore,
   130  	path string) (string, error) {
   131  
   132  	if v := ds.Capability.TopLevelDirectoryCreateSupported; v != nil && *v {
   133  		return path, nil
   134  	}
   135  
   136  	if dc == nil {
   137  		entities, err := mo.Ancestors(
   138  			ctx,
   139  			f.c,
   140  			f.c.ServiceContent.PropertyCollector,
   141  			ds.Self)
   142  		if err != nil {
   143  			return "", fmt.Errorf("failed to find ancestors: %w", err)
   144  		}
   145  		for _, entity := range entities {
   146  			if entity.Self.Type == "Datacenter" {
   147  				dc = object.NewDatacenter(f.c, entity.Self)
   148  				break
   149  			}
   150  		}
   151  	}
   152  
   153  	if dc == nil {
   154  		return "", errors.New("failed to find datacenter")
   155  	}
   156  
   157  	m := object.NewDatastoreNamespaceManager(f.c)
   158  	return m.ConvertNamespacePathToUuidPath(ctx, dc, path)
   159  }
   160  
   161  // ResolveLibraryItemStorage transforms the StorageURIs field in the provided
   162  // storage items from a datastore URL, ex.
   163  // "ds:///vmfs/volumes/DATASTORE_UUID/contentlib-LIB_UUID/ITEM_UUID/file.vmdk",
   164  // to the format that includes the datastore name, ex.
   165  // "[DATASTORE_NAME] contentlib-LIB_UUID/ITEM_UUID/file.vmdk".
   166  //
   167  // If datastoreMap is provided, then it will be updated with the datastores
   168  // involved in the resolver. The properties name, summary.url, and
   169  // capability.topLevelDirectoryCreateSupported will be available after the
   170  // resolver completes.
   171  //
   172  // If a storage item resides on a datastore that does not support the creation
   173  // of top-level directories, then this means the datastore is vSAN and the
   174  // storage item path needs to be further converted. If this occurs, then the
   175  // datacenter to which the datastore belongs is required. If the datacenter
   176  // parameter is non-nil, it is used, otherwise the datacenter for each datastore
   177  // is resolved as needed. It is much more efficient to send in the datacenter if
   178  // it is known ahead of time that the content library is stored on a vSAN
   179  // datastore.
   180  func (f *PathFinder) ResolveLibraryItemStorage(
   181  	ctx context.Context,
   182  	datacenter *object.Datacenter,
   183  	datastoreMap map[string]mo.Datastore,
   184  	storage []library.Storage) error {
   185  
   186  	// TODO:
   187  	// - reuse PathFinder.cache
   188  	// - the transform here isn't Content Library specific, but is currently
   189  	//   the only known use case
   190  	var ids []types.ManagedObjectReference
   191  
   192  	if datastoreMap == nil {
   193  		datastoreMap = map[string]mo.Datastore{}
   194  	}
   195  
   196  	// Currently ContentLibrary only supports a single storage backing, but this
   197  	// future proofs things.
   198  	for _, item := range storage {
   199  		id := item.StorageBacking.DatastoreID
   200  		if _, ok := datastoreMap[id]; ok {
   201  			continue
   202  		}
   203  		datastoreMap[id] = mo.Datastore{}
   204  		ids = append(
   205  			ids,
   206  			types.ManagedObjectReference{Type: "Datastore", Value: id})
   207  	}
   208  
   209  	var (
   210  		datastores []mo.Datastore
   211  		pc         = property.DefaultCollector(f.c)
   212  		props      = []string{
   213  			"name",
   214  			"summary.url",
   215  			"capability.topLevelDirectoryCreateSupported",
   216  		}
   217  	)
   218  
   219  	if err := pc.Retrieve(ctx, ids, props, &datastores); err != nil {
   220  		return err
   221  	}
   222  
   223  	for i := range datastores {
   224  		datastoreMap[datastores[i].Self.Value] = datastores[i]
   225  	}
   226  
   227  	for _, item := range storage {
   228  		ds := datastoreMap[item.StorageBacking.DatastoreID]
   229  		dsURL := ds.Summary.Url
   230  
   231  		for i := range item.StorageURIs {
   232  			szURI := item.StorageURIs[i]
   233  			uri, err := url.Parse(szURI)
   234  			if err != nil {
   235  				return fmt.Errorf(
   236  					"failed to parse storage URI %q: %w", szURI, err)
   237  			}
   238  
   239  			uri.OmitHost = false            // `ds://` required for ConvertNamespacePathToUuidPath()
   240  			uri.Path = path.Clean(uri.Path) // required for ConvertNamespacePathToUuidPath()
   241  			uri.RawQuery = ""
   242  
   243  			uriPath := uri.String()
   244  			u, err := f.convertPath(ctx, datacenter, ds, uriPath)
   245  			if err != nil {
   246  				return fmt.Errorf("failed to convert path %q: %w", uriPath, err)
   247  			}
   248  			u = strings.TrimPrefix(u, dsURL)
   249  			u = strings.TrimPrefix(u, "/")
   250  
   251  			item.StorageURIs[i] = (&object.DatastorePath{
   252  				Datastore: ds.Name,
   253  				Path:      u,
   254  			}).String()
   255  		}
   256  	}
   257  
   258  	return nil
   259  }