Skip to content

Commit 2d18366

Browse files
committed
Separate TTY and tar output in nerdctl build with tty
- Write buildctl output to terminal and tarball to file Signed-off-by: Christine Murimi <[email protected]>
1 parent b17673d commit 2d18366

File tree

1 file changed

+87
-27
lines changed

1 file changed

+87
-27
lines changed

pkg/cmd/builder/build.go

+87-27
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,22 @@ func (p platformParser) DefaultSpec() platforms.Platform {
6161
}
6262

6363
func Build(ctx context.Context, client *containerd.Client, options types.BuilderBuildOptions) error {
64-
buildctlBinary, buildctlArgs, needsLoading, metaFile, tags, cleanup, err := generateBuildctlArgs(ctx, client, options)
64+
buildCtlArgs, err := generateBuildctlArgs(ctx, client, options)
6565
if err != nil {
6666
return err
6767
}
68-
if cleanup != nil {
69-
defer cleanup()
68+
if buildCtlArgs.Cleanup != nil {
69+
defer buildCtlArgs.Cleanup()
7070
}
7171

72+
buildctlBinary := buildCtlArgs.BuildctlBinary
73+
buildctlArgs := buildCtlArgs.BuildctlArgs
74+
7275
log.L.Debugf("running %s %v", buildctlBinary, buildctlArgs)
7376
buildctlCmd := exec.Command(buildctlBinary, buildctlArgs...)
7477
buildctlCmd.Env = os.Environ()
7578

79+
needsLoading := buildCtlArgs.NeedsLoading
7680
var buildctlStdout io.Reader
7781
if needsLoading {
7882
buildctlStdout, err = buildctlCmd.StdoutPipe()
@@ -95,6 +99,26 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
9599
if err != nil {
96100
return err
97101
}
102+
103+
if buildCtlArgs.DestFile == "" {
104+
log.L.Debug("no tar file specified")
105+
} else {
106+
// Separate TTY (image loading) buildctl output and tarball output
107+
// Write buildctl output to stdout
108+
if _, err := io.Copy(os.Stdout, buildctlStdout); err != nil {
109+
return err
110+
}
111+
112+
// Open the tar file
113+
reader, err := os.Open(buildCtlArgs.DestFile)
114+
if err != nil {
115+
return fmt.Errorf("failed to open tar file: %v", err)
116+
}
117+
defer reader.Close()
118+
buildctlStdout = reader
119+
}
120+
121+
// Load the image into the containerd image store
98122
if err = loadImage(ctx, buildctlStdout, options.GOptions.Namespace, options.GOptions.Address, options.GOptions.Snapshotter, options.Stdout, platMC, options.Quiet); err != nil {
99123
return err
100124
}
@@ -105,7 +129,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
105129
}
106130

107131
if options.IidFile != "" {
108-
id, err := getDigestFromMetaFile(metaFile)
132+
id, err := getDigestFromMetaFile(buildCtlArgs.MetaFile)
109133
if err != nil {
110134
return err
111135
}
@@ -114,6 +138,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
114138
}
115139
}
116140

141+
tags := buildCtlArgs.Tags
117142
if len(tags) > 1 {
118143
log.L.Debug("Found more than 1 tag")
119144
imageService := client.ImageService()
@@ -160,7 +185,11 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
160185
client.Close()
161186
}()
162187
r := &readCounter{Reader: in}
163-
imgs, err := client.Import(ctx, r, containerd.WithDigestRef(archive.DigestTranslator(snapshotter)), containerd.WithSkipDigestRef(func(name string) bool { return name != "" }), containerd.WithImportPlatform(platMC))
188+
imgs, err := client.Import(ctx, r,
189+
containerd.WithDigestRef(archive.DigestTranslator(snapshotter)),
190+
containerd.WithSkipDigestRef(func(name string) bool { return name != "" }),
191+
containerd.WithImportPlatform(platMC),
192+
)
164193
if err != nil {
165194
if r.N == 0 {
166195
// Avoid confusing "unrecognized image format"
@@ -192,23 +221,34 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
192221
return nil
193222
}
194223

195-
func generateBuildctlArgs(ctx context.Context, client *containerd.Client, options types.BuilderBuildOptions) (buildCtlBinary string,
196-
buildctlArgs []string, needsLoading bool, metaFile string, tags []string, cleanup func(), err error) {
224+
type BuildctlArgsResult struct {
225+
BuildctlArgs []string
226+
BuildctlBinary string
227+
Cleanup func()
228+
DestFile string
229+
MetaFile string
230+
NeedsLoading bool // Specifies whether the image needs to be loaded into the containerd image store
231+
Tags []string
232+
}
197233

234+
func generateBuildctlArgs(ctx context.Context, client *containerd.Client, options types.BuilderBuildOptions) (result BuildctlArgsResult, err error) {
198235
buildctlBinary, err := buildkitutil.BuildctlBinary()
199236
if err != nil {
200-
return "", nil, false, "", nil, nil, err
237+
return result, err
201238
}
239+
result.BuildctlBinary = buildctlBinary
240+
241+
var defaultDest string
202242

203243
output := options.Output
204244
if output == "" {
205245
info, err := client.Server(ctx)
206246
if err != nil {
207-
return "", nil, false, "", nil, nil, err
247+
return result, err
208248
}
209249
sharable, err := isImageSharable(options.BuildKitHost, options.GOptions.Namespace, info.UUID, options.GOptions.Snapshotter, options.Platform)
210250
if err != nil {
211-
return "", nil, false, "", nil, nil, err
251+
return result, err
212252
}
213253
if sharable {
214254
output = "type=image,unpack=true" // ensure the target stage is unlazied (needed for any snapshotters)
@@ -219,7 +259,14 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
219259
// TODO: consider using type=oci for single-options.Platform build too
220260
output = "type=oci"
221261
}
222-
needsLoading = true
262+
result.NeedsLoading = true
263+
264+
// Set the default destination file
265+
defaultDestFile, err := filepath.Abs("output.tar")
266+
if err != nil {
267+
return result, fmt.Errorf("failed to set the default destination file path: %v", err)
268+
}
269+
defaultDest = fmt.Sprintf(",dest=%s", defaultDestFile)
223270
}
224271
} else {
225272
if !strings.Contains(output, "type=") {
@@ -229,32 +276,43 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
229276
}
230277
if strings.Contains(output, "type=docker") || strings.Contains(output, "type=oci") {
231278
if !strings.Contains(output, "dest=") {
232-
needsLoading = true
279+
result.NeedsLoading = true
233280
}
234281
}
235282
}
283+
284+
var tags []string
236285
if tags = strutil.DedupeStrSlice(options.Tag); len(tags) > 0 {
237286
ref := tags[0]
238287
parsedReference, err := referenceutil.Parse(ref)
239288
if err != nil {
240-
return "", nil, false, "", nil, nil, err
289+
return result, err
241290
}
242291
output += ",name=" + parsedReference.String()
243292

244293
// pick the first tag and add it to output
245294
for idx, tag := range tags {
246295
parsedReference, err = referenceutil.Parse(tag)
247296
if err != nil {
248-
return "", nil, false, "", nil, nil, err
297+
return result, err
249298
}
250299
tags[idx] = parsedReference.String()
251300
}
252301
} else if len(tags) == 0 {
253302
output = output + ",dangling-name-prefix=<none>"
254303
}
304+
result.Tags = tags
255305

256-
buildctlArgs = buildkitutil.BuildctlBaseArgs(options.BuildKitHost)
306+
// Add default destination file to output
307+
output += defaultDest
257308

309+
// Extract destination file from output
310+
if strings.Contains(output, "dest=") {
311+
_, destFilePath, _ := strings.Cut(output, "dest=")
312+
result.DestFile = destFilePath
313+
}
314+
315+
buildctlArgs := buildkitutil.BuildctlBaseArgs(options.BuildKitHost)
258316
buildctlArgs = append(buildctlArgs, []string{
259317
"build",
260318
"--progress=" + options.Progress,
@@ -271,9 +329,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
271329
var err error
272330
dir, err = buildkitutil.WriteTempDockerfile(options.Stdin)
273331
if err != nil {
274-
return "", nil, false, "", nil, nil, err
332+
return result, err
275333
}
276-
cleanup = func() {
334+
result.Cleanup = func() {
277335
os.RemoveAll(dir)
278336
}
279337
} else {
@@ -286,12 +344,12 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
286344
}
287345
dir, file, err = buildkitutil.BuildKitFile(dir, file)
288346
if err != nil {
289-
return "", nil, false, "", nil, nil, err
347+
return result, err
290348
}
291349

292350
buildCtx, err := parseContextNames(options.ExtendedBuildContext)
293351
if err != nil {
294-
return "", nil, false, "", nil, nil, err
352+
return result, err
295353
}
296354

297355
for k, v := range buildCtx {
@@ -306,7 +364,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
306364
if isOCILayout := strings.HasPrefix(v, "oci-layout://"); isOCILayout {
307365
args, err := parseBuildContextFromOCILayout(k, v)
308366
if err != nil {
309-
return "", nil, false, "", nil, nil, err
367+
return result, err
310368
}
311369

312370
buildctlArgs = append(buildctlArgs, args...)
@@ -315,7 +373,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
315373

316374
path, err := filepath.Abs(v)
317375
if err != nil {
318-
return "", nil, false, "", nil, nil, err
376+
return result, err
319377
}
320378
buildctlArgs = append(buildctlArgs, fmt.Sprintf("--local=%s=%s", k, path))
321379
buildctlArgs = append(buildctlArgs, fmt.Sprintf("--opt=context:%s=local:%s", k, k))
@@ -362,7 +420,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
362420
}
363421
}
364422
} else {
365-
return "", nil, false, "", nil, nil, fmt.Errorf("invalid build arg %q", ba)
423+
return result, fmt.Errorf("invalid build arg %q", ba)
366424
}
367425
}
368426

@@ -405,7 +463,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
405463
optAttestType := strings.TrimPrefix(optAttestType, "type=")
406464
buildctlArgs = append(buildctlArgs, fmt.Sprintf("--opt=attest:%s=%s", optAttestType, optAttestAttrs))
407465
} else {
408-
return "", nil, false, "", nil, nil, fmt.Errorf("attestation type not specified")
466+
return result, fmt.Errorf("attestation type not specified")
409467
}
410468
}
411469

@@ -434,11 +492,11 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
434492
if options.IidFile != "" {
435493
file, err := os.CreateTemp("", "buildkit-meta-*")
436494
if err != nil {
437-
return "", nil, false, "", nil, cleanup, err
495+
return result, err
438496
}
439497
defer file.Close()
440-
metaFile = file.Name()
441-
buildctlArgs = append(buildctlArgs, "--metadata-file="+metaFile)
498+
result.MetaFile = file.Name()
499+
buildctlArgs = append(buildctlArgs, "--metadata-file="+result.MetaFile)
442500
}
443501

444502
if options.NetworkMode != "" {
@@ -453,7 +511,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
453511
}
454512
}
455513

456-
return buildctlBinary, buildctlArgs, needsLoading, metaFile, tags, cleanup, nil
514+
result.BuildctlArgs = buildctlArgs
515+
516+
return result, nil
457517
}
458518

459519
func getDigestFromMetaFile(path string) (string, error) {

0 commit comments

Comments
 (0)