borrow wrappedreadline workarounds from terraform and implement a similar check for piped commands; this makes the cli experience much cleaner
This commit is contained in:
parent
b8ac1a800d
commit
df916e805e
|
@ -1,11 +1,14 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/hashicorp/packer/helper/wrappedreadline"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template"
|
"github.com/hashicorp/packer/template"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
|
@ -70,6 +73,11 @@ func (c *ConsoleCommand) Run(args []string) int {
|
||||||
Core: core,
|
Core: core,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if stdin is a pipe. If so, we evaluate directly.
|
||||||
|
if c.StdinPiped() {
|
||||||
|
return c.modePiped(session)
|
||||||
|
}
|
||||||
|
|
||||||
return c.modeInteractive(session)
|
return c.modeInteractive(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,12 +113,45 @@ func (*ConsoleCommand) AutocompleteFlags() complete.Flags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConsoleCommand) modeInteractive(session *REPLSession) int {
|
func (c *ConsoleCommand) modePiped(session *REPLSession) int {
|
||||||
|
var lastResult string
|
||||||
|
scanner := bufio.NewScanner(wrappedreadline.Stdin())
|
||||||
|
for scanner.Scan() {
|
||||||
|
result, err := session.Handle(strings.TrimSpace(scanner.Text()))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// Store the last result
|
||||||
|
lastResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the final result
|
||||||
|
c.Ui.Message(lastResult)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConsoleCommand) modeInteractive(session *REPLSession) int { // Setup the UI so we can output directly to stdout
|
||||||
|
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
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
// Read a line
|
// Read a line
|
||||||
line, err := c.Ui.Ask("> ")
|
line, err := l.Readline()
|
||||||
if err == packer.ErrInterrupted {
|
if err == readline.ErrInterrupt {
|
||||||
|
if len(line) == 0 {
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
} else if err == io.EOF {
|
} else if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -144,6 +185,8 @@ type REPLSession struct {
|
||||||
// The return value is the output and the error to show.
|
// The return value is the output and the error to show.
|
||||||
func (s *REPLSession) Handle(line string) (string, error) {
|
func (s *REPLSession) Handle(line string) (string, error) {
|
||||||
switch {
|
switch {
|
||||||
|
case strings.TrimSpace(line) == "":
|
||||||
|
return "", nil
|
||||||
case strings.TrimSpace(line) == "exit":
|
case strings.TrimSpace(line) == "exit":
|
||||||
return "", ErrSessionExit
|
return "", ErrSessionExit
|
||||||
case strings.TrimSpace(line) == "help":
|
case strings.TrimSpace(line) == "help":
|
||||||
|
|
|
@ -5,9 +5,11 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
kvflag "github.com/hashicorp/packer/helper/flag-kv"
|
kvflag "github.com/hashicorp/packer/helper/flag-kv"
|
||||||
sliceflag "github.com/hashicorp/packer/helper/flag-slice"
|
sliceflag "github.com/hashicorp/packer/helper/flag-slice"
|
||||||
|
"github.com/hashicorp/packer/helper/wrappedreadline"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template"
|
"github.com/hashicorp/packer/template"
|
||||||
)
|
)
|
||||||
|
@ -140,3 +142,14 @@ func (m *Meta) ValidateFlags() error {
|
||||||
// TODO
|
// TODO
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StdinPiped returns true if the input is piped.
|
||||||
|
func (m *Meta) StdinPiped() bool {
|
||||||
|
fi, err := wrappedreadline.Stdin().Stat()
|
||||||
|
if err != nil {
|
||||||
|
// If there is an error, let's just say its not piped
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return fi.Mode()&os.ModeNamedPipe != 0
|
||||||
|
}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -22,6 +22,7 @@ require (
|
||||||
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3
|
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3
|
||||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
|
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
|
||||||
github.com/cheggaaa/pb v1.0.27
|
github.com/cheggaaa/pb v1.0.27
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||||
github.com/creack/goselect v0.1.0 // indirect
|
github.com/creack/goselect v0.1.0 // indirect
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/digitalocean/godo v1.11.1
|
github.com/digitalocean/godo v1.11.1
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -76,6 +76,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXG
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
|
github.com/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
|
||||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Shamelessly copied from the Terraform repo because it wasn't worth vendoring
|
||||||
|
// out two hundred lines of code so Packer could use it too.
|
||||||
|
//
|
||||||
|
// wrappedreadline is a package that has helpers for interacting with
|
||||||
|
// readline from a panicwrap executable.
|
||||||
|
//
|
||||||
|
// panicwrap overrides the standard file descriptors so that the child process
|
||||||
|
// no longer looks like a TTY. The helpers here access the extra file descriptors
|
||||||
|
// passed by panicwrap to fix that.
|
||||||
|
//
|
||||||
|
// panicwrap should be checked for with panicwrap.Wrapped before using this
|
||||||
|
// librar, since this library won't adapt if the binary is not wrapped.
|
||||||
|
package wrappedreadline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/mitchellh/panicwrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.FuncGetWidth = TerminalWidth
|
||||||
|
cfg.FuncIsTerminal = IsTerminal
|
||||||
|
|
||||||
|
rm := RawMode{StdinFd: int(Stdin().Fd())}
|
||||||
|
cfg.FuncMakeRaw = rm.Enter
|
||||||
|
cfg.FuncExitRaw = rm.Exit
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal determines if this process is attached to a TTY.
|
||||||
|
func IsTerminal() bool {
|
||||||
|
// Windows is always a terminal
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same implementation as readline but with our custom fds
|
||||||
|
return readline.IsTerminal(int(Stdin().Fd())) &&
|
||||||
|
(readline.IsTerminal(int(Stdout().Fd())) ||
|
||||||
|
readline.IsTerminal(int(Stderr().Fd())))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TerminalWidth gets the terminal width in characters.
|
||||||
|
func TerminalWidth() int {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return readline.GetScreenWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(r.StdinFd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawMode) Exit() error {
|
||||||
|
if r.state == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return readline.Restore(r.StdinFd, r.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package provides access to the standard OS streams
|
||||||
|
// (stdin, stdout, stderr) even if wrapped under 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 standard streams. These are setup by the
|
||||||
|
// platform specific code in initPlatform.
|
||||||
|
var (
|
||||||
|
wrappedStdin *os.File
|
||||||
|
wrappedStdout *os.File
|
||||||
|
wrappedStderr *os.File
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Initialize the platform-specific code
|
||||||
|
initPlatform()
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
|
package wrappedreadline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getWidth impl for Unix
|
||||||
|
func getWidth() int {
|
||||||
|
stdoutFd := int(Stdout().Fd())
|
||||||
|
stderrFd := int(Stderr().Fd())
|
||||||
|
|
||||||
|
w := getWidthFd(stdoutFd)
|
||||||
|
if w < 0 {
|
||||||
|
w = getWidthFd(stderrFd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
type winsize struct {
|
||||||
|
Row uint16
|
||||||
|
Col uint16
|
||||||
|
Xpixel uint16
|
||||||
|
Ypixel uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// get width of the terminal
|
||||||
|
func getWidthFd(stdoutFd int) int {
|
||||||
|
ws := &winsize{}
|
||||||
|
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
|
||||||
|
uintptr(stdoutFd),
|
||||||
|
uintptr(syscall.TIOCGWINSZ),
|
||||||
|
uintptr(unsafe.Pointer(ws)))
|
||||||
|
|
||||||
|
if int(retCode) == -1 {
|
||||||
|
_ = errno
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(ws.Col)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPlatform() {
|
||||||
|
// The standard streams are passed in via extra file descriptors.
|
||||||
|
wrappedStdin = os.NewFile(uintptr(3), "stdin")
|
||||||
|
wrappedStdout = os.NewFile(uintptr(4), "stdout")
|
||||||
|
wrappedStderr = os.NewFile(uintptr(5), "stderr")
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package wrappedreadline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getWidth impl for other
|
||||||
|
func getWidth() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPlatform() {
|
||||||
|
wrappedStdin = openConsole("CONIN$", os.Stdin)
|
||||||
|
wrappedStdout = openConsole("CONOUT$", os.Stdout)
|
||||||
|
wrappedStderr = wrappedStdout
|
||||||
|
}
|
||||||
|
|
||||||
|
// openConsole opens a console handle, using a backup if it fails.
|
||||||
|
// This is used to get the exact console handle instead of the redirected
|
||||||
|
// handles from panicwrap.
|
||||||
|
func openConsole(name string, backup *os.File) *os.File {
|
||||||
|
// Convert to UTF16
|
||||||
|
path, err := syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] wrappedstreams: %s", err)
|
||||||
|
return backup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the share mode
|
||||||
|
var shareMode uint32
|
||||||
|
switch name {
|
||||||
|
case "CONIN$":
|
||||||
|
shareMode = syscall.FILE_SHARE_READ
|
||||||
|
case "CONOUT$":
|
||||||
|
shareMode = syscall.FILE_SHARE_WRITE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file
|
||||||
|
h, err := syscall.CreateFile(
|
||||||
|
path,
|
||||||
|
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||||
|
shareMode,
|
||||||
|
nil,
|
||||||
|
syscall.OPEN_EXISTING,
|
||||||
|
0, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] wrappedstreams: %s", err)
|
||||||
|
return backup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Go file
|
||||||
|
return os.NewFile(uintptr(h), name)
|
||||||
|
}
|
Loading…
Reference in New Issue