github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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  }