@@ -25,6 +25,7 @@ import (
25
25
"os"
26
26
"os/exec"
27
27
"path/filepath"
28
+ "runtime"
28
29
"strconv"
29
30
"strings"
30
31
@@ -61,18 +62,22 @@ func (p platformParser) DefaultSpec() platforms.Platform {
61
62
}
62
63
63
64
func Build (ctx context.Context , client * containerd.Client , options types.BuilderBuildOptions ) error {
64
- buildctlBinary , buildctlArgs , needsLoading , metaFile , tags , cleanup , err := generateBuildctlArgs (ctx , client , options )
65
+ buildCtlArgs , err := generateBuildctlArgs (ctx , client , options )
65
66
if err != nil {
66
67
return err
67
68
}
68
- if cleanup != nil {
69
- defer cleanup ()
69
+ if buildCtlArgs . Cleanup != nil {
70
+ defer buildCtlArgs . Cleanup ()
70
71
}
71
72
73
+ buildctlBinary := buildCtlArgs .BuildctlBinary
74
+ buildctlArgs := buildCtlArgs .BuildctlArgs
75
+
72
76
log .L .Debugf ("running %s %v" , buildctlBinary , buildctlArgs )
73
77
buildctlCmd := exec .Command (buildctlBinary , buildctlArgs ... )
74
78
buildctlCmd .Env = os .Environ ()
75
79
80
+ needsLoading := buildCtlArgs .NeedsLoading
76
81
var buildctlStdout io.Reader
77
82
if needsLoading {
78
83
buildctlStdout , err = buildctlCmd .StdoutPipe ()
@@ -95,6 +100,26 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
95
100
if err != nil {
96
101
return err
97
102
}
103
+
104
+ if buildCtlArgs .DestFile == "" {
105
+ log .L .Debug ("no tar file specified" )
106
+ } else {
107
+ // Separate TTY (image loading) buildctl output and tarball output
108
+ // Write buildctl output to stdout
109
+ if _ , err := io .Copy (os .Stdout , buildctlStdout ); err != nil {
110
+ return err
111
+ }
112
+
113
+ // Open the tar file
114
+ reader , err := os .Open (buildCtlArgs .DestFile )
115
+ if err != nil {
116
+ return fmt .Errorf ("failed to open tar file: %v" , err )
117
+ }
118
+ defer reader .Close ()
119
+ buildctlStdout = reader
120
+ }
121
+
122
+ // Load the image into the containerd image store
98
123
if err = loadImage (ctx , buildctlStdout , options .GOptions .Namespace , options .GOptions .Address , options .GOptions .Snapshotter , options .Stdout , platMC , options .Quiet ); err != nil {
99
124
return err
100
125
}
@@ -105,7 +130,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
105
130
}
106
131
107
132
if options .IidFile != "" {
108
- id , err := getDigestFromMetaFile (metaFile )
133
+ id , err := getDigestFromMetaFile (buildCtlArgs . MetaFile )
109
134
if err != nil {
110
135
return err
111
136
}
@@ -114,6 +139,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
114
139
}
115
140
}
116
141
142
+ tags := buildCtlArgs .Tags
117
143
if len (tags ) > 1 {
118
144
log .L .Debug ("Found more than 1 tag" )
119
145
imageService := client .ImageService ()
@@ -160,7 +186,11 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
160
186
client .Close ()
161
187
}()
162
188
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 ))
189
+ imgs , err := client .Import (ctx , r ,
190
+ containerd .WithDigestRef (archive .DigestTranslator (snapshotter )),
191
+ containerd .WithSkipDigestRef (func (name string ) bool { return name != "" }),
192
+ containerd .WithImportPlatform (platMC ),
193
+ )
164
194
if err != nil {
165
195
if r .N == 0 {
166
196
// Avoid confusing "unrecognized image format"
@@ -192,23 +222,40 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
192
222
return nil
193
223
}
194
224
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 ) {
225
+ type BuildctlArgsResult struct {
226
+ BuildctlArgs []string
227
+ BuildctlBinary string
228
+ Cleanup func ()
229
+ DestFile string
230
+ MetaFile string
231
+ NeedsLoading bool // Specifies whether the image needs to be loaded into the containerd image store
232
+ Tags []string
233
+ }
197
234
235
+ func generateBuildctlArgs (ctx context.Context , client * containerd.Client , options types.BuilderBuildOptions ) (result BuildctlArgsResult , err error ) {
198
236
buildctlBinary , err := buildkitutil .BuildctlBinary ()
199
237
if err != nil {
200
- return "" , nil , false , "" , nil , nil , err
238
+ return result , err
201
239
}
240
+ result .BuildctlBinary = buildctlBinary
241
+
242
+ // FIXME: Find a better path
243
+ // Set the default destination file
244
+ defaultDestFile , err := filepath .Abs ("output.tar" )
245
+ if err != nil {
246
+ return result , fmt .Errorf ("failed to set the default destination file path: %v" , err )
247
+ }
248
+ var defaultDest string
202
249
203
250
output := options .Output
204
251
if output == "" {
205
252
info , err := client .Server (ctx )
206
253
if err != nil {
207
- return "" , nil , false , "" , nil , nil , err
254
+ return result , err
208
255
}
209
256
sharable , err := isImageSharable (options .BuildKitHost , options .GOptions .Namespace , info .UUID , options .GOptions .Snapshotter , options .Platform )
210
257
if err != nil {
211
- return "" , nil , false , "" , nil , nil , err
258
+ return result , err
212
259
}
213
260
if sharable {
214
261
output = "type=image,unpack=true" // ensure the target stage is unlazied (needed for any snapshotters)
@@ -219,42 +266,61 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
219
266
// TODO: consider using type=oci for single-options.Platform build too
220
267
output = "type=oci"
221
268
}
222
- needsLoading = true
223
269
}
224
270
} else {
225
271
if ! strings .Contains (output , "type=" ) {
226
272
// should accept --output <DIR> as an alias of --output
227
273
// type=local,dest=<DIR>
228
274
output = fmt .Sprintf ("type=local,dest=%s" , output )
229
275
}
230
- if strings .Contains (output , "type=docker" ) || strings .Contains (output , "type=oci" ) {
231
- if ! strings .Contains (output , "dest=" ) {
232
- needsLoading = true
276
+ }
277
+
278
+ // The `buildctl build` command sends both tty logs and tar (binary data) to stdout.
279
+ // Windows terminals can't distinguish these, causing "archive/tar: invalid tar header" error.
280
+ // To prevent this, we direct tar output to a file, ensuring separate handling of tty and tar data.
281
+ // A default tar file is set (if unspecified) and then loaded into the containerd image store.
282
+ if strings .Contains (output , "type=docker" ) || strings .Contains (output , "type=oci" ) {
283
+ if ! strings .Contains (output , "dest=" ) {
284
+ result .NeedsLoading = true
285
+
286
+ if runtime .GOOS == "windows" {
287
+ defaultDest = fmt .Sprintf (",dest=%s" , defaultDestFile )
233
288
}
234
289
}
235
290
}
291
+
292
+ var tags []string
236
293
if tags = strutil .DedupeStrSlice (options .Tag ); len (tags ) > 0 {
237
294
ref := tags [0 ]
238
295
parsedReference , err := referenceutil .Parse (ref )
239
296
if err != nil {
240
- return "" , nil , false , "" , nil , nil , err
297
+ return result , err
241
298
}
242
299
output += ",name=" + parsedReference .String ()
243
300
244
301
// pick the first tag and add it to output
245
302
for idx , tag := range tags {
246
303
parsedReference , err = referenceutil .Parse (tag )
247
304
if err != nil {
248
- return "" , nil , false , "" , nil , nil , err
305
+ return result , err
249
306
}
250
307
tags [idx ] = parsedReference .String ()
251
308
}
252
309
} else if len (tags ) == 0 {
253
310
output = output + ",dangling-name-prefix=<none>"
254
311
}
312
+ result .Tags = tags
313
+
314
+ // Add default destination file to output
315
+ output += defaultDest
255
316
256
- buildctlArgs = buildkitutil .BuildctlBaseArgs (options .BuildKitHost )
317
+ // Extract destination file from output
318
+ if strings .Contains (output , "dest=" ) {
319
+ _ , destFilePath , _ := strings .Cut (output , "dest=" )
320
+ result .DestFile = destFilePath
321
+ }
257
322
323
+ buildctlArgs := buildkitutil .BuildctlBaseArgs (options .BuildKitHost )
258
324
buildctlArgs = append (buildctlArgs , []string {
259
325
"build" ,
260
326
"--progress=" + options .Progress ,
@@ -271,9 +337,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
271
337
var err error
272
338
dir , err = buildkitutil .WriteTempDockerfile (options .Stdin )
273
339
if err != nil {
274
- return "" , nil , false , "" , nil , nil , err
340
+ return result , err
275
341
}
276
- cleanup = func () {
342
+ result . Cleanup = func () {
277
343
os .RemoveAll (dir )
278
344
}
279
345
} else {
@@ -286,12 +352,12 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
286
352
}
287
353
dir , file , err = buildkitutil .BuildKitFile (dir , file )
288
354
if err != nil {
289
- return "" , nil , false , "" , nil , nil , err
355
+ return result , err
290
356
}
291
357
292
358
buildCtx , err := parseContextNames (options .ExtendedBuildContext )
293
359
if err != nil {
294
- return "" , nil , false , "" , nil , nil , err
360
+ return result , err
295
361
}
296
362
297
363
for k , v := range buildCtx {
@@ -306,7 +372,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
306
372
if isOCILayout := strings .HasPrefix (v , "oci-layout://" ); isOCILayout {
307
373
args , err := parseBuildContextFromOCILayout (k , v )
308
374
if err != nil {
309
- return "" , nil , false , "" , nil , nil , err
375
+ return result , err
310
376
}
311
377
312
378
buildctlArgs = append (buildctlArgs , args ... )
@@ -315,7 +381,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
315
381
316
382
path , err := filepath .Abs (v )
317
383
if err != nil {
318
- return "" , nil , false , "" , nil , nil , err
384
+ return result , err
319
385
}
320
386
buildctlArgs = append (buildctlArgs , fmt .Sprintf ("--local=%s=%s" , k , path ))
321
387
buildctlArgs = append (buildctlArgs , fmt .Sprintf ("--opt=context:%s=local:%s" , k , k ))
@@ -362,7 +428,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
362
428
}
363
429
}
364
430
} else {
365
- return "" , nil , false , "" , nil , nil , fmt .Errorf ("invalid build arg %q" , ba )
431
+ return result , fmt .Errorf ("invalid build arg %q" , ba )
366
432
}
367
433
}
368
434
@@ -405,7 +471,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
405
471
optAttestType := strings .TrimPrefix (optAttestType , "type=" )
406
472
buildctlArgs = append (buildctlArgs , fmt .Sprintf ("--opt=attest:%s=%s" , optAttestType , optAttestAttrs ))
407
473
} else {
408
- return "" , nil , false , "" , nil , nil , fmt .Errorf ("attestation type not specified" )
474
+ return result , fmt .Errorf ("attestation type not specified" )
409
475
}
410
476
}
411
477
@@ -434,11 +500,11 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
434
500
if options .IidFile != "" {
435
501
file , err := os .CreateTemp ("" , "buildkit-meta-*" )
436
502
if err != nil {
437
- return "" , nil , false , "" , nil , cleanup , err
503
+ return result , err
438
504
}
439
505
defer file .Close ()
440
- metaFile = file .Name ()
441
- buildctlArgs = append (buildctlArgs , "--metadata-file=" + metaFile )
506
+ result . MetaFile = file .Name ()
507
+ buildctlArgs = append (buildctlArgs , "--metadata-file=" + result . MetaFile )
442
508
}
443
509
444
510
if options .NetworkMode != "" {
@@ -453,7 +519,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
453
519
}
454
520
}
455
521
456
- return buildctlBinary , buildctlArgs , needsLoading , metaFile , tags , cleanup , nil
522
+ result .BuildctlArgs = buildctlArgs
523
+
524
+ return result , nil
457
525
}
458
526
459
527
func getDigestFromMetaFile (path string ) (string , error ) {
0 commit comments