New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
30431 implement format for history with docs #30962
Merged
thaJeztah
merged 1 commit into
moby:master
from
TheHipbot:30431-implement-format-for-history-with-docs
Apr 16, 2017
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package formatter | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/docker/docker/api/types/image" | ||
"github.com/docker/docker/pkg/stringid" | ||
"github.com/docker/docker/pkg/stringutils" | ||
units "github.com/docker/go-units" | ||
) | ||
|
||
const ( | ||
defaultHistoryTableFormat = "table {{.ID}}\t{{.CreatedSince}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}" | ||
nonHumanHistoryTableFormat = "table {{.ID}}\t{{.CreatedAt}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}" | ||
|
||
historyIDHeader = "IMAGE" | ||
createdByHeader = "CREATED BY" | ||
commentHeader = "COMMENT" | ||
) | ||
|
||
// NewHistoryFormat returns a format for rendering an HistoryContext | ||
func NewHistoryFormat(source string, quiet bool, human bool) Format { | ||
switch source { | ||
case TableFormatKey: | ||
switch { | ||
case quiet: | ||
return defaultQuietFormat | ||
case !human: | ||
return nonHumanHistoryTableFormat | ||
default: | ||
return defaultHistoryTableFormat | ||
} | ||
} | ||
|
||
return Format(source) | ||
} | ||
|
||
// HistoryWrite writes the context | ||
func HistoryWrite(ctx Context, human bool, histories []image.HistoryResponseItem) error { | ||
render := func(format func(subContext subContext) error) error { | ||
for _, history := range histories { | ||
historyCtx := &historyContext{trunc: ctx.Trunc, h: history, human: human} | ||
if err := format(historyCtx); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
historyCtx := &historyContext{} | ||
historyCtx.header = map[string]string{ | ||
"ID": historyIDHeader, | ||
"CreatedSince": createdSinceHeader, | ||
"CreatedAt": createdAtHeader, | ||
"CreatedBy": createdByHeader, | ||
"Size": sizeHeader, | ||
"Comment": commentHeader, | ||
} | ||
return ctx.Write(historyCtx, render) | ||
} | ||
|
||
type historyContext struct { | ||
HeaderContext | ||
trunc bool | ||
human bool | ||
h image.HistoryResponseItem | ||
} | ||
|
||
func (c *historyContext) MarshalJSON() ([]byte, error) { | ||
return marshalJSON(c) | ||
} | ||
|
||
func (c *historyContext) ID() string { | ||
if c.trunc { | ||
return stringid.TruncateID(c.h.ID) | ||
} | ||
return c.h.ID | ||
} | ||
|
||
func (c *historyContext) CreatedAt() string { | ||
var created string | ||
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0))) | ||
return created | ||
} | ||
|
||
func (c *historyContext) CreatedSince() string { | ||
var created string | ||
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0))) | ||
return created + " ago" | ||
} | ||
|
||
func (c *historyContext) CreatedBy() string { | ||
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1) | ||
if c.trunc { | ||
createdBy = stringutils.Ellipsis(createdBy, 45) | ||
} | ||
return createdBy | ||
} | ||
|
||
func (c *historyContext) Size() string { | ||
size := "" | ||
if c.human { | ||
size = units.HumanSizeWithPrecision(float64(c.h.Size), 3) | ||
} else { | ||
size = strconv.FormatInt(c.h.Size, 10) | ||
} | ||
return size | ||
} | ||
|
||
func (c *historyContext) Comment() string { | ||
return c.h.Comment | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package formatter | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"bytes" | ||
"github.com/docker/docker/api/types/image" | ||
"github.com/docker/docker/pkg/stringid" | ||
"github.com/docker/docker/pkg/stringutils" | ||
"github.com/docker/docker/pkg/testutil/assert" | ||
) | ||
|
||
type historyCase struct { | ||
historyCtx historyContext | ||
expValue string | ||
call func() string | ||
} | ||
|
||
func TestHistoryContext_ID(t *testing.T) { | ||
id := stringid.GenerateRandomID() | ||
|
||
var ctx historyContext | ||
cases := []historyCase{ | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{ID: id}, | ||
trunc: false, | ||
}, id, ctx.ID, | ||
}, | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{ID: id}, | ||
trunc: true, | ||
}, stringid.TruncateID(id), ctx.ID, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
ctx = c.historyCtx | ||
v := c.call() | ||
if strings.Contains(v, ",") { | ||
compareMultipleValues(t, v, c.expValue) | ||
} else if v != c.expValue { | ||
t.Fatalf("Expected %s, was %s\n", c.expValue, v) | ||
} | ||
} | ||
} | ||
|
||
func TestHistoryContext_CreatedSince(t *testing.T) { | ||
unixTime := time.Now().AddDate(0, 0, -7).Unix() | ||
expected := "7 days ago" | ||
|
||
var ctx historyContext | ||
cases := []historyCase{ | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{Created: unixTime}, | ||
trunc: false, | ||
human: true, | ||
}, expected, ctx.CreatedSince, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
ctx = c.historyCtx | ||
v := c.call() | ||
if strings.Contains(v, ",") { | ||
compareMultipleValues(t, v, c.expValue) | ||
} else if v != c.expValue { | ||
t.Fatalf("Expected %s, was %s\n", c.expValue, v) | ||
} | ||
} | ||
} | ||
|
||
func TestHistoryContext_CreatedBy(t *testing.T) { | ||
withTabs := `/bin/sh -c apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 && echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list && apt-get update && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates nginx=${NGINX_VERSION} nginx-module-xslt nginx-module-geoip nginx-module-image-filter nginx-module-perl nginx-module-njs gettext-base && rm -rf /var/lib/apt/lists/*` | ||
expected := `/bin/sh -c apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 && echo "deb http://nginx.org/packages/mainline/debian/ jessie nginx" >> /etc/apt/sources.list && apt-get update && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates nginx=${NGINX_VERSION} nginx-module-xslt nginx-module-geoip nginx-module-image-filter nginx-module-perl nginx-module-njs gettext-base && rm -rf /var/lib/apt/lists/*` | ||
|
||
var ctx historyContext | ||
cases := []historyCase{ | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{CreatedBy: withTabs}, | ||
trunc: false, | ||
}, expected, ctx.CreatedBy, | ||
}, | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{CreatedBy: withTabs}, | ||
trunc: true, | ||
}, stringutils.Ellipsis(expected, 45), ctx.CreatedBy, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
ctx = c.historyCtx | ||
v := c.call() | ||
if strings.Contains(v, ",") { | ||
compareMultipleValues(t, v, c.expValue) | ||
} else if v != c.expValue { | ||
t.Fatalf("Expected %s, was %s\n", c.expValue, v) | ||
} | ||
} | ||
} | ||
|
||
func TestHistoryContext_Size(t *testing.T) { | ||
size := int64(182964289) | ||
expected := "183MB" | ||
|
||
var ctx historyContext | ||
cases := []historyCase{ | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{Size: size}, | ||
trunc: false, | ||
human: true, | ||
}, expected, ctx.Size, | ||
}, { | ||
historyContext{ | ||
h: image.HistoryResponseItem{Size: size}, | ||
trunc: false, | ||
human: false, | ||
}, strconv.Itoa(182964289), ctx.Size, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
ctx = c.historyCtx | ||
v := c.call() | ||
if strings.Contains(v, ",") { | ||
compareMultipleValues(t, v, c.expValue) | ||
} else if v != c.expValue { | ||
t.Fatalf("Expected %s, was %s\n", c.expValue, v) | ||
} | ||
} | ||
} | ||
|
||
func TestHistoryContext_Comment(t *testing.T) { | ||
comment := "Some comment" | ||
|
||
var ctx historyContext | ||
cases := []historyCase{ | ||
{ | ||
historyContext{ | ||
h: image.HistoryResponseItem{Comment: comment}, | ||
trunc: false, | ||
}, comment, ctx.Comment, | ||
}, | ||
} | ||
|
||
for _, c := range cases { | ||
ctx = c.historyCtx | ||
v := c.call() | ||
if strings.Contains(v, ",") { | ||
compareMultipleValues(t, v, c.expValue) | ||
} else if v != c.expValue { | ||
t.Fatalf("Expected %s, was %s\n", c.expValue, v) | ||
} | ||
} | ||
} | ||
|
||
func TestHistoryContext_Table(t *testing.T) { | ||
out := bytes.NewBufferString("") | ||
unixTime := time.Now().AddDate(0, 0, -1).Unix() | ||
histories := []image.HistoryResponseItem{ | ||
{ID: "imageID1", Created: unixTime, CreatedBy: "/bin/bash ls && npm i && npm run test && karma -c karma.conf.js start && npm start && more commands here && the list goes on", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}}, | ||
{ID: "imageID2", Created: unixTime, CreatedBy: "/bin/bash echo", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}}, | ||
{ID: "imageID3", Created: unixTime, CreatedBy: "/bin/bash ls", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}}, | ||
{ID: "imageID4", Created: unixTime, CreatedBy: "/bin/bash grep", Size: int64(182964289), Comment: "Hi", Tags: []string{"image:tag2"}}, | ||
} | ||
expectedNoTrunc := `IMAGE CREATED CREATED BY SIZE COMMENT | ||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && karma -c karma.conf.js start && npm start && more commands here && the list goes on 183MB Hi | ||
imageID2 24 hours ago /bin/bash echo 183MB Hi | ||
imageID3 24 hours ago /bin/bash ls 183MB Hi | ||
imageID4 24 hours ago /bin/bash grep 183MB Hi | ||
` | ||
expectedTrunc := `IMAGE CREATED CREATED BY SIZE COMMENT | ||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && k... 183MB Hi | ||
imageID2 24 hours ago /bin/bash echo 183MB Hi | ||
imageID3 24 hours ago /bin/bash ls 183MB Hi | ||
imageID4 24 hours ago /bin/bash grep 183MB Hi | ||
` | ||
|
||
contexts := []struct { | ||
context Context | ||
expected string | ||
}{ | ||
{Context{ | ||
Format: NewHistoryFormat("table", false, true), | ||
Trunc: true, | ||
Output: out, | ||
}, | ||
expectedTrunc, | ||
}, | ||
{Context{ | ||
Format: NewHistoryFormat("table", false, true), | ||
Trunc: false, | ||
Output: out, | ||
}, | ||
expectedNoTrunc, | ||
}, | ||
} | ||
|
||
for _, context := range contexts { | ||
HistoryWrite(context.context, true, histories) | ||
assert.Equal(t, out.String(), context.expected) | ||
// Clean buffer | ||
out.Reset() | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it seems that
CreatedAt
andCreatedSince
is pretty much the same except for theago
. And--human
flag does not have an impact to the renderingI am wondering if
CreatedAt
should usetime.Unix(int64(c.h.Created), 0).String()
(just likedocker images --format
) instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up having to separate these again due to some changes in how the headers are output with the formatters. I added to the docs to have both in the template.