@@ -10,11 +10,13 @@ import (
1010 "github.com/containerd/platforms"
1111 "github.com/docker/cli/cli/command"
1212 "github.com/docker/cli/cli/streams"
13+ "github.com/docker/cli/internal/tui"
1314 "github.com/docker/docker/api/types/filters"
1415 imagetypes "github.com/docker/docker/api/types/image"
1516 "github.com/docker/docker/pkg/stringid"
1617 "github.com/docker/go-units"
1718 "github.com/morikuni/aec"
19+ "github.com/opencontainers/go-digest"
1820)
1921
2022type treeOptions struct {
@@ -42,6 +44,8 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
4244 view := treeView {
4345 images : make ([]topImage , 0 , len (images )),
4446 }
47+ attested := make (map [digest.Digest ]bool )
48+
4549 for _ , img := range images {
4650 details := imageDetails {
4751 ID : img .ID ,
@@ -52,6 +56,10 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
5256 var totalContent int64
5357 children := make ([]subImage , 0 , len (img .Manifests ))
5458 for _ , im := range img .Manifests {
59+ if im .Kind == imagetypes .ManifestKindAttestation {
60+ attested [im .AttestationData .For ] = true
61+ continue
62+ }
5563 if im .Kind != imagetypes .ManifestKindImage {
5664 continue
5765 }
@@ -89,6 +97,15 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
8997 })
9098 }
9199
100+ for _ , img := range view .images {
101+ for idx , sub := range img .Children {
102+ if attested [digest .Digest (sub .Details .ID )] {
103+ sub .Details .Attested = true
104+ img .Children [idx ] = sub
105+ }
106+ }
107+ }
108+
92109 sort .Slice (view .images , func (i , j int ) bool {
93110 return view .images [i ].created > view .images [j ].created
94111 })
@@ -101,6 +118,7 @@ type imageDetails struct {
101118 DiskUsage string
102119 InUse bool
103120 ContentSize string
121+ Attested bool
104122}
105123
106124type topImage struct {
@@ -119,6 +137,7 @@ type subImage struct {
119137
120138const columnSpacing = 3
121139
140+ //nolint:gocyclo
122141func printImageTree (dockerCLI command.Cli , view treeView ) error {
123142 out := dockerCLI .Out ()
124143 _ , width := out .GetTtySize ()
@@ -133,18 +152,45 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
133152 headerColor := aec .NewBuilder (aec .DefaultF , aec .Bold ).ANSI
134153 topNameColor := aec .NewBuilder (aec .BlueF , aec .Bold ).ANSI
135154 normalColor := aec .NewBuilder (aec .DefaultF ).ANSI
136- greenColor := aec .NewBuilder (aec .GreenF ).ANSI
137155 untaggedColor := aec .NewBuilder (aec .Faint ).ANSI
156+ var noneColor aec.ANSI = noColor {}
138157 if ! out .IsTerminal () {
139158 headerColor = noColor {}
140159 topNameColor = noColor {}
141160 normalColor = noColor {}
142- greenColor = noColor {}
143161 warningColor = noColor {}
144162 untaggedColor = noColor {}
145163 }
164+ isTerm := out .IsTerminal ()
146165
147166 _ , _ = fmt .Fprintln (out , warningColor .Apply ("WARNING: This is an experimental feature. The output may change and shouldn't be depended on." ))
167+
168+ chipAttested := tui.Printable {
169+ Plain : "A" ,
170+ Fancy : tui .Chip (233 , 49 , " A " ),
171+ }
172+ chipInUse := tui.Printable {
173+ Plain : "U" ,
174+ Fancy : tui .Chip (255 , 177 , " U " ),
175+ }
176+
177+ var legend string
178+ // Print legend
179+ legend += tui.Printable {
180+ Plain : "ADDITIONAL Legend: " ,
181+ Fancy : aec .Bold .Apply ("Legend → " ),
182+ }.String (isTerm )
183+ legend += chipAttested .String (isTerm )
184+ legend += " Attested | "
185+ legend += chipInUse .String (isTerm )
186+ legend += " In Use"
187+
188+ r := int (width ) - countChars (legend )
189+ if r < 0 {
190+ r = 0
191+ }
192+ _ , _ = fmt .Fprintln (out , strings .Repeat (" " , r )+ legend )
193+
148194 _ , _ = fmt .Fprintln (out , "" )
149195
150196 columns := []imgColumn {
@@ -178,15 +224,19 @@ func printImageTree(dockerCLI command.Cli, view treeView) error {
178224 },
179225 },
180226 {
181- Title : "In Use " ,
182- Align : alignCenter ,
183- Width : 6 ,
184- Color : & greenColor ,
227+ Title : "Additional " ,
228+ Align : alignLeft ,
229+ Width : len ( "Additional" ) ,
230+ Color : & noneColor ,
185231 DetailsValue : func (d * imageDetails ) string {
232+ var out string
233+ if d .Attested {
234+ out += chipAttested .String (isTerm )
235+ }
186236 if d .InUse {
187- return "✔"
237+ out += chipInUse . String ( isTerm )
188238 }
189- return " "
239+ return out
190240 },
191241 },
192242 }
@@ -307,11 +357,28 @@ type imgColumn struct {
307357}
308358
309359func truncateRunes (s string , length int ) string {
310- runes := []rune (s )
311- if len (runes ) > length {
312- return string (runes [:length - 3 ]) + "..."
360+ out := make ([]rune , 0 , length )
361+ ln := 0
362+ inEscape := false
363+ for _ , r := range s {
364+ if r == '\x1b' {
365+ inEscape = true
366+ continue
367+ }
368+ if inEscape {
369+ if r == 'm' {
370+ inEscape = false
371+ }
372+ continue
373+ }
374+
375+ ln += 1
376+ if ln == length - 3 {
377+ return string (out ) + "..."
378+ }
379+ out = append (out , r )
313380 }
314- return s
381+ return string ( out )
315382}
316383
317384func (h imgColumn ) Print (clr aec.ANSI , s string ) string {
@@ -325,8 +392,26 @@ func (h imgColumn) Print(clr aec.ANSI, s string) string {
325392 return h .PrintL (clr , s )
326393}
327394
395+ func cleanANSI (s string ) string {
396+ for {
397+ start := strings .Index (s , "\x1b " )
398+ if start == - 1 {
399+ return s
400+ }
401+ end := strings .Index (s [start :], "m" )
402+ if end == - 1 {
403+ return s
404+ }
405+ s = s [:start ] + s [start + end + 1 :]
406+ }
407+ }
408+
409+ func countChars (s string ) int {
410+ return utf8 .RuneCountInString (cleanANSI (s ))
411+ }
412+
328413func (h imgColumn ) PrintC (clr aec.ANSI , s string ) string {
329- ln := utf8 . RuneCountInString (s )
414+ ln := countChars (s )
330415
331416 if ln > h .Width {
332417 return clr .Apply (truncateRunes (s , h .Width ))
@@ -341,7 +426,7 @@ func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
341426}
342427
343428func (h imgColumn ) PrintL (clr aec.ANSI , s string ) string {
344- ln := utf8 . RuneCountInString (s )
429+ ln := countChars (s )
345430 if ln > h .Width {
346431 return clr .Apply (truncateRunes (s , h .Width ))
347432 }
@@ -350,7 +435,7 @@ func (h imgColumn) PrintL(clr aec.ANSI, s string) string {
350435}
351436
352437func (h imgColumn ) PrintR (clr aec.ANSI , s string ) string {
353- ln := utf8 . RuneCountInString (s )
438+ ln := countChars (s )
354439 if ln > h .Width {
355440 return clr .Apply (truncateRunes (s , h .Width ))
356441 }
0 commit comments