github.com/vmware/govmomi@v0.37.1/govc/library/info.go (about) 1 /* 2 Copyright (c) 2018-2023 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 library 18 19 import ( 20 "context" 21 "encoding/json" 22 "flag" 23 "fmt" 24 "io" 25 "path" 26 "strings" 27 "text/tabwriter" 28 "time" 29 30 "github.com/vmware/govmomi/govc/cli" 31 "github.com/vmware/govmomi/govc/flags" 32 "github.com/vmware/govmomi/object" 33 "github.com/vmware/govmomi/units" 34 "github.com/vmware/govmomi/vapi/library" 35 "github.com/vmware/govmomi/vapi/library/finder" 36 37 "github.com/vmware/govmomi/property" 38 "github.com/vmware/govmomi/vim25/mo" 39 "github.com/vmware/govmomi/vim25/types" 40 ) 41 42 type info struct { 43 *flags.ClientFlag 44 *flags.OutputFlag 45 *flags.DatacenterFlag 46 47 long bool 48 link bool 49 url bool 50 51 names map[string]string 52 } 53 54 func init() { 55 cli.Register("library.info", &info{}) 56 } 57 58 func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) { 59 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 60 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 61 cmd.ClientFlag.Register(ctx, f) 62 cmd.OutputFlag.Register(ctx, f) 63 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 64 cmd.DatacenterFlag.Register(ctx, f) 65 66 f.BoolVar(&cmd.long, "l", false, "Long listing format") 67 f.BoolVar(&cmd.link, "L", false, "List Datastore path only") 68 f.BoolVar(&cmd.url, "U", false, "List pub/sub URL(s) only") 69 70 cmd.names = make(map[string]string) 71 } 72 73 func (cmd *info) Process(ctx context.Context) error { 74 if err := cmd.ClientFlag.Process(ctx); err != nil { 75 return err 76 } 77 return nil 78 } 79 80 func (cmd *info) Description() string { 81 return `Display library information. 82 83 Examples: 84 govc library.info 85 govc library.info /lib1 86 govc library.info -l /lib1 | grep Size: 87 govc library.info /lib1/item1 88 govc library.info /lib1/item1/ 89 govc library.info */ 90 govc device.cdrom.insert -vm $vm -device cdrom-3000 $(govc library.info -L /lib1/item1/file1) 91 govc library.info -json | jq . 92 govc library.info -json /lib1/item1 | jq .` 93 } 94 95 type infoResultsWriter struct { 96 Result []finder.FindResult `json:"result"` 97 m *library.Manager 98 cmd *info 99 } 100 101 func (r infoResultsWriter) MarshalJSON() ([]byte, error) { 102 return json.Marshal(r.Result) 103 } 104 105 func (r infoResultsWriter) Dump() interface{} { 106 res := make([]interface{}, len(r.Result)) 107 for i := range r.Result { 108 res[i] = r.Result[0].GetResult() 109 } 110 return res 111 } 112 113 func (r infoResultsWriter) Write(w io.Writer) error { 114 if r.cmd.link { 115 for _, j := range r.Result { 116 p, err := r.cmd.getDatastoreFilePath(j) 117 if err != nil { 118 return err 119 } 120 if !r.cmd.long { 121 var path object.DatastorePath 122 path.FromString(p) 123 p = path.Path 124 } 125 fmt.Fprintln(w, p) 126 } 127 return nil 128 } 129 130 tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) 131 defer tw.Flush() 132 for _, j := range r.Result { 133 switch t := j.GetResult().(type) { 134 case library.Library: 135 if err := r.writeLibrary(tw, t, j); err != nil { 136 return err 137 } 138 case library.Item: 139 if err := r.writeItem(tw, t, j); err != nil { 140 return err 141 } 142 case library.File: 143 if err := r.writeFile(tw, t, j); err != nil { 144 return err 145 } 146 } 147 tw.Flush() 148 } 149 return nil 150 } 151 152 func (r infoResultsWriter) writeLibrary( 153 w io.Writer, v library.Library, res finder.FindResult) error { 154 155 published := v.Publication != nil && *v.Publication.Published 156 157 if r.cmd.url { 158 switch { 159 case v.Subscription != nil: 160 _, _ = fmt.Fprintf(w, "%s\n", v.Subscription.SubscriptionURL) 161 case published: 162 _, _ = fmt.Fprintf(w, "%s\n", v.Publication.PublishURL) 163 } 164 165 return nil 166 } 167 168 fmt.Fprintf(w, "Name:\t%s\n", v.Name) 169 fmt.Fprintf(w, " ID:\t%s\n", v.ID) 170 fmt.Fprintf(w, " Path:\t%s\n", res.GetPath()) 171 if v.Description != nil { 172 fmt.Fprintf(w, " Description:\t%s\n", *v.Description) 173 } 174 fmt.Fprintf(w, " Version:\t%s\n", v.Version) 175 fmt.Fprintf(w, " Created:\t%s\n", v.CreationTime.Format(time.ANSIC)) 176 fmt.Fprintf(w, " Security Policy ID\t%s\n", v.SecurityPolicyID) 177 fmt.Fprintf(w, " StorageBackings:\t\n") 178 for _, d := range v.Storage { 179 fmt.Fprintf(w, " DatastoreID:\t%s\n", d.DatastoreID) 180 fmt.Fprintf(w, " Type:\t%s\n", d.Type) 181 } 182 if r.cmd.long { 183 fmt.Fprintf(w, " Datastore Path:\t%s\n", r.cmd.getDatastorePath(res)) 184 items, err := r.m.GetLibraryItems(context.Background(), v.ID) 185 if err != nil { 186 return err 187 } 188 var size int64 189 for i := range items { 190 size += items[i].Size 191 } 192 fmt.Fprintf(w, " Size:\t%s\n", units.ByteSize(size)) 193 fmt.Fprintf(w, " Items:\t%d\n", len(items)) 194 } 195 if v.Subscription != nil { 196 dl := "All" 197 if v.Subscription.OnDemand != nil && *v.Subscription.OnDemand { 198 dl = "On Demand" 199 } 200 201 fmt.Fprintf(w, " Subscription:\t\n") 202 fmt.Fprintf(w, " AutoSync:\t%t\n", *v.Subscription.AutomaticSyncEnabled) 203 fmt.Fprintf(w, " URL:\t%s\n", v.Subscription.SubscriptionURL) 204 fmt.Fprintf(w, " Auth:\t%s\n", v.Subscription.AuthenticationMethod) 205 fmt.Fprintf(w, " Download:\t%s\n", dl) 206 } 207 if published { 208 fmt.Fprintf(w, " Publication:\t\n") 209 fmt.Fprintf(w, " URL:\t%s\n", v.Publication.PublishURL) 210 fmt.Fprintf(w, " Auth:\t%s\n", v.Publication.AuthenticationMethod) 211 } 212 return nil 213 } 214 215 func (r infoResultsWriter) writeItem( 216 w io.Writer, v library.Item, res finder.FindResult) error { 217 218 fmt.Fprintf(w, "Name:\t%s\n", v.Name) 219 fmt.Fprintf(w, " ID:\t%s\n", v.ID) 220 fmt.Fprintf(w, " Path:\t%s\n", res.GetPath()) 221 if v.Description != nil { 222 fmt.Fprintf(w, " Description:\t%s\n", *v.Description) 223 } 224 fmt.Fprintf(w, " Type:\t%s\n", v.Type) 225 fmt.Fprintf(w, " Size:\t%s\n", units.ByteSize(v.Size)) 226 fmt.Fprintf(w, " Cached:\t%t\n", v.Cached) 227 fmt.Fprintf(w, " Created:\t%s\n", v.CreationTime.Format(time.ANSIC)) 228 fmt.Fprintf(w, " Modified:\t%s\n", v.LastModifiedTime.Format(time.ANSIC)) 229 fmt.Fprintf(w, " Version:\t%s\n", v.Version) 230 if v.SecurityCompliance != nil { 231 fmt.Fprintf(w, " Security Compliance:\t%t\n", *v.SecurityCompliance) 232 } 233 if v.CertificateVerification != nil { 234 fmt.Fprintf(w, " Certificate Status:\t%s\n", v.CertificateVerification.Status) 235 } 236 if r.cmd.long { 237 fmt.Fprintf(w, " Datastore Path:\t%s\n", r.cmd.getDatastorePath(res)) 238 } 239 240 return nil 241 } 242 243 func (r infoResultsWriter) writeFile( 244 w io.Writer, v library.File, res finder.FindResult) error { 245 246 size := "-" 247 if v.Size != nil { 248 size = units.ByteSize(*v.Size).String() 249 } 250 fmt.Fprintf(w, "Name:\t%s\n", v.Name) 251 fmt.Fprintf(w, " Path:\t%s\n", res.GetPath()) 252 fmt.Fprintf(w, " Size:\t%s\n", size) 253 fmt.Fprintf(w, " Version:\t%s\n", v.Version) 254 255 if r.cmd.long { 256 fmt.Fprintf(w, " Datastore Path:\t%s\n", r.cmd.getDatastorePath(res)) 257 } 258 259 return nil 260 } 261 262 func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error { 263 c, err := cmd.RestClient() 264 if err != nil { 265 return err 266 } 267 268 m := library.NewManager(c) 269 finder := finder.NewFinder(m) 270 findResults, err := finder.Find(ctx, f.Args()...) 271 if err != nil { 272 return err 273 } 274 // Lookup the names(s) of the library's datastore(s). 275 for i := range findResults { 276 if t, ok := findResults[i].GetResult().(library.Library); ok { 277 for j := range t.Storage { 278 if t.Storage[j].Type == "DATASTORE" { 279 t.Storage[j].DatastoreID = cmd.getDatastoreName(t.Storage[j].DatastoreID) 280 } 281 } 282 } 283 } 284 return cmd.WriteResult(&infoResultsWriter{findResults, m, cmd}) 285 } 286 287 func (cmd *info) getDatastoreName(id string) string { 288 if name, ok := cmd.names[id]; ok { 289 return name 290 } 291 292 c, err := cmd.Client() 293 if err != nil { 294 return id 295 } 296 297 obj := types.ManagedObjectReference{ 298 Type: "Datastore", 299 Value: id, 300 } 301 pc := property.DefaultCollector(c) 302 var me mo.ManagedEntity 303 304 err = pc.RetrieveOne(context.Background(), obj, []string{"name"}, &me) 305 if err != nil { 306 return id 307 } 308 309 cmd.names[id] = me.Name 310 return me.Name 311 } 312 313 func (cmd *info) getDatastorePath(r finder.FindResult) string { 314 p, _ := cmd.getDatastoreFilePath(r) 315 return p 316 } 317 318 func (cmd *info) getDatastoreFilePath(r finder.FindResult) (string, error) { 319 switch x := r.GetResult().(type) { 320 case library.Library: 321 id := "" 322 if len(x.Storage) != 0 { 323 id = cmd.getDatastoreName(x.Storage[0].DatastoreID) 324 } 325 return fmt.Sprintf("[%s] contentlib-%s", id, x.ID), nil 326 case library.Item: 327 return fmt.Sprintf("%s/%s", cmd.getDatastorePath(r.GetParent()), x.ID), nil 328 case library.File: 329 return cmd.getDatastoreFileItemPath(r) 330 default: 331 return "", fmt.Errorf("unsupported type=%T", x) 332 } 333 } 334 335 // getDatastoreFileItemPath returns the absolute datastore path for a library.File 336 func (cmd *info) getDatastoreFileItemPath(r finder.FindResult) (string, error) { 337 name := r.GetName() 338 dir := cmd.getDatastorePath(r.GetParent()) 339 p := path.Join(dir, name) 340 341 lib := r.GetParent().GetParent().GetResult().(library.Library) 342 if len(lib.Storage) == 0 { 343 return p, nil 344 } 345 346 ctx := context.Background() 347 c, err := cmd.Client() 348 if err != nil { 349 return p, err 350 } 351 352 ref := types.ManagedObjectReference{Type: "Datastore", Value: lib.Storage[0].DatastoreID} 353 ds := object.NewDatastore(c, ref) 354 355 b, err := ds.Browser(ctx) 356 if err != nil { 357 return p, err 358 } 359 360 // The file ID isn't available via the API, so we use DatastoreBrowser to search 361 ext := path.Ext(name) 362 pat := strings.Replace(name, ext, "*"+ext, 1) 363 spec := types.HostDatastoreBrowserSearchSpec{ 364 MatchPattern: []string{pat}, 365 } 366 367 task, err := b.SearchDatastore(ctx, dir, &spec) 368 if err != nil { 369 return p, err 370 } 371 372 info, err := task.WaitForResult(ctx, nil) 373 if err != nil { 374 return p, err 375 } 376 377 res, ok := info.Result.(types.HostDatastoreBrowserSearchResults) 378 if !ok { 379 return p, fmt.Errorf("search(%s) result type=%T", pat, info.Result) 380 } 381 382 if len(res.File) != 1 { 383 return p, fmt.Errorf("search(%s) result files=%d", pat, len(res.File)) 384 } 385 386 return path.Join(dir, res.File[0].GetFileInfo().Path), nil 387 }