1515package docker
1616
1717import (
18+ "archive/tar"
19+ "bytes"
1820 "context"
1921 "fmt"
2022 "io"
2123 "os"
24+ "runtime"
25+ "strings"
2226
2327 "go.opentelemetry.io/otel"
2428 "golang.org/x/sync/errgroup"
@@ -38,6 +42,7 @@ import (
3842 "github.com/docker/docker/pkg/stdcopy"
3943 v1 "github.com/google/go-containerregistry/pkg/v1"
4044 "github.com/google/go-containerregistry/pkg/v1/empty"
45+ "github.com/google/go-containerregistry/pkg/v1/tarball"
4146 image_spec "github.com/opencontainers/image-spec/specs-go/v1"
4247)
4348
@@ -366,9 +371,106 @@ type dockerLoader struct {
366371 cli * client.Client
367372}
368373
374+ // filterXattrsForMacOS creates a wrapped layer that filters xattrs
375+ // mode can be "problematic" (only filter known problematic xattrs) or "all" (filter all xattrs)
376+ func filterXattrsForMacOS (ctx context.Context , originalLayer v1.Layer , mode string ) (v1.Layer , error ) {
377+ log := clog .FromContext (ctx )
378+ log .Debugf ("Filtering %s xattrs for MacOS compatibility" , mode )
379+
380+ rc , err := originalLayer .Uncompressed ()
381+ if err != nil {
382+ return nil , err
383+ }
384+ defer rc .Close ()
385+
386+ // Create a buffer for the new layer content
387+ var buf bytes.Buffer
388+
389+ // Process the tar file, filtering xattrs
390+ tr := tar .NewReader (rc )
391+ tw := tar .NewWriter (& buf )
392+
393+ for {
394+ hdr , err := tr .Next ()
395+ if err == io .EOF {
396+ break
397+ }
398+ if err != nil {
399+ return nil , err
400+ }
401+
402+ // Filter out xattrs based on mode
403+ if hdr .PAXRecords != nil {
404+ filteredPAXRecords := make (map [string ]string )
405+ for k , v := range hdr .PAXRecords {
406+ if mode == "all" {
407+ // In "all" mode, strip all xattrs
408+ if strings .HasPrefix (k , "SCHILY.xattr." ) {
409+ log .Debugf ("Filtering xattr %s for file %s" , k , hdr .Name )
410+ continue
411+ }
412+ } else {
413+ // In "problematic" mode (default), only strip problematic xattrs
414+ if strings .HasPrefix (k , "SCHILY.xattr.com.apple." ) ||
415+ strings .HasPrefix (k , "SCHILY.xattr.com.docker." ) {
416+ log .Debugf ("Filtering xattr %s for file %s" , k , hdr .Name )
417+ continue
418+ }
419+ }
420+ filteredPAXRecords [k ] = v
421+ }
422+ hdr .PAXRecords = filteredPAXRecords
423+ }
424+
425+ if err := tw .WriteHeader (hdr ); err != nil {
426+ return nil , err
427+ }
428+
429+ if hdr .Typeflag == tar .TypeReg {
430+ if _ , err := io .Copy (tw , tr ); err != nil {
431+ return nil , err
432+ }
433+ }
434+ }
435+
436+ if err := tw .Close (); err != nil {
437+ return nil , err
438+ }
439+
440+ // Create a new layer from the filtered content
441+ layerReader := func () (io.ReadCloser , error ) {
442+ return io .NopCloser (bytes .NewReader (buf .Bytes ())), nil
443+ }
444+
445+ // Create a new layer from the opener function
446+ layer , err := tarball .LayerFromOpener (layerReader )
447+ if err != nil {
448+ return nil , err
449+ }
450+
451+ return layer , nil
452+ }
453+
369454func (d * dockerLoader ) LoadImage (ctx context.Context , layer v1.Layer , arch apko_types.Architecture , bc * apko_build.Context ) (string , error ) {
370455 ctx , span := otel .Tracer ("melange" ).Start (ctx , "docker.LoadImage" )
371456 defer span .End ()
457+
458+ log := clog .FromContext (ctx )
459+
460+ // Detect MacOS platform
461+ isMacOS := runtime .GOOS == "darwin"
462+ if isMacOS {
463+ log .Debug ("Detected MacOS platform, using modified image loading approach" )
464+
465+ // First try: Filter only known problematic xattrs on MacOS
466+ filteredLayer , err := filterXattrsForMacOS (ctx , layer , "problematic" )
467+ if err != nil {
468+ log .Warnf ("Failed to filter xattrs for MacOS compatibility: %v" , err )
469+ log .Warn ("Continuing with original layer, but this may cause errors" )
470+ } else {
471+ layer = filteredLayer
472+ }
473+ }
372474
373475 creationTime , err := bc .GetBuildDateEpoch ()
374476 if err != nil {
@@ -380,10 +482,49 @@ func (d *dockerLoader) LoadImage(ctx context.Context, layer v1.Layer, arch apko_
380482 return "" , err
381483 }
382484
485+ // Try to load the image
383486 ref , err := apko_oci .LoadImage (ctx , img , []string {"melange:latest" })
384- if err != nil {
487+ if err != nil && isMacOS {
488+ // On MacOS, if loading fails, we might still have xattr errors
489+ log .Warnf ("Initial image load failed on MacOS: %v" , err )
490+
491+ // If we're on MacOS and got an error related to xattrs, try again with more aggressive filtering
492+ if strings .Contains (err .Error (), "xattr" ) {
493+ log .Info ("Attempting more aggressive xattr filtering..." )
494+
495+ // Try second approach: Filter ALL xattrs from the layer
496+ filteredLayer , err := filterXattrsForMacOS (ctx , layer , "all" )
497+ if err != nil {
498+ log .Warnf ("Failed to perform aggressive xattr filtering: %v" , err )
499+ return "" , fmt .Errorf ("failed to create MacOS-compatible layer: %w" , err )
500+ }
501+
502+ // Build a new image with the aggressively filtered layer
503+ img , err = apko_oci .BuildImageFromLayer (ctx , empty .Image , filteredLayer , bc .ImageConfiguration (), creationTime , arch )
504+ if err != nil {
505+ return "" , err
506+ }
507+
508+ // Try again with the fully filtered image
509+ ref , err = apko_oci .LoadImage (ctx , img , []string {"melange:latest" })
510+ if err != nil {
511+ log .Warnf ("Failed even with aggressive xattr filtering: %v" , err )
512+
513+ // Last resort - add explicit error handling advising the user
514+ if strings .Contains (err .Error (), "xattr" ) {
515+ return "" , fmt .Errorf ("unable to handle MacOS xattr issues: %w\n " +
516+ "Consider using the QEMU runner instead with MELANGE_EXTRA_OPTS=\" --runner=qemu\" " , err )
517+ }
518+ return "" , err
519+ }
520+ log .Info ("Successfully loaded image with aggressive xattr filtering" )
521+ } else {
522+ return "" , err
523+ }
524+ } else if err != nil {
385525 return "" , err
386526 }
527+
387528 return ref .String (), nil
388529}
389530
@@ -408,4 +549,4 @@ func (d *dockerLoader) RemoveImage(ctx context.Context, ref string) error {
408549 }
409550
410551 return nil
411- }
552+ }
0 commit comments