Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit df7ae09

Browse files
Merge pull request #743 from ndeloof/template
Support format option in docker app ls
2 parents 355ab4f + d5e1ff5 commit df7ae09

File tree

6 files changed

+270
-175
lines changed

6 files changed

+270
-175
lines changed

Gopkg.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/images_test.go

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ func TestImageList(t *testing.T) {
5959

6060
insertBundles(t, cmd)
6161

62-
expected := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
63-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
64-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
65-
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
62+
expected := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
63+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
64+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
65+
my.registry:5000/c-myapp latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
6666
`
67+
6768
expectImageListOutput(t, cmd, expected)
6869
})
6970
}
@@ -80,10 +81,10 @@ func TestImageListDigests(t *testing.T) {
8081
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
8182
cmd := info.configuredCmd
8283
insertBundles(t, cmd)
83-
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
84-
a-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
85-
b-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago
86-
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
84+
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME CREATED
85+
a-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
86+
b-simple-app latest <none> [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
87+
my.registry:5000/c-myapp latest <none> [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
8788
`
8889
expectImageListDigestsOutput(t, cmd, expected)
8990
})
@@ -114,7 +115,7 @@ Deleted: b-simple-app:latest`,
114115
Err: `b-simple-app:latest: reference not found`,
115116
})
116117

117-
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED\n"
118+
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME CREATED \n"
118119
expectImageListOutput(t, cmd, expectedOutput)
119120
})
120121
}
@@ -132,8 +133,8 @@ func TestImageTag(t *testing.T) {
132133
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
133134
icmd.RunCmd(cmd).Assert(t, icmd.Success)
134135

135-
singleImageExpectation := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
136-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
136+
singleImageExpectation := `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
137+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
137138
`
138139
expectImageListOutput(t, cmd, singleImageExpectation)
139140

@@ -182,63 +183,63 @@ a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
182183
// tag image with only names
183184
dockerAppImageTag("a-simple-app", "b-simple-app")
184185
icmd.RunCmd(cmd).Assert(t, icmd.Success)
185-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
186-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
187-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
186+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
187+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
188+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
188189
`)
189190

190191
// target tag
191192
dockerAppImageTag("a-simple-app", "a-simple-app:0.1")
192193
icmd.RunCmd(cmd).Assert(t, icmd.Success)
193-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
194-
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
195-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
196-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
194+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
195+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
196+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
197+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
197198
`)
198199

199200
// source tag
200201
dockerAppImageTag("a-simple-app:0.1", "c-simple-app")
201202
icmd.RunCmd(cmd).Assert(t, icmd.Success)
202-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
203-
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
204-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
205-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
206-
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
203+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
204+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
205+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
206+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
207+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
207208
`)
208209

209210
// source and target tags
210211
dockerAppImageTag("a-simple-app:0.1", "b-simple-app:0.2")
211212
icmd.RunCmd(cmd).Assert(t, icmd.Success)
212-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
213-
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
214-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
215-
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
216-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
217-
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
213+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
214+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
215+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
216+
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
217+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
218+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
218219
`)
219220

220221
// given a new application
221222
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
222223
icmd.RunCmd(cmd).Assert(t, icmd.Success)
223-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
224-
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
225-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
226-
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago
227-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
228-
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
229-
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
224+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
225+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
226+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
227+
b-simple-app 0.2 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
228+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
229+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
230+
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
230231
`)
231232

