Skip to content

Commit 1f03cd2

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

File tree

1 file changed

+220
-12
lines changed

1 file changed

+220
-12
lines changed

commands/history/inspect.go

Lines changed: 220 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,188 @@ 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+
// keyCacheFrom = "cache-from" // for registry only. deprecated in favor of keyCacheImports
96+
// keyCacheImports = "cache-imports" // JSON representation of []CacheOptionsEntry
97+
98+
// // Don't forget to update frontend documentation if you add
99+
// // a new build-arg: frontend/dockerfile/docs/reference.md
100+
// keyCacheNSArg = "build-arg:BUILDKIT_CACHE_MOUNT_NS"
101+
// keyMultiPlatformArg = "build-arg:BUILDKIT_MULTI_PLATFORM"
102+
// keyHostnameArg = "build-arg:BUILDKIT_SANDBOX_HOSTNAME"
103+
// keyDockerfileLintArg = "build-arg:BUILDKIT_DOCKERFILE_CHECK"
104+
// keyContextKeepGitDirArg = "build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR"
105+
// keySourceDateEpoch = "build-arg:SOURCE_DATE_EPOCH"
106+
107+
var context string
108+
var dockerfile string
109+
if st != nil {
110+
context = st.LocalPath
111+
dockerfile = st.DockerfilePath
112+
wd, _ := os.Getwd()
113+
114+
if dockerfile != "" && dockerfile != "-" {
115+
if rel, err := filepath.Rel(context, dockerfile); err == nil {
116+
dockerfile = rel
117+
}
118+
}
119+
if context != "" {
120+
if rel, err := filepath.Rel(wd, context); err == nil {
121+
context = rel
122+
}
123+
}
124+
}
125+
126+
if v, ok := attrs["context"]; ok && context == "" {
127+
delete(attrs, "context")
128+
context = v
129+
}
130+
if dockerfile == "" {
131+
if v, ok := attrs["filename"]; ok {
132+
dockerfile = v
133+
if dfdir, ok := attrs["vcs:localdir:dockerfile"]; ok {
134+
dockerfile = filepath.Join(dfdir, dockerfile)
135+
}
136+
}
137+
}
138+
delete(attrs, "filename")
139+
140+
if context != "" {
141+
fmt.Fprintf(tw, "Context:\t%s\n", context)
142+
}
143+
if dockerfile != "" {
144+
fmt.Fprintf(tw, "Dockerfile:\t%s\n", dockerfile)
145+
}
146+
if _, ok := attrs["context"]; !ok {
147+
if src, ok := attrs["vcs:source"]; ok {
148+
fmt.Fprintf(tw, "VCS Repository:\t%s\n", src)
149+
}
150+
if rev, ok := attrs["vcs:revision"]; ok {
151+
fmt.Fprintf(tw, "VCS Revision:\t%s\n", rev)
152+
}
153+
}
154+
155+
writeAttr("target", "Target", nil)
156+
writeAttr("platform", "Platform", func(v string) (string, bool) {
157+
return tryParseValue(v, func(v string) (string, error) {
158+
var pp []string
159+
for _, v := range strings.Split(v, ",") {
160+
p, err := platforms.Parse(v)
161+
if err != nil {
162+
return "", err
163+
}
164+
pp = append(pp, platforms.FormatAll(platforms.Normalize(p)))
165+
}
166+
return strings.Join(pp, ", "), nil
167+
}), true
168+
})
169+
writeAttr("build-arg:BUILDKIT_CONTEXT_KEEP_GIT_DIR", "Keep Git Dir", func(v string) (string, bool) {
170+
return tryParseValue(v, func(v string) (string, error) {
171+
b, err := strconv.ParseBool(v)
172+
if err != nil {
173+
return "", err
174+
}
175+
return strconv.FormatBool(b), nil
176+
}), true
177+
})
178+
179+
tw.Flush()
180+
181+
fmt.Fprintln(dockerCli.Out())
182+
183+
printTable(dockerCli.Out(), attrs, "context:", "Named Context")
81184

