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