232233
// can be tagged to an existing tag
233234
dockerAppImageTag("push-pull", "b-simple-app:0.2")
234235
icmd.RunCmd(cmd).Assert(t, icmd.Success)
235-
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED
236-
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago
237-
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
238-
b-simple-app 0.2 [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
239-
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
240-
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago
241-
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago
236+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME CREATED[ ]*
237+
a-simple-app 0.1 [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
238+
a-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
239+
b-simple-app 0.2 [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
240+
b-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
241+
c-simple-app latest [a-f0-9]{12} simple [La-z0-9 ]+ ago[ ]*
242+
push-pull latest [a-f0-9]{12} push-pull [La-z0-9 ]+ ago[ ]*
242243
`)
243244
})
244245
}

internal/commands/image/formatter.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package image
2+
3+
import (
4+
"time"
5+
6+
"github.com/docker/cli/cli/command/formatter"
7+
"github.com/docker/docker/pkg/stringid"
8+
"github.com/docker/go-units"
9+
)
10+
11+
const (
12+
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Name}}\t{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\t"
13+
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.Name}}\t\t{{if .CreatedSince }}{{.CreatedSince}}{{else}}N/A{{end}}\t"
14+
15+
imageIDHeader = "APP IMAGE ID"
16+
repositoryHeader = "REPOSITORY"
17+
tagHeader = "TAG"
18+
digestHeader = "DIGEST"
19+
imageNameHeader = "APP NAME"
20+
)
21+
22+
// NewImageFormat returns a format for rendering an ImageContext
23+
func NewImageFormat(source string, quiet bool, digest bool) formatter.Format {
24+
switch source {
25+
case formatter.TableFormatKey:
26+
switch {
27+
case quiet:
28+
return formatter.DefaultQuietFormat
29+
case digest:
30+
return defaultImageTableFormatWithDigest
31+
default:
32+
return defaultImageTableFormat
33+
}
34+
}
35+
36+
format := formatter.Format(source)
37+
if format.IsTable() && digest && !format.Contains("{{.Digest}}") {
38+
format += "\t{{.Digest}}"
39+
}
40+
return format
41+
}
42+
43+
// Write writes the formatter images using the ImageContext
44+
func Write(ctx formatter.Context, images []imageDesc) error {
45+
render := func(format func(subContext formatter.SubContext) error) error {
46+
return imageFormat(ctx, images, format)
47+
}
48+
return ctx.Write(newImageContext(), render)
49+
}
50+
51+
func imageFormat(ctx formatter.Context, images []imageDesc, format func(subContext formatter.SubContext) error) error {
52+
for _, image := range images {
53+
img := &imageContext{
54+
trunc: ctx.Trunc,
55+
i: image}
56+
if err := format(img); err != nil {
57+
return err
58+
}
59+
}
60+
return nil
61+
}
62+
63+
type imageContext struct {
64+
formatter.HeaderContext
65+
trunc bool
66+
i imageDesc
67+
}
68+
69+
func newImageContext() *imageContext {
70+
imageCtx := imageContext{}
71+
imageCtx.Header = formatter.SubHeaderContext{
72+
"ID": imageIDHeader,
73+
"Name": imageNameHeader,
74+
"Repository": repositoryHeader,
75+
"Tag": tagHeader,
76+
"Digest": digestHeader,
77+
"CreatedSince": formatter.CreatedSinceHeader,
78+
}
79+
return &imageCtx
80+
}
81+
82+
func (c *imageContext) MarshalJSON() ([]byte, error) {
83+
return formatter.MarshalJSON(c)
84+
}
85+
86+
func (c *imageContext) ID() string {
87+
if c.trunc {
88+
return stringid.TruncateID(c.i.ID)
89+
}
90+
return c.i.ID
91+
}
92+
93+
func (c *imageContext) Name() string {
94+
if c.i.Name == "" {
95+
return "<none>"
96+
}
97+
return c.i.Name
98+
}
99+
100+
func (c *imageContext) Repository() string {
101+
if c.i.Repository == "" {
102+
return "<none>"
103+
}
104+
return c.i.Repository
105+
}
106+
107+
func (c *imageContext) Tag() string {
108+
if c.i.Tag == "" {
109+
return "<none>"
110+
}
111+
return c.i.Tag
112+
}
113+
114+
func (c *imageContext) Digest() string {
115+
if c.i.Digest == "" {
116+
return "<none>"
117+
}
118+
return c.i.Digest
119+
}
120+
121+
func (c *imageContext) CreatedSince() string {
122+
if c.i.Created.IsZero() {
123+
return ""
124+
}
125+
return units.HumanDuration(time.Now().UTC().Sub(c.i.Created)) + " ago"
126+
}

0 commit comments

Comments
 (0)