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 }