@@ -2,16 +2,27 @@ package history
22
33import (
44 "context"
5+ "fmt"
6+ "io"
57 "log"
8+ "os"
9+ "path/filepath"
610 "slices"
11+ "strconv"
12+ "strings"
13+ "text/tabwriter"
14+ "time"
715
16+ "github.com/containerd/platforms"
817 "github.com/docker/buildx/builder"
918 "github.com/docker/buildx/localstate"
1019 "github.com/docker/buildx/util/cobrautil/completion"
1120 "github.com/docker/buildx/util/confutil"
1221 "github.com/docker/cli/cli/command"
1322 "github.com/pkg/errors"
1423 "github.com/spf13/cobra"
24+ "github.com/tonistiigi/go-csvvalue"
25+ "google.golang.org/grpc/codes"
1526)
1627
1728type inspectOptions struct {
@@ -64,21 +75,176 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
6475 log .Printf ("rec %+v" , rec )
6576 log .Printf ("st %+v" , st )
6677
67- // Context
68- // Dockerfile
69- // Target
70- // VCS Repo / Commit
71- // Platform
78+ tw := tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
7279
73- // Started
74- // Duration
75- // Number of steps
76- // Cached steps
77- // Status
80+ attrs := rec .FrontendAttrs
81+ delete (attrs , "frontend.caps" )
7882
79- // build-args
80- // exporters (image)
83+ writeAttr := func (k , name string , f func (v string ) (string , bool )) {
84+ if v , ok := attrs [k ]; ok {
85+ if f != nil {
86+ v , ok = f (v )
87+ }
88+ if ok {
89+ fmt .Fprintf (tw , "%s:\t %s\n " , name , v )
90+ }
91+ }
92+ delete (attrs , k )
93+ }
94+
95+ var context string
96+ var dockerfile string
97+ if st != nil {
98+ context = st .LocalPath
99+ dockerfile = st .DockerfilePath
100+ wd , _ := os .Getwd ()
101+
102+ if dockerfile != "" && dockerfile != "-" {
103+ if rel , err := filepath .Rel (context , dockerfile ); err == nil {
104+ dockerfile = rel
105+ }
106+ }
107+ if context != "" {
108+ if rel , err := filepath .Rel (wd , context ); err == nil {
109+ context = rel
110+ }
111+ }
112+ }
113+
114+ if v , ok := attrs ["context" ]; ok && context == "" {
115+ delete (attrs , "context" )
116+ context = v
117+ }
118+ if dockerfile == "" {
119+ if v , ok := attrs ["filename" ]; ok {
120+ dockerfile = v
121+ if dfdir , ok := attrs ["vcs:localdir:dockerfile" ]; ok {
122+ dockerfile = filepath .Join (dfdir , dockerfile )
123+ }
124+ }
125+ }
126+ delete (attrs , "filename" )
127+
128+ if context != "" {
129+ fmt .Fprintf (tw , "Context:\t %s\n " , context )
130+ }
131+ if dockerfile != "" {
132+ fmt .Fprintf (tw , "Dockerfile:\t %s\n " , dockerfile )
133+ }
134+ if _ , ok := attrs ["context" ]; ! ok {
135+ if src , ok := attrs ["vcs:source" ]; ok {
136+ fmt .Fprintf (tw , "VCS Repository:\t %s\n " , src )
137+ }
138+ if rev , ok := attrs ["vcs:revision" ]; ok {
139+ fmt .Fprintf (tw , "VCS Revision:\t %s\n " , rev )
140+ }
141+ }
142+
143+ writeAttr ("target" , "Target" , nil )
144+ writeAttr ("platform" , "Platform" , func (v string ) (string , bool ) {
145+ return tryParseValue (v , func (v string ) (string , error ) {
146+ var pp []string
147+ for _ , v := range strings .Split (v , "," ) {
148+ p , err := platforms .Parse (v )
149+ if err != nil {
150+ return "" , err
151+ }
152+ pp = append (pp , platforms .FormatAll (platforms .Normalize (p )))
153+ }
154+ return strings .Join (pp , ", " ), nil
155+ }), true
156+ })
157+ writeAttr ("build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR" , "Keep Git Dir" , func (v string ) (string , bool ) {
158+ return tryParseValue (v , func (v string ) (string , error ) {
159+ b , err := strconv .ParseBool (v )
160+ if err != nil {
161+ return "" , err
162+ }
163+ return strconv .FormatBool (b ), nil
164+ }), true
165+ })
166+
167+ tw .Flush ()
168+
169+ fmt .Fprintln (dockerCli .Out ())
170+
171+ printTable (dockerCli .Out (), attrs , "context:" , "Named Context" )
172+
173+ tw = tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
81174
175+ fmt .Fprintf (tw , "Started:\t %s\n " , rec .CreatedAt .AsTime ().Format ("2006-01-02 15:04:05" ))
176+ var duration time.Duration
177+ var status string
178+ if rec .CompletedAt != nil {
179+ duration = rec .CompletedAt .AsTime ().Sub (rec .CreatedAt .AsTime ())
180+ } else {
181+ duration = rec .currentTimestamp .Sub (rec .CreatedAt .AsTime ())
182+ status = " (running)"
183+ }
184+ fmt .Fprintf (tw , "Duration:\t %s%s\n " , formatDuration (duration ), status )
185+ if rec .Error != nil {
186+ if codes .Code (rec .Error .Code ) == codes .Canceled {
187+ fmt .Fprintf (tw , "Status:\t Canceled\n " )
188+ } else {
189+ fmt .Fprintf (tw , "Error:\t %s %s\n " , codes .Code (rec .Error .Code ).String (), rec .Error .Message )
190+ }
191+ }
192+ fmt .Fprintf (tw , "Build Steps:\t %d/%d (%.0f%% cached)\n " , rec .NumCompletedSteps , rec .NumTotalSteps , float64 (rec .NumCachedSteps )/ float64 (rec .NumTotalSteps )* 100 )
193+ tw .Flush ()
194+
195+ fmt .Fprintln (dockerCli .Out ())
196+
197+ tw = tabwriter .NewWriter (dockerCli .Out (), 1 , 8 , 1 , '\t' , 0 )
198+
199+ writeAttr ("force-network-mode" , "Network" , nil )
200+ writeAttr ("hostname" , "Hostname" , nil )
201+ writeAttr ("add-hosts" , "Extra Hosts" , func (v string ) (string , bool ) {
202+ return tryParseValue (v , func (v string ) (string , error ) {
203+ fields , err := csvvalue .Fields (v , nil )
204+ if err != nil {
205+ return "" , err
206+ }
207+ return strings .Join (fields , ", " ), nil
208+ }), true
209+ })
210+ writeAttr ("cgroup-parent" , "Cgroup Parent" , nil )
211+ writeAttr ("image-resolve-mode" , "Image Resolve Mode" , nil )
212+ writeAttr ("multi-platform" , "Force Multi-Platform" , nil )
213+ writeAttr ("build-arg:BUILDKIT_MULTI_PLATFORM" , "Force Multi-Platform" , nil )
214+ writeAttr ("no-cache" , "Disable Cache" , func (v string ) (string , bool ) {
215+ if v == "" {
216+ return "true" , true
217+ }
218+ return v , true
219+ })
220+ writeAttr ("shm-size" , "Shm Size" , nil )
221+ writeAttr ("ulimit" , "Resource Limits" , nil )
222+ writeAttr ("build-arg:BUILDKIT_CACHE_MOUNT_NS" , "Cache Mount Namespace" , nil )
223+ writeAttr ("build-arg:BUILDKIT_DOCKERFILE_CHECK" , "Dockerfile Check Config" , nil )
224+ writeAttr ("build-arg:SOURCE_DATE_EPOCH" , "Source Date Epoch" , nil )
225+ writeAttr ("build-arg:SANDBOX_HOSTNAME" , "Sandbox Hostname" , nil )
226+
227+ var unusedAttrs []string
228+ for k := range attrs {
229+ if strings .HasPrefix (k , "vcs:" ) || strings .HasPrefix (k , "build-arg:" ) || strings .HasPrefix (k , "label:" ) || strings .HasPrefix (k , "context:" ) {
230+ continue
231+ }
232+ unusedAttrs = append (unusedAttrs , k )
233+ }
234+ slices .Sort (unusedAttrs )
235+
236+ for _ , k := range unusedAttrs {
237+ fmt .Fprintf (tw , "%s:\t %s\n " , k , attrs [k ])
238+ }
239+
240+ tw .Flush ()
241+
242+ fmt .Fprintln (dockerCli .Out ())
243+
244+ printTable (dockerCli .Out (), attrs , "build-arg:" , "Build Arg" )
245+ printTable (dockerCli .Out (), attrs , "label:" , "Label" )
246+
247+ // exporters (image)
82248 // commands
83249 // error
84250 // materials
@@ -107,3 +273,33 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
107273
108274 return cmd
109275}
276+
277+ func tryParseValue (s string , f func (string ) (string , error )) string {
278+ v , err := f (s )
279+ if err != nil {
280+ return fmt .Sprintf ("%s (%v)" , s , err )
281+ }
282+ return v
283+ }
284+
285+ func printTable (w io.Writer , attrs map [string ]string , prefix , title string ) {
286+ var keys []string
287+ for k := range attrs {
288+ if strings .HasPrefix (k , prefix ) {
289+ keys = append (keys , strings .TrimPrefix (k , prefix ))
290+ }
291+ }
292+ slices .Sort (keys )
293+
294+ if len (keys ) == 0 {
295+ return
296+ }
297+
298+ tw := tabwriter .NewWriter (w , 1 , 8 , 1 , '\t' , 0 )
299+ fmt .Fprintf (tw , "%s\t VALUE\n " , strings .ToUpper (title ))
300+ for _ , k := range keys {
301+ fmt .Fprintf (tw , "%s\t %s\n " , k , attrs [prefix + k ])
302+ }
303+ tw .Flush ()
304+ fmt .Fprintln (w )
305+ }
0 commit comments