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