github.com/vmware/govmomi@v0.51.0/cli/vm/info.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 vm 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 "io" 12 "os" 13 "strings" 14 "text/tabwriter" 15 16 "github.com/vmware/govmomi/cli" 17 "github.com/vmware/govmomi/cli/flags" 18 "github.com/vmware/govmomi/find" 19 "github.com/vmware/govmomi/object" 20 "github.com/vmware/govmomi/property" 21 "github.com/vmware/govmomi/units" 22 "github.com/vmware/govmomi/vim25/mo" 23 "github.com/vmware/govmomi/vim25/types" 24 ) 25 26 type info struct { 27 *flags.ClientFlag 28 *flags.OutputFlag 29 *flags.SearchFlag 30 31 WaitForIP bool 32 General bool 33 ExtraConfig bool 34 Resources bool 35 ToolsConfigInfo bool 36 } 37 38 func init() { 39 cli.Register("vm.info", &info{}) 40 } 41 42 func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) { 43 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 44 cmd.ClientFlag.Register(ctx, f) 45 46 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 47 cmd.OutputFlag.Register(ctx, f) 48 49 cmd.SearchFlag, ctx = flags.NewSearchFlag(ctx, flags.SearchVirtualMachines) 50 cmd.SearchFlag.Register(ctx, f) 51 52 f.BoolVar(&cmd.WaitForIP, "waitip", false, "Wait for VM to acquire IP address") 53 f.BoolVar(&cmd.General, "g", true, "Show general summary") 54 f.BoolVar(&cmd.ExtraConfig, "e", false, "Show ExtraConfig") 55 f.BoolVar(&cmd.Resources, "r", false, "Show resource summary") 56 f.BoolVar(&cmd.ToolsConfigInfo, "t", false, "Show ToolsConfigInfo") 57 } 58 59 func (cmd *info) Process(ctx context.Context) error { 60 if err := cmd.ClientFlag.Process(ctx); err != nil { 61 return err 62 } 63 if err := cmd.OutputFlag.Process(ctx); err != nil { 64 return err 65 } 66 if err := cmd.SearchFlag.Process(ctx); err != nil { 67 return err 68 } 69 return nil 70 } 71 72 func (cmd *info) Usage() string { 73 return `VM...` 74 } 75 76 func (cmd *info) Description() string { 77 return `Display info for VM. 78 79 The '-r' flag displays additional info for CPU, memory and storage usage, 80 along with the VM's Datastores, Networks and PortGroups. 81 82 Examples: 83 govc vm.info $vm 84 govc vm.info -r $vm | grep Network: 85 govc vm.info -json $vm 86 govc find . -type m -runtime.powerState poweredOn | xargs govc vm.info` 87 } 88 89 func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error { 90 c, err := cmd.Client() 91 if err != nil { 92 return err 93 } 94 95 vms, err := cmd.VirtualMachines(f.Args()) 96 if err != nil { 97 if _, ok := err.(*find.NotFoundError); ok { 98 // Continue with empty VM slice 99 } else { 100 return err 101 } 102 } 103 104 refs := make([]types.ManagedObjectReference, 0, len(vms)) 105 for _, vm := range vms { 106 refs = append(refs, vm.Reference()) 107 } 108 109 var res infoResult 110 var props []string 111 112 if cmd.OutputFlag.All() { 113 props = nil // Load everything 114 } else { 115 props = []string{"summary"} // Load summary 116 if cmd.General { 117 props = append(props, "guest.ipAddress") 118 } 119 if cmd.ExtraConfig { 120 props = append(props, "config.extraConfig") 121 } 122 if cmd.Resources { 123 props = append(props, "datastore", "network") 124 } 125 if cmd.ToolsConfigInfo { 126 props = append(props, "config.tools") 127 } 128 } 129 130 pc := property.DefaultCollector(c) 131 if len(refs) != 0 { 132 err = pc.Retrieve(ctx, refs, props, &res.VirtualMachines) 133 if err != nil { 134 return err 135 } 136 } 137 138 if cmd.WaitForIP { 139 for i, vm := range res.VirtualMachines { 140 if vm.Guest == nil || vm.Guest.IpAddress == "" { 141 _, err = vms[i].WaitForIP(ctx) 142 if err != nil { 143 return err 144 } 145 // Reload virtual machine object 146 err = pc.RetrieveOne(ctx, vms[i].Reference(), props, &res.VirtualMachines[i]) 147 if err != nil { 148 return err 149 } 150 } 151 } 152 } 153 154 if !cmd.OutputFlag.All() { 155 res.objects = vms 156 res.cmd = cmd 157 if err = res.collectReferences(pc, ctx); err != nil { 158 return err 159 } 160 } 161 162 return cmd.WriteResult(&res) 163 } 164 165 type infoResult struct { 166 VirtualMachines []mo.VirtualMachine `json:"virtualMachines"` 167 objects []*object.VirtualMachine 168 entities map[types.ManagedObjectReference]string 169 cmd *info 170 } 171 172 // collectReferences builds a unique set of MORs to the set of VirtualMachines, 173 // so we can collect properties in a single call for each reference type {host,datastore,network}. 174 func (r *infoResult) collectReferences(pc *property.Collector, ctx context.Context) error { 175 // MOR -> Name map 176 r.entities = make(map[types.ManagedObjectReference]string) 177 178 var host []mo.HostSystem 179 var network []mo.Network 180 var opaque []mo.OpaqueNetwork 181 var dvp []mo.DistributedVirtualPortgroup 182 var datastore []mo.Datastore 183 // Table to drive inflating refs to their mo.* counterparts (dest) 184 // and save() the Name to r.entities w/o using reflection here. 185 // Note that we cannot use a []mo.ManagedEntity here, since mo.Network has its own 'Name' field, 186 // the mo.Network.ManagedEntity.Name field will not be set. 187 vrefs := map[string]*struct { 188 dest any 189 refs []types.ManagedObjectReference 190 save func() 191 }{ 192 "HostSystem": { 193 &host, nil, func() { 194 for _, e := range host { 195 r.entities[e.Reference()] = e.Name 196 } 197 }, 198 }, 199 "Network": { 200 &network, nil, func() { 201 for _, e := range network { 202 r.entities[e.Reference()] = e.Name 203 } 204 }, 205 }, 206 "OpaqueNetwork": { 207 &opaque, nil, func() { 208 for _, e := range opaque { 209 r.entities[e.Reference()] = e.Name 210 } 211 }, 212 }, 213 "DistributedVirtualPortgroup": { 214 &dvp, nil, func() { 215 for _, e := range dvp { 216 r.entities[e.Reference()] = e.Name 217 } 218 }, 219 }, 220 "Datastore": { 221 &datastore, nil, func() { 222 for _, e := range datastore { 223 r.entities[e.Reference()] = e.Name 224 } 225 }, 226 }, 227 } 228 229 xrefs := make(map[types.ManagedObjectReference]bool) 230 // Add MOR to vrefs[kind].refs avoiding any duplicates. 231 addRef := func(refs ...types.ManagedObjectReference) { 232 for _, ref := range refs { 233 if _, exists := xrefs[ref]; exists { 234 return 235 } 236 xrefs[ref] = true 237 vref := vrefs[ref.Type] 238 vref.refs = append(vref.refs, ref) 239 } 240 } 241 242 for _, vm := range r.VirtualMachines { 243 if r.cmd.General { 244 if ref := vm.Summary.Runtime.Host; ref != nil { 245 addRef(*ref) 246 } 247 } 248 249 if r.cmd.Resources { 250 addRef(vm.Datastore...) 251 addRef(vm.Network...) 252 } 253 } 254 255 for _, vref := range vrefs { 256 if vref.refs == nil { 257 continue 258 } 259 err := pc.Retrieve(ctx, vref.refs, []string{"name"}, vref.dest) 260 if err != nil { 261 return err 262 } 263 vref.save() 264 } 265 266 return nil 267 } 268 269 func (r *infoResult) entityNames(refs []types.ManagedObjectReference) string { 270 var names []string 271 for _, ref := range refs { 272 names = append(names, r.entities[ref]) 273 } 274 return strings.Join(names, ", ") 275 } 276 277 func (r *infoResult) Write(w io.Writer) error { 278 // Maintain order via r.objects as Property collector does not always return results in order. 279 objects := make(map[types.ManagedObjectReference]mo.VirtualMachine, len(r.VirtualMachines)) 280 for _, o := range r.VirtualMachines { 281 objects[o.Reference()] = o 282 } 283 284 tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 285 286 for _, o := range r.objects { 287 vm := objects[o.Reference()] 288 s := vm.Summary 289 290 fmt.Fprintf(tw, "Name:\t%s\n", s.Config.Name) 291 292 if r.cmd.General { 293 hostName := "<unavailable>" 294 295 if href := vm.Summary.Runtime.Host; href != nil { 296 if name, ok := r.entities[*href]; ok { 297 hostName = name 298 } 299 } 300 301 fmt.Fprintf(tw, " Path:\t%s\n", o.InventoryPath) 302 fmt.Fprintf(tw, " UUID:\t%s\n", s.Config.Uuid) 303 fmt.Fprintf(tw, " Guest name:\t%s\n", s.Config.GuestFullName) 304 fmt.Fprintf(tw, " Memory:\t%dMB\n", s.Config.MemorySizeMB) 305 fmt.Fprintf(tw, " CPU:\t%d vCPU(s)\n", s.Config.NumCpu) 306 fmt.Fprintf(tw, " Power state:\t%s\n", s.Runtime.PowerState) 307 fmt.Fprintf(tw, " Boot time:\t%s\n", s.Runtime.BootTime) 308 fmt.Fprintf(tw, " IP address:\t%s\n", s.Guest.IpAddress) 309 fmt.Fprintf(tw, " Host:\t%s\n", hostName) 310 } 311 312 if r.cmd.Resources { 313 if s.Storage == nil { 314 s.Storage = new(types.VirtualMachineStorageSummary) 315 } 316 fmt.Fprintf(tw, " CPU usage:\t%dMHz\n", s.QuickStats.OverallCpuUsage) 317 fmt.Fprintf(tw, " Host memory usage:\t%dMB\n", s.QuickStats.HostMemoryUsage) 318 fmt.Fprintf(tw, " Guest memory usage:\t%dMB\n", s.QuickStats.GuestMemoryUsage) 319 fmt.Fprintf(tw, " Storage uncommitted:\t%s\n", units.ByteSize(s.Storage.Uncommitted)) 320 fmt.Fprintf(tw, " Storage committed:\t%s\n", units.ByteSize(s.Storage.Committed)) 321 fmt.Fprintf(tw, " Storage unshared:\t%s\n", units.ByteSize(s.Storage.Unshared)) 322 fmt.Fprintf(tw, " Storage:\t%s\n", r.entityNames(vm.Datastore)) 323 fmt.Fprintf(tw, " Network:\t%s\n", r.entityNames(vm.Network)) 324 } 325 326 if r.cmd.ExtraConfig { 327 fmt.Fprintf(tw, " ExtraConfig:\n") 328 for _, v := range vm.Config.ExtraConfig { 329 fmt.Fprintf(tw, " %s:\t%s\n", v.GetOptionValue().Key, v.GetOptionValue().Value) 330 } 331 } 332 333 if r.cmd.ToolsConfigInfo { 334 t := vm.Config.Tools 335 fmt.Fprintf(tw, " ToolsConfigInfo:\n") 336 fmt.Fprintf(tw, " ToolsVersion:\t%d\n", t.ToolsVersion) 337 fmt.Fprintf(tw, " AfterPowerOn:\t%s\n", flags.NewOptionalBool(&t.AfterPowerOn).String()) 338 fmt.Fprintf(tw, " AfterResume:\t%s\n", flags.NewOptionalBool(&t.AfterResume).String()) 339 fmt.Fprintf(tw, " BeforeGuestStandby:\t%s\n", flags.NewOptionalBool(&t.BeforeGuestStandby).String()) 340 fmt.Fprintf(tw, " BeforeGuestShutdown:\t%s\n", flags.NewOptionalBool(&t.BeforeGuestShutdown).String()) 341 fmt.Fprintf(tw, " BeforeGuestReboot:\t%s\n", flags.NewOptionalBool(&t.BeforeGuestReboot).String()) 342 fmt.Fprintf(tw, " ToolsUpgradePolicy:\t%s\n", t.ToolsUpgradePolicy) 343 fmt.Fprintf(tw, " PendingCustomization:\t%s\n", t.PendingCustomization) 344 fmt.Fprintf(tw, " SyncTimeWithHost:\t%s\n", flags.NewOptionalBool(&t.SyncTimeWithHost).String()) 345 } 346 } 347 348 return tw.Flush() 349 }