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

Commit 9b4f291

Browse files
committed
Introduce --format option for docker app image ls
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 40a670c commit 9b4f291

File tree

1 file changed

+92
-31
lines changed

1 file changed

+92
-31
lines changed

internal/commands/image/list.go

Lines changed: 92 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ package image
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"fmt"
67
"io"
78
"strings"
89
"text/tabwriter"
910
"time"
1011

12+
"github.com/docker/cli/templates"
13+
"github.com/pkg/errors"
14+
1115
"github.com/docker/app/internal/packager"
1216
"github.com/docker/app/internal/relocated"
1317
"github.com/docker/app/internal/store"
@@ -20,13 +24,14 @@ import (
2024
)
2125

2226
type imageListOption struct {
23-
quiet bool
24-
digests bool
27+
quiet bool
28+
digests bool
29+
template string
2530
}
2631

2732
type imageListColumn struct {
2833
header string
29-
value func(p pkg) string
34+
value func(desc imageDesc) string
3035
}
3136

3237
func listCmd(dockerCli command.Cli) *cobra.Command {
@@ -52,6 +57,8 @@ func listCmd(dockerCli command.Cli) *cobra.Command {
5257
flags := cmd.Flags()
5358
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
5459
flags.BoolVarP(&options.digests, "digests", "", false, "Show image digests")
60+
cmd.Flags().StringVarP(&options.template, "format", "f", "", "Format the output using the given syntax or Go template")
61+
cmd.Flags().SetAnnotation("format", "experimentalCLI", []string{"true"}) //nolint:errcheck
5562

5663
return cmd
5764
}
@@ -94,10 +101,32 @@ func getPackages(bundleStore store.BundleStore, references []reference.Reference
94101

95102
func printImages(dockerCli command.Cli, refs []pkg, options imageListOption) error {
96103
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
104+
105+
list := []imageDesc{}
106+
for _, ref := range refs {
107+
list = append(list, getImageDesc(ref))
108+
}
109+
110+
if options.template == "json" {
111+
bytes, err := json.MarshalIndent(list, "", " ")
112+
if err != nil {
113+
return errors.Errorf("Failed to marshall json: %s", err)
114+
}
115+
_, err = dockerCli.Out().Write(bytes)
116+
return err
117+
}
118+
if options.template != "" {
119+
tmpl, err := templates.Parse(options.template)
120+
if err != nil {
121+
return errors.Errorf("Template parsing error: %s", err)
122+
}
123+
return tmpl.Execute(dockerCli.Out(), list)
124+
}
125+
97126
listColumns := getImageListColumns(options)
98127
printHeaders(w, listColumns)
99-
for _, ref := range refs {
100-
printValues(w, ref, listColumns)
128+
for _, desc := range list {
129+
printValues(w, desc, listColumns)
101130
}
102131

103132
return w.Flush()
@@ -137,55 +166,87 @@ func printHeaders(w io.Writer, listColumns []imageListColumn) {
137166
fmt.Fprintln(w, strings.Join(headers, "\t"))
138167
}
139168

140-
func printValues(w io.Writer, ref pkg, listColumns []imageListColumn) {
169+
func printValues(w io.Writer, desc imageDesc, listColumns []imageListColumn) {
141170
var values []string
142171
for _, column := range listColumns {
143-
values = append(values, column.value(ref))
172+
values = append(values, column.value(desc))
144173
}
145174
fmt.Fprintln(w, strings.Join(values, "\t"))
146175
}
147176

177+
type imageDesc struct {
178+
ID string `json:"id,omitempty"`
179+
Name string `json:"name,omitempty"`
180+
Repository string `json:"repository,omitempty"`
181+
Tag string `json:"tag,omitempty"`
182+
Digest string `json:"digest,omitempty"`
183+
Created time.Duration `json:"created,omitempty"`
184+
}
185+
186+
func getImageDesc(p pkg) imageDesc {
187+
var id string
188+
id, _ = getImageID(p)
189+
var repository string
190+
if n, ok := p.ref.(reference.Named); ok {
191+
repository = reference.FamiliarName(n)
192+
}
193+
var tag string
194+
if t, ok := p.ref.(reference.Tagged); ok {
195+
tag = t.Tag()
196+
}
197+
var digest string
198+
if t, ok := p.ref.(reference.Digested); ok {
199+
digest = t.Digest().String()
200+
}
201+
var created time.Duration
202+
if payload, err := packager.CustomPayload(p.bundle.Bundle); err == nil {
203+
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok {
204+
created = time.Now().UTC().Sub(createdPayload.CreatedTime())
205+
}
206+
}
207+
return imageDesc{
208+
ID: id,
209+
Name: p.bundle.Name,
210+
Repository: repository,
211+
Tag: tag,
212+
Digest: digest,
213+
Created: created,
214+
}
215+
}
216+
148217
func getImageListColumns(options imageListOption) []imageListColumn {
149218
columns := []imageListColumn{
150-
{"REPOSITORY", func(p pkg) string {
151-
if n, ok := p.ref.(reference.Named); ok {
152-
return reference.FamiliarName(n)
219+
{"REPOSITORY", func(desc imageDesc) string {
220+
if desc.Repository != "" {
221+
return desc.Repository
153222
}
154223
return "<none>"
155224
}},
156-
{"TAG", func(p pkg) string {
157-
if t, ok := p.ref.(reference.Tagged); ok {
158-
return t.Tag()
225+
{"TAG", func(desc imageDesc) string {
226+
if desc.Tag != "" {
227+
return desc.Tag
159228
}
160229
return "<none>"
161230
}},
162231
}
163232
if options.digests {
164-
columns = append(columns, imageListColumn{"DIGEST", func(p pkg) string {
165-
if t, ok := p.ref.(reference.Digested); ok {
166-
return t.Digest().String()
233+
columns = append(columns, imageListColumn{"DIGEST", func(desc imageDesc) string {
234+
if desc.Digest != "" {
235+
return desc.Digest
167236
}
168237
return "<none>"
169238
}})
170239
}
171240
columns = append(columns,
172-
imageListColumn{"APP IMAGE ID", func(p pkg) string {
173-
id, err := getImageID(p)
174-
if err != nil {
175-
return ""
176-
}
177-
return id
241+
imageListColumn{"APP IMAGE ID", func(desc imageDesc) string {
242+
return desc.ID
178243
}},
179-
imageListColumn{"APP NAME", func(p pkg) string {
180-
return p.bundle.Name
244+
imageListColumn{"APP NAME", func(desc imageDesc) string {
245+
return desc.Name
181246
}},
182-
imageListColumn{"CREATED", func(p pkg) string {
183-
payload, err := packager.CustomPayload(p.bundle.Bundle)
184-
if err != nil {
185-
return ""
186-
}
187-
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok {
188-
return units.HumanDuration(time.Now().UTC().Sub(createdPayload.CreatedTime())) + " ago"
247+
imageListColumn{"CREATED", func(desc imageDesc) string {
248+
if desc.Created > 0 {
249+
return units.HumanDuration(desc.Created) + " ago"
189250
}
190251
return ""
191252
}},

0 commit comments

Comments
 (0)