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  }