@@ -25,6 +25,7 @@ import (
2525 "os"
2626 "os/exec"
2727 "path/filepath"
28+ "runtime"
2829 "strconv"
2930 "strings"
3031
@@ -61,18 +62,22 @@ func (p platformParser) DefaultSpec() platforms.Platform {
6162}
6263
6364func 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 )
6566 if err != nil {
6667 return err
6768 }
68- if cleanup != nil {
69- defer cleanup ()
69+ if buildCtlArgs . Cleanup != nil {
70+ defer buildCtlArgs . Cleanup ()
7071 }
7172
73+ buildctlBinary := buildCtlArgs .BuildctlBinary
74+ buildctlArgs := buildCtlArgs .BuildctlArgs
75+
7276 log .L .Debugf ("running %s %v" , buildctlBinary , buildctlArgs )
7377 buildctlCmd := exec .Command (buildctlBinary , buildctlArgs ... )
7478 buildctlCmd .Env = os .Environ ()
7579
80+ needsLoading := buildCtlArgs .NeedsLoading
7681 var buildctlStdout io.Reader
7782 if needsLoading {
7883 buildctlStdout , err = buildctlCmd .StdoutPipe ()
@@ -95,6 +100,26 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
95100 if err != nil {
96101 return err
97102 }
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
98123 if err = loadImage (ctx , buildctlStdout , options .GOptions .Namespace , options .GOptions .Address , options .GOptions .Snapshotter , options .Stdout , platMC , options .Quiet ); err != nil {
99124 return err
100125 }
@@ -105,7 +130,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
105130 }
106131
107132 if options .IidFile != "" {
108- id , err := getDigestFromMetaFile (metaFile )
133+ id , err := getDigestFromMetaFile (buildCtlArgs . MetaFile )
109134 if err != nil {
110135 return err
111136 }
@@ -114,6 +139,7 @@ func Build(ctx context.Context, client *containerd.Client, options types.Builder
114139 }
115140 }
116141
142+ tags := buildCtlArgs .Tags
117143 if len (tags ) > 1 {
118144 log .L .Debug ("Found more than 1 tag" )
119145 imageService := client .ImageService ()
@@ -160,7 +186,11 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
160186 client .Close ()
161187 }()
162188 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+ )
164194 if err != nil {
165195 if r .N == 0 {
166196 // Avoid confusing "unrecognized image format"
@@ -192,23 +222,40 @@ func loadImage(ctx context.Context, in io.Reader, namespace, address, snapshotte
192222 return nil
193223}
194224
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+ }
197234
235+ func generateBuildctlArgs (ctx context.Context , client * containerd.Client , options types.BuilderBuildOptions ) (result BuildctlArgsResult , err error ) {
198236 buildctlBinary , err := buildkitutil .BuildctlBinary ()
199237 if err != nil {
200- return "" , nil , false , "" , nil , nil , err
238+ return result , err
201239 }
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
202249
203250 output := options .Output
204251 if output == "" {
205252 info , err := client .Server (ctx )
206253 if err != nil {
207- return "" , nil , false , "" , nil , nil , err
254+ return result , err
208255 }
209256 sharable , err := isImageSharable (options .BuildKitHost , options .GOptions .Namespace , info .UUID , options .GOptions .Snapshotter , options .Platform )
210257 if err != nil {
211- return "" , nil , false , "" , nil , nil , err
258+ return result , err
212259 }
213260 if sharable {
214261 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
219266 // TODO: consider using type=oci for single-options.Platform build too
220267 output = "type=oci"
221268 }
222- needsLoading = true
223269 }
224270 } else {
225271 if ! strings .Contains (output , "type=" ) {
226272 // should accept --output <DIR> as an alias of --output
227273 // type=local,dest=<DIR>
228274 output = fmt .Sprintf ("type=local,dest=%s" , output )
229275 }
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 )
233288 }
234289 }
235290 }
291+
292+ var tags []string
236293 if tags = strutil .DedupeStrSlice (options .Tag ); len (tags ) > 0 {
237294 ref := tags [0 ]
238295 parsedReference , err := referenceutil .Parse (ref )
239296 if err != nil {
240- return "" , nil , false , "" , nil , nil , err
297+ return result , err
241298 }
242299 output += ",name=" + parsedReference .String ()
243300
244301 // pick the first tag and add it to output
245302 for idx , tag := range tags {
246303 parsedReference , err = referenceutil .Parse (tag )
247304 if err != nil {
248- return "" , nil , false , "" , nil , nil , err
305+ return result , err
249306 }
250307 tags [idx ] = parsedReference .String ()
251308 }
252309 } else if len (tags ) == 0 {
253310 output = output + ",dangling-name-prefix=<none>"
254311 }
312+ result .Tags = tags
313+
314+ // Add default destination file to output
315+ output += defaultDest
255316
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+ }
257322
323+ buildctlArgs := buildkitutil .BuildctlBaseArgs (options .BuildKitHost )
258324 buildctlArgs = append (buildctlArgs , []string {
259325 "build" ,
260326 "--progress=" + options .Progress ,
@@ -271,9 +337,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
271337 var err error
272338 dir , err = buildkitutil .WriteTempDockerfile (options .Stdin )
273339 if err != nil {
274- return "" , nil , false , "" , nil , nil , err
340+ return result , err
275341 }
276- cleanup = func () {
342+ result . Cleanup = func () {
277343 os .RemoveAll (dir )
278344 }
279345 } else {
@@ -286,12 +352,12 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
286352 }
287353 dir , file , err = buildkitutil .BuildKitFile (dir , file )
288354 if err != nil {
289- return "" , nil , false , "" , nil , nil , err
355+ return result , err
290356 }
291357
292358 buildCtx , err := parseContextNames (options .ExtendedBuildContext )
293359 if err != nil {
294- return "" , nil , false , "" , nil , nil , err
360+ return result , err
295361 }
296362
297363 for k , v := range buildCtx {
@@ -306,7 +372,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
306372 if isOCILayout := strings .HasPrefix (v , "oci-layout://" ); isOCILayout {
307373 args , err := parseBuildContextFromOCILayout (k , v )
308374 if err != nil {
309- return "" , nil , false , "" , nil , nil , err
375+ return result , err
310376 }
311377
312378 buildctlArgs = append (buildctlArgs , args ... )
@@ -315,7 +381,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
315381
316382 path , err := filepath .Abs (v )
317383 if err != nil {
318- return "" , nil , false , "" , nil , nil , err
384+ return result , err
319385 }
320386 buildctlArgs = append (buildctlArgs , fmt .Sprintf ("--local=%s=%s" , k , path ))
321387 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
362428 }
363429 }
364430 } else {
365- return "" , nil , false , "" , nil , nil , fmt .Errorf ("invalid build arg %q" , ba )
431+ return result , fmt .Errorf ("invalid build arg %q" , ba )
366432 }
367433 }
368434
@@ -405,7 +471,7 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
405471 optAttestType := strings .TrimPrefix (optAttestType , "type=" )
406472 buildctlArgs = append (buildctlArgs , fmt .Sprintf ("--opt=attest:%s=%s" , optAttestType , optAttestAttrs ))
407473 } else {
408- return "" , nil , false , "" , nil , nil , fmt .Errorf ("attestation type not specified" )
474+ return result , fmt .Errorf ("attestation type not specified" )
409475 }
410476 }
411477
@@ -434,11 +500,11 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
434500 if options .IidFile != "" {
435501 file , err := os .CreateTemp ("" , "buildkit-meta-*" )
436502 if err != nil {
437- return "" , nil , false , "" , nil , cleanup , err
503+ return result , err
438504 }
439505 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 )
442508 }
443509
444510 if options .NetworkMode != "" {
@@ -453,7 +519,9 @@ func generateBuildctlArgs(ctx context.Context, client *containerd.Client, option
453519 }
454520 }
455521
456- return buildctlBinary , buildctlArgs , needsLoading , metaFile , tags , cleanup , nil
522+ result .BuildctlArgs = buildctlArgs
523+
524+ return result , nil
457525}
458526
459527func getDigestFromMetaFile (path string ) (string , error ) {
0 commit comments