diff --git a/CHANGELOG.md b/CHANGELOG.md index 9889183fe..f9864c475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Rudimentary security groups port scanner inspector via `awless inspect -i port_scanner` - Template: compile time check of undefined or unused references +- [#78](https://github.com/wallix/awless/issues/78): Show progress when uploadgin object to storage ### Bugfixes diff --git a/aws/driver/driver_funcs.go b/aws/driver/driver_funcs.go index e55f68996..7ad4d96e9 100644 --- a/aws/driver/driver_funcs.go +++ b/aws/driver/driver_funcs.go @@ -34,6 +34,7 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/s3" + "github.com/mitchellh/ioprogress" "github.com/wallix/awless/cloud" "github.com/wallix/awless/console" ) @@ -557,6 +558,44 @@ func (d *S3Driver) Create_Storageobject_DryRun(params map[string]interface{}) (i return nil, nil } +type progressReadSeeker struct { + file *os.File + reader *ioprogress.Reader +} + +func newProgressReader(f *os.File) (*progressReadSeeker, error) { + finfo, err := f.Stat() + if err != nil { + return nil, err + } + + draw := func(progress, total int64) string { + // &s3.PutObjectInput.Body will be read twice + // once in memory and a second time for the HTTP upload + // here we only display for the actual HTTP upload + if progress > total { + return ioprogress.DrawTextFormatBytes(progress/2, total) + } + return "" + } + + reader := &ioprogress.Reader{ + DrawFunc: ioprogress.DrawTerminalf(os.Stdout, draw), + Reader: f, + Size: finfo.Size(), + } + + return &progressReadSeeker{file: f, reader: reader}, nil +} + +func (pr *progressReadSeeker) Read(p []byte) (int, error) { + return pr.reader.Read(p) +} + +func (pr *progressReadSeeker) Seek(offset int64, whence int) (int64, error) { + return pr.file.Seek(offset, whence) +} + func (d *S3Driver) Create_Storageobject(params map[string]interface{}) (interface{}, error) { input := &s3.PutObjectInput{} @@ -565,7 +604,12 @@ func (d *S3Driver) Create_Storageobject(params map[string]interface{}) (interfac return nil, err } defer f.Close() - input.Body = f + + progressR, err := newProgressReader(f) + if err != nil { + return nil, err + } + input.Body = progressR var fileName string if n, ok := params["name"].(string); ok && n != "" { @@ -581,6 +625,8 @@ func (d *S3Driver) Create_Storageobject(params map[string]interface{}) (interfac return nil, err } + d.logger.Infof("uploading '%s'", fileName) + output, err := d.PutObject(input) if err != nil { return nil, fmt.Errorf("create storageobject: %s", err) diff --git a/vendor/github.com/mitchellh/ioprogress/LICENSE b/vendor/github.com/mitchellh/ioprogress/LICENSE new file mode 100644 index 000000000..229851590 --- /dev/null +++ b/vendor/github.com/mitchellh/ioprogress/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/mitchellh/ioprogress/README.md b/vendor/github.com/mitchellh/ioprogress/README.md new file mode 100644 index 000000000..3d291e9d1 --- /dev/null +++ b/vendor/github.com/mitchellh/ioprogress/README.md @@ -0,0 +1,42 @@ +# ioprogress + +ioprogress is a Go (golang) library with implementations of `io.Reader` +and `io.Writer` that draws progress bars. The primary use case for these +are for CLI applications but alternate progress bar writers can be supplied +for alternate environments. + +## Example + +![Progress](http://g.recordit.co/GO5HxT16QH.gif) + +## Installation + +Standard `go get`: + +``` +$ go get github.com/mitchellh/ioprogress +``` + +## Usage + +Here is an example of outputting a basic progress bar to the CLI as +we're "downloading" from some other `io.Reader` (perhaps from a network +connection): + +```go +// Imagine this came from some external source, such as a network connection, +// and that we have the full size of it, such as from a Content-Length HTTP +// header. +var r io.Reader + +// Create the progress reader +progressR := &ioprogress.Reader{ + Reader: r, + Size: rSize, +} + +// Copy all of the reader to some local file f. As it copies, the +// progressR will write progress to the terminal on os.Stdout. This is +// customizable. +io.Copy(f, progressR) +``` diff --git a/vendor/github.com/mitchellh/ioprogress/draw.go b/vendor/github.com/mitchellh/ioprogress/draw.go new file mode 100644 index 000000000..83b7305a0 --- /dev/null +++ b/vendor/github.com/mitchellh/ioprogress/draw.go @@ -0,0 +1,104 @@ +package ioprogress + +import ( + "fmt" + "io" + "os" + "strings" +) + +// DrawFunc is the callback type for drawing progress. +type DrawFunc func(int64, int64) error + +// DrawTextFormatFunc is a callback used by DrawFuncs that draw text in +// order to format the text into some more human friendly format. +type DrawTextFormatFunc func(int64, int64) string + +var defaultDrawFunc DrawFunc + +func init() { + defaultDrawFunc = DrawTerminal(os.Stdout) +} + +// DrawTerminal returns a DrawFunc that draws a progress bar to an io.Writer +// that is assumed to be a terminal (and therefore respects carriage returns). +func DrawTerminal(w io.Writer) DrawFunc { + return DrawTerminalf(w, func(progress, total int64) string { + return fmt.Sprintf("%d/%d", progress, total) + }) +} + +// DrawTerminalf returns a DrawFunc that draws a progress bar to an io.Writer +// that is formatted with the given formatting function. +func DrawTerminalf(w io.Writer, f DrawTextFormatFunc) DrawFunc { + var maxLength int + + return func(progress, total int64) error { + if progress == -1 && total == -1 { + _, err := fmt.Fprintf(w, "\n") + return err + } + + // Make sure we pad it to the max length we've ever drawn so that + // we don't have trailing characters. + line := f(progress, total) + if len(line) < maxLength { + line = fmt.Sprintf( + "%s%s", + line, + strings.Repeat(" ", maxLength-len(line))) + } + maxLength = len(line) + + _, err := fmt.Fprint(w, line+"\r") + return err + } +} + +var byteUnits = []string{"B", "KB", "MB", "GB", "TB", "PB"} + +// DrawTextFormatBytes is a DrawTextFormatFunc that formats the progress +// and total into human-friendly byte formats. +func DrawTextFormatBytes(progress, total int64) string { + return fmt.Sprintf("%s/%s", byteUnitStr(progress), byteUnitStr(total)) +} + +// DrawTextFormatBar returns a DrawTextFormatFunc that draws a progress +// bar with the given width (in characters). This can be used in conjunction +// with another DrawTextFormatFunc to create a progress bar with bytes, for +// example: +// +// bar := DrawTextFormatBar(20) +// func(progress, total int64) string { +// return fmt.Sprintf( +// "%s %s", +// bar(progress, total), +// DrawTextFormatBytes(progress, total)) +// } +// +func DrawTextFormatBar(width int64) DrawTextFormatFunc { + width -= 2 + + return func(progress, total int64) string { + current := int64((float64(progress) / float64(total)) * float64(width)) + return fmt.Sprintf( + "[%s%s]", + strings.Repeat("=", int(current)), + strings.Repeat(" ", int(width-current))) + } +} + +func byteUnitStr(n int64) string { + var unit string + size := float64(n) + for i := 1; i < len(byteUnits); i++ { + if size < 1000 { + unit = byteUnits[i-1] + break + } + + size = size / 1000 + } + + return fmt.Sprintf("%.3g %s", size, unit) +} diff --git a/vendor/github.com/mitchellh/ioprogress/reader.go b/vendor/github.com/mitchellh/ioprogress/reader.go new file mode 100644 index 000000000..a408b6dbd --- /dev/null +++ b/vendor/github.com/mitchellh/ioprogress/reader.go @@ -0,0 +1,110 @@ +package ioprogress + +import ( + "io" + "time" +) + +// Reader is an implementation of io.Reader that draws the progress of +// reading some data. +type Reader struct { + // Reader is the underlying reader to read from + Reader io.Reader + + // Size is the total size of the data coming out of the reader. + Size int64 + + // DrawFunc is the callback to invoke to draw the progress bar. By + // default, this will be DrawTerminal(os.Stdout). + // + // DrawInterval is the minimum time to wait between reads to update the + // progress bar. + DrawFunc DrawFunc + DrawInterval time.Duration + + progress int64 + lastDraw time.Time +} + +// Read reads from the underlying reader and invokes the DrawFunc if +// appropriate. The DrawFunc is executed when there is data that is +// read (progress is made) and at least DrawInterval time has passed. +func (r *Reader) Read(p []byte) (int, error) { + // If we haven't drawn before, initialize the progress bar + if r.lastDraw.IsZero() { + r.initProgress() + } + + // Read from the underlying source + n, err := r.Reader.Read(p) + + // Always increment the progress even if there was an error + r.progress += int64(n) + + // If we don't have any errors, then draw the progress. If we are + // at the end of the data, then finish the progress. + if err == nil { + // Only draw if we read data or we've never read data before (to + // initialize the progress bar). + if n > 0 { + r.drawProgress() + } + } + if err == io.EOF { + r.finishProgress() + } + + return n, err +} + +func (r *Reader) drawProgress() { + // If we've drawn before, then make sure that the draw interval + // has passed before we draw again. + interval := r.DrawInterval + if interval == 0 { + interval = time.Second + } + if !r.lastDraw.IsZero() { + nextDraw := r.lastDraw.Add(interval) + if time.Now().Before(nextDraw) { + return + } + } + + // Draw + f := r.drawFunc() + f(r.progress, r.Size) + + // Record this draw so that we don't draw again really quickly + r.lastDraw = time.Now() +} + +func (r *Reader) finishProgress() { + // Only output the final draw if we drawed prior + if !r.lastDraw.IsZero() { + f := r.drawFunc() + f(r.progress, r.Size) + + // Print a newline + f(-1, -1) + + // Reset lastDraw so we don't finish again + var zeroDraw time.Time + r.lastDraw = zeroDraw + } +} + +func (r *Reader) initProgress() { + var zeroDraw time.Time + r.lastDraw = zeroDraw + r.drawProgress() + r.lastDraw = zeroDraw +} + +func (r *Reader) drawFunc() DrawFunc { + if r.DrawFunc == nil { + return defaultDrawFunc + } + + return r.DrawFunc +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 45196478e..b319e7308 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -530,6 +530,12 @@ "revision": "737072b4e32b7a5018b4a7125da8d12de90e8045", "revisionTime": "2016-10-12T01:35:12Z" }, + { + "checksumSHA1": "NB5fzIAruRSs7H8vH27kLXEGVBA=", + "path": "github.com/mitchellh/ioprogress", + "revision": "8163955264568045f462ae7e2d6d07b2001fc997", + "revisionTime": "2015-05-21T21:15:56Z" + }, { "checksumSHA1": "KCJhN9Dx329wAN/SeL4CxeypPyk=", "path": "github.com/mitchellh/mapstructure",