packer-cn/packer/ui.go

467 lines
11 KiB
Go
Raw Normal View History

package packer
import (
"bytes"
"errors"
"fmt"
"io"
2013-05-21 14:40:07 -04:00
"log"
2013-06-14 18:59:54 -04:00
"os"
"os/signal"
2013-07-31 17:43:34 -04:00
"runtime"
"strings"
"sync"
2014-02-21 20:43:45 -05:00
"syscall"
2013-08-11 22:05:07 -04:00
"time"
"unicode"
2020-02-06 11:07:58 -05:00
getter "github.com/hashicorp/go-getter/v2"
)
var ErrInterrupted = errors.New("interrupted")
2013-06-03 16:35:43 -04:00
type UiColor uint
const (
UiColorRed UiColor = 31
UiColorGreen = 32
UiColorYellow = 33
UiColorBlue = 34
UiColorMagenta = 35
UiColorCyan = 36
)
// The Ui interface handles all communication for Packer with the outside
// world. This sort of control allows us to strictly control how output
// is formatted and various levels of output.
type Ui interface {
Ask(string) (string, error)
Say(string)
2013-06-03 14:30:38 -04:00
Message(string)
Error(string)
2013-08-11 21:16:00 -04:00
Machine(string, ...string)
// TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser)
getter.ProgressTracker
}
type NoopUi struct {
PB NoopProgressTracker
}
var _ Ui = new(NoopUi)
func (*NoopUi) Ask(string) (string, error) { return "", errors.New("this is a noop ui") }
func (*NoopUi) Say(string) { return }
func (*NoopUi) Message(string) { return }
func (*NoopUi) Error(string) { return }
func (*NoopUi) Machine(string, ...string) { return }
func (u *NoopUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
return u.PB.TrackProgress(src, currentSize, totalSize, stream)
}
2013-06-03 16:35:43 -04:00
// ColoredUi is a UI that is colored using terminal colors.
type ColoredUi struct {
2013-06-12 13:41:58 -04:00
Color UiColor
ErrorColor UiColor
Ui Ui
PB getter.ProgressTracker
2013-06-03 16:35:43 -04:00
}
var _ Ui = new(ColoredUi)
func (u *ColoredUi) Ask(query string) (string, error) {
return u.Ui.Ask(u.colorize(query, u.Color, true))
}
2013-06-03 16:35:43 -04:00
func (u *ColoredUi) Say(message string) {
2013-06-12 13:41:58 -04:00
u.Ui.Say(u.colorize(message, u.Color, true))
2013-06-03 16:35:43 -04:00
}
func (u *ColoredUi) Message(message string) {
2013-06-12 13:41:58 -04:00
u.Ui.Message(u.colorize(message, u.Color, false))
2013-06-03 16:35:43 -04:00
}
func (u *ColoredUi) Error(message string) {
2013-06-12 13:41:58 -04:00
color := u.ErrorColor
if color == 0 {
color = UiColorRed
}
u.Ui.Error(u.colorize(message, color, true))
2013-06-03 16:35:43 -04:00
}
2013-08-11 21:16:00 -04:00
func (u *ColoredUi) Machine(t string, args ...string) {
// Don't colorize machine-readable output
u.Ui.Machine(t, args...)
}
func (u *ColoredUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
return u.Ui.TrackProgress(u.colorize(src, u.Color, false), currentSize, totalSize, stream)
}
2013-06-12 13:41:58 -04:00
func (u *ColoredUi) colorize(message string, color UiColor, bold bool) string {
if !u.supportsColors() {
return message
}
attr := 0
if bold {
attr = 1
}
return fmt.Sprintf("\033[%d;%dm%s\033[0m", attr, color, message)
2013-06-03 16:35:43 -04:00
}
func (u *ColoredUi) supportsColors() bool {
// Never use colors if we have this environmental variable
if os.Getenv("PACKER_NO_COLOR") != "" {
return false
}
// For now, on non-Windows machine, just assume it does
if runtime.GOOS != "windows" {
return true
}
// On Windows, if we appear to be in Cygwin, then it does
cygwin := os.Getenv("CYGWIN") != "" ||
os.Getenv("OSTYPE") == "cygwin" ||
os.Getenv("TERM") == "cygwin"
return cygwin
}
// TargetedUI is a UI that wraps another UI implementation and modifies
// the output to indicate a specific target. Specifically, all Say output
// is prefixed with the target name. Message output is not prefixed but
// is offset by the length of the target so that output is lined up properly
// with Say output. Machine-readable output has the proper target set.
type TargetedUI struct {
Target string
Ui Ui
}
var _ Ui = new(TargetedUI)
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) Ask(query string) (string, error) {
return u.Ui.Ask(u.prefixLines(true, query))
}
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) Say(message string) {
u.Ui.Say(u.prefixLines(true, message))
2013-06-03 14:30:38 -04:00
}
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) Message(message string) {
u.Ui.Message(u.prefixLines(false, message))
2013-05-21 16:20:51 -04:00
}
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) Error(message string) {
u.Ui.Error(u.prefixLines(true, message))
}
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) Machine(t string, args ...string) {
// Prefix in the target, then pass through
u.Ui.Machine(fmt.Sprintf("%s,%s", u.Target, t), args...)
2013-08-11 21:16:00 -04:00
}
2017-03-29 15:44:42 -04:00
func (u *TargetedUI) prefixLines(arrow bool, message string) string {
arrowText := "==>"
if !arrow {
arrowText = strings.Repeat(" ", len(arrowText))
}
var result bytes.Buffer
for _, line := range strings.Split(message, "\n") {
result.WriteString(fmt.Sprintf("%s %s: %s\n", arrowText, u.Target, line))
}
return strings.TrimRightFunc(result.String(), unicode.IsSpace)
2013-05-21 16:20:51 -04:00
}
func (u *TargetedUI) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) io.ReadCloser {
return u.Ui.TrackProgress(u.prefixLines(false, src), currentSize, totalSize, stream)
}
// The BasicUI is a UI that reads and writes from a standard Go reader
// and writer. It is safe to be called from multiple goroutines. Machine
// readable output is simply logged for this UI.
type BasicUi struct {
Reader io.Reader
Writer io.Writer
ErrorWriter io.Writer
l sync.Mutex
interrupted bool
TTY TTY
PB getter.ProgressTracker
}
var _ Ui = new(BasicUi)
func (rw *BasicUi) Ask(query string) (string, error) {
rw.l.Lock()
defer rw.l.Unlock()
if rw.interrupted {
return "", ErrInterrupted
}
2019-03-06 09:52:59 -05:00
if rw.TTY == nil {
return "", errors.New("no available tty")
}
2013-06-14 18:59:54 -04:00
sigCh := make(chan os.Signal, 1)
2017-09-08 14:31:19 -04:00
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
2013-06-14 18:59:54 -04:00
defer signal.Stop(sigCh)
log.Printf("ui: ask: %s", query)
if query != "" {
if _, err := fmt.Fprint(rw.Writer, query+" "); err != nil {
return "", err
}
}
2013-06-14 18:59:54 -04:00
result := make(chan string, 1)
go func() {
2019-03-06 09:52:59 -05:00
line, err := rw.TTY.ReadString()
2019-02-27 10:56:25 -05:00
if err != nil {
2013-06-14 18:59:54 -04:00
log.Printf("ui: scan err: %s", err)
return
2013-06-14 18:59:54 -04:00
}
result <- strings.TrimSpace(line)
2013-06-14 18:59:54 -04:00
}()
select {
case line := <-result:
return line, nil
case <-sigCh:
// Print a newline so that any further output starts properly
// on a new line.
fmt.Fprintln(rw.Writer)
// Mark that we were interrupted so future Ask calls fail.
rw.interrupted = true
return "", ErrInterrupted
2013-06-14 18:59:54 -04:00
}
}
func (rw *BasicUi) Say(message string) {
rw.l.Lock()
defer rw.l.Unlock()
// Use LogSecretFilter to scrub out sensitive variables
for s := range LogSecretFilter.s {
if s != "" {
message = strings.Replace(message, s, "<sensitive>", -1)
}
}
log.Printf("ui: %s", message)
_, err := fmt.Fprint(rw.Writer, message+"\n")
2013-05-08 20:09:10 -04:00
if err != nil {
log.Printf("[ERR] Failed to write to UI: %s", err)
2013-05-08 20:09:10 -04:00
}
}
2013-05-08 18:12:48 -04:00
func (rw *BasicUi) Message(message string) {
rw.l.Lock()
defer rw.l.Unlock()
// Use LogSecretFilter to scrub out sensitive variables
for s := range LogSecretFilter.s {
if s != "" {
message = strings.Replace(message, s, "<sensitive>", -1)
}
}
2013-06-03 14:30:38 -04:00
log.Printf("ui: %s", message)
_, err := fmt.Fprint(rw.Writer, message+"\n")
2013-06-03 14:30:38 -04:00
if err != nil {
log.Printf("[ERR] Failed to write to UI: %s", err)
2013-06-03 14:30:38 -04:00
}
}
func (rw *BasicUi) Error(message string) {
rw.l.Lock()
defer rw.l.Unlock()
2014-02-21 21:29:15 -05:00
writer := rw.ErrorWriter
if writer == nil {
writer = rw.Writer
}
// Use LogSecretFilter to scrub out sensitive variables
for s := range LogSecretFilter.s {
if s != "" {
message = strings.Replace(message, s, "<sensitive>", -1)
}
}
log.Printf("ui error: %s", message)
2014-02-21 21:29:15 -05:00
_, err := fmt.Fprint(writer, message+"\n")
2013-05-08 20:09:10 -04:00
if err != nil {
log.Printf("[ERR] Failed to write to UI: %s", err)
2013-05-08 20:09:10 -04:00
}
2013-05-08 18:12:48 -04:00
}
2013-08-11 21:16:00 -04:00
func (rw *BasicUi) Machine(t string, args ...string) {
log.Printf("machine readable: %s %#v", t, args)
2013-08-11 21:16:00 -04:00
}
2013-08-11 22:05:07 -04:00
func (rw *BasicUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return rw.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// MachineReadableUi is a UI that only outputs machine-readable output
// to the given Writer.
type MachineReadableUi struct {
Writer io.Writer
PB NoopProgressTracker
}
var _ Ui = new(MachineReadableUi)
2013-08-11 22:05:07 -04:00
func (u *MachineReadableUi) Ask(query string) (string, error) {
return "", errors.New("machine-readable UI can't ask")
}
func (u *MachineReadableUi) Say(message string) {
u.Machine("ui", "say", message)
}
func (u *MachineReadableUi) Message(message string) {
u.Machine("ui", "message", message)
}
func (u *MachineReadableUi) Error(message string) {
u.Machine("ui", "error", message)
}
func (u *MachineReadableUi) Machine(category string, args ...string) {
now := time.Now().UTC()
// Determine if we have a target, and set it
target := ""
commaIdx := strings.Index(category, ",")
if commaIdx > -1 {
target = category[0:commaIdx]
category = category[commaIdx+1:]
}
// Prepare the args
for i, v := range args {
2019-06-12 12:50:27 -04:00
// Use LogSecretFilter to scrub out sensitive variables
for s := range LogSecretFilter.s {
if s != "" {
args[i] = strings.Replace(args[i], s, "<sensitive>", -1)
}
}
args[i] = strings.Replace(v, ",", "%!(PACKER_COMMA)", -1)
args[i] = strings.Replace(args[i], "\r", "\\r", -1)
args[i] = strings.Replace(args[i], "\n", "\\n", -1)
2013-08-11 22:05:07 -04:00
}
argsString := strings.Join(args, ",")
_, err := fmt.Fprintf(u.Writer, "%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
2013-08-11 22:05:07 -04:00
if err != nil {
if err == syscall.EPIPE || strings.Contains(err.Error(), "broken pipe") {
2014-02-21 20:43:45 -05:00
// Ignore epipe errors because that just means that the file
// is probably closed or going to /dev/null or something.
} else {
panic(err)
}
2013-08-11 22:05:07 -04:00
}
log.Printf("%d,%s,%s,%s\n", now.Unix(), target, category, argsString)
2013-08-11 22:05:07 -04:00
}
func (u *MachineReadableUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return u.PB.TrackProgress(src, currentSize, totalSize, stream)
}
// TimestampedUi is a UI that wraps another UI implementation and
// prefixes each message with an RFC3339 timestamp
type TimestampedUi struct {
Ui Ui
PB getter.ProgressTracker
}
var _ Ui = new(TimestampedUi)
2018-09-29 09:09:24 -04:00
func (u *TimestampedUi) Ask(query string) (string, error) {
return u.Ui.Ask(query)
}
func (u *TimestampedUi) Say(message string) {
u.Ui.Say(u.timestampLine(message))
}
func (u *TimestampedUi) Message(message string) {
u.Ui.Message(u.timestampLine(message))
}
func (u *TimestampedUi) Error(message string) {
u.Ui.Error(u.timestampLine(message))
}
func (u *TimestampedUi) Machine(message string, args ...string) {
u.Ui.Machine(message, args...)
}
func (u *TimestampedUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
return u.Ui.TrackProgress(src, currentSize, totalSize, stream)
}
2018-09-29 09:09:24 -04:00
func (u *TimestampedUi) timestampLine(string string) string {
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
}
// Safe is a UI that wraps another UI implementation and
// provides concurrency-safe access
type SafeUi struct {
Sem chan int
Ui Ui
PB getter.ProgressTracker
}
var _ Ui = new(SafeUi)
func (u *SafeUi) Ask(s string) (string, error) {
u.Sem <- 1
ret, err := u.Ui.Ask(s)
<-u.Sem
return ret, err
}
func (u *SafeUi) Say(s string) {
u.Sem <- 1
u.Ui.Say(s)
<-u.Sem
}
func (u *SafeUi) Message(s string) {
u.Sem <- 1
u.Ui.Message(s)
<-u.Sem
}
func (u *SafeUi) Error(s string) {
u.Sem <- 1
u.Ui.Error(s)
<-u.Sem
}
func (u *SafeUi) Machine(t string, args ...string) {
u.Sem <- 1
u.Ui.Machine(t, args...)
<-u.Sem
}
func (u *SafeUi) TrackProgress(src string, currentSize, totalSize int64, stream io.ReadCloser) (body io.ReadCloser) {
u.Sem <- 1
ret := u.Ui.TrackProgress(src, currentSize, totalSize, stream)
<-u.Sem
return ret
}