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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/hashicorp/packer/helper/wrappedreadline"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -70,6 +73,11 @@ func (c *ConsoleCommand) Run(args []string) int {
|
|||
Core: core,
|
||||
}
|
||||
|
||||
// Determine if stdin is a pipe. If so, we evaluate directly.
|
||||
if c.StdinPiped() {
|
||||
return c.modePiped(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 {
|
||||
// Read a line
|
||||
line, err := c.Ui.Ask("> ")
|
||||
if err == packer.ErrInterrupted {
|
||||
break
|
||||
line, err := l.Readline()
|
||||
if err == readline.ErrInterrupt {
|
||||
if len(line) == 0 {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
@ -144,6 +185,8 @@ type REPLSession struct {
|
|||
// The return value is the output and the error to show.
|
||||
func (s *REPLSession) Handle(line string) (string, error) {
|
||||
switch {
|
||||
case strings.TrimSpace(line) == "":
|
||||
return "", nil
|
||||
case strings.TrimSpace(line) == "exit":
|
||||
return "", ErrSessionExit
|
||||
case strings.TrimSpace(line) == "help":
|
||||
|
|
|
@ -5,9 +5,11 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
kvflag "github.com/hashicorp/packer/helper/flag-kv"
|
||||
sliceflag "github.com/hashicorp/packer/helper/flag-slice"
|
||||
"github.com/hashicorp/packer/helper/wrappedreadline"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template"
|
||||
)
|
||||
|
@ -140,3 +142,14 @@ func (m *Meta) ValidateFlags() error {
|
|||
// TODO
|
||||
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/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
|
||||
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/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
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/cheggaaa/pb v1.0.27 h1:wIkZHkNfC7R6GI5w7l/PdAdzXzlrbcI3p8OAlnkTsnc=
|
||||
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/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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