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 }