github.com/jorgemarey/terraform@v0.6.7-0.20151113041428-536ba76b21bb/command/ui_input.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "os/signal" 12 "strings" 13 "sync" 14 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mitchellh/colorstring" 17 ) 18 19 var defaultInputReader io.Reader 20 var defaultInputWriter io.Writer 21 22 // UIInput is an implementation of terraform.UIInput that asks the CLI 23 // for input stdin. 24 type UIInput struct { 25 // Colorize will color the output. 26 Colorize *colorstring.Colorize 27 28 // Reader and Writer for IO. If these aren't set, they will default to 29 // Stdout and Stderr respectively. 30 Reader io.Reader 31 Writer io.Writer 32 33 interrupted bool 34 l sync.Mutex 35 once sync.Once 36 } 37 38 func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { 39 i.once.Do(i.init) 40 41 r := i.Reader 42 w := i.Writer 43 if r == nil { 44 r = defaultInputReader 45 } 46 if w == nil { 47 w = defaultInputWriter 48 } 49 if r == nil { 50 r = os.Stdin 51 } 52 if w == nil { 53 w = os.Stdout 54 } 55 56 // Make sure we only ask for input once at a time. Terraform 57 // should enforce this, but it doesn't hurt to verify. 58 i.l.Lock() 59 defer i.l.Unlock() 60 61 // If we're interrupted, then don't ask for input 62 if i.interrupted { 63 return "", errors.New("interrupted") 64 } 65 66 // Listen for interrupts so we can cancel the input ask 67 sigCh := make(chan os.Signal, 1) 68 signal.Notify(sigCh, os.Interrupt) 69 defer signal.Stop(sigCh) 70 71 // Build the output format for asking 72 var buf bytes.Buffer 73 buf.WriteString("[reset]") 74 buf.WriteString(fmt.Sprintf("[bold]%s[reset]\n", opts.Query)) 75 if opts.Description != "" { 76 s := bufio.NewScanner(strings.NewReader(opts.Description)) 77 for s.Scan() { 78 buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) 79 } 80 buf.WriteString("\n") 81 } 82 if opts.Default != "" { 83 buf.WriteString(" [bold]Default:[reset] ") 84 buf.WriteString(opts.Default) 85 buf.WriteString("\n") 86 } 87 buf.WriteString(" [bold]Enter a value:[reset] ") 88 89 // Ask the user for their input 90 if _, err := fmt.Fprint(w, i.Colorize.Color(buf.String())); err != nil { 91 return "", err 92 } 93 94 // Listen for the input in a goroutine. This will allow us to 95 // interrupt this if we are interrupted (SIGINT) 96 result := make(chan string, 1) 97 go func() { 98 var line string 99 if _, err := fmt.Fscanln(r, &line); err != nil { 100 log.Printf("[ERR] UIInput scan err: %s", err) 101 } 102 103 result <- line 104 }() 105 106 select { 107 case line := <-result: 108 fmt.Fprint(w, "\n") 109 110 if line == "" { 111 line = opts.Default 112 } 113 114 return line, nil 115 case <-sigCh: 116 // Print a newline so that any further output starts properly 117 // on a new line. 118 fmt.Fprintln(w) 119 120 // Mark that we were interrupted so future Ask calls fail. 121 i.interrupted = true 122 123 return "", errors.New("interrupted") 124 } 125 } 126 127 func (i *UIInput) init() { 128 if i.Colorize == nil { 129 i.Colorize = &colorstring.Colorize{ 130 Colors: colorstring.DefaultColors, 131 Disable: true, 132 } 133 } 134 }