Added support for the progress bar to automatically determine its width using the minimum length from a packer.UI and the terminal dimensions via kernel32.GetConsoleScreenBufferInfo or an ioctl (TIOCGWINSZ) to "/dev/tty".
This commit is contained in:
parent
ac27e54c95
commit
fd2fef8738
|
@ -1,9 +1,15 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import "github.com/cheggaaa/pb"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/cheggaaa/pb"
|
||||||
|
"github.com/hashicorp/packer/packer"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// Default progress bar appearance
|
// Default progress bar appearance
|
||||||
func GetDefaultProgressBar() pb.ProgressBar {
|
func GetNewProgressBar(ui *packer.Ui) pb.ProgressBar {
|
||||||
bar := pb.New64(0)
|
bar := pb.New64(0)
|
||||||
bar.ShowPercent = true
|
bar.ShowPercent = true
|
||||||
bar.ShowCounters = true
|
bar.ShowCounters = true
|
||||||
|
@ -16,5 +22,40 @@ func GetDefaultProgressBar() pb.ProgressBar {
|
||||||
bar.SetRefreshRate(1 * time.Second)
|
bar.SetRefreshRate(1 * time.Second)
|
||||||
bar.SetWidth(80)
|
bar.SetWidth(80)
|
||||||
|
|
||||||
|
// If there's no UI set, then the progress bar doesn't need anything else
|
||||||
|
if ui == nil {
|
||||||
|
return *bar
|
||||||
|
}
|
||||||
|
UI := *ui
|
||||||
|
|
||||||
|
// Now check the UI's width to adjust the progress bar
|
||||||
|
uiWidth := UI.GetMinimumLength() + len("\n")
|
||||||
|
|
||||||
|
// If the UI's width is signed, then this interface doesn't really
|
||||||
|
// benefit from a progress bar
|
||||||
|
if uiWidth < 0 {
|
||||||
|
log.Println("Refusing to render progress-bar for unsupported UI.")
|
||||||
|
return *bar
|
||||||
|
}
|
||||||
|
bar.Callback = UI.Message
|
||||||
|
|
||||||
|
// Figure out the terminal width if possible
|
||||||
|
width, _, err := GetTerminalDimensions()
|
||||||
|
if err != nil {
|
||||||
|
newerr := fmt.Errorf("Unable to determine terminal dimensions: %v", err)
|
||||||
|
log.Printf("Using default width (%d) for progress-bar due to error: %s", bar.GetWidth(), newerr)
|
||||||
|
return *bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the progress bar's width according to the terminal size
|
||||||
|
// and whatever width is returned from the UI
|
||||||
|
if width > uiWidth {
|
||||||
|
width -= uiWidth
|
||||||
|
bar.SetWidth(width)
|
||||||
|
} else {
|
||||||
|
newerr := fmt.Errorf("Terminal width (%d) is smaller than UI message width (%d).", width, uiWidth)
|
||||||
|
log.Printf("Using default width (%d) for progress-bar due to error: %s", bar.GetWidth(), newerr)
|
||||||
|
}
|
||||||
|
|
||||||
return *bar
|
return *bar
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
// Imports for determining terminal information across platforms
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// posix api
|
||||||
|
func GetTerminalDimensions() (width, height int, err error) {
|
||||||
|
|
||||||
|
// open up a handle to the current tty
|
||||||
|
tty, err := os.Open("/dev/tty")
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
|
||||||
|
// convert the handle into a file descriptor
|
||||||
|
fd := int(tty.Fd())
|
||||||
|
|
||||||
|
// use it to make an Ioctl
|
||||||
|
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the width and height
|
||||||
|
return int(ws.Col), int(ws.Row), nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// windows constants and structures pulled from msdn
|
||||||
|
const (
|
||||||
|
STD_INPUT_HANDLE = -10
|
||||||
|
STD_OUTPUT_HANDLE = -11
|
||||||
|
STD_ERROR_HANDLE = -12
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
SHORT int16
|
||||||
|
WORD uint16
|
||||||
|
|
||||||
|
SMALL_RECT struct {
|
||||||
|
Left, Top, Right, Bottom SHORT
|
||||||
|
}
|
||||||
|
COORD struct {
|
||||||
|
X, Y SHORT
|
||||||
|
}
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||||
|
dwSize, dwCursorPosition COORD
|
||||||
|
wAttributes WORD
|
||||||
|
srWindow SMALL_RECT
|
||||||
|
dwMaximumWindowSize COORD
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Low-level functions that call into Windows API for getting console info
|
||||||
|
var KERNEL32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
var KERNEL32_GetStdHandleProc = KERNEL32.NewProc("GetStdHandle")
|
||||||
|
var KERNEL32_GetConsoleScreenBufferInfoProc = KERNEL32.NewProc("GetConsoleScreenBufferInfo")
|
||||||
|
|
||||||
|
func KERNEL32_GetStdHandle(nStdHandle int32) (syscall.Handle, error) {
|
||||||
|
res, _, err := KERNEL32_GetStdHandleProc.Call(uintptr(nStdHandle))
|
||||||
|
if res == uintptr(syscall.InvalidHandle) {
|
||||||
|
return syscall.InvalidHandle, error(err)
|
||||||
|
}
|
||||||
|
return syscall.Handle(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func KERNEL32_GetConsoleScreenBufferInfo(hConsoleOutput syscall.Handle, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||||
|
ok, _, err := KERNEL32_GetConsoleScreenBufferInfoProc.Call(uintptr(hConsoleOutput), uintptr(unsafe.Pointer(info)))
|
||||||
|
if int(ok) == 0 {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// windows api
|
||||||
|
func GetTerminalDimensions() (width, height int, err error) {
|
||||||
|
var (
|
||||||
|
fd syscall.Handle
|
||||||
|
csbi CONSOLE_SCREEN_BUFFER_INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
// grab the handle for stdout
|
||||||
|
/*
|
||||||
|
if fd, err = KERNEL32_GetStdHandle(STD_OUTPUT_HANDLE); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if fd, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
// grab the dimensions for the console
|
||||||
|
if err = KERNEL32_GetConsoleScreenBufferInfo(fd, &csbi); err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// whee...
|
||||||
|
return int(csbi.dwSize.X), int(csbi.dwSize.Y), nil
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cheggaaa/pb"
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
"github.com/hashicorp/packer/helper/useragent"
|
"github.com/hashicorp/packer/helper/useragent"
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
|
@ -65,8 +64,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste
|
||||||
ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
|
ui.Say(fmt.Sprintf("Retrieving %s", s.Description))
|
||||||
|
|
||||||
// Get a default-looking progress bar and connect it to the ui.
|
// Get a default-looking progress bar and connect it to the ui.
|
||||||
bar := GetDefaultProgressBar()
|
bar := GetNewProgressBar(&ui)
|
||||||
bar.Callback = ui.Message
|
|
||||||
|
|
||||||
// First try to use any already downloaded file
|
// First try to use any already downloaded file
|
||||||
// If it fails, proceed to regular download logic
|
// If it fails, proceed to regular download logic
|
||||||
|
@ -145,9 +143,8 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag
|
||||||
var path string
|
var path string
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
// Get a default looking progress bar and connect it to the ui.
|
// Get a default-looking progress bar and connect it to the ui.
|
||||||
bar := GetDefaultProgressBar()
|
bar := GetNewProgressBar(&ui)
|
||||||
bar.Callback = ui.Message
|
|
||||||
|
|
||||||
// Create download client with config and progress bar
|
// Create download client with config and progress bar
|
||||||
download := NewDownloadClient(config, bar)
|
download := NewDownloadClient(config, bar)
|
||||||
|
|
|
@ -60,6 +60,13 @@ func (u *Ui) Say(message string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Ui) GetMinimumLength() (result int) {
|
||||||
|
if err := u.client.Call("Ui.GetMinimumLength", nil, &result); err != nil {
|
||||||
|
log.Printf("Error in Ui RPC call: %s", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UiServer) Ask(query string, reply *string) (err error) {
|
func (u *UiServer) Ask(query string, reply *string) (err error) {
|
||||||
*reply, err = u.ui.Ask(query)
|
*reply, err = u.ui.Ask(query)
|
||||||
return
|
return
|
||||||
|
@ -91,3 +98,9 @@ func (u *UiServer) Say(message *string, reply *interface{}) error {
|
||||||
*reply = nil
|
*reply = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UiServer) GetMinimumLength(noargs *interface{}, reply *int) error {
|
||||||
|
res := u.ui.GetMinimumLength()
|
||||||
|
*reply = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,17 +6,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type testUi struct {
|
type testUi struct {
|
||||||
askCalled bool
|
askCalled bool
|
||||||
askQuery string
|
askQuery string
|
||||||
errorCalled bool
|
errorCalled bool
|
||||||
errorMessage string
|
errorMessage string
|
||||||
machineCalled bool
|
machineCalled bool
|
||||||
machineType string
|
machineType string
|
||||||
machineArgs []string
|
machineArgs []string
|
||||||
messageCalled bool
|
messageCalled bool
|
||||||
messageMessage string
|
messageMessage string
|
||||||
sayCalled bool
|
sayCalled bool
|
||||||
sayMessage string
|
sayMessage string
|
||||||
|
getMinimumLengthCalled bool
|
||||||
|
getMinimumLengthValue int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *testUi) Ask(query string) (string, error) {
|
func (u *testUi) Ask(query string) (string, error) {
|
||||||
|
@ -46,6 +48,12 @@ func (u *testUi) Say(message string) {
|
||||||
u.sayMessage = message
|
u.sayMessage = message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *testUi) GetMinimumLength() int {
|
||||||
|
u.getMinimumLengthCalled = true
|
||||||
|
u.getMinimumLengthValue = -1
|
||||||
|
return u.getMinimumLengthValue
|
||||||
|
}
|
||||||
|
|
||||||
func TestUiRPC(t *testing.T) {
|
func TestUiRPC(t *testing.T) {
|
||||||
// Create the UI to test
|
// Create the UI to test
|
||||||
ui := new(testUi)
|
ui := new(testUi)
|
||||||
|
@ -93,6 +101,14 @@ func TestUiRPC(t *testing.T) {
|
||||||
t.Fatal("machine should be called")
|
t.Fatal("machine should be called")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uiClient.GetMinimumLength()
|
||||||
|
if !ui.getMinimumLengthCalled {
|
||||||
|
t.Fatal("getminimumlength should be called")
|
||||||
|
}
|
||||||
|
if ui.getMinimumLengthValue != -1 {
|
||||||
|
t.Fatal("getminimumlength should be -1")
|
||||||
|
}
|
||||||
|
|
||||||
if ui.machineType != "foo" {
|
if ui.machineType != "foo" {
|
||||||
t.Fatalf("bad type: %#v", ui.machineType)
|
t.Fatalf("bad type: %#v", ui.machineType)
|
||||||
}
|
}
|
||||||
|
|
19
packer/ui.go
19
packer/ui.go
|
@ -37,6 +37,7 @@ type Ui interface {
|
||||||
Message(string)
|
Message(string)
|
||||||
Error(string)
|
Error(string)
|
||||||
Machine(string, ...string)
|
Machine(string, ...string)
|
||||||
|
GetMinimumLength() int
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColoredUi is a UI that is colored using terminal colors.
|
// ColoredUi is a UI that is colored using terminal colors.
|
||||||
|
@ -132,6 +133,10 @@ func (u *ColoredUi) supportsColors() bool {
|
||||||
return cygwin
|
return cygwin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ColoredUi) GetMinimumLength() int {
|
||||||
|
return u.Ui.GetMinimumLength()
|
||||||
|
}
|
||||||
|
|
||||||
func (u *TargetedUI) Ask(query string) (string, error) {
|
func (u *TargetedUI) Ask(query string) (string, error) {
|
||||||
return u.Ui.Ask(u.prefixLines(true, query))
|
return u.Ui.Ask(u.prefixLines(true, query))
|
||||||
}
|
}
|
||||||
|
@ -168,6 +173,12 @@ func (u *TargetedUI) prefixLines(arrow bool, message string) string {
|
||||||
return strings.TrimRightFunc(result.String(), unicode.IsSpace)
|
return strings.TrimRightFunc(result.String(), unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *TargetedUI) GetMinimumLength() int {
|
||||||
|
var dummy string
|
||||||
|
dummy = u.prefixLines(false, "")
|
||||||
|
return len(dummy) + len("\n")
|
||||||
|
}
|
||||||
|
|
||||||
func (rw *BasicUi) Ask(query string) (string, error) {
|
func (rw *BasicUi) Ask(query string) (string, error) {
|
||||||
rw.l.Lock()
|
rw.l.Lock()
|
||||||
defer rw.l.Unlock()
|
defer rw.l.Unlock()
|
||||||
|
@ -260,6 +271,10 @@ func (rw *BasicUi) Machine(t string, args ...string) {
|
||||||
log.Printf("machine readable: %s %#v", t, args)
|
log.Printf("machine readable: %s %#v", t, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *BasicUi) GetMinimumLength() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (u *MachineReadableUi) Ask(query string) (string, error) {
|
func (u *MachineReadableUi) Ask(query string) (string, error) {
|
||||||
return "", errors.New("machine-readable UI can't ask")
|
return "", errors.New("machine-readable UI can't ask")
|
||||||
}
|
}
|
||||||
|
@ -305,3 +320,7 @@ func (u *MachineReadableUi) Machine(category string, args ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *MachineReadableUi) GetMinimumLength() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
|
@ -13,3 +13,6 @@ func (su *uiStub) Machine(string, ...string) {}
|
||||||
func (su *uiStub) Message(string) {}
|
func (su *uiStub) Message(string) {}
|
||||||
|
|
||||||
func (su *uiStub) Say(msg string) {}
|
func (su *uiStub) Say(msg string) {}
|
||||||
|
func (su *uiStub) GetMinimumLength() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
|
@ -123,6 +123,10 @@ func (u *ui) Machine(s1 string, s2 ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) GetMinimumLength() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
type communicator struct{}
|
type communicator struct{}
|
||||||
|
|
||||||
func (c communicator) Start(*packer.RemoteCmd) error {
|
func (c communicator) Start(*packer.RemoteCmd) error {
|
||||||
|
|
|
@ -599,3 +599,7 @@ func (ui *Ui) Machine(t string, args ...string) {
|
||||||
ui.ui.Machine(t, args...)
|
ui.ui.Machine(t, args...)
|
||||||
<-ui.sem
|
<-ui.sem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ui *Ui) GetMinimumLength() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package file
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -125,8 +126,16 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
err = comm.Download(src, f)
|
// Get a default progress bar
|
||||||
if err != nil {
|
pb := common.GetNewProgressBar(&ui)
|
||||||
|
bar := pb.Start()
|
||||||
|
defer bar.Finish()
|
||||||
|
|
||||||
|
// Create MultiWriter for the current progress
|
||||||
|
pf := io.MultiWriter(f, bar)
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
if err = comm.Download(src, pf); err != nil {
|
||||||
ui.Error(fmt.Sprintf("Download failed: %s", err))
|
ui.Error(fmt.Sprintf("Download failed: %s", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -166,8 +175,16 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er
|
||||||
dst = filepath.Join(dst, filepath.Base(src))
|
dst = filepath.Join(dst, filepath.Base(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = comm.Upload(dst, f, &fi)
|
// Get a default progress bar
|
||||||
if err != nil {
|
pb := common.GetNewProgressBar(&ui)
|
||||||
|
bar := pb.Start()
|
||||||
|
defer bar.Finish()
|
||||||
|
|
||||||
|
// Create ProxyReader for the current progress
|
||||||
|
pf := bar.NewProxyReader(f)
|
||||||
|
|
||||||
|
// Upload the file
|
||||||
|
if err = comm.Upload(dst, pf, &fi); err != nil {
|
||||||
ui.Error(fmt.Sprintf("Upload failed: %s", err))
|
ui.Error(fmt.Sprintf("Upload failed: %s", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,10 @@ func (su *stubUi) Say(msg string) {
|
||||||
su.sayMessages += msg
|
su.sayMessages += msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (su *stubUi) GetMinimumLength() int {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
func TestProvisionerProvision_SendsFile(t *testing.T) {
|
func TestProvisionerProvision_SendsFile(t *testing.T) {
|
||||||
var p Provisioner
|
var p Provisioner
|
||||||
tf, err := ioutil.TempFile("", "packer")
|
tf, err := ioutil.TempFile("", "packer")
|
||||||
|
|
Loading…
Reference in New Issue