diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 55c84458b13..a2c1cca4d19 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -483,6 +483,11 @@ func (ir *ImageEngine) Load(ctx context.Context, options entities.ImageLoadOptio loadedImages, err := ir.Libpod.LibimageRuntime().Load(ctx, options.Input, loadOptions) if err != nil { + // Surface ENOSPC immediately — no point wrapping in a generic error. + // The error string arrives from a subprocess, so we check the message. + if strings.Contains(err.Error(), "no space left on device") { + return nil, fmt.Errorf("loading image: %w", syscall.ENOSPC) + } return nil, err } return &entities.ImageLoadReport{Names: loadedImages}, nil diff --git a/pkg/domain/infra/abi/images_test.go b/pkg/domain/infra/abi/images_test.go index 5739d604d09..cabac4df0b4 100644 --- a/pkg/domain/infra/abi/images_test.go +++ b/pkg/domain/infra/abi/images_test.go @@ -3,6 +3,10 @@ package abi import ( + "errors" + "fmt" + "strings" + "syscall" "testing" "github.com/stretchr/testify/assert" @@ -18,3 +22,34 @@ func TestToDomainHistoryLayer(t *testing.T) { newLayer := toDomainHistoryLayer(&layer) assert.Equal(t, layer.Size, newLayer.Size) } + +// TestLoadImageENOSPCWrapping verifies that the ENOSPC-detection logic used +// in (*ImageEngine).Load wraps disk-full errors so that callers can detect +// them with errors.Is(err, syscall.ENOSPC). +func TestLoadImageENOSPCWrapping(t *testing.T) { + // applyENOSPCCheck reproduces the inline check added to the Load function. + applyENOSPCCheck := func(loadErr error) error { + if loadErr == nil { + return nil + } + if strings.Contains(loadErr.Error(), "no space left on device") { + return fmt.Errorf("loading image: %w", syscall.ENOSPC) + } + return loadErr + } + + // A subprocess error that contains the canonical ENOSPC string. + enospcErr := errors.New("writing blob: no space left on device") + result := applyENOSPCCheck(enospcErr) + assert.True(t, errors.Is(result, syscall.ENOSPC), + "expected syscall.ENOSPC in error chain, got: %v", result) + + // A generic error must not be mistaken for ENOSPC. + otherErr := errors.New("permission denied") + result = applyENOSPCCheck(otherErr) + assert.False(t, errors.Is(result, syscall.ENOSPC), + "non-ENOSPC error must not wrap syscall.ENOSPC") + + // No error must pass through as nil. + assert.NoError(t, applyENOSPCCheck(nil)) +}