Skip to content

Commit dfdd89d

Browse files
committed
add buildx history inspect formatting
Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 8db538c commit dfdd89d

File tree

1 file changed

+208
-12
lines changed

1 file changed

+208
-12
lines changed

commands/history/inspect.go

Lines changed: 208 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,27 @@ package history
22

33
import (
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

1728
type 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:\tCanceled\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\tVALUE\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

Comments
 (0)