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 }