github.com/vmware/govmomi@v0.37.1/govc/flags/output.go (about) 1 /* 2 Copyright (c) 2014-2016 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 "sync" 30 "time" 31 32 "github.com/dougm/pretty" 33 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 54 formatError bool 55 formatIndent bool 56 } 57 58 var outputFlagKey = flagKey("output") 59 60 func NewOutputFlag(ctx context.Context) (*OutputFlag, context.Context) { 61 if v := ctx.Value(outputFlagKey); v != nil { 62 return v.(*OutputFlag), ctx 63 } 64 65 v := &OutputFlag{Out: os.Stdout} 66 ctx = context.WithValue(ctx, outputFlagKey, v) 67 return v, ctx 68 } 69 70 func (flag *OutputFlag) Register(ctx context.Context, f *flag.FlagSet) { 71 flag.RegisterOnce(func() { 72 f.BoolVar(&flag.JSON, "json", false, "Enable JSON output") 73 f.BoolVar(&flag.XML, "xml", false, "Enable XML output") 74 f.BoolVar(&flag.Dump, "dump", false, "Enable Go output") 75 // Avoid adding more flags for now.. 76 flag.formatIndent = os.Getenv("GOVC_INDENT") != "false" // Default to indented output 77 flag.formatError = os.Getenv("GOVC_FORMAT_ERROR") != "false" // Default to formatted errors 78 }) 79 } 80 81 func (flag *OutputFlag) Process(ctx context.Context) error { 82 return flag.ProcessOnce(func() error { 83 if !flag.All() { 84 // Assume we have a tty if not outputting JSON 85 flag.TTY = true 86 } 87 88 return nil 89 }) 90 } 91 92 // Log outputs the specified string, prefixed with the current time. 93 // A newline is not automatically added. If the specified string 94 // starts with a '\r', the current line is cleared first. 95 func (flag *OutputFlag) Log(s string) (int, error) { 96 if len(s) > 0 && s[0] == '\r' { 97 flag.Write([]byte{'\r', 033, '[', 'K'}) 98 s = s[1:] 99 } 100 101 return flag.WriteString(time.Now().Format("[02-01-06 15:04:05] ") + s) 102 } 103 104 func (flag *OutputFlag) Write(b []byte) (int, error) { 105 if !flag.TTY { 106 return 0, nil 107 } 108 109 w := flag.Out 110 if w == nil { 111 w = os.Stdout 112 } 113 n, err := w.Write(b) 114 if w == os.Stdout { 115 os.Stdout.Sync() 116 } 117 return n, err 118 } 119 120 func (flag *OutputFlag) WriteString(s string) (int, error) { 121 return flag.Write([]byte(s)) 122 } 123 124 func (flag *OutputFlag) All() bool { 125 return flag.JSON || flag.XML || flag.Dump 126 } 127 128 func dumpValue(val interface{}) interface{} { 129 type dumper interface { 130 Dump() interface{} 131 } 132 133 if d, ok := val.(dumper); ok { 134 return d.Dump() 135 } 136 137 rval := reflect.ValueOf(val) 138 if rval.Type().Kind() != reflect.Ptr { 139 return val 140 } 141 142 rval = rval.Elem() 143 if rval.Type().Kind() == reflect.Struct { 144 f := rval.Field(0) 145 if f.Type().Kind() == reflect.Slice { 146 // common case for the various 'type infoResult' 147 if f.Len() == 1 { 148 return f.Index(0).Interface() 149 } 150 return f.Interface() 151 } 152 153 if rval.NumField() == 1 && rval.Type().Field(0).Anonymous { 154 // common case where govc type wraps govmomi type to implement OutputWriter 155 return f.Interface() 156 } 157 } 158 159 return val 160 } 161 162 func (flag *OutputFlag) WriteResult(result OutputWriter) error { 163 var err error 164 165 switch { 166 case flag.Dump: 167 format := "%#v\n" 168 if flag.formatIndent { 169 format = "%# v\n" 170 } 171 _, err = pretty.Fprintf(flag.Out, format, dumpValue(result)) 172 case flag.JSON: 173 e := json.NewEncoder(flag.Out) 174 if flag.formatIndent { 175 e.SetIndent("", " ") 176 } 177 err = e.Encode(result) 178 case flag.XML: 179 e := xml.NewEncoder(flag.Out) 180 if flag.formatIndent { 181 e.Indent("", " ") 182 } 183 err = e.Encode(dumpValue(result)) 184 if err == nil { 185 fmt.Fprintln(flag.Out) 186 } 187 default: 188 err = result.Write(flag.Out) 189 } 190 191 return err 192 } 193 194 func (flag *OutputFlag) WriteError(err error) bool { 195 if flag.formatError { 196 flag.Out = os.Stderr 197 return flag.WriteResult(&errorOutput{err}) == nil 198 } 199 return false 200 } 201 202 type errorOutput struct { 203 error 204 } 205 206 func (e errorOutput) Write(w io.Writer) error { 207 reason := e.error.Error() 208 var messages []string 209 var faults []types.LocalizableMessage 210 211 switch err := e.error.(type) { 212 case task.Error: 213 faults = err.LocalizedMethodFault.Fault.GetMethodFault().FaultMessage 214 if err.Description != nil { 215 reason = fmt.Sprintf("%s (%s)", reason, err.Description.Message) 216 } 217 default: 218 if soap.IsSoapFault(err) { 219 detail := soap.ToSoapFault(err).Detail.Fault 220 if f, ok := detail.(types.BaseMethodFault); ok { 221 faults = f.GetMethodFault().FaultMessage 222 } 223 } 224 } 225 226 for _, m := range faults { 227 if m.Message != "" && !strings.HasPrefix(m.Message, "[context]") { 228 messages = append(messages, fmt.Sprintf("%s (%s)", m.Message, m.Key)) 229 } 230 } 231 232 messages = append(messages, reason) 233 234 for _, message := range messages { 235 if _, err := fmt.Fprintf(w, "%s: %s\n", os.Args[0], message); err != nil { 236 return err 237 } 238 } 239 240 return nil 241 } 242 243 func (e errorOutput) Dump() interface{} { 244 if f, ok := e.error.(task.Error); ok { 245 return f.LocalizedMethodFault 246 } 247 if soap.IsSoapFault(e.error) { 248 return soap.ToSoapFault(e.error) 249 } 250 if soap.IsVimFault(e.error) { 251 return soap.ToVimFault(e.error) 252 } 253 return e 254 } 255 256 func (e errorOutput) canEncode() bool { 257 switch e.error.(type) { 258 case task.Error: 259 return true 260 } 261 return soap.IsSoapFault(e.error) || soap.IsVimFault(e.error) 262 } 263 264 // cannotEncode causes cli.Run to output err.Error() as it would without an error format specified 265 var cannotEncode = errors.New("cannot encode error") 266 267 func (e errorOutput) MarshalJSON() ([]byte, error) { 268 _, ok := e.error.(json.Marshaler) 269 if ok || e.canEncode() { 270 return json.Marshal(e.error) 271 } 272 return nil, cannotEncode 273 } 274 275 func (e errorOutput) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error { 276 _, ok := e.error.(xml.Marshaler) 277 if ok || e.canEncode() { 278 return encoder.Encode(e.error) 279 } 280 return cannotEncode 281 } 282 283 type progressLogger struct { 284 flag *OutputFlag 285 prefix string 286 287 wg sync.WaitGroup 288 289 sink chan chan progress.Report 290 done chan struct{} 291 } 292 293 func newProgressLogger(flag *OutputFlag, prefix string) *progressLogger { 294 p := &progressLogger{ 295 flag: flag, 296 prefix: prefix, 297 298 sink: make(chan chan progress.Report), 299 done: make(chan struct{}), 300 } 301 302 p.wg.Add(1) 303 304 go p.loopA() 305 306 return p 307 } 308 309 // loopA runs before Sink() has been called. 310 func (p *progressLogger) loopA() { 311 var err error 312 313 defer p.wg.Done() 314 315 tick := time.NewTicker(100 * time.Millisecond) 316 defer tick.Stop() 317 318 called := false 319 320 for stop := false; !stop; { 321 select { 322 case ch := <-p.sink: 323 err = p.loopB(tick, ch) 324 stop = true 325 called = true 326 case <-p.done: 327 stop = true 328 case <-tick.C: 329 line := fmt.Sprintf("\r%s", p.prefix) 330 p.flag.Log(line) 331 } 332 } 333 334 if err != nil && err != io.EOF { 335 p.flag.Log(fmt.Sprintf("\r%sError: %s\n", p.prefix, err)) 336 } else if called { 337 p.flag.Log(fmt.Sprintf("\r%sOK\n", p.prefix)) 338 } 339 } 340 341 // loopA runs after Sink() has been called. 342 func (p *progressLogger) loopB(tick *time.Ticker, ch <-chan progress.Report) error { 343 var r progress.Report 344 var ok bool 345 var err error 346 347 for ok = true; ok; { 348 select { 349 case r, ok = <-ch: 350 if !ok { 351 break 352 } 353 err = r.Error() 354 case <-tick.C: 355 line := fmt.Sprintf("\r%s", p.prefix) 356 if r != nil { 357 line += fmt.Sprintf("(%.0f%%", r.Percentage()) 358 detail := r.Detail() 359 if detail != "" { 360 line += fmt.Sprintf(", %s", detail) 361 } 362 line += ")" 363 } 364 p.flag.Log(line) 365 } 366 } 367 368 return err 369 } 370 371 func (p *progressLogger) Sink() chan<- progress.Report { 372 ch := make(chan progress.Report) 373 p.sink <- ch 374 return ch 375 } 376 377 func (p *progressLogger) Wait() { 378 close(p.done) 379 p.wg.Wait() 380 } 381 382 func (flag *OutputFlag) ProgressLogger(prefix string) *progressLogger { 383 return newProgressLogger(flag, prefix) 384 }