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

Commit 8acd0a8

Browse files
authored
Merge pull request #709 from zappy-shu/add-digest-flag-to-ls
Rework docker app image ls and add the --digests flag
2 parents f141d47 + 24ced43 commit 8acd0a8

File tree

3 files changed

+205
-91
lines changed

3 files changed

+205
-91
lines changed

e2e/images_test.go

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import (
44
"bufio"
55
"fmt"
66
"path/filepath"
7+
"regexp"
78
"strings"
89
"testing"
910

1011
"gotest.tools/assert"
11-
"gotest.tools/fs"
1212
"gotest.tools/icmd"
1313
)
1414

@@ -22,10 +22,20 @@ func insertBundles(t *testing.T, cmd icmd.Cmd, info dindSwarmAndRegistryInfo) {
2222
icmd.RunCmd(cmd).Assert(t, icmd.Success)
2323
}
2424

25+
func assertImageListOutput(t *testing.T, cmd icmd.Cmd, expected string) {
26+
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
27+
match, _ := regexp.MatchString(expected, result.Stdout())
28+
assert.Assert(t, match)
29+
}
30+
2531
func expectImageListOutput(t *testing.T, cmd icmd.Cmd, output string) {
2632
cmd.Command = dockerCli.Command("app", "image", "ls")
27-
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
28-
assert.Equal(t, result.Stdout(), output)
33+
assertImageListOutput(t, cmd, output)
34+
}
35+
36+
func expectImageListDigestsOutput(t *testing.T, cmd icmd.Cmd, output string) {
37+
cmd.Command = dockerCli.Command("app", "image", "ls", "--digests")
38+
assertImageListOutput(t, cmd, output)
2939
}
3040

3141
func verifyImageIDListOutput(t *testing.T, cmd icmd.Cmd, count int, distinct int) {
@@ -48,36 +58,44 @@ func verifyImageIDListOutput(t *testing.T, cmd icmd.Cmd, count int, distinct int
4858
func TestImageList(t *testing.T) {
4959
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
5060
cmd := info.configuredCmd
51-
dir := fs.NewDir(t, "")
52-
defer dir.Remove()
5361

5462
insertBundles(t, cmd, info)
5563

56-
expected := `APP IMAGE APP NAME
57-
%s push-pull
58-
a-simple-app:latest simple
59-
b-simple-app:latest simple
64+
expected := `REPOSITORY TAG APP IMAGE ID APP NAME
65+
%s latest [a-f0-9]{12} push-pull
66+
a-simple-app latest [a-f0-9]{12} simple
67+
b-simple-app latest [a-f0-9]{12} simple
6068
`
61-
expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp:latest")
69+
expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
6270
expectImageListOutput(t, cmd, expectedOutput)
6371
})
6472
}
6573

6674
func TestImageListQuiet(t *testing.T) {
6775
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
6876
cmd := info.configuredCmd
69-
dir := fs.NewDir(t, "")
70-
defer dir.Remove()
7177
insertBundles(t, cmd, info)
7278
verifyImageIDListOutput(t, cmd, 3, 2)
7379
})
7480
}
7581

82+
func TestImageListDigests(t *testing.T) {
83+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
84+
cmd := info.configuredCmd
85+
insertBundles(t, cmd, info)
86+
expected := `REPOSITORY TAG DIGEST APP IMAGE ID APP NAME
87+
%s latest <none> [a-f0-9]{12} push-pull
88+
a-simple-app latest <none> [a-f0-9]{12} simple
89+
b-simple-app latest <none> [a-f0-9]{12} simple
90+
`
91+
expectedOutput := fmt.Sprintf(expected, info.registryAddress+"/c-myapp")
92+
expectImageListDigestsOutput(t, cmd, expectedOutput)
93+
})
94+
}
95+
7696
func TestImageRm(t *testing.T) {
7797
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
7898
cmd := info.configuredCmd
79-
dir := fs.NewDir(t, "")
80-
defer dir.Remove()
8199

82100
insertBundles(t, cmd, info)
83101

@@ -100,7 +118,7 @@ Deleted: b-simple-app:latest`,
100118
Err: `b-simple-app:latest: reference not found`,
101119
})
102120

103-
expectedOutput := "APP IMAGE APP NAME\n"
121+
expectedOutput := "REPOSITORY TAG APP IMAGE ID APP NAME\n"
104122
expectImageListOutput(t, cmd, expectedOutput)
105123
})
106124
}
@@ -118,8 +136,8 @@ func TestImageTag(t *testing.T) {
118136
cmd.Command = dockerCli.Command("app", "build", "--tag", "a-simple-app", filepath.Join("testdata", "simple"))
119137
icmd.RunCmd(cmd).Assert(t, icmd.Success)
120138

121-
singleImageExpectation := `APP IMAGE APP NAME
122-
a-simple-app:latest simple
139+
singleImageExpectation := `REPOSITORY TAG APP IMAGE ID APP NAME
140+
a-simple-app latest [a-f0-9]{12} simple
123141
`
124142
expectImageListOutput(t, cmd, singleImageExpectation)
125143

@@ -168,63 +186,63 @@ a-simple-app:latest simple
168186
// tag image with only names
169187
dockerAppImageTag("a-simple-app", "b-simple-app")
170188
icmd.RunCmd(cmd).Assert(t, icmd.Success)
171-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
172-
a-simple-app:latest simple
173-
b-simple-app:latest simple
189+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
190+
a-simple-app latest [a-f0-9]{12} simple
191+
b-simple-app latest [a-f0-9]{12} simple
174192
`)
175193

