Add docker dependencies: timeutils, jsonmessage

This commit is contained in:
Dawn Chen 2015-06-04 13:02:09 -07:00
parent 988a413acf
commit bbea2863c2
6 changed files with 311 additions and 0 deletions

10
Godeps/Godeps.json generated
View File

@ -149,6 +149,11 @@
"Comment": "v2.2.0-23-g5ca8014",
"Rev": "5ca80149b9d3f8b863af0e2bb6742e608603bd99"
},
{
"ImportPath": "github.com/docker/docker/pkg/jsonmessage",
"Comment": "v1.7.rc",
"Rev": "0cd6c05d8112e9246b734107d54e2855e3d5fec5"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.4.1-1714-ged66853",
@ -164,6 +169,11 @@
"Comment": "v1.4.1-1714-ged66853",
"Rev": "ed6685369740035b0af9675bf9add52d0af7657b"
},
{
"ImportPath": "github.com/docker/docker/pkg/timeutils",
"Comment": "v1.7.rc",
"Rev": "0cd6c05d8112e9246b734107d54e2855e3d5fec5"
},
{
"ImportPath": "github.com/docker/docker/pkg/units",
"Comment": "v1.4.1-1714-ged66853",

View File

@ -0,0 +1,172 @@
package jsonmessage
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/pkg/timeutils"
"github.com/docker/docker/pkg/units"
)
type JSONError struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
func (e *JSONError) Error() string {
return e.Message
}
type JSONProgress struct {
terminalFd uintptr
Current int `json:"current,omitempty"`
Total int `json:"total,omitempty"`
Start int64 `json:"start,omitempty"`
}
func (p *JSONProgress) String() string {
var (
width = 200
pbBox string
numbersBox string
timeLeftBox string
)
ws, err := term.GetWinsize(p.terminalFd)
if err == nil {
width = int(ws.Width)
}
if p.Current <= 0 && p.Total <= 0 {
return ""
}
current := units.HumanSize(float64(p.Current))
if p.Total <= 0 {
return fmt.Sprintf("%8v", current)
}
total := units.HumanSize(float64(p.Total))
percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
if percentage > 50 {
percentage = 50
}
if width > 110 {
// this number can't be negetive gh#7136
numSpaces := 0
if 50-percentage > 0 {
numSpaces = 50 - percentage
}
pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
}
numbersBox = fmt.Sprintf("%8v/%v", current, total)
if p.Current > 0 && p.Start > 0 && percentage < 50 {
fromStart := time.Now().UTC().Sub(time.Unix(int64(p.Start), 0))
perEntry := fromStart / time.Duration(p.Current)
left := time.Duration(p.Total-p.Current) * perEntry
left = (left / time.Second) * time.Second
if width > 50 {
timeLeftBox = " " + left.String()
}
}
return pbBox + numbersBox + timeLeftBox
}
type JSONMessage struct {
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
Progress *JSONProgress `json:"progressDetail,omitempty"`
ProgressMessage string `json:"progress,omitempty"` //deprecated
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
Time int64 `json:"time,omitempty"`
Error *JSONError `json:"errorDetail,omitempty"`
ErrorMessage string `json:"error,omitempty"` //deprecated
}
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
if jm.Error != nil {
if jm.Error.Code == 401 {
return fmt.Errorf("Authentication is required.")
}
return jm.Error
}
var endl string
if isTerminal && jm.Stream == "" && jm.Progress != nil {
// <ESC>[2K = erase entire current line
fmt.Fprintf(out, "%c[2K\r", 27)
endl = "\r"
} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
return nil
}
if jm.Time != 0 {
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(timeutils.RFC3339NanoFixed))
}
if jm.ID != "" {
fmt.Fprintf(out, "%s: ", jm.ID)
}
if jm.From != "" {
fmt.Fprintf(out, "(from %s) ", jm.From)
}
if jm.Progress != nil && isTerminal {
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
} else if jm.ProgressMessage != "" { //deprecated
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
} else if jm.Stream != "" {
fmt.Fprintf(out, "%s%s", jm.Stream, endl)
} else {
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
}
return nil
}
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool) error {
var (
dec = json.NewDecoder(in)
ids = make(map[string]int)
diff = 0
)
for {
var jm JSONMessage
if err := dec.Decode(&jm); err != nil {
if err == io.EOF {
break
}
return err
}
if jm.Progress != nil {
jm.Progress.terminalFd = terminalFd
}
if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
line, ok := ids[jm.ID]
if !ok {
line = len(ids)
ids[jm.ID] = line
if isTerminal {
fmt.Fprintf(out, "\n")
}
diff = 0
} else {
diff = len(ids) - line
}
if jm.ID != "" && isTerminal {
// <ESC>[{diff}A = move cursor up diff rows
fmt.Fprintf(out, "%c[%dA", 27, diff)
}
}
err := jm.Display(out, isTerminal)
if jm.ID != "" && isTerminal {
// <ESC>[{diff}B = move cursor down diff rows
fmt.Fprintf(out, "%c[%dB", 27, diff)
}
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,38 @@
package jsonmessage
import (
"testing"
)
func TestError(t *testing.T) {
je := JSONError{404, "Not found"}
if je.Error() != "Not found" {
t.Fatalf("Expected 'Not found' got '%s'", je.Error())
}
}
func TestProgress(t *testing.T) {
jp := JSONProgress{}
if jp.String() != "" {
t.Fatalf("Expected empty string, got '%s'", jp.String())
}
expected := " 1 B"
jp2 := JSONProgress{Current: 1}
if jp2.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp2.String())
}
expected = "[=========================> ] 50 B/100 B"
jp3 := JSONProgress{Current: 50, Total: 100}
if jp3.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp3.String())
}
// this number can't be negetive gh#7136
expected = "[==================================================>] 50 B/40 B"
jp4 := JSONProgress{Current: 50, Total: 40}
if jp4.String() != expected {
t.Fatalf("Expected %q, got %q", expected, jp4.String())
}
}

