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  }