176194
// target tag
177195
dockerAppImageTag("a-simple-app", "a-simple-app:0.1")
178196
icmd.RunCmd(cmd).Assert(t, icmd.Success)
179-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
180-
a-simple-app:0.1 simple
181-
a-simple-app:latest simple
182-
b-simple-app:latest simple
197+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
198+
a-simple-app 0.1 [a-f0-9]{12} simple
199+
a-simple-app latest [a-f0-9]{12} simple
200+
b-simple-app latest [a-f0-9]{12} simple
183201
`)
184202

185203
// source tag
186204
dockerAppImageTag("a-simple-app:0.1", "c-simple-app")
187205
icmd.RunCmd(cmd).Assert(t, icmd.Success)
188-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
189-
a-simple-app:0.1 simple
190-
a-simple-app:latest simple
191-
b-simple-app:latest simple
192-
c-simple-app:latest simple
206+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
207+
a-simple-app 0.1 [a-f0-9]{12} simple
208+
a-simple-app latest [a-f0-9]{12} simple
209+
b-simple-app latest [a-f0-9]{12} simple
210+
c-simple-app latest [a-f0-9]{12} simple
193211
`)
194212

195213
// source and target tags
196214
dockerAppImageTag("a-simple-app:0.1", "b-simple-app:0.2")
197215
icmd.RunCmd(cmd).Assert(t, icmd.Success)
198-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
199-
a-simple-app:0.1 simple
200-
a-simple-app:latest simple
201-
b-simple-app:0.2 simple
202-
b-simple-app:latest simple
203-
c-simple-app:latest simple
216+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
217+
a-simple-app 0.1 [a-f0-9]{12} simple
218+
a-simple-app latest [a-f0-9]{12} simple
219+
b-simple-app 0.2 [a-f0-9]{12} simple
220+
b-simple-app latest [a-f0-9]{12} simple
221+
c-simple-app latest [a-f0-9]{12} simple
204222
`)
205223

206224
// given a new application
207225
cmd.Command = dockerCli.Command("app", "build", "--tag", "push-pull", filepath.Join("testdata", "push-pull"))
208226
icmd.RunCmd(cmd).Assert(t, icmd.Success)
209-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
210-
a-simple-app:0.1 simple
211-
a-simple-app:latest simple
212-
b-simple-app:0.2 simple
213-
b-simple-app:latest simple
214-
c-simple-app:latest simple
215-
push-pull:latest push-pull
227+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
228+
a-simple-app 0.1 [a-f0-9]{12} simple
229+
a-simple-app latest [a-f0-9]{12} simple
230+
b-simple-app 0.2 [a-f0-9]{12} simple
231+
b-simple-app latest [a-f0-9]{12} simple
232+
c-simple-app latest [a-f0-9]{12} simple
233+
push-pull latest [a-f0-9]{12} push-pull
216234
`)
217235

