From 16ecb3ad9a4b235a29adc4e8b3eb7ec7e35f5d47 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Mon, 5 Feb 2018 15:13:06 -0600 Subject: [PATCH 1/7] Reverted removal of progress-bar that was done by commit 5d97b105a8b6baf2cf3004e3765f5df66113a1a1 and added some missing arguments that were missed during the rebase. Modified the default progress bar's width to 80 as a result of the conversation on PR #5851. --- common/config.go | 5 +- common/download.go | 48 +- common/download_test.go | 25 +- common/progress.go | 20 + common/step_download.go | 27 +- vendor/github.com/cheggaaa/pb/LICENSE | 12 + vendor/github.com/cheggaaa/pb/README.md | 179 +++++++ vendor/github.com/cheggaaa/pb/format.go | 118 +++++ vendor/github.com/cheggaaa/pb/pb.go | 469 ++++++++++++++++++ vendor/github.com/cheggaaa/pb/pb_win.go | 141 ++++++ vendor/github.com/cheggaaa/pb/pb_x.go | 108 ++++ vendor/github.com/cheggaaa/pb/pool.go | 82 +++ vendor/github.com/cheggaaa/pb/pool_win.go | 45 ++ vendor/github.com/cheggaaa/pb/pool_x.go | 29 ++ vendor/github.com/cheggaaa/pb/reader.go | 25 + vendor/github.com/cheggaaa/pb/runecount.go | 17 + vendor/github.com/cheggaaa/pb/termios_bsd.go | 9 + vendor/github.com/cheggaaa/pb/termios_sysv.go | 13 + vendor/vendor.json | 6 + 19 files changed, 1341 insertions(+), 37 deletions(-) create mode 100644 common/progress.go create mode 100644 vendor/github.com/cheggaaa/pb/LICENSE create mode 100644 vendor/github.com/cheggaaa/pb/README.md create mode 100644 vendor/github.com/cheggaaa/pb/format.go create mode 100644 vendor/github.com/cheggaaa/pb/pb.go create mode 100644 vendor/github.com/cheggaaa/pb/pb_win.go create mode 100644 vendor/github.com/cheggaaa/pb/pb_x.go create mode 100644 vendor/github.com/cheggaaa/pb/pool.go create mode 100644 vendor/github.com/cheggaaa/pb/pool_win.go create mode 100644 vendor/github.com/cheggaaa/pb/pool_x.go create mode 100644 vendor/github.com/cheggaaa/pb/reader.go create mode 100644 vendor/github.com/cheggaaa/pb/runecount.go create mode 100644 vendor/github.com/cheggaaa/pb/termios_bsd.go create mode 100644 vendor/github.com/cheggaaa/pb/termios_sysv.go diff --git a/common/config.go b/common/config.go index aa632ceae..1487ea292 100644 --- a/common/config.go +++ b/common/config.go @@ -2,6 +2,7 @@ package common import ( "fmt" + "github.com/cheggaaa/pb" "net/url" "os" "path/filepath" @@ -54,7 +55,7 @@ func SupportedProtocol(u *url.URL) bool { // build a dummy NewDownloadClient since this is the only place that valid // protocols are actually exposed. - cli := NewDownloadClient(&DownloadConfig{}) + cli := NewDownloadClient(&DownloadConfig{}, *pb.New(0)) // Iterate through each downloader to see if a protocol was found. ok := false @@ -186,7 +187,7 @@ func FileExistsLocally(original string) bool { // First create a dummy downloader so we can figure out which // protocol to use. - cli := NewDownloadClient(&DownloadConfig{}) + cli := NewDownloadClient(&DownloadConfig{}, *pb.New(0)) d, ok := cli.config.DownloaderMap[u.Scheme] if !ok { return false diff --git a/common/download.go b/common/download.go index 09457ef0b..62fcc908e 100644 --- a/common/download.go +++ b/common/download.go @@ -18,6 +18,9 @@ import ( "strings" ) +// required import for progress-bar +import "github.com/cheggaaa/pb" + // imports related to each Downloader implementation import ( "io" @@ -81,22 +84,23 @@ func HashForType(t string) hash.Hash { // NewDownloadClient returns a new DownloadClient for the given // configuration. -func NewDownloadClient(c *DownloadConfig) *DownloadClient { +func NewDownloadClient(c *DownloadConfig, bar pb.ProgressBar) *DownloadClient { const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */ // Create downloader map if it hasn't been specified already. if c.DownloaderMap == nil { c.DownloaderMap = map[string]Downloader{ - "file": &FileDownloader{bufferSize: nil}, - "http": &HTTPDownloader{userAgent: c.UserAgent}, - "https": &HTTPDownloader{userAgent: c.UserAgent}, - "smb": &SMBDownloader{bufferSize: nil}, + "file": &FileDownloader{progress: &bar, bufferSize: nil}, + "http": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent}, + "https": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent}, + "smb": &SMBDownloader{progress: &bar, bufferSize: nil}, } } return &DownloadClient{config: c} } -// A downloader implements the ability to transfer, cancel, or resume a file. +// A downloader implements the ability to transfer a file, and cancel or resume +// it. type Downloader interface { Resume() Cancel() @@ -205,14 +209,6 @@ func (d *DownloadClient) Get() (string, error) { return finalPath, err } -func (d *DownloadClient) PercentProgress() int { - if d.downloader == nil { - return -1 - } - - return int((float64(d.downloader.Progress()) / float64(d.downloader.Total())) * 100) -} - // VerifyChecksum tests that the path matches the checksum for the // download. func (d *DownloadClient) VerifyChecksum(path string) (bool, error) { @@ -238,6 +234,8 @@ type HTTPDownloader struct { current uint64 total uint64 userAgent string + + progress *pb.ProgressBar } func (d *HTTPDownloader) Cancel() { @@ -330,6 +328,10 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { d.total = d.current + uint64(resp.ContentLength) + d.progress.Total = int64(d.total) + progressBar := d.progress.Start() + progressBar.Set64(int64(d.current)) + var buffer [4096]byte for { n, err := resp.Body.Read(buffer[:]) @@ -338,6 +340,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { } d.current += uint64(n) + progressBar.Set64(int64(d.current)) if _, werr := dst.Write(buffer[:n]); werr != nil { return werr @@ -347,6 +350,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { break } } + progressBar.Finish() return nil } @@ -366,6 +370,8 @@ type FileDownloader struct { active bool current uint64 total uint64 + + progress *pb.ProgressBar } func (d *FileDownloader) Progress() uint64 { @@ -456,6 +462,9 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { } d.total = uint64(fi.Size()) + d.progress.Total = int64(d.total) + progressBar := d.progress.Start() + // no bufferSize specified, so copy synchronously. if d.bufferSize == nil { var n int64 @@ -463,6 +472,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { d.active = false d.current += uint64(n) + progressBar.Set64(int64(d.current)) // use a goro in case someone else wants to enable cancel/resume } else { @@ -475,6 +485,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { } d.current += uint64(n) + progressBar.Set64(int64(d.current)) } d.active = false e <- err @@ -483,6 +494,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { // ...and we spin until it's done err = <-errch } + progressBar.Finish() f.Close() return err } @@ -495,6 +507,8 @@ type SMBDownloader struct { active bool current uint64 total uint64 + + progress *pb.ProgressBar } func (d *SMBDownloader) Progress() uint64 { @@ -567,6 +581,9 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { } d.total = uint64(fi.Size()) + d.progress.Total = int64(d.total) + progressBar := d.progress.Start() + // no bufferSize specified, so copy synchronously. if d.bufferSize == nil { var n int64 @@ -574,6 +591,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { d.active = false d.current += uint64(n) + progressBar.Set64(int64(d.current)) // use a goro in case someone else wants to enable cancel/resume } else { @@ -586,6 +604,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { } d.current += uint64(n) + progressBar.Set64(int64(d.current)) } d.active = false e <- err @@ -594,6 +613,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { // ...and as usual we spin until it's done err = <-errch } + progressBar.Finish() f.Close() return err } diff --git a/common/download_test.go b/common/download_test.go index c9175b013..6100be1c2 100644 --- a/common/download_test.go +++ b/common/download_test.go @@ -12,6 +12,8 @@ import ( "runtime" "strings" "testing" + + "github.com/cheggaaa/pb" ) func TestDownloadClientVerifyChecksum(t *testing.T) { @@ -36,7 +38,7 @@ func TestDownloadClientVerifyChecksum(t *testing.T) { Checksum: checksum, } - d := NewDownloadClient(config) + d := NewDownloadClient(config, *pb.New64(0)) result, err := d.VerifyChecksum(tf.Name()) if err != nil { t.Fatalf("Verify err: %s", err) @@ -59,7 +61,7 @@ func TestDownloadClient_basic(t *testing.T) { Url: ts.URL + "/basic.txt", TargetPath: tf.Name(), CopyFile: true, - }) + }, *pb.New64(0)) path, err := client.Get() if err != nil { @@ -95,7 +97,7 @@ func TestDownloadClient_checksumBad(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }) + }, *pb.New64(0)) if _, err := client.Get(); err == nil { t.Fatal("should error") @@ -121,7 +123,7 @@ func TestDownloadClient_checksumGood(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }) + }, *pb.New64(0)) path, err := client.Get() if err != nil { @@ -153,7 +155,8 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }) + }, *pb.New64(0)) + path, err := client.Get() if err != nil { t.Fatalf("err: %s", err) @@ -183,7 +186,7 @@ func TestDownloadClient_notFound(t *testing.T) { client := NewDownloadClient(&DownloadConfig{ Url: ts.URL + "/not-found.txt", TargetPath: tf.Name(), - }) + }, *pb.New64(0)) if _, err := client.Get(); err == nil { t.Fatal("should error") @@ -211,7 +214,7 @@ func TestDownloadClient_resume(t *testing.T) { Url: ts.URL, TargetPath: tf.Name(), CopyFile: true, - }) + }, *pb.New64(0)) path, err := client.Get() if err != nil { @@ -273,7 +276,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) { CopyFile: true, } - client := NewDownloadClient(config) + client := NewDownloadClient(config, *pb.New64(0)) _, err = client.Get() if err != nil { t.Fatal(err) @@ -306,7 +309,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) { CopyFile: true, } - client := NewDownloadClient(config) + client := NewDownloadClient(config, *pb.New64(0)) _, err = client.Get() if err != nil { t.Fatal(err) @@ -405,7 +408,7 @@ func TestDownloadFileUrl(t *testing.T) { CopyFile: false, } - client := NewDownloadClient(config) + client := NewDownloadClient(config, *pb.New64(0)) // Verify that we fail to match the checksum _, err = client.Get() @@ -436,7 +439,7 @@ func SimulateFileUriDownload(t *testing.T, uri string) (string, error) { } // go go go - client := NewDownloadClient(config) + client := NewDownloadClient(config, *pb.New64(0)) path, err := client.Get() // ignore any non-important checksum errors if it's not a unc path diff --git a/common/progress.go b/common/progress.go new file mode 100644 index 000000000..c90810c9d --- /dev/null +++ b/common/progress.go @@ -0,0 +1,20 @@ +package common + +import "github.com/cheggaaa/pb" + +// Default progress bar appearance +func GetDefaultProgressBar() pb.ProgressBar { + bar := pb.New64(0) + bar.ShowPercent = true + bar.ShowCounters = true + bar.ShowSpeed = false + bar.ShowBar = true + bar.ShowTimeLeft = false + bar.ShowFinalTime = false + bar.SetUnits(pb.U_BYTES) + bar.Format("[=>-]") + bar.SetRefreshRate(1 * time.Second) + bar.SetWidth(80) + + return *bar +} diff --git a/common/step_download.go b/common/step_download.go index 1719c5987..c920cf5d2 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -8,6 +8,7 @@ import ( "log" "time" + "github.com/cheggaaa/pb" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/useragent" "github.com/hashicorp/packer/packer" @@ -63,6 +64,10 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) + // Get a default-looking progress bar and connect it to the ui. + bar := GetDefaultProgressBar() + bar.Callback = ui.Message + // First try to use any already downloaded file // If it fails, proceed to regular download logic @@ -96,7 +101,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste } downloadConfigs[i] = config - if match, _ := NewDownloadClient(config).VerifyChecksum(config.TargetPath); match { + if match, _ := NewDownloadClient(config, bar).VerifyChecksum(config.TargetPath); match { ui.Message(fmt.Sprintf("Found already downloaded, initial checksum matched, no download needed: %s", url)) finalPath = config.TargetPath break @@ -139,7 +144,13 @@ func (s *StepDownload) Cleanup(multistep.StateBag) {} func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag) (string, error, bool) { var path string ui := state.Get("ui").(packer.Ui) - download := NewDownloadClient(config) + + // Get a default looking progress bar and connect it to the ui. + bar := GetDefaultProgressBar() + bar.Callback = ui.Message + + // Create download client with config and progress bar + download := NewDownloadClient(config, bar) downloadCompleteCh := make(chan error, 1) go func() { @@ -148,12 +159,11 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag downloadCompleteCh <- err }() - progressTicker := time.NewTicker(5 * time.Second) - defer progressTicker.Stop() - for { select { case err := <-downloadCompleteCh: + bar.Finish() + if err != nil { return "", err, true } @@ -164,13 +174,10 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag } return path, nil, true - case <-progressTicker.C: - progress := download.PercentProgress() - if progress >= 0 { - ui.Message(fmt.Sprintf("Download progress: %d%%", progress)) - } + case <-time.After(1 * time.Second): if _, ok := state.GetOk(multistep.StateCancelled); ok { + bar.Finish() ui.Say("Interrupt received. Cancelling download...") return "", nil, false } diff --git a/vendor/github.com/cheggaaa/pb/LICENSE b/vendor/github.com/cheggaaa/pb/LICENSE new file mode 100644 index 000000000..511970333 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2012-2015, Sergey Cherepanov +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/github.com/cheggaaa/pb/README.md b/vendor/github.com/cheggaaa/pb/README.md new file mode 100644 index 000000000..948545110 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/README.md @@ -0,0 +1,179 @@ +# Terminal progress bar for Go + +[![Join the chat at https://gitter.im/cheggaaa/pb](https://badges.gitter.im/cheggaaa/pb.svg)](https://gitter.im/cheggaaa/pb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +Simple progress bar for console programs. + +Please check the new version https://github.com/cheggaaa/pb/tree/v2 (currently, it's beta) + +## Installation + +``` +go get gopkg.in/cheggaaa/pb.v1 +``` + +## Usage + +```Go +package main + +import ( + "gopkg.in/cheggaaa/pb.v1" + "time" +) + +func main() { + count := 100000 + bar := pb.StartNew(count) + for i := 0; i < count; i++ { + bar.Increment() + time.Sleep(time.Millisecond) + } + bar.FinishPrint("The End!") +} + +``` + +Result will be like this: + +``` +> go run test.go +37158 / 100000 [================>_______________________________] 37.16% 1m11s +``` + +## Customization + +```Go +// create bar +bar := pb.New(count) + +// refresh info every second (default 200ms) +bar.SetRefreshRate(time.Second) + +// show percents (by default already true) +bar.ShowPercent = true + +// show bar (by default already true) +bar.ShowBar = true + +// no counters +bar.ShowCounters = false + +// show "time left" +bar.ShowTimeLeft = true + +// show average speed +bar.ShowSpeed = true + +// sets the width of the progress bar +bar.SetWidth(80) + +// sets the width of the progress bar, but if terminal size smaller will be ignored +bar.SetMaxWidth(80) + +// convert output to readable format (like KB, MB) +bar.SetUnits(pb.U_BYTES) + +// and start +bar.Start() +``` + +## Progress bar for IO Operations + +```go +// create and start bar +bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) +bar.Start() + +// my io.Reader +r := myReader + +// my io.Writer +w := myWriter + +// create proxy reader +reader := bar.NewProxyReader(r) + +// and copy from pb reader +io.Copy(w, reader) + +``` + +```go +// create and start bar +bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) +bar.Start() + +// my io.Reader +r := myReader + +// my io.Writer +w := myWriter + +// create multi writer +writer := io.MultiWriter(w, bar) + +// and copy +io.Copy(writer, r) + +bar.Finish() +``` + +## Custom Progress Bar Look-and-feel + +```go +bar.Format("<.- >") +``` + +## Multiple Progress Bars (experimental and unstable) + +Do not print to terminal while pool is active. + +```go +package main + +import ( + "math/rand" + "sync" + "time" + + "gopkg.in/cheggaaa/pb.v1" +) + +func main() { + // create bars + first := pb.New(200).Prefix("First ") + second := pb.New(200).Prefix("Second ") + third := pb.New(200).Prefix("Third ") + // start pool + pool, err := pb.StartPool(first, second, third) + if err != nil { + panic(err) + } + // update bars + wg := new(sync.WaitGroup) + for _, bar := range []*pb.ProgressBar{first, second, third} { + wg.Add(1) + go func(cb *pb.ProgressBar) { + for n := 0; n < 200; n++ { + cb.Increment() + time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) + } + cb.Finish() + wg.Done() + }(bar) + } + wg.Wait() + // close pool + pool.Stop() +} +``` + +The result will be as follows: + +``` +$ go run example/multiple.go +First 141 / 1000 [===============>---------------------------------------] 14.10 % 44s +Second 139 / 1000 [==============>---------------------------------------] 13.90 % 44s +Third 152 / 1000 [================>--------------------------------------] 15.20 % 40s +``` diff --git a/vendor/github.com/cheggaaa/pb/format.go b/vendor/github.com/cheggaaa/pb/format.go new file mode 100644 index 000000000..0723561c2 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/format.go @@ -0,0 +1,118 @@ +package pb + +import ( + "fmt" + "time" +) + +type Units int + +const ( + // U_NO are default units, they represent a simple value and are not formatted at all. + U_NO Units = iota + // U_BYTES units are formatted in a human readable way (B, KiB, MiB, ...) + U_BYTES + // U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...) + U_BYTES_DEC + // U_DURATION units are formatted in a human readable way (3h14m15s) + U_DURATION +) + +const ( + KiB = 1024 + MiB = 1048576 + GiB = 1073741824 + TiB = 1099511627776 + + KB = 1e3 + MB = 1e6 + GB = 1e9 + TB = 1e12 +) + +func Format(i int64) *formatter { + return &formatter{n: i} +} + +type formatter struct { + n int64 + unit Units + width int + perSec bool +} + +func (f *formatter) To(unit Units) *formatter { + f.unit = unit + return f +} + +func (f *formatter) Width(width int) *formatter { + f.width = width + return f +} + +func (f *formatter) PerSec() *formatter { + f.perSec = true + return f +} + +func (f *formatter) String() (out string) { + switch f.unit { + case U_BYTES: + out = formatBytes(f.n) + case U_BYTES_DEC: + out = formatBytesDec(f.n) + case U_DURATION: + out = formatDuration(f.n) + default: + out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) + } + if f.perSec { + out += "/s" + } + return +} + +// Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B +func formatBytes(i int64) (result string) { + switch { + case i >= TiB: + result = fmt.Sprintf("%.02f TiB", float64(i)/TiB) + case i >= GiB: + result = fmt.Sprintf("%.02f GiB", float64(i)/GiB) + case i >= MiB: + result = fmt.Sprintf("%.02f MiB", float64(i)/MiB) + case i >= KiB: + result = fmt.Sprintf("%.02f KiB", float64(i)/KiB) + default: + result = fmt.Sprintf("%d B", i) + } + return +} + +// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B +func formatBytesDec(i int64) (result string) { + switch { + case i >= TB: + result = fmt.Sprintf("%.02f TB", float64(i)/TB) + case i >= GB: + result = fmt.Sprintf("%.02f GB", float64(i)/GB) + case i >= MB: + result = fmt.Sprintf("%.02f MB", float64(i)/MB) + case i >= KB: + result = fmt.Sprintf("%.02f KB", float64(i)/KB) + default: + result = fmt.Sprintf("%d B", i) + } + return +} + +func formatDuration(n int64) (result string) { + d := time.Duration(n) + if d > time.Hour*24 { + result = fmt.Sprintf("%dd", d/24/time.Hour) + d -= (d / time.Hour / 24) * (time.Hour * 24) + } + result = fmt.Sprintf("%s%v", result, d) + return +} diff --git a/vendor/github.com/cheggaaa/pb/pb.go b/vendor/github.com/cheggaaa/pb/pb.go new file mode 100644 index 000000000..19eb4d1a9 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pb.go @@ -0,0 +1,469 @@ +// Simple console progress bars +package pb + +import ( + "fmt" + "io" + "math" + "strings" + "sync" + "sync/atomic" + "time" + "unicode/utf8" +) + +// Current version +const Version = "1.0.19" + +const ( + // Default refresh rate - 200ms + DEFAULT_REFRESH_RATE = time.Millisecond * 200 + FORMAT = "[=>-]" +) + +// DEPRECATED +// variables for backward compatibility, from now do not work +// use pb.Format and pb.SetRefreshRate +var ( + DefaultRefreshRate = DEFAULT_REFRESH_RATE + BarStart, BarEnd, Empty, Current, CurrentN string +) + +// Create new progress bar object +func New(total int) *ProgressBar { + return New64(int64(total)) +} + +// Create new progress bar object using int64 as total +func New64(total int64) *ProgressBar { + pb := &ProgressBar{ + Total: total, + RefreshRate: DEFAULT_REFRESH_RATE, + ShowPercent: true, + ShowCounters: true, + ShowBar: true, + ShowTimeLeft: true, + ShowFinalTime: true, + Units: U_NO, + ManualUpdate: false, + finish: make(chan struct{}), + } + return pb.Format(FORMAT) +} + +// Create new object and start +func StartNew(total int) *ProgressBar { + return New(total).Start() +} + +// Callback for custom output +// For example: +// bar.Callback = func(s string) { +// mySuperPrint(s) +// } +// +type Callback func(out string) + +type ProgressBar struct { + current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) + previous int64 + + Total int64 + RefreshRate time.Duration + ShowPercent, ShowCounters bool + ShowSpeed, ShowTimeLeft, ShowBar bool + ShowFinalTime bool + Output io.Writer + Callback Callback + NotPrint bool + Units Units + Width int + ForceWidth bool + ManualUpdate bool + AutoStat bool + + // Default width for the time box. + UnitsWidth int + TimeBoxWidth int + + finishOnce sync.Once //Guards isFinish + finish chan struct{} + isFinish bool + + startTime time.Time + startValue int64 + + changeTime time.Time + + prefix, postfix string + + mu sync.Mutex + lastPrint string + + BarStart string + BarEnd string + Empty string + Current string + CurrentN string + + AlwaysUpdate bool +} + +// Start print +func (pb *ProgressBar) Start() *ProgressBar { + pb.startTime = time.Now() + pb.startValue = atomic.LoadInt64(&pb.current) + if pb.Total == 0 { + pb.ShowTimeLeft = false + pb.ShowPercent = false + pb.AutoStat = false + } + if !pb.ManualUpdate { + pb.Update() // Initial printing of the bar before running the bar refresher. + go pb.refresher() + } + return pb +} + +// Increment current value +func (pb *ProgressBar) Increment() int { + return pb.Add(1) +} + +// Get current value +func (pb *ProgressBar) Get() int64 { + c := atomic.LoadInt64(&pb.current) + return c +} + +// Set current value +func (pb *ProgressBar) Set(current int) *ProgressBar { + return pb.Set64(int64(current)) +} + +// Set64 sets the current value as int64 +func (pb *ProgressBar) Set64(current int64) *ProgressBar { + atomic.StoreInt64(&pb.current, current) + return pb +} + +// Add to current value +func (pb *ProgressBar) Add(add int) int { + return int(pb.Add64(int64(add))) +} + +func (pb *ProgressBar) Add64(add int64) int64 { + return atomic.AddInt64(&pb.current, add) +} + +// Set prefix string +func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { + pb.prefix = prefix + return pb +} + +// Set postfix string +func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { + pb.postfix = postfix + return pb +} + +// Set custom format for bar +// Example: bar.Format("[=>_]") +// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter +func (pb *ProgressBar) Format(format string) *ProgressBar { + var formatEntries []string + if utf8.RuneCountInString(format) == 5 { + formatEntries = strings.Split(format, "") + } else { + formatEntries = strings.Split(format, "\x00") + } + if len(formatEntries) == 5 { + pb.BarStart = formatEntries[0] + pb.BarEnd = formatEntries[4] + pb.Empty = formatEntries[3] + pb.Current = formatEntries[1] + pb.CurrentN = formatEntries[2] + } + return pb +} + +// Set bar refresh rate +func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { + pb.RefreshRate = rate + return pb +} + +// Set units +// bar.SetUnits(U_NO) - by default +// bar.SetUnits(U_BYTES) - for Mb, Kb, etc +func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { + pb.Units = units + return pb +} + +// Set max width, if width is bigger than terminal width, will be ignored +func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { + pb.Width = width + pb.ForceWidth = false + return pb +} + +// Set bar width +func (pb *ProgressBar) SetWidth(width int) *ProgressBar { + pb.Width = width + pb.ForceWidth = true + return pb +} + +// End print +func (pb *ProgressBar) Finish() { + //Protect multiple calls + pb.finishOnce.Do(func() { + close(pb.finish) + pb.write(atomic.LoadInt64(&pb.current)) + pb.mu.Lock() + defer pb.mu.Unlock() + switch { + case pb.Output != nil: + fmt.Fprintln(pb.Output) + case !pb.NotPrint: + fmt.Println() + } + pb.isFinish = true + }) +} + +// IsFinished return boolean +func (pb *ProgressBar) IsFinished() bool { + pb.mu.Lock() + defer pb.mu.Unlock() + return pb.isFinish +} + +// End print and write string 'str' +func (pb *ProgressBar) FinishPrint(str string) { + pb.Finish() + if pb.Output != nil { + fmt.Fprintln(pb.Output, str) + } else { + fmt.Println(str) + } +} + +// implement io.Writer +func (pb *ProgressBar) Write(p []byte) (n int, err error) { + n = len(p) + pb.Add(n) + return +} + +// implement io.Reader +func (pb *ProgressBar) Read(p []byte) (n int, err error) { + n = len(p) + pb.Add(n) + return +} + +// Create new proxy reader over bar +// Takes io.Reader or io.ReadCloser +func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { + return &Reader{r, pb} +} + +func (pb *ProgressBar) write(current int64) { + width := pb.GetWidth() + + var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string + + // percents + if pb.ShowPercent { + var percent float64 + if pb.Total > 0 { + percent = float64(current) / (float64(pb.Total) / float64(100)) + } else { + percent = float64(current) / float64(100) + } + percentBox = fmt.Sprintf(" %6.02f%%", percent) + } + + // counters + if pb.ShowCounters { + current := Format(current).To(pb.Units).Width(pb.UnitsWidth) + if pb.Total > 0 { + total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth) + countersBox = fmt.Sprintf(" %s / %s ", current, total) + } else { + countersBox = fmt.Sprintf(" %s / ? ", current) + } + } + + // time left + pb.mu.Lock() + currentFromStart := current - pb.startValue + fromStart := time.Now().Sub(pb.startTime) + lastChangeTime := pb.changeTime + fromChange := lastChangeTime.Sub(pb.startTime) + pb.mu.Unlock() + select { + case <-pb.finish: + if pb.ShowFinalTime { + var left time.Duration + left = (fromStart / time.Second) * time.Second + timeLeftBox = fmt.Sprintf(" %s", left.String()) + } + default: + if pb.ShowTimeLeft && currentFromStart > 0 { + perEntry := fromChange / time.Duration(currentFromStart) + var left time.Duration + if pb.Total > 0 { + left = time.Duration(pb.Total-currentFromStart) * perEntry + left -= time.Since(lastChangeTime) + left = (left / time.Second) * time.Second + } else { + left = time.Duration(currentFromStart) * perEntry + left = (left / time.Second) * time.Second + } + if left > 0 { + timeLeft := Format(int64(left)).To(U_DURATION).String() + timeLeftBox = fmt.Sprintf(" %s", timeLeft) + } + } + } + + if len(timeLeftBox) < pb.TimeBoxWidth { + timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) + } + + // speed + if pb.ShowSpeed && currentFromStart > 0 { + fromStart := time.Now().Sub(pb.startTime) + speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) + speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() + } + + barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) + // bar + if pb.ShowBar { + size := width - barWidth + if size > 0 { + if pb.Total > 0 { + curSize := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) + emptySize := size - curSize + barBox = pb.BarStart + if emptySize < 0 { + emptySize = 0 + } + if curSize > size { + curSize = size + } + + cursorLen := escapeAwareRuneCountInString(pb.Current) + if emptySize <= 0 { + barBox += strings.Repeat(pb.Current, curSize/cursorLen) + } else if curSize > 0 { + cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN) + cursorRepetitions := (curSize - cursorEndLen) / cursorLen + barBox += strings.Repeat(pb.Current, cursorRepetitions) + barBox += pb.CurrentN + } + + emptyLen := escapeAwareRuneCountInString(pb.Empty) + barBox += strings.Repeat(pb.Empty, emptySize/emptyLen) + barBox += pb.BarEnd + } else { + pos := size - int(current)%int(size) + barBox = pb.BarStart + if pos-1 > 0 { + barBox += strings.Repeat(pb.Empty, pos-1) + } + barBox += pb.Current + if size-pos-1 > 0 { + barBox += strings.Repeat(pb.Empty, size-pos-1) + } + barBox += pb.BarEnd + } + } + } + + // check len + out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix + if cl := escapeAwareRuneCountInString(out); cl < width { + end = strings.Repeat(" ", width-cl) + } + + // and print! + pb.mu.Lock() + pb.lastPrint = out + end + isFinish := pb.isFinish + pb.mu.Unlock() + switch { + case isFinish: + return + case pb.Output != nil: + fmt.Fprint(pb.Output, "\r"+out+end) + case pb.Callback != nil: + pb.Callback(out + end) + case !pb.NotPrint: + fmt.Print("\r" + out + end) + } +} + +// GetTerminalWidth - returns terminal width for all platforms. +func GetTerminalWidth() (int, error) { + return terminalWidth() +} + +func (pb *ProgressBar) GetWidth() int { + if pb.ForceWidth { + return pb.Width + } + + width := pb.Width + termWidth, _ := terminalWidth() + if width == 0 || termWidth <= width { + width = termWidth + } + + return width +} + +// Write the current state of the progressbar +func (pb *ProgressBar) Update() { + c := atomic.LoadInt64(&pb.current) + p := atomic.LoadInt64(&pb.previous) + if p != c { + pb.mu.Lock() + pb.changeTime = time.Now() + pb.mu.Unlock() + atomic.StoreInt64(&pb.previous, c) + } + pb.write(c) + if pb.AutoStat { + if c == 0 { + pb.startTime = time.Now() + pb.startValue = 0 + } else if c >= pb.Total && pb.isFinish != true { + pb.Finish() + } + } +} + +// String return the last bar print +func (pb *ProgressBar) String() string { + pb.mu.Lock() + defer pb.mu.Unlock() + return pb.lastPrint +} + +// Internal loop for refreshing the progressbar +func (pb *ProgressBar) refresher() { + for { + select { + case <-pb.finish: + return + case <-time.After(pb.RefreshRate): + pb.Update() + } + } +} diff --git a/vendor/github.com/cheggaaa/pb/pb_win.go b/vendor/github.com/cheggaaa/pb/pb_win.go new file mode 100644 index 000000000..72f682835 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pb_win.go @@ -0,0 +1,141 @@ +// +build windows + +package pb + +import ( + "errors" + "fmt" + "os" + "sync" + "syscall" + "unsafe" +) + +var tty = os.Stdin + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + + // GetConsoleScreenBufferInfo retrieves information about the + // specified console screen buffer. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + + // GetConsoleMode retrieves the current input mode of a console's + // input buffer or the current output mode of a console screen buffer. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx + getConsoleMode = kernel32.NewProc("GetConsoleMode") + + // SetConsoleMode sets the input mode of a console's input buffer + // or the output mode of a console screen buffer. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx + setConsoleMode = kernel32.NewProc("SetConsoleMode") + + // SetConsoleCursorPosition sets the cursor position in the + // specified console screen buffer. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx + setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") +) + +type ( + // Defines the coordinates of the upper left and lower right corners + // of a rectangle. + // See + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx + smallRect struct { + Left, Top, Right, Bottom int16 + } + + // Defines the coordinates of a character cell in a console screen + // buffer. The origin of the coordinate system (0,0) is at the top, left cell + // of the buffer. + // See + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx + coordinates struct { + X, Y int16 + } + + word int16 + + // Contains information about a console screen buffer. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx + consoleScreenBufferInfo struct { + dwSize coordinates + dwCursorPosition coordinates + wAttributes word + srWindow smallRect + dwMaximumWindowSize coordinates + } +) + +// terminalWidth returns width of the terminal. +func terminalWidth() (width int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, error(e) + } + return int(info.dwSize.X) - 1, nil +} + +func getCursorPos() (pos coordinates, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return info.dwCursorPosition, error(e) + } + return info.dwCursorPosition, nil +} + +func setCursorPos(pos coordinates) error { + _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0) + if e != 0 { + return error(e) + } + return nil +} + +var ErrPoolWasStarted = errors.New("Bar pool was started") + +var echoLocked bool +var echoLockMutex sync.Mutex + +var oldState word + +func lockEcho() (quit chan int, err error) { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + if echoLocked { + err = ErrPoolWasStarted + return + } + echoLocked = true + + if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 { + err = fmt.Errorf("Can't get terminal settings: %v", e) + return + } + + newState := oldState + const ENABLE_ECHO_INPUT = 0x0004 + const ENABLE_LINE_INPUT = 0x0002 + newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) + if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 { + err = fmt.Errorf("Can't set terminal settings: %v", e) + return + } + return +} + +func unlockEcho() (err error) { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + if !echoLocked { + return + } + echoLocked = false + if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 { + err = fmt.Errorf("Can't set terminal settings") + } + return +} diff --git a/vendor/github.com/cheggaaa/pb/pb_x.go b/vendor/github.com/cheggaaa/pb/pb_x.go new file mode 100644 index 000000000..bbbe7c2d6 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pb_x.go @@ -0,0 +1,108 @@ +// +build linux darwin freebsd netbsd openbsd solaris dragonfly +// +build !appengine + +package pb + +import ( + "errors" + "fmt" + "os" + "os/signal" + "sync" + "syscall" + + "golang.org/x/sys/unix" +) + +var ErrPoolWasStarted = errors.New("Bar pool was started") + +var ( + echoLockMutex sync.Mutex + origTermStatePtr *unix.Termios + tty *os.File +) + +func init() { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + + var err error + tty, err = os.Open("/dev/tty") + if err != nil { + tty = os.Stdin + } +} + +// terminalWidth returns width of the terminal. +func terminalWidth() (int, error) { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + + fd := int(tty.Fd()) + + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, err + } + + return int(ws.Col), nil +} + +func lockEcho() (quit chan int, err error) { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + if origTermStatePtr != nil { + return quit, ErrPoolWasStarted + } + + fd := int(tty.Fd()) + + oldTermStatePtr, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + if err != nil { + return nil, fmt.Errorf("Can't get terminal settings: %v", err) + } + + oldTermios := *oldTermStatePtr + newTermios := oldTermios + newTermios.Lflag &^= syscall.ECHO + newTermios.Lflag |= syscall.ICANON | syscall.ISIG + newTermios.Iflag |= syscall.ICRNL + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil { + return nil, fmt.Errorf("Can't set terminal settings: %v", err) + } + + quit = make(chan int, 1) + go catchTerminate(quit) + return +} + +func unlockEcho() error { + echoLockMutex.Lock() + defer echoLockMutex.Unlock() + if origTermStatePtr == nil { + return nil + } + + fd := int(tty.Fd()) + + if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil { + return fmt.Errorf("Can't set terminal settings: %v", err) + } + + origTermStatePtr = nil + + return nil +} + +// listen exit signals and restore terminal state +func catchTerminate(quit chan int) { + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL) + defer signal.Stop(sig) + select { + case <-quit: + unlockEcho() + case <-sig: + unlockEcho() + } +} diff --git a/vendor/github.com/cheggaaa/pb/pool.go b/vendor/github.com/cheggaaa/pb/pool.go new file mode 100644 index 000000000..bc1a13886 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pool.go @@ -0,0 +1,82 @@ +// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows + +package pb + +import ( + "io" + "sync" + "time" +) + +// Create and start new pool with given bars +// You need call pool.Stop() after work +func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) { + pool = new(Pool) + if err = pool.start(); err != nil { + return + } + pool.Add(pbs...) + return +} + +type Pool struct { + Output io.Writer + RefreshRate time.Duration + bars []*ProgressBar + lastBarsCount int + quit chan int + m sync.Mutex + finishOnce sync.Once +} + +// Add progress bars. +func (p *Pool) Add(pbs ...*ProgressBar) { + p.m.Lock() + defer p.m.Unlock() + for _, bar := range pbs { + bar.ManualUpdate = true + bar.NotPrint = true + bar.Start() + p.bars = append(p.bars, bar) + } +} + +func (p *Pool) start() (err error) { + p.RefreshRate = DefaultRefreshRate + quit, err := lockEcho() + if err != nil { + return + } + p.quit = make(chan int) + go p.writer(quit) + return +} + +func (p *Pool) writer(finish chan int) { + var first = true + for { + select { + case <-time.After(p.RefreshRate): + if p.print(first) { + p.print(false) + finish <- 1 + return + } + first = false + case <-p.quit: + finish <- 1 + return + } + } +} + +// Restore terminal state and close pool +func (p *Pool) Stop() error { + // Wait until one final refresh has passed. + time.Sleep(p.RefreshRate) + + p.finishOnce.Do(func() { + close(p.quit) + }) + return unlockEcho() +} diff --git a/vendor/github.com/cheggaaa/pb/pool_win.go b/vendor/github.com/cheggaaa/pb/pool_win.go new file mode 100644 index 000000000..63598d378 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pool_win.go @@ -0,0 +1,45 @@ +// +build windows + +package pb + +import ( + "fmt" + "log" +) + +func (p *Pool) print(first bool) bool { + p.m.Lock() + defer p.m.Unlock() + var out string + if !first { + coords, err := getCursorPos() + if err != nil { + log.Panic(err) + } + coords.Y -= int16(p.lastBarsCount) + if coords.Y < 0 { + coords.Y = 0 + } + coords.X = 0 + + err = setCursorPos(coords) + if err != nil { + log.Panic(err) + } + } + isFinished := true + for _, bar := range p.bars { + if !bar.IsFinished() { + isFinished = false + } + bar.Update() + out += fmt.Sprintf("\r%s\n", bar.String()) + } + if p.Output != nil { + fmt.Fprint(p.Output, out) + } else { + fmt.Print(out) + } + p.lastBarsCount = len(p.bars) + return isFinished +} diff --git a/vendor/github.com/cheggaaa/pb/pool_x.go b/vendor/github.com/cheggaaa/pb/pool_x.go new file mode 100644 index 000000000..a8ae14d2f --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/pool_x.go @@ -0,0 +1,29 @@ +// +build linux darwin freebsd netbsd openbsd solaris dragonfly + +package pb + +import "fmt" + +func (p *Pool) print(first bool) bool { + p.m.Lock() + defer p.m.Unlock() + var out string + if !first { + out = fmt.Sprintf("\033[%dA", p.lastBarsCount) + } + isFinished := true + for _, bar := range p.bars { + if !bar.IsFinished() { + isFinished = false + } + bar.Update() + out += fmt.Sprintf("\r%s\n", bar.String()) + } + if p.Output != nil { + fmt.Fprint(p.Output, out) + } else { + fmt.Print(out) + } + p.lastBarsCount = len(p.bars) + return isFinished +} diff --git a/vendor/github.com/cheggaaa/pb/reader.go b/vendor/github.com/cheggaaa/pb/reader.go new file mode 100644 index 000000000..9f3148b54 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/reader.go @@ -0,0 +1,25 @@ +package pb + +import ( + "io" +) + +// It's proxy reader, implement io.Reader +type Reader struct { + io.Reader + bar *ProgressBar +} + +func (r *Reader) Read(p []byte) (n int, err error) { + n, err = r.Reader.Read(p) + r.bar.Add(n) + return +} + +// Close the reader when it implements io.Closer +func (r *Reader) Close() (err error) { + if closer, ok := r.Reader.(io.Closer); ok { + return closer.Close() + } + return +} diff --git a/vendor/github.com/cheggaaa/pb/runecount.go b/vendor/github.com/cheggaaa/pb/runecount.go new file mode 100644 index 000000000..c617c55ec --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/runecount.go @@ -0,0 +1,17 @@ +package pb + +import ( + "github.com/mattn/go-runewidth" + "regexp" +) + +// Finds the control character sequences (like colors) +var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d") + +func escapeAwareRuneCountInString(s string) int { + n := runewidth.StringWidth(s) + for _, sm := range ctrlFinder.FindAllString(s, -1) { + n -= runewidth.StringWidth(sm) + } + return n +} diff --git a/vendor/github.com/cheggaaa/pb/termios_bsd.go b/vendor/github.com/cheggaaa/pb/termios_bsd.go new file mode 100644 index 000000000..517ea8ed7 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/termios_bsd.go @@ -0,0 +1,9 @@ +// +build darwin freebsd netbsd openbsd dragonfly +// +build !appengine + +package pb + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA +const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/cheggaaa/pb/termios_sysv.go b/vendor/github.com/cheggaaa/pb/termios_sysv.go new file mode 100644 index 000000000..b10f61859 --- /dev/null +++ b/vendor/github.com/cheggaaa/pb/termios_sysv.go @@ -0,0 +1,13 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux solaris +// +build !appengine + +package pb + +import "golang.org/x/sys/unix" + +const ioctlReadTermios = unix.TCGETS +const ioctlWriteTermios = unix.TCSETS diff --git a/vendor/vendor.json b/vendor/vendor.json index 8e50f2acf..93f044f93 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -591,6 +591,12 @@ "revision": "528c74964609a58f7c17471525659c9b71cd499b", "revisionTime": "2018-02-10T03:43:46Z" }, + { + "checksumSHA1": "ymc5+iJ+1ipls3ihqPdzMjFYCqo=", + "path": "github.com/cheggaaa/pb", + "revision": "18d384da9bdc1e5a08fc2a62a494c321d9ae74ea", + "revisionTime": "2017-12-14T13:20:59Z" + }, { "checksumSHA1": "/5cvgU+J4l7EhMXTK76KaCAfOuU=", "comment": "v1.0.0-3-g6d21280", From ac27e54c959c457d55a5785d670fd5c647f17e20 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Fri, 27 Apr 2018 01:20:56 -0500 Subject: [PATCH 2/7] Updated vendor package github.com/cheggaaa/pb to v1.0.22 --- vendor/github.com/cheggaaa/pb/pb.go | 36 +++++++++++-------- vendor/github.com/cheggaaa/pb/pb_win.go | 2 +- vendor/github.com/cheggaaa/pb/pb_x.go | 16 ++++----- vendor/github.com/cheggaaa/pb/pool.go | 48 +++++++++++++++++-------- vendor/vendor.json | 14 ++++---- 5 files changed, 72 insertions(+), 44 deletions(-) diff --git a/vendor/github.com/cheggaaa/pb/pb.go b/vendor/github.com/cheggaaa/pb/pb.go index 19eb4d1a9..eddc807b1 100644 --- a/vendor/github.com/cheggaaa/pb/pb.go +++ b/vendor/github.com/cheggaaa/pb/pb.go @@ -13,7 +13,7 @@ import ( ) // Current version -const Version = "1.0.19" +const Version = "1.0.22" const ( // Default refresh rate - 200ms @@ -37,16 +37,17 @@ func New(total int) *ProgressBar { // Create new progress bar object using int64 as total func New64(total int64) *ProgressBar { pb := &ProgressBar{ - Total: total, - RefreshRate: DEFAULT_REFRESH_RATE, - ShowPercent: true, - ShowCounters: true, - ShowBar: true, - ShowTimeLeft: true, - ShowFinalTime: true, - Units: U_NO, - ManualUpdate: false, - finish: make(chan struct{}), + Total: total, + RefreshRate: DEFAULT_REFRESH_RATE, + ShowPercent: true, + ShowCounters: true, + ShowBar: true, + ShowTimeLeft: true, + ShowElapsedTime: false, + ShowFinalTime: true, + Units: U_NO, + ManualUpdate: false, + finish: make(chan struct{}), } return pb.Format(FORMAT) } @@ -72,7 +73,7 @@ type ProgressBar struct { RefreshRate time.Duration ShowPercent, ShowCounters bool ShowSpeed, ShowTimeLeft, ShowBar bool - ShowFinalTime bool + ShowFinalTime, ShowElapsedTime bool Output io.Writer Callback Callback NotPrint bool @@ -274,7 +275,7 @@ func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { func (pb *ProgressBar) write(current int64) { width := pb.GetWidth() - var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string + var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string // percents if pb.ShowPercent { @@ -305,6 +306,11 @@ func (pb *ProgressBar) write(current int64) { lastChangeTime := pb.changeTime fromChange := lastChangeTime.Sub(pb.startTime) pb.mu.Unlock() + + if pb.ShowElapsedTime { + timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second) + } + select { case <-pb.finish: if pb.ShowFinalTime { @@ -342,7 +348,7 @@ func (pb *ProgressBar) write(current int64) { speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() } - barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) + barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) // bar if pb.ShowBar { size := width - barWidth @@ -387,7 +393,7 @@ func (pb *ProgressBar) write(current int64) { } // check len - out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix + out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix if cl := escapeAwareRuneCountInString(out); cl < width { end = strings.Repeat(" ", width-cl) } diff --git a/vendor/github.com/cheggaaa/pb/pb_win.go b/vendor/github.com/cheggaaa/pb/pb_win.go index 72f682835..2c67e1947 100644 --- a/vendor/github.com/cheggaaa/pb/pb_win.go +++ b/vendor/github.com/cheggaaa/pb/pb_win.go @@ -102,7 +102,7 @@ var echoLockMutex sync.Mutex var oldState word -func lockEcho() (quit chan int, err error) { +func lockEcho() (shutdownCh chan struct{}, err error) { echoLockMutex.Lock() defer echoLockMutex.Unlock() if echoLocked { diff --git a/vendor/github.com/cheggaaa/pb/pb_x.go b/vendor/github.com/cheggaaa/pb/pb_x.go index bbbe7c2d6..8e05770ce 100644 --- a/vendor/github.com/cheggaaa/pb/pb_x.go +++ b/vendor/github.com/cheggaaa/pb/pb_x.go @@ -48,21 +48,21 @@ func terminalWidth() (int, error) { return int(ws.Col), nil } -func lockEcho() (quit chan int, err error) { +func lockEcho() (shutdownCh chan struct{}, err error) { echoLockMutex.Lock() defer echoLockMutex.Unlock() if origTermStatePtr != nil { - return quit, ErrPoolWasStarted + return shutdownCh, ErrPoolWasStarted } fd := int(tty.Fd()) - oldTermStatePtr, err := unix.IoctlGetTermios(fd, ioctlReadTermios) + origTermStatePtr, err = unix.IoctlGetTermios(fd, ioctlReadTermios) if err != nil { return nil, fmt.Errorf("Can't get terminal settings: %v", err) } - oldTermios := *oldTermStatePtr + oldTermios := *origTermStatePtr newTermios := oldTermios newTermios.Lflag &^= syscall.ECHO newTermios.Lflag |= syscall.ICANON | syscall.ISIG @@ -71,8 +71,8 @@ func lockEcho() (quit chan int, err error) { return nil, fmt.Errorf("Can't set terminal settings: %v", err) } - quit = make(chan int, 1) - go catchTerminate(quit) + shutdownCh = make(chan struct{}) + go catchTerminate(shutdownCh) return } @@ -95,12 +95,12 @@ func unlockEcho() error { } // listen exit signals and restore terminal state -func catchTerminate(quit chan int) { +func catchTerminate(shutdownCh chan struct{}) { sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL) defer signal.Stop(sig) select { - case <-quit: + case <-shutdownCh: unlockEcho() case <-sig: unlockEcho() diff --git a/vendor/github.com/cheggaaa/pb/pool.go b/vendor/github.com/cheggaaa/pb/pool.go index bc1a13886..f44baa01f 100644 --- a/vendor/github.com/cheggaaa/pb/pool.go +++ b/vendor/github.com/cheggaaa/pb/pool.go @@ -12,19 +12,28 @@ import ( // You need call pool.Stop() after work func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) { pool = new(Pool) - if err = pool.start(); err != nil { + if err = pool.Start(); err != nil { return } pool.Add(pbs...) return } +// NewPool initialises a pool with progress bars, but +// doesn't start it. You need to call Start manually +func NewPool(pbs ...*ProgressBar) (pool *Pool) { + pool = new(Pool) + pool.Add(pbs...) + return +} + type Pool struct { Output io.Writer RefreshRate time.Duration bars []*ProgressBar lastBarsCount int - quit chan int + shutdownCh chan struct{} + workerCh chan struct{} m sync.Mutex finishOnce sync.Once } @@ -41,30 +50,38 @@ func (p *Pool) Add(pbs ...*ProgressBar) { } } -func (p *Pool) start() (err error) { +func (p *Pool) Start() (err error) { p.RefreshRate = DefaultRefreshRate - quit, err := lockEcho() + p.shutdownCh, err = lockEcho() if err != nil { return } - p.quit = make(chan int) - go p.writer(quit) + p.workerCh = make(chan struct{}) + go p.writer() return } -func (p *Pool) writer(finish chan int) { +func (p *Pool) writer() { var first = true + defer func() { + if first == false { + p.print(false) + } else { + p.print(true) + p.print(false) + } + close(p.workerCh) + }() + for { select { case <-time.After(p.RefreshRate): if p.print(first) { p.print(false) - finish <- 1 return } first = false - case <-p.quit: - finish <- 1 + case <-p.shutdownCh: return } } @@ -72,11 +89,14 @@ func (p *Pool) writer(finish chan int) { // Restore terminal state and close pool func (p *Pool) Stop() error { - // Wait until one final refresh has passed. - time.Sleep(p.RefreshRate) - p.finishOnce.Do(func() { - close(p.quit) + close(p.shutdownCh) }) + + // Wait for the worker to complete + select { + case <-p.workerCh: + } + return unlockEcho() } diff --git a/vendor/vendor.json b/vendor/vendor.json index 93f044f93..afdefca3c 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -585,18 +585,20 @@ "path": "github.com/biogo/hts/bgzf", "revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0" }, + { + "checksumSHA1": "A/OK8uWTeJYUL6XXFwOLWw1IZLQ=", + "path": "github.com/cheggaaa/pb", + "revision": "72b964305fba1230d3d818711138195f22b9ceea", + "revisionTime": "2018-02-18T15:37:33Z", + "version": "v1.0.22", + "versionExact": "v1.0.22" + }, { "checksumSHA1": "X2/71FBrn4pA3WcA620ySVO0uHU=", "path": "github.com/creack/goselect", "revision": "528c74964609a58f7c17471525659c9b71cd499b", "revisionTime": "2018-02-10T03:43:46Z" }, - { - "checksumSHA1": "ymc5+iJ+1ipls3ihqPdzMjFYCqo=", - "path": "github.com/cheggaaa/pb", - "revision": "18d384da9bdc1e5a08fc2a62a494c321d9ae74ea", - "revisionTime": "2017-12-14T13:20:59Z" - }, { "checksumSHA1": "/5cvgU+J4l7EhMXTK76KaCAfOuU=", "comment": "v1.0.0-3-g6d21280", From fd2fef87386228aa9b6541c99dd6332a2d5511e7 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Tue, 7 Aug 2018 15:31:52 -0500 Subject: [PATCH 3/7] 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". --- common/progress.go | 45 ++++++++++++++- common/progress_posix.go | 32 +++++++++++ common/progress_windows.go | 82 ++++++++++++++++++++++++++++ common/step_download.go | 9 +-- packer/rpc/ui.go | 13 +++++ packer/rpc/ui_test.go | 38 +++++++++---- packer/ui.go | 19 +++++++ provisioner/ansible-local/ui_stub.go | 3 + provisioner/ansible/adapter_test.go | 4 ++ provisioner/ansible/provisioner.go | 4 ++ provisioner/file/provisioner.go | 25 +++++++-- provisioner/file/provisioner_test.go | 4 ++ 12 files changed, 255 insertions(+), 23 deletions(-) create mode 100644 common/progress_posix.go create mode 100644 common/progress_windows.go diff --git a/common/progress.go b/common/progress.go index c90810c9d..476eff55d 100644 --- a/common/progress.go +++ b/common/progress.go @@ -1,9 +1,15 @@ package common -import "github.com/cheggaaa/pb" +import ( + "fmt" + "github.com/cheggaaa/pb" + "github.com/hashicorp/packer/packer" + "log" + "time" +) // Default progress bar appearance -func GetDefaultProgressBar() pb.ProgressBar { +func GetNewProgressBar(ui *packer.Ui) pb.ProgressBar { bar := pb.New64(0) bar.ShowPercent = true bar.ShowCounters = true @@ -16,5 +22,40 @@ func GetDefaultProgressBar() pb.ProgressBar { bar.SetRefreshRate(1 * time.Second) 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 } diff --git a/common/progress_posix.go b/common/progress_posix.go new file mode 100644 index 000000000..317307635 --- /dev/null +++ b/common/progress_posix.go @@ -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 +} diff --git a/common/progress_windows.go b/common/progress_windows.go new file mode 100644 index 000000000..676ab538d --- /dev/null +++ b/common/progress_windows.go @@ -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 +} diff --git a/common/step_download.go b/common/step_download.go index c920cf5d2..efd4607a7 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -8,7 +8,6 @@ import ( "log" "time" - "github.com/cheggaaa/pb" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/useragent" "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)) // Get a default-looking progress bar and connect it to the ui. - bar := GetDefaultProgressBar() - bar.Callback = ui.Message + bar := GetNewProgressBar(&ui) // First try to use any already downloaded file // If it fails, proceed to regular download logic @@ -145,9 +143,8 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag var path string ui := state.Get("ui").(packer.Ui) - // Get a default looking progress bar and connect it to the ui. - bar := GetDefaultProgressBar() - bar.Callback = ui.Message + // Get a default-looking progress bar and connect it to the ui. + bar := GetNewProgressBar(&ui) // Create download client with config and progress bar download := NewDownloadClient(config, bar) diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go index 1c6356a65..19553cc30 100644 --- a/packer/rpc/ui.go +++ b/packer/rpc/ui.go @@ -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) { *reply, err = u.ui.Ask(query) return @@ -91,3 +98,9 @@ func (u *UiServer) Say(message *string, reply *interface{}) error { *reply = nil return nil } + +func (u *UiServer) GetMinimumLength(noargs *interface{}, reply *int) error { + res := u.ui.GetMinimumLength() + *reply = res + return nil +} diff --git a/packer/rpc/ui_test.go b/packer/rpc/ui_test.go index 4dd2d7036..42f9d6ae0 100644 --- a/packer/rpc/ui_test.go +++ b/packer/rpc/ui_test.go @@ -6,17 +6,19 @@ import ( ) type testUi struct { - askCalled bool - askQuery string - errorCalled bool - errorMessage string - machineCalled bool - machineType string - machineArgs []string - messageCalled bool - messageMessage string - sayCalled bool - sayMessage string + askCalled bool + askQuery string + errorCalled bool + errorMessage string + machineCalled bool + machineType string + machineArgs []string + messageCalled bool + messageMessage string + sayCalled bool + sayMessage string + getMinimumLengthCalled bool + getMinimumLengthValue int } func (u *testUi) Ask(query string) (string, error) { @@ -46,6 +48,12 @@ func (u *testUi) Say(message string) { u.sayMessage = message } +func (u *testUi) GetMinimumLength() int { + u.getMinimumLengthCalled = true + u.getMinimumLengthValue = -1 + return u.getMinimumLengthValue +} + func TestUiRPC(t *testing.T) { // Create the UI to test ui := new(testUi) @@ -93,6 +101,14 @@ func TestUiRPC(t *testing.T) { 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" { t.Fatalf("bad type: %#v", ui.machineType) } diff --git a/packer/ui.go b/packer/ui.go index c16a2eae0..73b07ea0e 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -37,6 +37,7 @@ type Ui interface { Message(string) Error(string) Machine(string, ...string) + GetMinimumLength() int } // ColoredUi is a UI that is colored using terminal colors. @@ -132,6 +133,10 @@ func (u *ColoredUi) supportsColors() bool { return cygwin } +func (u *ColoredUi) GetMinimumLength() int { + return u.Ui.GetMinimumLength() +} + func (u *TargetedUI) Ask(query string) (string, error) { 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) } +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) { rw.l.Lock() defer rw.l.Unlock() @@ -260,6 +271,10 @@ func (rw *BasicUi) Machine(t string, args ...string) { log.Printf("machine readable: %s %#v", t, args) } +func (u *BasicUi) GetMinimumLength() int { + return 0 +} + func (u *MachineReadableUi) Ask(query string) (string, error) { 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 +} diff --git a/provisioner/ansible-local/ui_stub.go b/provisioner/ansible-local/ui_stub.go index 4faa2a215..86d3cba86 100644 --- a/provisioner/ansible-local/ui_stub.go +++ b/provisioner/ansible-local/ui_stub.go @@ -13,3 +13,6 @@ func (su *uiStub) Machine(string, ...string) {} func (su *uiStub) Message(string) {} func (su *uiStub) Say(msg string) {} +func (su *uiStub) GetMinimumLength() int { + return -1 +} diff --git a/provisioner/ansible/adapter_test.go b/provisioner/ansible/adapter_test.go index 2ab2d350b..f8be403f5 100644 --- a/provisioner/ansible/adapter_test.go +++ b/provisioner/ansible/adapter_test.go @@ -123,6 +123,10 @@ func (u *ui) Machine(s1 string, s2 ...string) { } } +func (u *ui) GetMinimumLength() int { + return -1 +} + type communicator struct{} func (c communicator) Start(*packer.RemoteCmd) error { diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 99c4d07d9..7b32e81ed 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -599,3 +599,7 @@ func (ui *Ui) Machine(t string, args ...string) { ui.ui.Machine(t, args...) <-ui.sem } + +func (ui *Ui) GetMinimumLength() int { + return -1 +} diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index a0e8d2465..d734eaf06 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -3,6 +3,7 @@ package file import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -125,8 +126,16 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator) } defer f.Close() - err = comm.Download(src, f) - if err != nil { + // Get a default progress bar + 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)) return err } @@ -166,8 +175,16 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er dst = filepath.Join(dst, filepath.Base(src)) } - err = comm.Upload(dst, f, &fi) - if err != nil { + // Get a default progress bar + 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)) return err } diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index 175082aae..6c749f1e5 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -120,6 +120,10 @@ func (su *stubUi) Say(msg string) { su.sayMessages += msg } +func (su *stubUi) GetMinimumLength() int { + return -1 +} + func TestProvisionerProvision_SendsFile(t *testing.T) { var p Provisioner tf, err := ioutil.TempFile("", "packer") From 0f10032b3d64c040910156621667a6abcdd8b787 Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Fri, 10 Aug 2018 15:08:28 -0500 Subject: [PATCH 4/7] Moved the progress bar from common to the packer.UI interface and refactored it so that the terminal width is calculated based on each interface which returns a custom progressbar specific to its ui. --- common/config.go | 5 +- common/download.go | 42 +++++--- common/progress.go | 61 ------------ common/step_download.go | 8 +- packer/rpc/ui.go | 8 +- packer/rpc/ui_test.go | 54 ++++++----- packer/ui.go | 95 ++++++++++++++++--- .../progress_posix.go => packer/ui_posix.go | 2 +- .../ui_windows.go | 2 +- provisioner/ansible-local/ui_stub.go | 6 +- provisioner/ansible/adapter_test.go | 4 +- provisioner/ansible/provisioner.go | 4 +- provisioner/file/provisioner.go | 4 +- provisioner/file/provisioner_test.go | 4 +- 14 files changed, 167 insertions(+), 132 deletions(-) delete mode 100644 common/progress.go rename common/progress_posix.go => packer/ui_posix.go (97%) rename common/progress_windows.go => packer/ui_windows.go (99%) diff --git a/common/config.go b/common/config.go index 1487ea292..1826e3a10 100644 --- a/common/config.go +++ b/common/config.go @@ -2,7 +2,6 @@ package common import ( "fmt" - "github.com/cheggaaa/pb" "net/url" "os" "path/filepath" @@ -55,7 +54,7 @@ func SupportedProtocol(u *url.URL) bool { // build a dummy NewDownloadClient since this is the only place that valid // protocols are actually exposed. - cli := NewDownloadClient(&DownloadConfig{}, *pb.New(0)) + cli := NewDownloadClient(&DownloadConfig{}, nil) // Iterate through each downloader to see if a protocol was found. ok := false @@ -187,7 +186,7 @@ func FileExistsLocally(original string) bool { // First create a dummy downloader so we can figure out which // protocol to use. - cli := NewDownloadClient(&DownloadConfig{}, *pb.New(0)) + cli := NewDownloadClient(&DownloadConfig{}, nil) d, ok := cli.config.DownloaderMap[u.Scheme] if !ok { return false diff --git a/common/download.go b/common/download.go index 62fcc908e..b80741f3f 100644 --- a/common/download.go +++ b/common/download.go @@ -19,7 +19,9 @@ import ( ) // required import for progress-bar -import "github.com/cheggaaa/pb" +import ( + "github.com/hashicorp/packer/packer" +) // imports related to each Downloader implementation import ( @@ -84,16 +86,21 @@ func HashForType(t string) hash.Hash { // NewDownloadClient returns a new DownloadClient for the given // configuration. -func NewDownloadClient(c *DownloadConfig, bar pb.ProgressBar) *DownloadClient { +func NewDownloadClient(c *DownloadConfig, bar packer.ProgressBar) *DownloadClient { const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */ + // If bar is nil, then use a dummy progress bar that doesn't do anything + if bar == nil { + bar = packer.GetDummyProgressBar() + } + // Create downloader map if it hasn't been specified already. if c.DownloaderMap == nil { c.DownloaderMap = map[string]Downloader{ - "file": &FileDownloader{progress: &bar, bufferSize: nil}, - "http": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent}, - "https": &HTTPDownloader{progress: &bar, userAgent: c.UserAgent}, - "smb": &SMBDownloader{progress: &bar, bufferSize: nil}, + "file": &FileDownloader{progress: bar, bufferSize: nil}, + "http": &HTTPDownloader{progress: bar, userAgent: c.UserAgent}, + "https": &HTTPDownloader{progress: bar, userAgent: c.UserAgent}, + "smb": &SMBDownloader{progress: bar, bufferSize: nil}, } } return &DownloadClient{config: c} @@ -235,7 +242,7 @@ type HTTPDownloader struct { total uint64 userAgent string - progress *pb.ProgressBar + progress packer.ProgressBar } func (d *HTTPDownloader) Cancel() { @@ -328,8 +335,9 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { d.total = d.current + uint64(resp.ContentLength) - d.progress.Total = int64(d.total) - progressBar := d.progress.Start() + bar := d.progress + bar.Total = int64(d.total) + progressBar := bar.Start() progressBar.Set64(int64(d.current)) var buffer [4096]byte @@ -371,7 +379,7 @@ type FileDownloader struct { current uint64 total uint64 - progress *pb.ProgressBar + progress packer.ProgressBar } func (d *FileDownloader) Progress() uint64 { @@ -462,8 +470,10 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { } d.total = uint64(fi.Size()) - d.progress.Total = int64(d.total) - progressBar := d.progress.Start() + bar := d.progress + bar.Total = int64(d.total) + progressBar := bar.Start() + progressBar.Set64(int64(d.current)) // no bufferSize specified, so copy synchronously. if d.bufferSize == nil { @@ -508,7 +518,7 @@ type SMBDownloader struct { current uint64 total uint64 - progress *pb.ProgressBar + progress packer.ProgressBar } func (d *SMBDownloader) Progress() uint64 { @@ -581,8 +591,10 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { } d.total = uint64(fi.Size()) - d.progress.Total = int64(d.total) - progressBar := d.progress.Start() + bar := d.progress + bar.Total = int64(d.total) + progressBar := bar.Start() + progressBar.Set64(int64(d.current)) // no bufferSize specified, so copy synchronously. if d.bufferSize == nil { diff --git a/common/progress.go b/common/progress.go deleted file mode 100644 index 476eff55d..000000000 --- a/common/progress.go +++ /dev/null @@ -1,61 +0,0 @@ -package common - -import ( - "fmt" - "github.com/cheggaaa/pb" - "github.com/hashicorp/packer/packer" - "log" - "time" -) - -// Default progress bar appearance -func GetNewProgressBar(ui *packer.Ui) pb.ProgressBar { - bar := pb.New64(0) - bar.ShowPercent = true - bar.ShowCounters = true - bar.ShowSpeed = false - bar.ShowBar = true - bar.ShowTimeLeft = false - bar.ShowFinalTime = false - bar.SetUnits(pb.U_BYTES) - bar.Format("[=>-]") - bar.SetRefreshRate(1 * time.Second) - 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 -} diff --git a/common/step_download.go b/common/step_download.go index efd4607a7..e8520091d 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -63,8 +63,8 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) - // Get a default-looking progress bar and connect it to the ui. - bar := GetNewProgressBar(&ui) + // Get a progress bar from the ui so we can hand it off to the download client + bar := ui.GetProgressBar() // First try to use any already downloaded file // If it fails, proceed to regular download logic @@ -143,8 +143,8 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag var path string ui := state.Get("ui").(packer.Ui) - // Get a default-looking progress bar and connect it to the ui. - bar := GetNewProgressBar(&ui) + // Get a progress bar and hand it off to the download client + bar := ui.GetProgressBar() // Create download client with config and progress bar download := NewDownloadClient(config, bar) diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go index 19553cc30..80508be82 100644 --- a/packer/rpc/ui.go +++ b/packer/rpc/ui.go @@ -60,8 +60,8 @@ func (u *Ui) Say(message string) { } } -func (u *Ui) GetMinimumLength() (result int) { - if err := u.client.Call("Ui.GetMinimumLength", nil, &result); err != nil { +func (u *Ui) GetProgressBar() (result packer.ProgressBar) { + if err := u.client.Call("Ui.GetProgressBar", nil, &result); err != nil { log.Printf("Error in Ui RPC call: %s", err) } return @@ -99,8 +99,8 @@ func (u *UiServer) Say(message *string, reply *interface{}) error { return nil } -func (u *UiServer) GetMinimumLength(noargs *interface{}, reply *int) error { - res := u.ui.GetMinimumLength() +func (u *UiServer) GetProgressBar(noargs *interface{}, reply *packer.ProgressBar) error { + res := u.ui.GetProgressBar() *reply = res return nil } diff --git a/packer/rpc/ui_test.go b/packer/rpc/ui_test.go index 42f9d6ae0..eef4be13c 100644 --- a/packer/rpc/ui_test.go +++ b/packer/rpc/ui_test.go @@ -1,24 +1,26 @@ package rpc import ( + "github.com/hashicorp/packer/packer" "reflect" "testing" ) type testUi struct { - askCalled bool - askQuery string - errorCalled bool - errorMessage string - machineCalled bool - machineType string - machineArgs []string - messageCalled bool - messageMessage string - sayCalled bool - sayMessage string - getMinimumLengthCalled bool - getMinimumLengthValue int + askCalled bool + askQuery string + errorCalled bool + errorMessage string + machineCalled bool + machineType string + machineArgs []string + messageCalled bool + messageMessage string + sayCalled bool + sayMessage string + getProgressBarCalled bool + getProgressBarValue packer.ProgressBar + progessBarCallbackWasCalled bool } func (u *testUi) Ask(query string) (string, error) { @@ -48,10 +50,13 @@ func (u *testUi) Say(message string) { u.sayMessage = message } -func (u *testUi) GetMinimumLength() int { - u.getMinimumLengthCalled = true - u.getMinimumLengthValue = -1 - return u.getMinimumLengthValue +func (u *testUi) GetProgressBar() packer.ProgressBar { + u.getProgressBarCalled = true + u.getProgressBarValue = packer.GetDummyProgressBar() + u.getProgressBarValue.Callback = func(string) { + u.progessBarCallbackWasCalled = true + } + return u.getProgressBarValue } func TestUiRPC(t *testing.T) { @@ -101,12 +106,17 @@ func TestUiRPC(t *testing.T) { t.Fatal("machine should be called") } - uiClient.GetMinimumLength() - if !ui.getMinimumLengthCalled { - t.Fatal("getminimumlength should be called") + bar := uiClient.GetProgressBar() + if !ui.getProgressBarCalled { + t.Fatal("getprogressbar should be called") } - if ui.getMinimumLengthValue != -1 { - t.Fatal("getminimumlength should be -1") + + if bar.Callback == nil { + t.Fatal("getprogressbar returned a bar with an empty callback") + } + bar.Callback("test") + if !ui.progessBarCallbackWasCalled { + t.Fatal("progressbarcallback should be called") } if ui.machineType != "foo" { diff --git a/packer/ui.go b/packer/ui.go index 73b07ea0e..cd88a1eab 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -15,6 +15,8 @@ import ( "syscall" "time" "unicode" + + "github.com/cheggaaa/pb" ) type UiColor uint @@ -28,6 +30,15 @@ const ( UiColorCyan = 36 ) +// The ProgressBar interface is used for abstracting cheggaaa's progress- +// bar, or any other progress bar. If a UI does not support a progress- +// bar, then it must return a null progress bar. +const ( + DefaultProgressBarWidth = 80 +) + +type ProgressBar = *pb.ProgressBar + // The Ui interface handles all communication for Packer with the outside // world. This sort of control allows us to strictly control how output // is formatted and various levels of output. @@ -37,7 +48,7 @@ type Ui interface { Message(string) Error(string) Machine(string, ...string) - GetMinimumLength() int + GetProgressBar() ProgressBar } // ColoredUi is a UI that is colored using terminal colors. @@ -133,8 +144,9 @@ func (u *ColoredUi) supportsColors() bool { return cygwin } -func (u *ColoredUi) GetMinimumLength() int { - return u.Ui.GetMinimumLength() +func (u *ColoredUi) GetProgressBar() ProgressBar { + log.Printf("ColoredUi: Using wrapped UI for progress bar.\n") + return u.Ui.GetProgressBar() } func (u *TargetedUI) Ask(query string) (string, error) { @@ -173,10 +185,9 @@ func (u *TargetedUI) prefixLines(arrow bool, message string) string { 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 (u *TargetedUI) GetProgressBar() ProgressBar { + log.Printf("TargetedUI: Using wrapped UI for progress bar.\n") + return u.Ui.GetProgressBar() } func (rw *BasicUi) Ask(query string) (string, error) { @@ -271,8 +282,21 @@ func (rw *BasicUi) Machine(t string, args ...string) { log.Printf("machine readable: %s %#v", t, args) } -func (u *BasicUi) GetMinimumLength() int { - return 0 +func (rw *BasicUi) GetProgressBar() ProgressBar { + width := calculateProgressBarWidth(0) + + log.Printf("BasicUi: Using progress bar width: %d\n", width) + + bar := GetDefaultProgressBar() + bar.SetWidth(width) + bar.Callback = func(message string) { + rw.l.Lock() + defer rw.l.Unlock() + + // Discard the error here so we don't emit an error everytime the progress-bar fails to update + fmt.Fprint(rw.Writer, message) + } + return bar } func (u *MachineReadableUi) Ask(query string) (string, error) { @@ -321,6 +345,55 @@ func (u *MachineReadableUi) Machine(category string, args ...string) { } } -func (u *MachineReadableUi) GetMinimumLength() int { - return -1 +func (u *MachineReadableUi) GetProgressBar() ProgressBar { + log.Printf("MachineReadableUi: Using dummy progress bar.\n") + bar := GetDummyProgressBar() + return bar +} + +// Return a dummy progress bar that doesn't do anything +func GetDummyProgressBar() *pb.ProgressBar { + return pb.New64(0) +} + +// Get a progress bar with the default appearance +func GetDefaultProgressBar() *pb.ProgressBar { + bar := pb.New64(0) + bar.ShowPercent = true + bar.ShowCounters = true + bar.ShowSpeed = false + bar.ShowBar = true + bar.ShowTimeLeft = false + bar.ShowFinalTime = false + bar.SetUnits(pb.U_BYTES) + bar.Format("[=>-]") + bar.SetRefreshRate(1 * time.Second) + return bar +} + +// Figure out the terminal dimensions and use it to calculate the available rendering space +func calculateProgressBarWidth(length int) int { + // If the UI's width is signed, then this is an interface that doesn't really benefit from a progress bar + if length < 0 { + log.Println("Refusing to render progress-bar for unsupported UI.") + return length + } + + // 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", DefaultProgressBarWidth, newerr) + return DefaultProgressBarWidth + } + + // If the terminal width is smaller than the requested length, then complain + if width < length { + newerr := fmt.Errorf("Terminal width (%d) is smaller than UI message width (%d).", width, length) + log.Printf("Using default width (%d) for progress-bar due to error: %s", DefaultProgressBarWidth, newerr) + return DefaultProgressBarWidth + } + + // Otherwise subtract the minimum length and return it + return width - length } diff --git a/common/progress_posix.go b/packer/ui_posix.go similarity index 97% rename from common/progress_posix.go rename to packer/ui_posix.go index 317307635..27c94e8ee 100644 --- a/common/progress_posix.go +++ b/packer/ui_posix.go @@ -1,6 +1,6 @@ // +build !windows -package common +package packer // Imports for determining terminal information across platforms import ( diff --git a/common/progress_windows.go b/packer/ui_windows.go similarity index 99% rename from common/progress_windows.go rename to packer/ui_windows.go index 676ab538d..ce99c3a35 100644 --- a/common/progress_windows.go +++ b/packer/ui_windows.go @@ -1,6 +1,6 @@ // +build windows -package common +package packer import ( "syscall" diff --git a/provisioner/ansible-local/ui_stub.go b/provisioner/ansible-local/ui_stub.go index 86d3cba86..bc2e40628 100644 --- a/provisioner/ansible-local/ui_stub.go +++ b/provisioner/ansible-local/ui_stub.go @@ -1,5 +1,7 @@ package ansiblelocal +import "github.com/hashicorp/packer/packer" + type uiStub struct{} func (su *uiStub) Ask(string) (string, error) { @@ -13,6 +15,6 @@ func (su *uiStub) Machine(string, ...string) {} func (su *uiStub) Message(string) {} func (su *uiStub) Say(msg string) {} -func (su *uiStub) GetMinimumLength() int { - return -1 +func (su *uiStub) GetProcessBar() packer.ProgressBar { + return packer.GetDummyProgressBar() } diff --git a/provisioner/ansible/adapter_test.go b/provisioner/ansible/adapter_test.go index f8be403f5..92e669a97 100644 --- a/provisioner/ansible/adapter_test.go +++ b/provisioner/ansible/adapter_test.go @@ -123,8 +123,8 @@ func (u *ui) Machine(s1 string, s2 ...string) { } } -func (u *ui) GetMinimumLength() int { - return -1 +func (u *ui) GetProgressBar() packer.ProgressBar { + return packer.GetDummyProgressBar() } type communicator struct{} diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 7b32e81ed..9cea3e435 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -600,6 +600,6 @@ func (ui *Ui) Machine(t string, args ...string) { <-ui.sem } -func (ui *Ui) GetMinimumLength() int { - return -1 +func (ui *Ui) GetProgressBar() packer.ProgressBar { + return packer.GetDummyProgressBar() } diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index d734eaf06..616625a25 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -127,7 +127,7 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator) defer f.Close() // Get a default progress bar - pb := common.GetNewProgressBar(&ui) + pb := ui.GetProgressBar() bar := pb.Start() defer bar.Finish() @@ -176,7 +176,7 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er } // Get a default progress bar - pb := common.GetNewProgressBar(&ui) + pb := ui.GetProgressBar() bar := pb.Start() defer bar.Finish() diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index 6c749f1e5..b804f0e32 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -120,8 +120,8 @@ func (su *stubUi) Say(msg string) { su.sayMessages += msg } -func (su *stubUi) GetMinimumLength() int { - return -1 +func (su *stubUi) GetProgressBar() packer.ProgressBar { + return packer.GetDummyProgressBar() } func TestProvisionerProvision_SendsFile(t *testing.T) { From dc2088318eef4779018e7a4d03f65cf98c2bc95f Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sat, 18 Aug 2018 22:46:40 -0500 Subject: [PATCH 5/7] Updated vendor package github.com/cheggaaa/pb to v1.0.25. --- vendor/github.com/cheggaaa/pb/README.md | 6 +-- vendor/github.com/cheggaaa/pb/format.go | 9 +++- vendor/github.com/cheggaaa/pb/pb.go | 57 ++++++++++++++++++------- vendor/github.com/cheggaaa/pb/pb_win.go | 2 + vendor/vendor.json | 10 ++--- 5 files changed, 59 insertions(+), 25 deletions(-) diff --git a/vendor/github.com/cheggaaa/pb/README.md b/vendor/github.com/cheggaaa/pb/README.md index 948545110..cdc6c9a49 100644 --- a/vendor/github.com/cheggaaa/pb/README.md +++ b/vendor/github.com/cheggaaa/pb/README.md @@ -173,7 +173,7 @@ The result will be as follows: ``` $ go run example/multiple.go -First 141 / 1000 [===============>---------------------------------------] 14.10 % 44s -Second 139 / 1000 [==============>---------------------------------------] 13.90 % 44s -Third 152 / 1000 [================>--------------------------------------] 15.20 % 40s +First 34 / 200 [=========>---------------------------------------------] 17.00% 00m08s +Second 42 / 200 [===========>------------------------------------------] 21.00% 00m06s +Third 36 / 200 [=========>---------------------------------------------] 18.00% 00m08s ``` diff --git a/vendor/github.com/cheggaaa/pb/format.go b/vendor/github.com/cheggaaa/pb/format.go index 0723561c2..8bb8a7a1d 100644 --- a/vendor/github.com/cheggaaa/pb/format.go +++ b/vendor/github.com/cheggaaa/pb/format.go @@ -113,6 +113,13 @@ func formatDuration(n int64) (result string) { result = fmt.Sprintf("%dd", d/24/time.Hour) d -= (d / time.Hour / 24) * (time.Hour * 24) } - result = fmt.Sprintf("%s%v", result, d) + if d > time.Hour { + result = fmt.Sprintf("%s%dh", result, d/time.Hour) + d -= d / time.Hour * time.Hour + } + m := d / time.Minute + d -= m * time.Minute + s := d / time.Second + result = fmt.Sprintf("%s%02dm%02ds", result, m, s) return } diff --git a/vendor/github.com/cheggaaa/pb/pb.go b/vendor/github.com/cheggaaa/pb/pb.go index eddc807b1..8289ab0fb 100644 --- a/vendor/github.com/cheggaaa/pb/pb.go +++ b/vendor/github.com/cheggaaa/pb/pb.go @@ -13,7 +13,7 @@ import ( ) // Current version -const Version = "1.0.22" +const Version = "1.0.25" const ( // Default refresh rate - 200ms @@ -114,7 +114,7 @@ type ProgressBar struct { func (pb *ProgressBar) Start() *ProgressBar { pb.startTime = time.Now() pb.startValue = atomic.LoadInt64(&pb.current) - if pb.Total == 0 { + if atomic.LoadInt64(&pb.Total) == 0 { pb.ShowTimeLeft = false pb.ShowPercent = false pb.AutoStat = false @@ -222,7 +222,7 @@ func (pb *ProgressBar) Finish() { //Protect multiple calls pb.finishOnce.Do(func() { close(pb.finish) - pb.write(atomic.LoadInt64(&pb.current)) + pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current)) pb.mu.Lock() defer pb.mu.Unlock() switch { @@ -272,7 +272,7 @@ func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { return &Reader{r, pb} } -func (pb *ProgressBar) write(current int64) { +func (pb *ProgressBar) write(total, current int64) { width := pb.GetWidth() var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string @@ -280,8 +280,8 @@ func (pb *ProgressBar) write(current int64) { // percents if pb.ShowPercent { var percent float64 - if pb.Total > 0 { - percent = float64(current) / (float64(pb.Total) / float64(100)) + if total > 0 { + percent = float64(current) / (float64(total) / float64(100)) } else { percent = float64(current) / float64(100) } @@ -291,9 +291,9 @@ func (pb *ProgressBar) write(current int64) { // counters if pb.ShowCounters { current := Format(current).To(pb.Units).Width(pb.UnitsWidth) - if pb.Total > 0 { - total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth) - countersBox = fmt.Sprintf(" %s / %s ", current, total) + if total > 0 { + totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth) + countersBox = fmt.Sprintf(" %s / %s ", current, totalS) } else { countersBox = fmt.Sprintf(" %s / ? ", current) } @@ -322,8 +322,8 @@ func (pb *ProgressBar) write(current int64) { if pb.ShowTimeLeft && currentFromStart > 0 { perEntry := fromChange / time.Duration(currentFromStart) var left time.Duration - if pb.Total > 0 { - left = time.Duration(pb.Total-currentFromStart) * perEntry + if total > 0 { + left = time.Duration(total-currentFromStart) * perEntry left -= time.Since(lastChangeTime) left = (left / time.Second) * time.Second } else { @@ -353,8 +353,8 @@ func (pb *ProgressBar) write(current int64) { if pb.ShowBar { size := width - barWidth if size > 0 { - if pb.Total > 0 { - curSize := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) + if total > 0 { + curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size))) emptySize := size - curSize barBox = pb.BarStart if emptySize < 0 { @@ -400,9 +400,10 @@ func (pb *ProgressBar) write(current int64) { // and print! pb.mu.Lock() + defer pb.mu.Unlock() pb.lastPrint = out + end isFinish := pb.isFinish - pb.mu.Unlock() + switch { case isFinish: return @@ -438,18 +439,19 @@ func (pb *ProgressBar) GetWidth() int { func (pb *ProgressBar) Update() { c := atomic.LoadInt64(&pb.current) p := atomic.LoadInt64(&pb.previous) + t := atomic.LoadInt64(&pb.Total) if p != c { pb.mu.Lock() pb.changeTime = time.Now() pb.mu.Unlock() atomic.StoreInt64(&pb.previous, c) } - pb.write(c) + pb.write(t, c) if pb.AutoStat { if c == 0 { pb.startTime = time.Now() pb.startValue = 0 - } else if c >= pb.Total && pb.isFinish != true { + } else if c >= t && pb.isFinish != true { pb.Finish() } } @@ -462,6 +464,29 @@ func (pb *ProgressBar) String() string { return pb.lastPrint } +// SetTotal atomically sets new total count +func (pb *ProgressBar) SetTotal(total int) *ProgressBar { + return pb.SetTotal64(int64(total)) +} + +// SetTotal64 atomically sets new total count +func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar { + atomic.StoreInt64(&pb.Total, total) + return pb +} + +// Reset bar and set new total count +// Does effect only on finished bar +func (pb *ProgressBar) Reset(total int) *ProgressBar { + pb.mu.Lock() + defer pb.mu.Unlock() + if pb.isFinish { + pb.SetTotal(total).Set(0) + atomic.StoreInt64(&pb.previous, 0) + } + return pb +} + // Internal loop for refreshing the progressbar func (pb *ProgressBar) refresher() { for { diff --git a/vendor/github.com/cheggaaa/pb/pb_win.go b/vendor/github.com/cheggaaa/pb/pb_win.go index 2c67e1947..9595e8236 100644 --- a/vendor/github.com/cheggaaa/pb/pb_win.go +++ b/vendor/github.com/cheggaaa/pb/pb_win.go @@ -124,6 +124,8 @@ func lockEcho() (shutdownCh chan struct{}, err error) { err = fmt.Errorf("Can't set terminal settings: %v", e) return } + + shutdownCh = make(chan struct{}) return } diff --git a/vendor/vendor.json b/vendor/vendor.json index afdefca3c..17957e3fc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -586,12 +586,12 @@ "revision": "50da7d4131a3b5c9d063932461cab4d1fafb20b0" }, { - "checksumSHA1": "A/OK8uWTeJYUL6XXFwOLWw1IZLQ=", + "checksumSHA1": "7GMQgpfoSQv4QcaREE2GzSZvlQI=", "path": "github.com/cheggaaa/pb", - "revision": "72b964305fba1230d3d818711138195f22b9ceea", - "revisionTime": "2018-02-18T15:37:33Z", - "version": "v1.0.22", - "versionExact": "v1.0.22" + "revision": "2af8bbdea9e99e83b3ac400d8f6b6d1b8cbbf338", + "revisionTime": "2018-05-21T09:56:06Z", + "version": "v1.0.25", + "versionExact": "v1.0.25" }, { "checksumSHA1": "X2/71FBrn4pA3WcA620ySVO0uHU=", From 5726927cba1309e667607f0265444c2b30175a7c Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sat, 18 Aug 2018 23:06:14 -0500 Subject: [PATCH 6/7] Moved the progress bar out of packer.Ui and unlinked it out of all the packer.Ui implementations. Split up the terminal-related functions into a separate terminal.go and calculate the progress bar width by traversing through packer.Ui to avoid the issue with github.com/ugorji/go/codec serializing private members (or unsafe pointers) of structs. Shuffled some arguments around in getConsoleScreenBufferInfo in common/terminal_windows.go so that the interface forces the user to correctly declare a _CONSOLE_SCREEN_BUFFER_INFO type. --- common/download.go | 21 +-- common/download_test.go | 25 ++- common/progress.go | 118 +++++++++++++++ common/progress_test.go | 142 ++++++++++++++++++ common/step_download.go | 4 +- common/terminal.go | 6 + .../ui_posix.go => common/terminal_posix.go | 10 +- common/terminal_test.go | 9 ++ common/terminal_windows.go | 86 +++++++++++ packer/rpc/ui.go | 13 -- packer/rpc/ui_test.go | 48 ++---- packer/ui.go | 92 ------------ packer/ui_windows.go | 82 ---------- provisioner/ansible-local/ui_stub.go | 5 - provisioner/ansible/adapter_test.go | 4 - provisioner/ansible/provisioner.go | 4 - provisioner/file/provisioner.go | 4 +- provisioner/file/provisioner_test.go | 4 - 18 files changed, 403 insertions(+), 274 deletions(-) create mode 100644 common/progress.go create mode 100644 common/progress_test.go create mode 100644 common/terminal.go rename packer/ui_posix.go => common/terminal_posix.go (70%) create mode 100644 common/terminal_test.go create mode 100644 common/terminal_windows.go delete mode 100644 packer/ui_windows.go diff --git a/common/download.go b/common/download.go index b80741f3f..f0e88420b 100644 --- a/common/download.go +++ b/common/download.go @@ -18,11 +18,6 @@ import ( "strings" ) -// required import for progress-bar -import ( - "github.com/hashicorp/packer/packer" -) - // imports related to each Downloader implementation import ( "io" @@ -86,12 +81,12 @@ func HashForType(t string) hash.Hash { // NewDownloadClient returns a new DownloadClient for the given // configuration. -func NewDownloadClient(c *DownloadConfig, bar packer.ProgressBar) *DownloadClient { +func NewDownloadClient(c *DownloadConfig, bar ProgressBar) *DownloadClient { const mtu = 1500 /* ethernet */ - 20 /* ipv4 */ - 20 /* tcp */ // If bar is nil, then use a dummy progress bar that doesn't do anything if bar == nil { - bar = packer.GetDummyProgressBar() + bar = GetDummyProgressBar() } // Create downloader map if it hasn't been specified already. @@ -242,7 +237,7 @@ type HTTPDownloader struct { total uint64 userAgent string - progress packer.ProgressBar + progress ProgressBar } func (d *HTTPDownloader) Cancel() { @@ -336,7 +331,7 @@ func (d *HTTPDownloader) Download(dst *os.File, src *url.URL) error { d.total = d.current + uint64(resp.ContentLength) bar := d.progress - bar.Total = int64(d.total) + bar.SetTotal64(int64(d.total)) progressBar := bar.Start() progressBar.Set64(int64(d.current)) @@ -379,7 +374,7 @@ type FileDownloader struct { current uint64 total uint64 - progress packer.ProgressBar + progress ProgressBar } func (d *FileDownloader) Progress() uint64 { @@ -471,7 +466,7 @@ func (d *FileDownloader) Download(dst *os.File, src *url.URL) error { d.total = uint64(fi.Size()) bar := d.progress - bar.Total = int64(d.total) + bar.SetTotal64(int64(d.total)) progressBar := bar.Start() progressBar.Set64(int64(d.current)) @@ -518,7 +513,7 @@ type SMBDownloader struct { current uint64 total uint64 - progress packer.ProgressBar + progress ProgressBar } func (d *SMBDownloader) Progress() uint64 { @@ -592,7 +587,7 @@ func (d *SMBDownloader) Download(dst *os.File, src *url.URL) error { d.total = uint64(fi.Size()) bar := d.progress - bar.Total = int64(d.total) + bar.SetTotal64(int64(d.total)) progressBar := bar.Start() progressBar.Set64(int64(d.current)) diff --git a/common/download_test.go b/common/download_test.go index 6100be1c2..153e8daf2 100644 --- a/common/download_test.go +++ b/common/download_test.go @@ -12,8 +12,6 @@ import ( "runtime" "strings" "testing" - - "github.com/cheggaaa/pb" ) func TestDownloadClientVerifyChecksum(t *testing.T) { @@ -38,7 +36,7 @@ func TestDownloadClientVerifyChecksum(t *testing.T) { Checksum: checksum, } - d := NewDownloadClient(config, *pb.New64(0)) + d := NewDownloadClient(config, nil) result, err := d.VerifyChecksum(tf.Name()) if err != nil { t.Fatalf("Verify err: %s", err) @@ -61,7 +59,7 @@ func TestDownloadClient_basic(t *testing.T) { Url: ts.URL + "/basic.txt", TargetPath: tf.Name(), CopyFile: true, - }, *pb.New64(0)) + }, nil) path, err := client.Get() if err != nil { @@ -97,7 +95,7 @@ func TestDownloadClient_checksumBad(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }, *pb.New64(0)) + }, nil) if _, err := client.Get(); err == nil { t.Fatal("should error") @@ -123,7 +121,7 @@ func TestDownloadClient_checksumGood(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }, *pb.New64(0)) + }, nil) path, err := client.Get() if err != nil { @@ -155,8 +153,7 @@ func TestDownloadClient_checksumNoDownload(t *testing.T) { Hash: HashForType("md5"), Checksum: checksum, CopyFile: true, - }, *pb.New64(0)) - + }, nil) path, err := client.Get() if err != nil { t.Fatalf("err: %s", err) @@ -186,7 +183,7 @@ func TestDownloadClient_notFound(t *testing.T) { client := NewDownloadClient(&DownloadConfig{ Url: ts.URL + "/not-found.txt", TargetPath: tf.Name(), - }, *pb.New64(0)) + }, nil) if _, err := client.Get(); err == nil { t.Fatal("should error") @@ -214,7 +211,7 @@ func TestDownloadClient_resume(t *testing.T) { Url: ts.URL, TargetPath: tf.Name(), CopyFile: true, - }, *pb.New64(0)) + }, nil) path, err := client.Get() if err != nil { @@ -276,7 +273,7 @@ func TestDownloadClient_usesDefaultUserAgent(t *testing.T) { CopyFile: true, } - client := NewDownloadClient(config, *pb.New64(0)) + client := NewDownloadClient(config, nil) _, err = client.Get() if err != nil { t.Fatal(err) @@ -309,7 +306,7 @@ func TestDownloadClient_setsUserAgent(t *testing.T) { CopyFile: true, } - client := NewDownloadClient(config, *pb.New64(0)) + client := NewDownloadClient(config, nil) _, err = client.Get() if err != nil { t.Fatal(err) @@ -408,7 +405,7 @@ func TestDownloadFileUrl(t *testing.T) { CopyFile: false, } - client := NewDownloadClient(config, *pb.New64(0)) + client := NewDownloadClient(config, nil) // Verify that we fail to match the checksum _, err = client.Get() @@ -439,7 +436,7 @@ func SimulateFileUriDownload(t *testing.T, uri string) (string, error) { } // go go go - client := NewDownloadClient(config, *pb.New64(0)) + client := NewDownloadClient(config, nil) path, err := client.Get() // ignore any non-important checksum errors if it's not a unc path diff --git a/common/progress.go b/common/progress.go new file mode 100644 index 000000000..6ab12dcd3 --- /dev/null +++ b/common/progress.go @@ -0,0 +1,118 @@ +package common + +import ( + "fmt" + "github.com/cheggaaa/pb" + "github.com/hashicorp/packer/packer" + "log" + "time" +) + +// The ProgressBar interface is used for abstracting cheggaaa's progress- +// bar, or any other progress bar. If a UI does not support a progress- +// bar, then it must return a null progress bar. +const ( + DefaultProgressBarWidth = 80 +) + +type ProgressBar = *pb.ProgressBar + +// Figure out the terminal dimensions and use it to calculate the available rendering space +func calculateProgressBarWidth(length int) int { + // If the UI's width is signed, then this is an interface that doesn't really benefit from a progress bar + if length < 0 { + log.Println("Refusing to render progress-bar for unsupported UI.") + return length + } + + // 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", DefaultProgressBarWidth, newerr) + return DefaultProgressBarWidth + } + + // If the terminal width is smaller than the requested length, then complain + if width < length { + newerr := fmt.Errorf("Terminal width (%d) is smaller than UI message width (%d).", width, length) + log.Printf("Using default width (%d) for progress-bar due to error: %s", DefaultProgressBarWidth, newerr) + return DefaultProgressBarWidth + } + + // Otherwise subtract the minimum length and return it + return width - length +} + +// Get a progress bar with the default appearance +func GetDefaultProgressBar() ProgressBar { + bar := pb.New64(0) + bar.ShowPercent = true + bar.ShowCounters = true + bar.ShowSpeed = false + bar.ShowBar = true + bar.ShowTimeLeft = false + bar.ShowFinalTime = false + bar.SetUnits(pb.U_BYTES) + bar.Format("[=>-]") + bar.SetRefreshRate(1 * time.Second) + return bar +} + +// Return a dummy progress bar that doesn't do anything +func GetDummyProgressBar() ProgressBar { + bar := pb.New64(0) + bar.ManualUpdate = true + return bar +} + +// Given a packer.Ui, calculate the number of characters that a packer.Ui will +// prefix a message with. Then we can use this to calculate the progress bar's width. +func calculateUiPrefixLength(ui packer.Ui) int { + var recursiveCalculateUiPrefixLength func(packer.Ui, int) int + + // Define a recursive closure that traverses through all the known packer.Ui types + // and aggregates the length of the message prefix from each particular type + recursiveCalculateUiPrefixLength = func(ui packer.Ui, agg int) int { + switch ui.(type) { + + case *packer.ColoredUi: + // packer.ColoredUi is simply a wrapper around .Ui + u := ui.(*packer.ColoredUi) + return recursiveCalculateUiPrefixLength(u.Ui, agg) + + case *packer.TargetedUI: + // A TargetedUI added the .Target and an arrow by default + const targetedArrowText = "==>" + u := ui.(*packer.TargetedUI) + res := fmt.Sprintf("%s %s: ", targetedArrowText, u.Target) + return recursiveCalculateUiPrefixLength(u.Ui, agg+len(res)) + + case *packer.BasicUi: + // The standard BasicUi appends only a newline + return agg + len("\n") + + case *packer.MachineReadableUi: + // MachineReadableUi doesn't emit anything...like at all + return 0 + } + + log.Printf("Calculating the message prefix length for packer.Ui type (%T) is not implemented. Using the current aggregated length of %d.", ui, agg) + return agg + } + return recursiveCalculateUiPrefixLength(ui, 0) +} + +func GetProgressBar(ui packer.Ui) ProgressBar { + uiPrefixLength := calculateUiPrefixLength(ui) + width := calculateProgressBarWidth(uiPrefixLength) + + log.Printf("ProgressBar: Using progress bar width: %d\n", width) + + bar := GetDefaultProgressBar() + bar.SetWidth(width) + bar.Callback = func(message string) { + ui.Message(message) + } + return bar +} diff --git a/common/progress_test.go b/common/progress_test.go new file mode 100644 index 000000000..028492f88 --- /dev/null +++ b/common/progress_test.go @@ -0,0 +1,142 @@ +package common + +import ( + "github.com/hashicorp/packer/packer" + "testing" +) + +// test packer.Ui implementation to verify that progress bar is being written +type testProgressBarUi struct { + messageCalled bool + messageMessage string +} + +func (u *testProgressBarUi) Say(string) {} +func (u *testProgressBarUi) Error(string) {} +func (u *testProgressBarUi) Machine(string, ...string) {} + +func (u *testProgressBarUi) Ask(string) (string, error) { + return "", nil +} +func (u *testProgressBarUi) Message(message string) { + u.messageCalled = true + u.messageMessage = message +} + +// ..and now let's begin our actual tests +func TestCalculateUiPrefixLength_Unknown(t *testing.T) { + ui := &testProgressBarUi{} + + expected := 0 + if res := calculateUiPrefixLength(ui); res != expected { + t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected) + } +} + +func TestCalculateUiPrefixLength_BasicUi(t *testing.T) { + ui := &packer.BasicUi{} + + expected := 1 + if res := calculateUiPrefixLength(ui); res != expected { + t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected) + } +} + +func TestCalculateUiPrefixLength_TargetedUI(t *testing.T) { + ui := &packer.TargetedUI{} + ui.Target = "TestTarget" + arrowText := "==>" + + expected := len(arrowText + " " + ui.Target + ": ") + if res := calculateUiPrefixLength(ui); res != expected { + t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected) + } +} + +func TestCalculateUiPrefixLength_TargetedUIWrappingBasicUi(t *testing.T) { + ui := &packer.TargetedUI{} + ui.Target = "TestTarget" + ui.Ui = &packer.BasicUi{} + arrowText := "==>" + + expected := len(arrowText + " " + ui.Target + ": " + "\n") + if res := calculateUiPrefixLength(ui); res != expected { + t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected) + } +} + +func TestCalculateUiPrefixLength_TargetedUIWrappingMachineUi(t *testing.T) { + ui := &packer.TargetedUI{} + ui.Target = "TestTarget" + ui.Ui = &packer.MachineReadableUi{} + + expected := 0 + if res := calculateUiPrefixLength(ui); res != expected { + t.Fatalf("calculateUiPrefixLength should have returned a length of %d", expected) + } +} +func TestDefaultProgressBar(t *testing.T) { + var callbackCalled bool + + // Initialize the default progress bar + bar := GetDefaultProgressBar() + bar.Callback = func(state string) { + callbackCalled = true + t.Logf("TestDefaultProgressBar emitted %#v", state) + } + bar.SetTotal64(1) + + // Set it off + progressBar := bar.Start() + progressBar.Set64(1) + + // Check to see that the callback was hit + if !callbackCalled { + t.Fatalf("TestDefaultProgressBar.Callback should be called") + } +} + +func TestDummyProgressBar(t *testing.T) { + var callbackCalled bool + + // Initialize the dummy progress bar + bar := GetDummyProgressBar() + bar.Callback = func(state string) { + callbackCalled = true + t.Logf("TestDummyProgressBar emitted %#v", state) + } + bar.SetTotal64(1) + + // Now we can go + progressBar := bar.Start() + progressBar.Set64(1) + + // Check to see that the callback was hit + if callbackCalled { + t.Fatalf("TestDummyProgressBar.Callback should not be called") + } +} + +func TestUiProgressBar(t *testing.T) { + + ui := &testProgressBarUi{} + + // Initialize the Ui progress bar + bar := GetProgressBar(ui, nil) + bar.SetTotal64(1) + + // Ensure that callback has been set to something + if bar.Callback == nil { + t.Fatalf("TestUiProgressBar.Callback should be initialized") + } + + // Now we can go + progressBar := bar.Start() + progressBar.Set64(1) + + // Check to see that the callback was hit + if !ui.messageCalled { + t.Fatalf("TestUiProgressBar.messageCalled should be called") + } + t.Logf("TestUiProgressBar emitted %#v", ui.messageMessage) +} diff --git a/common/step_download.go b/common/step_download.go index e8520091d..52361b188 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -64,7 +64,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) // Get a progress bar from the ui so we can hand it off to the download client - bar := ui.GetProgressBar() + bar := GetProgressBar(ui) // First try to use any already downloaded file // If it fails, proceed to regular download logic @@ -144,7 +144,7 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag ui := state.Get("ui").(packer.Ui) // Get a progress bar and hand it off to the download client - bar := ui.GetProgressBar() + bar := GetProgressBar(ui) // Create download client with config and progress bar download := NewDownloadClient(config, bar) diff --git a/common/terminal.go b/common/terminal.go new file mode 100644 index 000000000..4e862880a --- /dev/null +++ b/common/terminal.go @@ -0,0 +1,6 @@ +package common + +// call into one of the platform-specific implementations to get the current terminal dimensions +func GetTerminalDimensions() (width, height int, err error) { + return platformGetTerminalDimensions() +} diff --git a/packer/ui_posix.go b/common/terminal_posix.go similarity index 70% rename from packer/ui_posix.go rename to common/terminal_posix.go index 27c94e8ee..6c69d79e9 100644 --- a/packer/ui_posix.go +++ b/common/terminal_posix.go @@ -1,6 +1,6 @@ // +build !windows -package packer +package common // Imports for determining terminal information across platforms import ( @@ -9,7 +9,13 @@ import ( ) // posix api -func GetTerminalDimensions() (width, height int, err error) { +func platformGetTerminalDimensions() (width, height int, err error) { + + // grab the handle to stdin + // XXX: in some cases, packer closes stdin, so the following can't be guaranteed + /* + tty := os.Stdin + */ // open up a handle to the current tty tty, err := os.Open("/dev/tty") diff --git a/common/terminal_test.go b/common/terminal_test.go new file mode 100644 index 000000000..f1c66b092 --- /dev/null +++ b/common/terminal_test.go @@ -0,0 +1,9 @@ +package common + +import "testing" + +func TestGetTerminalDimensions(t *testing.T) { + if _, _, err := GetTerminalDimensions(); err != nil { + t.Fatalf("Unable to get terminal dimensions: %s", err) + } +} diff --git a/common/terminal_windows.go b/common/terminal_windows.go new file mode 100644 index 000000000..556873eb0 --- /dev/null +++ b/common/terminal_windows.go @@ -0,0 +1,86 @@ +// +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 to get the console screen buffer info +func getConsoleScreenBufferInfo(csbi *_CONSOLE_SCREEN_BUFFER_INFO) (err error) { + var ( + bi _CONSOLE_SCREEN_BUFFER_INFO + fd syscall.Handle + ) + + // Re-open CONOUT$ as in some instances, stdout may be closed and guaranteed an stdout + if fd, err = syscall.Open("CONOUT$", syscall.O_RDWR, 0); err != nil { + return err + } + defer syscall.Close(fd) + + // grab the dimensions for the console + if err = kernel32_GetConsoleScreenBufferInfo(fd, &bi); err != nil { + return err + } + + *csbi = bi + return nil +} + +func platformGetTerminalDimensions() (width, height int, err error) { + var csbi _CONSOLE_SCREEN_BUFFER_INFO + + if err = getConsoleScreenBufferInfo(&csbi); err != nil { + return 0, 0, err + } + + return int(csbi.dwSize.X), int(csbi.dwSize.Y), nil +} diff --git a/packer/rpc/ui.go b/packer/rpc/ui.go index 80508be82..1c6356a65 100644 --- a/packer/rpc/ui.go +++ b/packer/rpc/ui.go @@ -60,13 +60,6 @@ func (u *Ui) Say(message string) { } } -func (u *Ui) GetProgressBar() (result packer.ProgressBar) { - if err := u.client.Call("Ui.GetProgressBar", nil, &result); err != nil { - log.Printf("Error in Ui RPC call: %s", err) - } - return -} - func (u *UiServer) Ask(query string, reply *string) (err error) { *reply, err = u.ui.Ask(query) return @@ -98,9 +91,3 @@ func (u *UiServer) Say(message *string, reply *interface{}) error { *reply = nil return nil } - -func (u *UiServer) GetProgressBar(noargs *interface{}, reply *packer.ProgressBar) error { - res := u.ui.GetProgressBar() - *reply = res - return nil -} diff --git a/packer/rpc/ui_test.go b/packer/rpc/ui_test.go index eef4be13c..4dd2d7036 100644 --- a/packer/rpc/ui_test.go +++ b/packer/rpc/ui_test.go @@ -1,26 +1,22 @@ package rpc import ( - "github.com/hashicorp/packer/packer" "reflect" "testing" ) type testUi struct { - askCalled bool - askQuery string - errorCalled bool - errorMessage string - machineCalled bool - machineType string - machineArgs []string - messageCalled bool - messageMessage string - sayCalled bool - sayMessage string - getProgressBarCalled bool - getProgressBarValue packer.ProgressBar - progessBarCallbackWasCalled bool + askCalled bool + askQuery string + errorCalled bool + errorMessage string + machineCalled bool + machineType string + machineArgs []string + messageCalled bool + messageMessage string + sayCalled bool + sayMessage string } func (u *testUi) Ask(query string) (string, error) { @@ -50,15 +46,6 @@ func (u *testUi) Say(message string) { u.sayMessage = message } -func (u *testUi) GetProgressBar() packer.ProgressBar { - u.getProgressBarCalled = true - u.getProgressBarValue = packer.GetDummyProgressBar() - u.getProgressBarValue.Callback = func(string) { - u.progessBarCallbackWasCalled = true - } - return u.getProgressBarValue -} - func TestUiRPC(t *testing.T) { // Create the UI to test ui := new(testUi) @@ -106,19 +93,6 @@ func TestUiRPC(t *testing.T) { t.Fatal("machine should be called") } - bar := uiClient.GetProgressBar() - if !ui.getProgressBarCalled { - t.Fatal("getprogressbar should be called") - } - - if bar.Callback == nil { - t.Fatal("getprogressbar returned a bar with an empty callback") - } - bar.Callback("test") - if !ui.progessBarCallbackWasCalled { - t.Fatal("progressbarcallback should be called") - } - if ui.machineType != "foo" { t.Fatalf("bad type: %#v", ui.machineType) } diff --git a/packer/ui.go b/packer/ui.go index cd88a1eab..c16a2eae0 100644 --- a/packer/ui.go +++ b/packer/ui.go @@ -15,8 +15,6 @@ import ( "syscall" "time" "unicode" - - "github.com/cheggaaa/pb" ) type UiColor uint @@ -30,15 +28,6 @@ const ( UiColorCyan = 36 ) -// The ProgressBar interface is used for abstracting cheggaaa's progress- -// bar, or any other progress bar. If a UI does not support a progress- -// bar, then it must return a null progress bar. -const ( - DefaultProgressBarWidth = 80 -) - -type ProgressBar = *pb.ProgressBar - // The Ui interface handles all communication for Packer with the outside // world. This sort of control allows us to strictly control how output // is formatted and various levels of output. @@ -48,7 +37,6 @@ type Ui interface { Message(string) Error(string) Machine(string, ...string) - GetProgressBar() ProgressBar } // ColoredUi is a UI that is colored using terminal colors. @@ -144,11 +132,6 @@ func (u *ColoredUi) supportsColors() bool { return cygwin } -func (u *ColoredUi) GetProgressBar() ProgressBar { - log.Printf("ColoredUi: Using wrapped UI for progress bar.\n") - return u.Ui.GetProgressBar() -} - func (u *TargetedUI) Ask(query string) (string, error) { return u.Ui.Ask(u.prefixLines(true, query)) } @@ -185,11 +168,6 @@ func (u *TargetedUI) prefixLines(arrow bool, message string) string { return strings.TrimRightFunc(result.String(), unicode.IsSpace) } -func (u *TargetedUI) GetProgressBar() ProgressBar { - log.Printf("TargetedUI: Using wrapped UI for progress bar.\n") - return u.Ui.GetProgressBar() -} - func (rw *BasicUi) Ask(query string) (string, error) { rw.l.Lock() defer rw.l.Unlock() @@ -282,23 +260,6 @@ func (rw *BasicUi) Machine(t string, args ...string) { log.Printf("machine readable: %s %#v", t, args) } -func (rw *BasicUi) GetProgressBar() ProgressBar { - width := calculateProgressBarWidth(0) - - log.Printf("BasicUi: Using progress bar width: %d\n", width) - - bar := GetDefaultProgressBar() - bar.SetWidth(width) - bar.Callback = func(message string) { - rw.l.Lock() - defer rw.l.Unlock() - - // Discard the error here so we don't emit an error everytime the progress-bar fails to update - fmt.Fprint(rw.Writer, message) - } - return bar -} - func (u *MachineReadableUi) Ask(query string) (string, error) { return "", errors.New("machine-readable UI can't ask") } @@ -344,56 +305,3 @@ func (u *MachineReadableUi) Machine(category string, args ...string) { } } } - -func (u *MachineReadableUi) GetProgressBar() ProgressBar { - log.Printf("MachineReadableUi: Using dummy progress bar.\n") - bar := GetDummyProgressBar() - return bar -} - -// Return a dummy progress bar that doesn't do anything -func GetDummyProgressBar() *pb.ProgressBar { - return pb.New64(0) -} - -// Get a progress bar with the default appearance -func GetDefaultProgressBar() *pb.ProgressBar { - bar := pb.New64(0) - bar.ShowPercent = true - bar.ShowCounters = true - bar.ShowSpeed = false - bar.ShowBar = true - bar.ShowTimeLeft = false - bar.ShowFinalTime = false - bar.SetUnits(pb.U_BYTES) - bar.Format("[=>-]") - bar.SetRefreshRate(1 * time.Second) - return bar -} - -// Figure out the terminal dimensions and use it to calculate the available rendering space -func calculateProgressBarWidth(length int) int { - // If the UI's width is signed, then this is an interface that doesn't really benefit from a progress bar - if length < 0 { - log.Println("Refusing to render progress-bar for unsupported UI.") - return length - } - - // 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", DefaultProgressBarWidth, newerr) - return DefaultProgressBarWidth - } - - // If the terminal width is smaller than the requested length, then complain - if width < length { - newerr := fmt.Errorf("Terminal width (%d) is smaller than UI message width (%d).", width, length) - log.Printf("Using default width (%d) for progress-bar due to error: %s", DefaultProgressBarWidth, newerr) - return DefaultProgressBarWidth - } - - // Otherwise subtract the minimum length and return it - return width - length -} diff --git a/packer/ui_windows.go b/packer/ui_windows.go deleted file mode 100644 index ce99c3a35..000000000 --- a/packer/ui_windows.go +++ /dev/null @@ -1,82 +0,0 @@ -// +build windows - -package packer - -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 -} diff --git a/provisioner/ansible-local/ui_stub.go b/provisioner/ansible-local/ui_stub.go index bc2e40628..4faa2a215 100644 --- a/provisioner/ansible-local/ui_stub.go +++ b/provisioner/ansible-local/ui_stub.go @@ -1,7 +1,5 @@ package ansiblelocal -import "github.com/hashicorp/packer/packer" - type uiStub struct{} func (su *uiStub) Ask(string) (string, error) { @@ -15,6 +13,3 @@ func (su *uiStub) Machine(string, ...string) {} func (su *uiStub) Message(string) {} func (su *uiStub) Say(msg string) {} -func (su *uiStub) GetProcessBar() packer.ProgressBar { - return packer.GetDummyProgressBar() -} diff --git a/provisioner/ansible/adapter_test.go b/provisioner/ansible/adapter_test.go index 92e669a97..2ab2d350b 100644 --- a/provisioner/ansible/adapter_test.go +++ b/provisioner/ansible/adapter_test.go @@ -123,10 +123,6 @@ func (u *ui) Machine(s1 string, s2 ...string) { } } -func (u *ui) GetProgressBar() packer.ProgressBar { - return packer.GetDummyProgressBar() -} - type communicator struct{} func (c communicator) Start(*packer.RemoteCmd) error { diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index 9cea3e435..99c4d07d9 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -599,7 +599,3 @@ func (ui *Ui) Machine(t string, args ...string) { ui.ui.Machine(t, args...) <-ui.sem } - -func (ui *Ui) GetProgressBar() packer.ProgressBar { - return packer.GetDummyProgressBar() -} diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index 616625a25..bda35bf31 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -127,7 +127,7 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator) defer f.Close() // Get a default progress bar - pb := ui.GetProgressBar() + pb := common.GetProgressBar(ui) bar := pb.Start() defer bar.Finish() @@ -176,7 +176,7 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er } // Get a default progress bar - pb := ui.GetProgressBar() + pb := common.GetProgressBar(ui) bar := pb.Start() defer bar.Finish() diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index b804f0e32..175082aae 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -120,10 +120,6 @@ func (su *stubUi) Say(msg string) { su.sayMessages += msg } -func (su *stubUi) GetProgressBar() packer.ProgressBar { - return packer.GetDummyProgressBar() -} - func TestProvisionerProvision_SendsFile(t *testing.T) { var p Provisioner tf, err := ioutil.TempFile("", "packer") From cf9bbe3ecf001f7897b5af6ff03bc024228c4c9b Mon Sep 17 00:00:00 2001 From: Ali Rizvi-Santiago Date: Sun, 19 Aug 2018 01:01:31 -0500 Subject: [PATCH 7/7] Incorporated the hack demonstrated by @SwampDragons in order to deal with packer.rpc.Ui not exporting any information about what it's doing to anybody. --- common/progress.go | 35 +++++++++++++++++++++++++++++---- common/step_download.go | 4 ++-- provisioner/file/provisioner.go | 4 ++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/common/progress.go b/common/progress.go index 6ab12dcd3..41b79a27f 100644 --- a/common/progress.go +++ b/common/progress.go @@ -3,11 +3,17 @@ package common import ( "fmt" "github.com/cheggaaa/pb" + "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" + "github.com/hashicorp/packer/packer/rpc" "log" + "reflect" "time" ) +// This is the arrow from packer/ui.go -> TargetedUI.prefixLines +const targetedUIArrowText = "==>" + // The ProgressBar interface is used for abstracting cheggaaa's progress- // bar, or any other progress bar. If a UI does not support a progress- // bar, then it must return a null progress bar. @@ -82,16 +88,19 @@ func calculateUiPrefixLength(ui packer.Ui) int { return recursiveCalculateUiPrefixLength(u.Ui, agg) case *packer.TargetedUI: - // A TargetedUI added the .Target and an arrow by default - const targetedArrowText = "==>" + // A TargetedUI adds the .Target and an arrow by default u := ui.(*packer.TargetedUI) - res := fmt.Sprintf("%s %s: ", targetedArrowText, u.Target) + res := fmt.Sprintf("%s %s: ", targetedUIArrowText, u.Target) return recursiveCalculateUiPrefixLength(u.Ui, agg+len(res)) case *packer.BasicUi: // The standard BasicUi appends only a newline return agg + len("\n") + // packer.rpc.Ui returns 0 here to trigger the hack described later + case *rpc.Ui: + return 0 + case *packer.MachineReadableUi: // MachineReadableUi doesn't emit anything...like at all return 0 @@ -103,12 +112,30 @@ func calculateUiPrefixLength(ui packer.Ui) int { return recursiveCalculateUiPrefixLength(ui, 0) } -func GetProgressBar(ui packer.Ui) ProgressBar { +func GetPackerConfigFromStateBag(state multistep.StateBag) *PackerConfig { + config := state.Get("config") + rConfig := reflect.Indirect(reflect.ValueOf(config)) + iPackerConfig := rConfig.FieldByName("PackerConfig").Interface() + packerConfig := iPackerConfig.(PackerConfig) + return &packerConfig +} + +func GetProgressBar(ui packer.Ui, config *PackerConfig) ProgressBar { + // Figure out the prefix length by quering the UI uiPrefixLength := calculateUiPrefixLength(ui) + + // hack to deal with packer.rpc.Ui courtesy of @Swampdragons + if _, ok := ui.(*rpc.Ui); uiPrefixLength == 0 && config != nil && ok { + res := fmt.Sprintf("%s %s: \n", targetedUIArrowText, config.PackerBuildName) + uiPrefixLength = len(res) + } + + // Now we can use the prefix length to calculate the progress bar width width := calculateProgressBarWidth(uiPrefixLength) log.Printf("ProgressBar: Using progress bar width: %d\n", width) + // Get a default progress bar and set some output defaults bar := GetDefaultProgressBar() bar.SetWidth(width) bar.Callback = func(message string) { diff --git a/common/step_download.go b/common/step_download.go index 52361b188..c55e12632 100644 --- a/common/step_download.go +++ b/common/step_download.go @@ -64,7 +64,7 @@ func (s *StepDownload) Run(_ context.Context, state multistep.StateBag) multiste ui.Say(fmt.Sprintf("Retrieving %s", s.Description)) // Get a progress bar from the ui so we can hand it off to the download client - bar := GetProgressBar(ui) + bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state)) // First try to use any already downloaded file // If it fails, proceed to regular download logic @@ -144,7 +144,7 @@ func (s *StepDownload) download(config *DownloadConfig, state multistep.StateBag ui := state.Get("ui").(packer.Ui) // Get a progress bar and hand it off to the download client - bar := GetProgressBar(ui) + bar := GetProgressBar(ui, GetPackerConfigFromStateBag(state)) // Create download client with config and progress bar download := NewDownloadClient(config, bar) diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index bda35bf31..13e2aa8e6 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -127,7 +127,7 @@ func (p *Provisioner) ProvisionDownload(ui packer.Ui, comm packer.Communicator) defer f.Close() // Get a default progress bar - pb := common.GetProgressBar(ui) + pb := common.GetProgressBar(ui, &p.config.PackerConfig) bar := pb.Start() defer bar.Finish() @@ -176,7 +176,7 @@ func (p *Provisioner) ProvisionUpload(ui packer.Ui, comm packer.Communicator) er } // Get a default progress bar - pb := common.GetProgressBar(ui) + pb := common.GetProgressBar(ui, &p.config.PackerConfig) bar := pb.Start() defer bar.Finish()