github.com/vmware/govmomi@v0.51.0/cli/host/esxcli/esxcli.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 esxcli
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"os/exec"
    15  	"sort"
    16  	"strings"
    17  	"text/tabwriter"
    18  
    19  	"github.com/dougm/pretty"
    20  
    21  	"github.com/vmware/govmomi/cli"
    22  	"github.com/vmware/govmomi/cli/esx"
    23  	"github.com/vmware/govmomi/cli/flags"
    24  	"github.com/vmware/govmomi/internal"
    25  )
    26  
    27  type esxcli struct {
    28  	*flags.HostSystemFlag
    29  
    30  	hints bool
    31  	trace bool
    32  }
    33  
    34  func init() {
    35  	cli.Register("host.esxcli", &esxcli{})
    36  }
    37  
    38  func (cmd *esxcli) Usage() string {
    39  	return "COMMAND [ARG]..."
    40  }
    41  
    42  func (cmd *esxcli) Register(ctx context.Context, f *flag.FlagSet) {
    43  	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
    44  	cmd.HostSystemFlag.Register(ctx, f)
    45  
    46  	f.BoolVar(&cmd.hints, "hints", true, "Use command info hints when formatting output")
    47  	if cli.ShowUnreleased() {
    48  		f.BoolVar(&cmd.trace, "T", false, "Write esxcli nested SOAP traffic to stderr")
    49  	}
    50  }
    51  
    52  func (cmd *esxcli) Description() string {
    53  	return `Invoke esxcli command on HOST.
    54  
    55  Output is rendered in table form when possible, unless disabled with '-hints=false'.
    56  
    57  Examples:
    58    govc host.esxcli network ip connection list
    59    govc host.esxcli system settings advanced set -o /Net/GuestIPHack -i 1
    60    govc host.esxcli network firewall ruleset set -r remoteSerialPort -e true
    61    govc host.esxcli network firewall set -e false
    62    govc host.esxcli hardware platform get`
    63  }
    64  
    65  func (cmd *esxcli) Process(ctx context.Context) error {
    66  	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
    67  		return err
    68  	}
    69  	return nil
    70  }
    71  
    72  func fmtXML(s string) {
    73  	cmd := exec.Command("xmlstarlet", "fo")
    74  	cmd.Stdout = os.Stderr
    75  	cmd.Stderr = os.Stderr
    76  	cmd.Stdin = strings.NewReader(s)
    77  	if err := cmd.Run(); err != nil {
    78  		panic(err) // yes xmlstarlet is required (your eyes will thank you)
    79  	}
    80  }
    81  
    82  func (cmd *esxcli) Trace(req *internal.ExecuteSoapRequest, res *internal.ExecuteSoapResponse) {
    83  	x := res.Returnval
    84  
    85  	if req.Moid == "ha-dynamic-type-manager-local-cli-cliinfo" {
    86  		if x.Fault == nil {
    87  			return // TODO: option to trace this
    88  		}
    89  	}
    90  
    91  	pretty.Fprintf(os.Stderr, "%# v\n", req)
    92  
    93  	if x.Fault == nil {
    94  		fmtXML(res.Returnval.Response)
    95  	} else {
    96  		fmt.Fprintln(os.Stderr, "Message=", x.Fault.FaultMsg)
    97  		if x.Fault.FaultDetail != "" {
    98  			fmt.Fprint(os.Stderr, "Detail=")
    99  			fmtXML(x.Fault.FaultDetail)
   100  		}
   101  	}
   102  }
   103  
   104  func (cmd *esxcli) Run(ctx context.Context, f *flag.FlagSet) error {
   105  	if f.NArg() == 0 {
   106  		return flag.ErrHelp
   107  	}
   108  
   109  	c, err := cmd.Client()
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	host, err := cmd.HostSystem()
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	e, err := esx.NewExecutor(ctx, c, host)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	if cmd.trace {
   124  		e.Trace = cmd.Trace
   125  	}
   126  
   127  	res, err := e.Run(ctx, f.Args())
   128  	if err != nil {
   129  		if f, ok := err.(*esx.Fault); ok {
   130  			return errors.New(f.MessageDetail())
   131  		}
   132  		return err
   133  	}
   134  
   135  	if len(res.Values) == 0 {
   136  		if res.String != "" {
   137  			fmt.Print(res.String)
   138  			if !strings.HasSuffix(res.String, "\n") {
   139  				fmt.Println()
   140  			}
   141  		}
   142  		return nil
   143  	}
   144  
   145  	return cmd.WriteResult(&result{res, cmd})
   146  }
   147  
   148  type result struct {
   149  	*esx.Response
   150  	cmd *esxcli
   151  }
   152  
   153  func (r *result) Dump() any {
   154  	return r.Response
   155  }
   156  
   157  func (r *result) Write(w io.Writer) error {
   158  	var formatType string
   159  	if r.cmd.hints {
   160  		formatType = r.Info.Hints.Formatter()
   161  	}
   162  
   163  	switch formatType {
   164  	case "table":
   165  		r.cmd.formatTable(w, r.Response)
   166  	default:
   167  		r.cmd.formatSimple(w, r.Response)
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  func (cmd *esxcli) formatSimple(w io.Writer, res *esx.Response) {
   174  	var keys []string
   175  	for key := range res.Values[0] {
   176  		keys = append(keys, key)
   177  	}
   178  	sort.Strings(keys)
   179  
   180  	tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
   181  
   182  	for i, rv := range res.Values {
   183  		if i > 0 {
   184  			fmt.Fprintln(tw)
   185  			_ = tw.Flush()
   186  		}
   187  		for _, key := range keys {
   188  			fmt.Fprintf(tw, "%s:\t%s\n", key, strings.Join(rv[key], ", "))
   189  		}
   190  	}
   191  
   192  	_ = tw.Flush()
   193  }
   194  
   195  func (cmd *esxcli) formatTable(w io.Writer, res *esx.Response) {
   196  	fields := res.Info.Hints.Fields()
   197  	if len(fields) == 0 {
   198  		cmd.formatSimple(w, res)
   199  		return
   200  	}
   201  	tw := tabwriter.NewWriter(w, len(fields), 0, 2, ' ', 0)
   202  
   203  	var hr []string
   204  	for _, name := range fields {
   205  		hr = append(hr, strings.Repeat("-", len(name)))
   206  	}
   207  
   208  	fmt.Fprintln(tw, strings.Join(fields, "\t"))
   209  	fmt.Fprintln(tw, strings.Join(hr, "\t"))
   210  
   211  	for _, vals := range res.Values {
   212  		var row []string
   213  
   214  		for _, name := range fields {
   215  			key := strings.Replace(name, " ", "", -1)
   216  			if val, ok := vals[key]; ok {
   217  				row = append(row, strings.Join(val, ", "))
   218  			} else {
   219  				row = append(row, "")
   220  			}
   221  		}
   222  
   223  		fmt.Fprintln(tw, strings.Join(row, "\t"))
   224  	}
   225  
   226  	_ = tw.Flush()
   227  }