github.com/vmware/govmomi@v0.43.0/vapi/library/finder/path.go (about)

     1  /*
     2  Copyright (c) 2024-2024 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  	"fmt"
    22  	"net/url"
    23  	"path"
    24  	"strings"
    25  
    26  	"github.com/vmware/govmomi/internal"
    27  	"github.com/vmware/govmomi/object"
    28  	"github.com/vmware/govmomi/property"
    29  	"github.com/vmware/govmomi/vapi/library"
    30  	"github.com/vmware/govmomi/vim25"
    31  	"github.com/vmware/govmomi/vim25/mo"
    32  	"github.com/vmware/govmomi/vim25/types"
    33  )
    34  
    35  // PathFinder is used to find the Datastore path of a library.Library, library.Item or library.File.
    36  type PathFinder struct {
    37  	m     *library.Manager
    38  	c     *vim25.Client
    39  	cache map[string]string
    40  }
    41  
    42  // NewPathFinder creates a new PathFinder instance.
    43  func NewPathFinder(m *library.Manager, c *vim25.Client) *PathFinder {
    44  	return &PathFinder{
    45  		m:     m,
    46  		c:     c,
    47  		cache: make(map[string]string),
    48  	}
    49  }
    50  
    51  // Path returns the absolute datastore path for a Library, Item or File.
    52  // The cache is used by DatastoreName().
    53  func (f *PathFinder) Path(ctx context.Context, r FindResult) (string, error) {
    54  	switch l := r.GetResult().(type) {
    55  	case library.Library:
    56  		id := ""
    57  		if len(l.Storage) != 0 {
    58  			var err error
    59  			id, err = f.datastoreName(ctx, l.Storage[0].DatastoreID)
    60  			if err != nil {
    61  				return "", err
    62  			}
    63  		}
    64  		return fmt.Sprintf("[%s] contentlib-%s", id, l.ID), nil
    65  	case library.Item:
    66  		p, err := f.Path(ctx, r.GetParent())
    67  		if err != nil {
    68  			return "", err
    69  		}
    70  		return fmt.Sprintf("%s/%s", p, l.ID), nil
    71  	case library.File:
    72  		return f.getFileItemPath(ctx, r)
    73  	default:
    74  		return "", fmt.Errorf("unsupported type=%T", l)
    75  	}
    76  }
    77  
    78  // getFileItemPath returns the absolute datastore path for a library.File
    79  func (f *PathFinder) getFileItemPath(ctx context.Context, r FindResult) (string, error) {
    80  	name := r.GetName()
    81  
    82  	dir, err := f.Path(ctx, r.GetParent())
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  
    87  	p := path.Join(dir, name)
    88  
    89  	lib := r.GetParent().GetParent().GetResult().(library.Library)
    90  	if len(lib.Storage) == 0 {
    91  		return p, nil
    92  	}
    93  
    94  	// storage file name includes a uuid, for example:
    95  	//  "ubuntu-14.04.6-server-amd64.iso" -> "ubuntu-14.04.6-server-amd64_0653e3f3-b4f4-41fb-9b72-c4102450e3dc.iso"
    96  	s, err := f.m.GetLibraryItemStorage(ctx, r.GetParent().GetID(), name)
    97  	if err != nil {
    98  		return p, err
    99  	}
   100  	// Currently there can only be 1 storage URI
   101  	if len(s) == 0 {
   102  		return p, nil
   103  	}
   104  
   105  	uris := s[0].StorageURIs
   106  	if len(uris) == 0 {
   107  		return p, nil
   108  	}
   109  	u, err := url.Parse(uris[0])
   110  	if err != nil {
   111  		return p, err
   112  	}
   113  
   114  	return path.Join(dir, path.Base(u.Path)), nil
   115  }
   116  
   117  // datastoreName returns the Datastore.Name for the given id.
   118  func (f *PathFinder) datastoreName(ctx context.Context, id string) (string, error) {
   119  	if name, ok := f.cache[id]; ok {
   120  		return name, nil
   121  	}
   122  
   123  	obj := types.ManagedObjectReference{
   124  		Type:  "Datastore",
   125  		Value: id,
   126  	}
   127  
   128  	ds := object.NewDatastore(f.c, obj)
   129  	name, err := ds.ObjectName(ctx)
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  
   134  	f.cache[id] = name
   135  	return name, nil
   136  }
   137  
   138  func (f *PathFinder) convertPath(ctx context.Context, b *mo.Datastore, path string) (string, error) {
   139  	if !internal.IsDatastoreVSAN(*b) {
   140  		return path, nil
   141  	}
   142  
   143  	var dc *object.Datacenter
   144  
   145  	entities, err := mo.Ancestors(ctx, f.c, f.c.ServiceContent.PropertyCollector, b.Self)
   146  	if err != nil {
   147  		return "", err
   148  	}
   149  
   150  	for _, entity := range entities {
   151  		if entity.Self.Type == "Datacenter" {
   152  			dc = object.NewDatacenter(f.c, entity.Self)
   153  			break
   154  		}
   155  	}
   156  
   157  	m := object.NewDatastoreNamespaceManager(f.c)
   158  	return m.ConvertNamespacePathToUuidPath(ctx, dc, path)
   159  }
   160  
   161  // ResolveLibraryItemStorage transforms StorageURIs Datastore url (uuid) to Datastore name.
   162  func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []library.Storage) error {
   163  	// TODO:
   164  	// - reuse PathFinder.cache
   165  	// - the transform here isn't Content Library specific, but is currently the only known use case
   166  	backing := map[string]*mo.Datastore{}
   167  	var ids []types.ManagedObjectReference
   168  
   169  	// don't think we can have more than 1 Datastore backing currently, future proof anyhow
   170  	for _, item := range storage {
   171  		id := item.StorageBacking.DatastoreID
   172  		if _, ok := backing[id]; ok {
   173  			continue
   174  		}
   175  		backing[id] = nil
   176  		ids = append(ids, types.ManagedObjectReference{Type: "Datastore", Value: id})
   177  	}
   178  
   179  	var ds []mo.Datastore
   180  	pc := property.DefaultCollector(f.c)
   181  	props := []string{"name", "summary.url", "summary.type"}
   182  	if err := pc.Retrieve(ctx, ids, props, &ds); err != nil {
   183  		return err
   184  	}
   185  
   186  	for i := range ds {
   187  		backing[ds[i].Self.Value] = &ds[i]
   188  	}
   189  
   190  	for _, item := range storage {
   191  		b := backing[item.StorageBacking.DatastoreID]
   192  		dsurl := b.Summary.Url
   193  
   194  		for i := range item.StorageURIs {
   195  			uri, err := url.Parse(item.StorageURIs[i])
   196  			if err != nil {
   197  				return err
   198  			}
   199  			uri.Path = path.Clean(uri.Path) // required for ConvertNamespacePathToUuidPath()
   200  			uri.RawQuery = ""
   201  			u, err := f.convertPath(ctx, b, uri.String())
   202  			if err != nil {
   203  				return err
   204  			}
   205  			u = strings.TrimPrefix(u, dsurl)
   206  			u = strings.TrimPrefix(u, "/")
   207  
   208  			item.StorageURIs[i] = (&object.DatastorePath{
   209  				Datastore: b.Name,
   210  				Path:      u,
   211  			}).String()
   212  		}
   213  	}
   214  
   215  	return nil
   216  }