185+
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
186+
187+
fmt.Fprintf(tw, "Started:\t%s\n", rec.CreatedAt.AsTime().Format("2006-01-02 15:04:05"))
188+
var duration time.Duration
189+
var status string
190+
if rec.CompletedAt != nil {
191+
duration = rec.CompletedAt.AsTime().Sub(rec.CreatedAt.AsTime())
192+
} else {
193+
duration = rec.currentTimestamp.Sub(rec.CreatedAt.AsTime())
194+
status = " (running)"
195+
}
196+
fmt.Fprintf(tw, "Duration:\t%s%s\n", formatDuration(duration), status)
197+
if rec.Error != nil {
198+
if codes.Code(rec.Error.Code) == codes.Canceled {
199+
fmt.Fprintf(tw, "Status:\tCanceled\n")
200+
} else {
201+
fmt.Fprintf(tw, "Error:\t%s %s\n", codes.Code(rec.Error.Code).String(), rec.Error.Message)
202+
}
203+
}
204+
fmt.Fprintf(tw, "Build Steps:\t%d/%d (%.0f%% cached)\n", rec.NumCompletedSteps, rec.NumTotalSteps, float64(rec.NumCachedSteps)/float64(rec.NumTotalSteps)*100)
205+
tw.Flush()
206+
207+
fmt.Fprintln(dockerCli.Out())
208+
209+
tw = tabwriter.NewWriter(dockerCli.Out(), 1, 8, 1, '\t', 0)
210+
211+
writeAttr("force-network-mode", "Network", nil)
212+
writeAttr("hostname", "Hostname", nil)
213+
writeAttr("add-hosts", "Extra Hosts", func(v string) (string, bool) {
214+
return tryParseValue(v, func(v string) (string, error) {
215+
fields, err := csvvalue.Fields(v, nil)
216+
if err != nil {
217+
return "", err
218+
}
219+
return strings.Join(fields, ", "), nil
220+
}), true
221+
})
222+
writeAttr("cgroup-parent", "Cgroup Parent", nil)
223+
writeAttr("image-resolve-mode", "Image Resolve Mode", nil)
224+
writeAttr("multi-platform", "Force Multi-Platform", nil)
225+
writeAttr("build-arg:BUILDKIT_MULTI_PLATFORM", "Force Multi-Platform", nil)
226+
writeAttr("no-cache", "Disable Cache", func(v string) (string, bool) {
227+
if v == "" {
228+
return "true", true
229+
}
230+
return v, true
231+
})
232+
writeAttr("shm-size", "Shm Size", nil)
233+
writeAttr("ulimit", "Resource Limits", nil)
234+
writeAttr("build-arg:BUILDKIT_CACHE_MOUNT_NS", "Cache Mount Namespace", nil)
235+
writeAttr("build-arg:BUILDKIT_DOCKERFILE_CHECK", "Dockerfile Check Config", nil)
236+
writeAttr("build-arg:SOURCE_DATE_EPOCH", "Source Date Epoch", nil)
237+
writeAttr("build-arg:SANDBOX_HOSTNAME", "Sandbox Hostname", nil)
238+
239+
var unusedAttrs []string
240+
for k := range attrs {
241+
if strings.HasPrefix(k, "vcs:") || strings.HasPrefix(k, "build-arg:") || strings.HasPrefix(k, "label:") || strings.HasPrefix(k, "context:") {
242+
continue
243+
}
244+
unusedAttrs = append(unusedAttrs, k)
245+
}
246+
slices.Sort(unusedAttrs)
247+
248+
for _, k := range unusedAttrs {
249+
fmt.Fprintf(tw, "%s:\t%s\n", k, attrs[k])
250+
}
251+
252+
tw.Flush()
253+
254+
fmt.Fprintln(dockerCli.Out())
255+
256+
printTable(dockerCli.Out(), attrs, "build-arg:", "Build Arg")
257+
printTable(dockerCli.Out(), attrs, "label:", "Label")
258+
259+
// exporters (image)
82260
// commands
83261
// error
84262
// materials
@@ -107,3 +285,33 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
107285

108286
return cmd
109287
}
288+
289+
func tryParseValue(s string, f func(string) (string, error)) string {
290+
v, err := f(s)
291+
if err != nil {
292+
return fmt.Sprintf("%s (%v)", s, err)
293+
}
294+
return v
295+
}
296+
297+
func printTable(w io.Writer, attrs map[string]string, prefix, title string) {
298+
var keys []string
299+
for k := range attrs {
300+
if strings.HasPrefix(k, prefix) {
301+
keys = append(keys, strings.TrimPrefix(k, prefix))
302+
}
303+
}
304+
slices.Sort(keys)
305+
306+
if len(keys) == 0 {
307+
return
308+
}
309+
310+
tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
311+
fmt.Fprintf(tw, "%s\tVALUE\n", strings.ToUpper(title))
312+
for _, k := range keys {
313+
fmt.Fprintf(tw, "%s\t%s\n", k, attrs[prefix+k])
314+
}
315+
tw.Flush()
316+
fmt.Fprintln(w)
317+
}

0 commit comments

Comments
 (0)