diff --git a/command/ui_input.go b/command/ui_input.go new file mode 100644 index 000000000..1a67cbb3b --- /dev/null +++ b/command/ui_input.go @@ -0,0 +1,82 @@ +package command + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "os/signal" + "sync" + + "github.com/hashicorp/terraform/terraform" +) + +// UIInput is an implementation of terraform.UIInput that asks the CLI +// for input stdin. +type UIInput struct { + // Reader and Writer for IO. If these aren't set, they will default to + // Stdout and Stderr respectively. + Reader io.Reader + Writer io.Writer + + interrupted bool + l sync.Mutex +} + +func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { + r := i.Reader + w := i.Writer + if r == nil { + r = os.Stdin + } + if w == nil { + w = os.Stdout + } + + // Make sure we only ask for input once at a time. Terraform + // should enforce this, but it doesn't hurt to verify. + i.l.Lock() + defer i.l.Unlock() + + // If we're interrupted, then don't ask for input + if i.interrupted { + return "", errors.New("interrupted") + } + + // Listen for interrupts so we can cancel the input ask + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + defer signal.Stop(sigCh) + + // Ask the user for their input + if _, err := fmt.Fprint(w, opts.Query); err != nil { + return "", err + } + + // Listen for the input in a goroutine. This will allow us to + // interrupt this if we are interrupted (SIGINT) + result := make(chan string, 1) + go func() { + var line string + if _, err := fmt.Fscanln(r, &line); err != nil { + log.Printf("[ERR] UIInput scan err: %s", err) + } + + result <- line + }() + + select { + case line := <-result: + return line, nil + case <-sigCh: + // Print a newline so that any further output starts properly + // on a new line. + fmt.Fprintln(w) + + // Mark that we were interrupted so future Ask calls fail. + i.interrupted = true + + return "", errors.New("interrupted") + } +} diff --git a/command/ui_input_test.go b/command/ui_input_test.go new file mode 100644 index 000000000..b968d73b1 --- /dev/null +++ b/command/ui_input_test.go @@ -0,0 +1,28 @@ +package command + +import ( + "bytes" + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestUIInput_impl(t *testing.T) { + var _ terraform.UIInput = new(UIInput) +} + +func TestUIInputInput(t *testing.T) { + i := &UIInput{ + Reader: bytes.NewBufferString("foo\n"), + Writer: bytes.NewBuffer(nil), + } + + v, err := i.Input(&terraform.InputOpts{}) + if err != nil { + t.Fatalf("err: %s", err) + } + + if v != "foo" { + t.Fatalf("bad: %#v", v) + } +}