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 }