218236
// can be tagged to an existing tag
219237
dockerAppImageTag("push-pull", "b-simple-app:0.2")
220238
icmd.RunCmd(cmd).Assert(t, icmd.Success)
221-
expectImageListOutput(t, cmd, `APP IMAGE APP NAME
222-
a-simple-app:0.1 simple
223-
a-simple-app:latest simple
224-
b-simple-app:0.2 push-pull
225-
b-simple-app:latest simple
226-
c-simple-app:latest simple
227-
push-pull:latest push-pull
239+
expectImageListOutput(t, cmd, `REPOSITORY TAG APP IMAGE ID APP NAME
240+
a-simple-app 0.1 [a-f0-9]{12} simple
241+
a-simple-app latest [a-f0-9]{12} simple
242+
b-simple-app 0.2 [a-f0-9]{12} push-pull
243+
b-simple-app latest [a-f0-9]{12} simple
244+
c-simple-app latest [a-f0-9]{12} simple
245+
push-pull latest [a-f0-9]{12} push-pull
228246
`)
229247
})
230248
}

internal/commands/image/list.go

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import (
1717
)
1818

1919
type imageListOption struct {
20-
quiet bool
20+
quiet bool
21+
digests bool
22+
}
23+
24+
type imageListColumn struct {
25+
header string
26+
value func(p pkg) string
2127
}
2228

2329
func listCmd(dockerCli command.Cli) *cobra.Command {
@@ -42,6 +48,7 @@ func listCmd(dockerCli command.Cli) *cobra.Command {
4248
}
4349
flags := cmd.Flags()
4450
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
51+
flags.BoolVarP(&options.digests, "digests", "", false, "Show image digests")
4552

4653
return cmd
4754
}
@@ -60,7 +67,7 @@ func runList(dockerCli command.Cli, options imageListOption, bundleStore store.B
6067
if options.quiet {
6168
return printImageIDs(dockerCli, pkgs)
6269
}
63-
return printImages(dockerCli, pkgs)
70+
return printImages(dockerCli, pkgs, options)
6471
}
6572

6673
func getPackages(bundleStore store.BundleStore, references []reference.Reference) ([]pkg, error) {
@@ -82,12 +89,12 @@ func getPackages(bundleStore store.BundleStore, references []reference.Reference
8289
return packages, nil
8390
}
8491

85-
func printImages(dockerCli command.Cli, refs []pkg) error {
92+
func printImages(dockerCli command.Cli, refs []pkg, options imageListOption) error {
8693
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
87-
88-
printHeaders(w)
94+
listColumns := getImageListColumns(options)
95+
printHeaders(w, listColumns)
8996
for _, ref := range refs {
90-
printValues(w, ref)
97+
printValues(w, ref, listColumns)
9198
}
9299

93100
return w.Flush()
@@ -97,49 +104,81 @@ func printImageIDs(dockerCli command.Cli, refs []pkg) error {
97104
var buf bytes.Buffer
98105

99106
for _, ref := range refs {
100-
id, ok := ref.ref.(store.ID)
101-
if !ok {
102-
var err error
103-
id, err = store.FromBundle(ref.bundle)
104-
if err != nil {
105-
return err
106-
}
107+
id, err := getImageID(ref)
108+
if err != nil {
109+
return err
107110
}
108-
fmt.Fprintln(&buf, stringid.TruncateID(id.String()))
111+
fmt.Fprintln(&buf, id)
109112
}
110113
fmt.Fprint(dockerCli.Out(), buf.String())
111114
return nil
112115
}
113116

114-
func printHeaders(w io.Writer) {
117+
func getImageID(p pkg) (string, error) {
118+
id, ok := p.ref.(store.ID)
119+
if !ok {
120+
var err error
121+
id, err = store.FromBundle(p.bundle)
122+
if err != nil {
123+
return "", err
124+
}
125+
}
126+
return stringid.TruncateID(id.String()), nil
127+
}
128+
129+
func printHeaders(w io.Writer, listColumns []imageListColumn) {
115130
var headers []string
116131
for _, column := range listColumns {
117132
headers = append(headers, column.header)
118133
}
119134
fmt.Fprintln(w, strings.Join(headers, "\t"))
120135
}
121136

122-
func printValues(w io.Writer, ref pkg) {
137+
func printValues(w io.Writer, ref pkg, listColumns []imageListColumn) {
123138
var values []string
124139
for _, column := range listColumns {
125140
values = append(values, column.value(ref))
126141
}
127142
fmt.Fprintln(w, strings.Join(values, "\t"))
128143
}
129144

130-
var (
131-
listColumns = []struct {
132-
header string
133-
value func(p pkg) string
134-
}{
135-
{"APP IMAGE", func(p pkg) string {
145+
func getImageListColumns(options imageListOption) []imageListColumn {
146+
columns := []imageListColumn{
147+
{"REPOSITORY", func(p pkg) string {
148+
if n, ok := p.ref.(reference.Named); ok {
149+
return reference.FamiliarName(n)
150+
}
136151
return reference.FamiliarString(p.ref)
137152
}},
138-
{"APP NAME", func(p pkg) string {
139-
return p.bundle.Name
153+
{"TAG", func(p pkg) string {
154+
if t, ok := p.ref.(reference.Tagged); ok {
155+
return t.Tag()
156+
}
157+
return "<none>"
140158
}},
141159
}
142-
)
160+
if options.digests {
161+
columns = append(columns, imageListColumn{"DIGEST", func(p pkg) string {
162+
if t, ok := p.ref.(reference.Digested); ok {
163+
return t.Digest().String()
164+
}
165+
return "<none>"
166+
}})
167+
}
168+
columns = append(columns,
169+
imageListColumn{"APP IMAGE ID", func(p pkg) string {
170+
id, err := getImageID(p)
171+
if err != nil {
172+
return ""
173+
}
174+
return id
175+
}},
176+
imageListColumn{"APP NAME", func(p pkg) string {
177+
return p.bundle.Name
178+
}},
179+
)
180+
return columns
181+
}
143182

144183
type pkg struct {
145184
ref reference.Reference

0 commit comments

Comments
 (0)