gopkg.in/mitchellh/packer.v1@v1.3.2/packer/ui.go (about)

     1  package packer
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"os/signal"
    12  	"runtime"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  	"unicode"
    18  )
    19  
    20  type UiColor uint
    21  
    22  const (
    23  	UiColorRed     UiColor = 31
    24  	UiColorGreen           = 32
    25  	UiColorYellow          = 33
    26  	UiColorBlue            = 34
    27  	UiColorMagenta         = 35
    28  	UiColorCyan            = 36
    29  )
    30  
    31  // The Ui interface handles all communication for Packer with the outside
    32  // world. This sort of control allows us to strictly control how output
    33  // is formatted and various levels of output.
    34  type Ui interface {
    35  	Ask(string) (string, error)
    36  	Say(string)
    37  	Message(string)
    38  	Error(string)
    39  	Machine(string, ...string)
    40  	ProgressBar() ProgressBar
    41  }
    42  
    43  type NoopUi struct{}
    44  
    45  var _ Ui = new(NoopUi)
    46  
    47  func (*NoopUi) Ask(string) (string, error) { return "", errors.New("this is a noop ui") }
    48  func (*NoopUi) Say(string)                 { return }
    49  func (*NoopUi) Message(string)             { return }
    50  func (*NoopUi) Error(string)               { return }
    51  func (*NoopUi) Machine(string, ...string)  { return }
    52  func (*NoopUi) ProgressBar() ProgressBar   { return new(NoopProgressBar) }
    53  
    54  // ColoredUi is a UI that is colored using terminal colors.
    55  type ColoredUi struct {
    56  	Color      UiColor
    57  	ErrorColor UiColor
    58  	Ui         Ui
    59  }
    60  
    61  var _ Ui = new(ColoredUi)
    62  
    63  // TargetedUI is a UI that wraps another UI implementation and modifies
    64  // the output to indicate a specific target. Specifically, all Say output
    65  // is prefixed with the target name. Message output is not prefixed but
    66  // is offset by the length of the target so that output is lined up properly
    67  // with Say output. Machine-readable output has the proper target set.
    68  type TargetedUI struct {
    69  	Target string
    70  	Ui     Ui
    71  }
    72  
    73  var _ Ui = new(TargetedUI)
    74  
    75  // The BasicUI is a UI that reads and writes from a standard Go reader
    76  // and writer. It is safe to be called from multiple goroutines. Machine
    77  // readable output is simply logged for this UI.
    78  type BasicUi struct {
    79  	Reader      io.Reader
    80  	Writer      io.Writer
    81  	ErrorWriter io.Writer
    82  	l           sync.Mutex
    83  	interrupted bool
    84  	scanner     *bufio.Scanner
    85  	StackableProgressBar
    86  }
    87  
    88  var _ Ui = new(BasicUi)
    89  
    90  func (bu *BasicUi) ProgressBar() ProgressBar {
    91  	return &bu.StackableProgressBar
    92  }
    93  
    94  // MachineReadableUi is a UI that only outputs machine-readable output
    95  // to the given Writer.
    96  type MachineReadableUi struct {
    97  	Writer io.Writer
    98  }
    99  
   100  var _ Ui = new(MachineReadableUi)
   101  
   102  func (u *ColoredUi) Ask(query string) (string, error) {
   103  	return u.Ui.Ask(u.colorize(query, u.Color, true))
   104  }
   105  
   106  func (u *ColoredUi) Say(message string) {
   107  	u.Ui.Say(u.colorize(message, u.Color, true))
   108  }
   109  
   110  func (u *ColoredUi) Message(message string) {
   111  	u.Ui.Message(u.colorize(message, u.Color, false))
   112  }
   113  
   114  func (u *ColoredUi) Error(message string) {
   115  	color := u.ErrorColor
   116  	if color == 0 {
   117  		color = UiColorRed
   118  	}
   119  
   120  	u.Ui.Error(u.colorize(message, color, true))
   121  }
   122  
   123  func (u *ColoredUi) Machine(t string, args ...string) {
   124  	// Don't colorize machine-readable output
   125  	u.Ui.Machine(t, args...)
   126  }
   127  
   128  func (u *ColoredUi) ProgressBar() ProgressBar {
   129  	return u.Ui.ProgressBar() //TODO(adrien): color me
   130  }
   131  
   132  func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
   133  	if !u.supportsColors() {
   134  		return message
   135  	}
   136  
   137  	attr := 0
   138  	if bold {
   139  		attr = 1
   140  	}
   141  
   142  	return fmt.Sprintf("\033[%d;%dm%s\033[0m", attr, color, message)
   143  }
   144  
   145  func (u *ColoredUi) supportsColors() bool {
   146  	// Never use colors if we have this environmental variable
   147  	if os.Getenv("PACKER_NO_COLOR") != "" {
   148  		return false
   149  	}
   150  
   151  	// For now, on non-Windows machine, just assume it does
   152  	if runtime.GOOS != "windows" {
   153  		return true
   154  	}
   155  
   156  	// On Windows, if we appear to be in Cygwin, then it does
   157  	cygwin := os.Getenv("CYGWIN") != "" ||
   158  		os.Getenv("OSTYPE") == "cygwin" ||
   159  		os.Getenv("TERM") == "cygwin"
   160  
   161  	return cygwin
   162  }
   163  
   164  func (u *TargetedUI) Ask(query string) (string, error) {
   165  	return u.Ui.Ask(u.prefixLines(true, query))
   166  }
   167  
   168  func (u *TargetedUI) Say(message string) {
   169  	u.Ui.Say(u.prefixLines(true, message))
   170  }
   171  
   172  func (u *TargetedUI) Message(message string) {
   173  	u.Ui.Message(u.prefixLines(false, message))
   174  }
   175  
   176  func (u *TargetedUI) Error(message string) {
   177  	u.Ui.Error(u.prefixLines(true, message))
   178  }
   179  
   180  func (u *TargetedUI) Machine(t string, args ...string) {
   181  	// Prefix in the target, then pass through
   182  	u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
   183  }
   184  
   185  func (u *TargetedUI) ProgressBar() ProgressBar {
   186  	return u.Ui.ProgressBar()
   187  }
   188  
   189  func (u *TargetedUI) prefixLines(arrow bool, message string) string {
   190  	arrowText := "==>"
   191  	if !arrow {
   192  		arrowText = strings.Repeat(" ", len(arrowText))
   193  	}
   194  
   195  	var result bytes.Buffer
   196  
   197  	for _, line := range strings.Split(message, "\n") {
   198  		result.WriteString(fmt.Sprintf("%s %s: %s\n", arrowText, u.Target, line))
   199  	}
   200  
   201  	return strings.TrimRightFunc(result.String(), unicode.IsSpace)
   202  }
   203  
   204  func (rw *BasicUi) Ask(query string) (string, error) {
   205  	rw.l.Lock()
   206  	defer rw.l.Unlock()
   207  
   208  	if rw.interrupted {
   209  		return "", errors.New("interrupted")
   210  	}
   211  
   212  	if rw.scanner == nil {
   213  		rw.scanner = bufio.NewScanner(rw.Reader)
   214  	}
   215  	sigCh := make(chan os.Signal, 1)
   216  	signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
   217  	defer signal.Stop(sigCh)
   218  
   219  	log.Printf("ui: ask: %s", query)
   220  	if query != "" {
   221  		if _, err := fmt.Fprint(rw.Writer, query+" "); err != nil {
   222  			return "", err
   223  		}
   224  	}
   225  
   226  	result := make(chan string, 1)
   227  	go func() {
   228  		var line string
   229  		if rw.scanner.Scan() {
   230  			line = rw.scanner.Text()
   231  		}
   232  		if err := rw.scanner.Err(); err != nil {
   233  			log.Printf("ui: scan err: %s", err)
   234  			return
   235  		}
   236  		result <- line
   237  	}()
   238  
   239  	select {
   240  	case line := <-result:
   241  		return line, nil
   242  	case <-sigCh:
   243  		// Print a newline so that any further output starts properly
   244  		// on a new line.
   245  		fmt.Fprintln(rw.Writer)
   246  
   247  		// Mark that we were interrupted so future Ask calls fail.
   248  		rw.interrupted = true
   249  
   250  		return "", errors.New("interrupted")
   251  	}
   252  }
   253  
   254  func (rw *BasicUi) Say(message string) {
   255  	rw.l.Lock()
   256  	defer rw.l.Unlock()
   257  
   258  	log.Printf("ui: %s", message)
   259  	_, err := fmt.Fprint(rw.Writer, message+"\n")
   260  	if err != nil {
   261  		log.Printf("[ERR] Failed to write to UI: %s", err)
   262  	}
   263  }
   264  
   265  func (rw *BasicUi) Message(message string) {
   266  	rw.l.Lock()
   267  	defer rw.l.Unlock()
   268  
   269  	log.Printf("ui: %s", message)
   270  	_, err := fmt.Fprint(rw.Writer, message+"\n")
   271  	if err != nil {
   272  		log.Printf("[ERR] Failed to write to UI: %s", err)
   273  	}
   274  }
   275  
   276  func (rw *BasicUi) Error(message string) {
   277  	rw.l.Lock()
   278  	defer rw.l.Unlock()
   279  
   280  	writer := rw.ErrorWriter
   281  	if writer == nil {
   282  		writer = rw.Writer
   283  	}
   284  
   285  	log.Printf("ui error: %s", message)
   286  	_, err := fmt.Fprint(writer, message+"\n")
   287  	if err != nil {
   288  		log.Printf("[ERR] Failed to write to UI: %s", err)
   289  	}
   290  }
   291  
   292  func (rw *BasicUi) Machine(t string, args ...string) {
   293  	log.Printf("machine readable: %s %#v", t, args)
   294  }
   295  
   296  func (u *MachineReadableUi) Ask(query string) (string, error) {
   297  	return "", errors.New("machine-readable UI can't ask")
   298  }
   299  
   300  func (u *MachineReadableUi) Say(message string) {
   301  	u.Machine("ui", "say", message)
   302  }
   303  
   304  func (u *MachineReadableUi) Message(message string) {
   305  	u.Machine("ui", "message", message)
   306  }
   307  
   308  func (u *MachineReadableUi) Error(message string) {
   309  	u.Machine("ui", "error", message)
   310  }
   311  
   312  func (u *MachineReadableUi) Machine(category string, args ...string) {
   313  	now := time.Now().UTC()
   314  
   315  	// Determine if we have a target, and set it
   316  	target := ""
   317  	commaIdx := strings.Index(category, ",")
   318  	if commaIdx > -1 {
   319  		target = category[0:commaIdx]
   320  		category = category[commaIdx+1:]
   321  	}
   322  
   323  	// Prepare the args
   324  	for i, v := range args {
   325  		args[i] = strings.Replace(v, ",", "%!(PACKER_COMMA)", -1)
   326  		args[i] = strings.Replace(args[i], "\r", "\\r", -1)
   327  		args[i] = strings.Replace(args[i], "\n", "\\n", -1)
   328  	}
   329  	argsString := strings.Join(args, ",")
   330  
   331  	_, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
   332  	if err != nil {
   333  		if err == syscall.EPIPE || strings.Contains(err.Error(), "broken pipe") {
   334  			// Ignore epipe errors because that just means that the file
   335  			// is probably closed or going to /dev/null or something.
   336  		} else {
   337  			panic(err)
   338  		}
   339  	}
   340  }
   341  
   342  func (u *MachineReadableUi) ProgressBar() ProgressBar {
   343  	return new(NoopProgressBar)
   344  }
   345  
   346  // TimestampedUi is a UI that wraps another UI implementation and prefixes
   347  // prefixes each message with an RFC3339 timestamp
   348  type TimestampedUi struct {
   349  	Ui Ui
   350  }
   351  
   352  var _ Ui = new(TimestampedUi)
   353  
   354  func (u *TimestampedUi) Ask(query string) (string, error) {
   355  	return u.Ui.Ask(query)
   356  }
   357  
   358  func (u *TimestampedUi) Say(message string) {
   359  	u.Ui.Say(u.timestampLine(message))
   360  }
   361  
   362  func (u *TimestampedUi) Message(message string) {
   363  	u.Ui.Message(u.timestampLine(message))
   364  }
   365  
   366  func (u *TimestampedUi) Error(message string) {
   367  	u.Ui.Error(u.timestampLine(message))
   368  }
   369  
   370  func (u *TimestampedUi) Machine(message string, args ...string) {
   371  	u.Ui.Machine(message, args...)
   372  }
   373  
   374  func (u *TimestampedUi) ProgressBar() ProgressBar { return u.Ui.ProgressBar() }
   375  
   376  func (u *TimestampedUi) timestampLine(string string) string {
   377  	return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
   378  }