diff --git a/command/console.go b/command/console.go index c18ff1bc2..e68570169 100644 --- a/command/console.go +++ b/command/console.go @@ -3,14 +3,12 @@ package command import ( "bufio" "fmt" - "io" "os" "strings" - "github.com/hashicorp/terraform/helper/wrappedreadline" + "github.com/hashicorp/terraform/helper/wrappedstreams" "github.com/hashicorp/terraform/repl" - "github.com/chzyer/readline" "github.com/mitchellh/cli" ) @@ -63,8 +61,8 @@ func (c *ConsoleCommand) Run(args []string) int { // Setup the UI so we can output directly to stdout ui := &cli.BasicUi{ - Writer: c.Stdout(), - ErrorWriter: c.Stderr(), + Writer: wrappedstreams.Stdout(), + ErrorWriter: wrappedstreams.Stderr(), } // IO Loop @@ -82,7 +80,7 @@ func (c *ConsoleCommand) Run(args []string) int { func (c *ConsoleCommand) modePiped(session *repl.Session, ui cli.Ui) int { var lastResult string - scanner := bufio.NewScanner(c.Stdin()) + scanner := bufio.NewScanner(wrappedstreams.Stdin()) for scanner.Scan() { // Handle it. If there is an error exit immediately result, err := session.Handle(strings.TrimSpace(scanner.Text())) @@ -101,50 +99,6 @@ func (c *ConsoleCommand) modePiped(session *repl.Session, ui cli.Ui) int { return 0 } -func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int { - // Configure input - l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{ - Prompt: "> ", - InterruptPrompt: "^C", - EOFPrompt: "exit", - HistorySearchFold: true, - })) - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Error initializing console: %s", - err)) - return 1 - } - defer l.Close() - - for { - // Read a line - line, err := l.Readline() - if err == readline.ErrInterrupt { - if len(line) == 0 { - break - } else { - continue - } - } else if err == io.EOF { - break - } - - out, err := session.Handle(line) - if err == repl.ErrSessionExit { - break - } - if err != nil { - ui.Error(err.Error()) - continue - } - - ui.Output(out) - } - - return 0 -} - func (c *ConsoleCommand) Help() string { helpText := ` Usage: terraform console [options] [DIR] diff --git a/command/console_interactive.go b/command/console_interactive.go new file mode 100644 index 000000000..f963528bc --- /dev/null +++ b/command/console_interactive.go @@ -0,0 +1,61 @@ +// +build !solaris + +// The readline library we use doesn't currently support solaris so +// we just build tag it off. + +package command + +import ( + "fmt" + "io" + + "github.com/hashicorp/terraform/helper/wrappedreadline" + "github.com/hashicorp/terraform/repl" + + "github.com/chzyer/readline" + "github.com/mitchellh/cli" +) + +func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int { + // Configure input + l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{ + Prompt: "> ", + InterruptPrompt: "^C", + EOFPrompt: "exit", + HistorySearchFold: true, + })) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error initializing console: %s", + err)) + return 1 + } + defer l.Close() + + for { + // Read a line + line, err := l.Readline() + if err == readline.ErrInterrupt { + if len(line) == 0 { + break + } else { + continue + } + } else if err == io.EOF { + break + } + + out, err := session.Handle(line) + if err == repl.ErrSessionExit { + break + } + if err != nil { + ui.Error(err.Error()) + continue + } + + ui.Output(out) + } + + return 0 +} diff --git a/command/console_interactive_solaris.go b/command/console_interactive_solaris.go new file mode 100644 index 000000000..ecb025d4f --- /dev/null +++ b/command/console_interactive_solaris.go @@ -0,0 +1,18 @@ +// +build solaris + +package command + +import ( + "fmt" + + "github.com/hashicorp/terraform/repl" + "github.com/mitchellh/cli" +) + +func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int { + ui.Error(fmt.Sprintf( + "The readline library Terraform currently uses for the interactive\n" + + "console is not supported by Solaris. Interactive mode is therefore\n" + + "not supported on Solaris currently.")) + return 1 +} diff --git a/command/meta.go b/command/meta.go index 8d434ba59..4655f6d1b 100644 --- a/command/meta.go +++ b/command/meta.go @@ -15,12 +15,11 @@ import ( "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/helper/experiment" - "github.com/hashicorp/terraform/helper/wrappedreadline" + "github.com/hashicorp/terraform/helper/wrappedstreams" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" "github.com/mitchellh/colorstring" - "github.com/mitchellh/panicwrap" ) // Meta are the meta-options that are available on all or most commands. @@ -314,7 +313,7 @@ func (m *Meta) Input() bool { // StdinPiped returns true if the input is piped. func (m *Meta) StdinPiped() bool { - fi, err := m.Stdin().Stat() + fi, err := wrappedstreams.Stdin().Stat() if err != nil { // If there is an error, let's just say its not piped return false @@ -323,36 +322,6 @@ func (m *Meta) StdinPiped() bool { return fi.Mode()&os.ModeNamedPipe != 0 } -// Stdin returns the stdin for this command. -func (m *Meta) Stdin() *os.File { - stdin := os.Stdin - if panicwrap.Wrapped(nil) { - stdin = wrappedreadline.Stdin - } - - return stdin -} - -// Stdout returns the stdout for this command. -func (m *Meta) Stdout() *os.File { - stdout := os.Stdout - if panicwrap.Wrapped(nil) { - stdout = wrappedreadline.Stdout - } - - return stdout -} - -// Stderr returns the stderr for this command. -func (m *Meta) Stderr() *os.File { - stderr := os.Stderr - if panicwrap.Wrapped(nil) { - stderr = wrappedreadline.Stderr - } - - return stderr -} - // contextOpts returns the options to use to initialize a Terraform // context with the settings from this Meta. func (m *Meta) contextOpts() *terraform.ContextOpts { diff --git a/helper/wrappedreadline/wrappedreadline.go b/helper/wrappedreadline/wrappedreadline.go index ba54e1271..86d7b9215 100644 --- a/helper/wrappedreadline/wrappedreadline.go +++ b/helper/wrappedreadline/wrappedreadline.go @@ -10,38 +10,24 @@ package wrappedreadline import ( - "os" "runtime" "github.com/chzyer/readline" -) -// These are the file descriptor numbers for the original stdin, stdout, stderr -// streams from the parent process. -const ( - StdinFd = 3 - StdoutFd = 4 - StderrFd = 5 -) - -// These are the *os.File values for the standard streams. -var ( - Stdin = os.NewFile(uintptr(StdinFd), "stdin") - Stdout = os.NewFile(uintptr(StdoutFd), "stdout") - Stderr = os.NewFile(uintptr(StderrFd), "stderr") + "github.com/hashicorp/terraform/helper/wrappedstreams" ) // Override overrides the values in readline.Config that need to be // set with wrapped values. func Override(cfg *readline.Config) *readline.Config { - cfg.Stdin = Stdin - cfg.Stdout = Stdout - cfg.Stderr = Stderr + cfg.Stdin = wrappedstreams.Stdin() + cfg.Stdout = wrappedstreams.Stdout() + cfg.Stderr = wrappedstreams.Stderr() cfg.FuncGetWidth = TerminalWidth cfg.FuncIsTerminal = IsTerminal - var rm RawMode + rm := RawMode{StdinFd: int(wrappedstreams.Stdin().Fd())} cfg.FuncMakeRaw = rm.Enter cfg.FuncExitRaw = rm.Exit @@ -56,7 +42,9 @@ func IsTerminal() bool { } // Same implementation as readline but with our custom fds - return readline.IsTerminal(StdinFd) && (readline.IsTerminal(StdoutFd) || readline.IsTerminal(StderrFd)) + return readline.IsTerminal(int(wrappedstreams.Stdin().Fd())) && + (readline.IsTerminal(int(wrappedstreams.Stdout().Fd())) || + readline.IsTerminal(int(wrappedstreams.Stderr().Fd()))) } // TerminalWidth gets the terminal width in characters. @@ -70,11 +58,13 @@ func TerminalWidth() int { // RawMode is a helper for entering and exiting raw mode. type RawMode struct { + StdinFd int + state *readline.State } func (r *RawMode) Enter() (err error) { - r.state, err = readline.MakeRaw(StdinFd) + r.state, err = readline.MakeRaw(r.StdinFd) return err } @@ -83,5 +73,5 @@ func (r *RawMode) Exit() error { return nil } - return readline.Restore(StdinFd, r.state) + return readline.Restore(r.StdinFd, r.state) } diff --git a/helper/wrappedreadline/wrappedreadline_unix.go b/helper/wrappedreadline/wrappedreadline_unix.go index ba7246265..4e410c7b7 100644 --- a/helper/wrappedreadline/wrappedreadline_unix.go +++ b/helper/wrappedreadline/wrappedreadline_unix.go @@ -5,13 +5,18 @@ package wrappedreadline import ( "syscall" "unsafe" + + "github.com/hashicorp/terraform/helper/wrappedstreams" ) // getWidth impl for Unix func getWidth() int { - w := getWidthFd(StdoutFd) + stdoutFd := int(wrappedstreams.Stdout().Fd()) + stderrFd := int(wrappedstreams.Stderr().Fd()) + + w := getWidthFd(stdoutFd) if w < 0 { - w = getWidthFd(StderrFd) + w = getWidthFd(stderrFd) } return w diff --git a/helper/wrappedstreams/streams.go b/helper/wrappedstreams/streams.go new file mode 100644 index 000000000..c1d6706e8 --- /dev/null +++ b/helper/wrappedstreams/streams.go @@ -0,0 +1,47 @@ +// Package wrappedstreams provides access to the standard OS streams +// (stdin, stdout, stderr) even if wrapped under panicwrap. +package wrappedstreams + +import ( + "os" + + "github.com/mitchellh/panicwrap" +) + +// Stdin returns the true stdin of the process. +func Stdin() *os.File { + stdin := os.Stdin + if panicwrap.Wrapped(nil) { + stdin = wrappedStdin + } + + return stdin +} + +// Stdout returns the true stdout of the process. +func Stdout() *os.File { + stdout := os.Stdout + if panicwrap.Wrapped(nil) { + stdout = wrappedStdout + } + + return stdout +} + +// Stderr returns the true stderr of the process. +func Stderr() *os.File { + stderr := os.Stderr + if panicwrap.Wrapped(nil) { + stderr = wrappedStderr + } + + return stderr +} + +// These are the wrapped streams. There doesn't appear to be a negative +// impact of opening these files even if the file descriptor doesn't exist. +var ( + wrappedStdin = os.NewFile(uintptr(3), "stdin") + wrappedStdout = os.NewFile(uintptr(4), "stdout") + wrappedStderr = os.NewFile(uintptr(5), "stderr") +)