View File

@ -0,0 +1,26 @@
package timeutils
import (
"errors"
"time"
)
const (
// RFC3339NanoFixed is our own version of RFC339Nano because we want one
// that pads the nano seconds part with zeros to ensure
// the timestamps are aligned in the logs.
RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
// JSONFormat is the format used by FastMarshalJSON
JSONFormat = `"` + time.RFC3339Nano + `"`
)
// FastMarshalJSON avoids one of the extra allocations that
// time.MarshalJSON is making.
func FastMarshalJSON(t time.Time) (string, error) {
if y := t.Year(); y < 0 || y >= 10000 {
// RFC 3339 is clear that years are 4 digits exactly.
// See golang.org/issue/4556#c15 for more discussion.
return "", errors.New("time.MarshalJSON: year outside of range [0,9999]")
}
return t.Format(JSONFormat), nil
}

View File

@ -0,0 +1,29 @@
package timeutils
import (
"strconv"
"strings"
"time"
)
// GetTimestamp tries to parse given string as RFC3339 time
// or Unix timestamp (with seconds precision), if successful
//returns a Unix timestamp as string otherwise returns value back.
func GetTimestamp(value string) string {
var format string
if strings.Contains(value, ".") {
format = time.RFC3339Nano
} else {
format = time.RFC3339
}
loc := time.FixedZone(time.Now().Zone())
if len(value) < len(format) {
format = format[:len(value)]
}
t, err := time.ParseInLocation(format, value, loc)
if err != nil {
return value
}
return strconv.FormatInt(t.Unix(), 10)
}

View File

@ -0,0 +1,36 @@
package timeutils
import (
"testing"
)
func TestGetTimestamp(t *testing.T) {
cases := []struct{ in, expected string }{
{"0", "-62167305600"}, // 0 gets parsed year 0
// Partial RFC3339 strings get parsed with second precision
{"2006-01-02T15:04:05.999999999+07:00", "1136189045"},
{"2006-01-02T15:04:05.999999999Z", "1136214245"},
{"2006-01-02T15:04:05.999999999", "1136214245"},
{"2006-01-02T15:04:05", "1136214245"},
{"2006-01-02T15:04", "1136214240"},
{"2006-01-02T15", "1136214000"},
{"2006-01-02T", "1136160000"},
{"2006-01-02", "1136160000"},
{"2006", "1136073600"},
{"2015-05-13T20:39:09Z", "1431549549"},
// unix timestamps returned as is
{"1136073600", "1136073600"},
// String fallback
{"invalid", "invalid"},
}
for _, c := range cases {
o := GetTimestamp(c.in)
if o != c.expected {
t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o)
}
}
}