github.com/vmware/govmomi@v0.43.0/govc/flags/output.go (about) 1 /* 2 Copyright (c) 2014-2024 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 flags 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "flag" 24 "fmt" 25 "io" 26 "os" 27 "reflect" 28 "strings" 29 "time" 30 31 "github.com/dougm/pretty" 32 33 "github.com/vmware/govmomi/govc/cli" 34 "github.com/vmware/govmomi/task" 35 "github.com/vmware/govmomi/vim25/progress" 36 "github.com/vmware/govmomi/vim25/soap" 37 "github.com/vmware/govmomi/vim25/types" 38 "github.com/vmware/govmomi/vim25/xml" 39 ) 40 41 type OutputWriter interface { 42 Write(io.Writer) error 43 } 44 45 type OutputFlag struct { 46 common 47 48 JSON bool 49 XML bool 50 TTY bool 51 Dump bool 52 Out io.Writer 53 Spec bool 54 55 formatError bool 56 formatIndent bool 57 } 58 59 var outputFlagKey = flagKey("output") 60 61 func NewOutputFlag(ctx context.Context) (*OutputFlag, context.Context) { 62 if v := ctx.Value(outputFlagKey); v != nil { 63 return v.(*OutputFlag), ctx 64 } 65 66 v := &OutputFlag{Out: os.Stdout} 67 ctx = context.WithValue(ctx, outputFlagKey, v) 68 return v, ctx 69 } 70 71 func (flag *OutputFlag) Register(ctx context.Context, f *flag.FlagSet) { 72 flag.RegisterOnce(func() { 73 f.BoolVar(&flag.JSON, "json", false, "Enable JSON output") 74 f.BoolVar(&flag.XML, "xml", false, "Enable XML output") 75 f.BoolVar(&flag.Dump, "dump", false, "Enable Go output") 76 if cli.ShowUnreleased() { 77 f.BoolVar(&flag.Spec, "spec", false, "Output spec without sending request") 78 } 79 // Avoid adding more flags for now.. 80 flag.formatIndent = os.Getenv("GOVC_INDENT") != "false" // Default to indented output 81 flag.formatError = os.Getenv("GOVC_FORMAT_ERROR") != "false" // Default to formatted errors 82 }) 83 } 84 85 func (flag *OutputFlag) Process(ctx context.Context) error { 86 return flag.ProcessOnce(func() error { 87 if !flag.All() { 88 // Assume we have a tty if not outputting JSON 89 flag.TTY = true 90 } 91 92 return nil 93 }) 94 } 95 96 // Log outputs the specified string, prefixed with the current time. 97 // A newline is not automatically added. If the specified string 98 // starts with a '\r', the current line is cleared first. 99 func (flag *OutputFlag) Log(s string) (int, error) { 100 if len(s) > 0 && s[0] == '\r' { 101 flag.Write([]byte{'\r', 033, '[', 'K'}) 102 s = s[1:] 103 } 104 105 return flag.WriteString(time.Now().Format("[02-01-06 15:04:05] ") + s) 106 } 107 108 func (flag *OutputFlag) Write(b []byte) (int, error) { 109 if !flag.TTY { 110 return 0, nil 111 } 112 113 w := flag.Out 114 if w == nil { 115 w = os.Stdout 116 } 117 n, err := w.Write(b) 118 if w == os.Stdout { 119 os.Stdout.Sync() 120 } 121 return n, err 122 } 123 124 func (flag *OutputFlag) WriteString(s string) (int, error) { 125 return flag.Write([]byte(s)) 126 } 127 128 func (flag *OutputFlag) All() bool { 129 return flag.JSON || flag.XML || flag.Dump 130 } 131 132 func dumpValue(val interface{}) interface{} { 133 type dumper interface { 134 Dump() interface{} 135 } 136 137 if d, ok := val.(dumper); ok { 138 return d.Dump() 139 } 140 141 rval := reflect.ValueOf(val) 142 if rval.Type().Kind() != reflect.Ptr { 143 return val 144 } 145 146 rval = rval.Elem() 147 if rval.Type().Kind() == reflect.Struct { 148 f := rval.Field(0) 149 if f.Type().Kind() == reflect.Slice { 150 // common case for the various 'type infoResult' 151 if f.Len() == 1 { 152 return f.Index(0).Interface() 153 } 154 return f.Interface() 155 } 156 157 if rval.NumField() == 1 && rval.Type().Field(0).Anonymous { 158 // common case where govc type wraps govmomi type to implement OutputWriter 159 return f.Interface() 160 } 161 } 162 163 return val 164 } 165 166 type outputAny struct { 167 Value any 168 } 169 170 func (*outputAny) Write(io.Writer) error { 171 return nil 172 } 173 174 func (a *outputAny) Dump() interface{} { 175 return a.Value 176 } 177 178 func (flag *OutputFlag) WriteAny(val any) error { 179 if !flag.All() { 180 flag.XML = true 181 } 182 return flag.WriteResult(&outputAny{val}) 183 } 184 185 func (flag *OutputFlag) WriteResult(result OutputWriter) error { 186 var err error 187 188 switch { 189 case flag.Dump: 190 format := "%#v\n" 191 if flag.formatIndent { 192 format = "%# v\n" 193 } 194 _, err = pretty.Fprintf(flag.Out, format, dumpValue(result)) 195 case flag.JSON: 196 e := json.NewEncoder(flag.Out) 197 if flag.formatIndent { 198 e.SetIndent("", " ") 199 } 200 err = e.Encode(result) 201 case flag.XML: 202 e := xml.NewEncoder(flag.Out) 203 if flag.formatIndent { 204 e.Indent("", " ") 205 } 206 err = e.Encode(dumpValue(result)) 207 if err == nil { 208 fmt.Fprintln(flag.Out) 209 } 210 default: 211 err = result.Write(flag.Out) 212 } 213 214 return err 215 } 216 217 func (flag *OutputFlag) WriteError(err error) bool { 218 if flag.formatError { 219 flag.Out = os.Stderr 220 return flag.WriteResult(&errorOutput{err}) == nil 221 } 222 return false 223 } 224 225 type errorOutput struct { 226 error 227 } 228 229 func (e errorOutput) Write(w io.Writer) error { 230 reason := e.Error() 231 var messages []string 232 var faults []types.LocalizableMessage 233 234 switch err := e.error.(type) { 235 case task.Error: 236 faults = err.LocalizedMethodFault.Fault.GetMethodFault().FaultMessage 237 if err.Description != nil { 238 reason = fmt.Sprintf("%s (%s)", reason, err.Description.Message) 239 } 240 default: 241 if soap.IsSoapFault(err) { 242 detail := soap.ToSoapFault(err).Detail.Fault 243 if f, ok := detail.(types.BaseMethodFault); ok { 244 faults = f.GetMethodFault().FaultMessage 245 } 246 } 247 } 248 249 for _, m := range faults { 250 if m.Message != "" && !strings.HasPrefix(m.Message, "[context]") { 251 messages = append(messages, fmt.Sprintf("%s (%s)", m.Message, m.Key)) 252 } 253 } 254 255 messages = append(messages, reason) 256 257 for _, message := range messages { 258 if _, err := fmt.Fprintf(w, "%s: %s\n", os.Args[0], message); err != nil { 259 return err 260 } 261 } 262 263 return nil 264 } 265 266 func (e errorOutput) Dump() interface{} { 267 if f, ok := e.error.(task.Error); ok { 268 return f.LocalizedMethodFault 269 } 270 if soap.IsSoapFault(e.error) { 271 return soap.ToSoapFault(e.error) 272 } 273 if soap.IsVimFault(e.error) { 274 return soap.ToVimFault(e.error) 275 } 276 return e 277 } 278 279 func (e errorOutput) canEncode() bool { 280 switch e.error.(type) { 281 case task.Error: 282 return true 283 } 284 return soap.IsSoapFault(e.error) || soap.IsVimFault(e.error) 285 } 286 287 // errCannotEncode causes cli.Run to output err.Error() as it would without an error format specified 288 var errCannotEncode = errors.New("cannot encode error") 289 290 func (e errorOutput) MarshalJSON() ([]byte, error) { 291 _, ok := e.error.(json.Marshaler) 292 if ok || e.canEncode() { 293 return json.Marshal(e.error) 294 } 295 return nil, errCannotEncode 296 } 297 298 func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error { 299 _, ok := e.error.(xml.Marshaler) 300 if ok || e.canEncode() { 301 return encoder.Encode(e.error) 302 } 303 return errCannotEncode 304 } 305 306 func (flag *OutputFlag) ProgressLogger(prefix string) *progress.ProgressLogger { 307 return progress.NewProgressLogger(flag.Log, prefix) 308 }