diff --git a/exporter/containerimage/export.go b/exporter/containerimage/export.go index f9a00ae4c..0dd8255e2 100644 --- a/exporter/containerimage/export.go +++ b/exporter/containerimage/export.go @@ -14,6 +14,7 @@ import ( "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/leases" "github.com/containerd/containerd/v2/core/remotes/docker" + remoteserrors "github.com/containerd/containerd/v2/core/remotes/errors" "github.com/containerd/containerd/v2/pkg/epoch" "github.com/containerd/containerd/v2/pkg/labels" "github.com/containerd/containerd/v2/pkg/rootfs" @@ -355,6 +356,13 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source if e.push { err = e.pushImage(ctx, src, sessionID, targetName, desc.Digest) if err != nil { + var statusErr remoteserrors.ErrUnexpectedStatus + if errors.As(err, &statusErr) { + var dErr docker.Errors + if err1 := json.Unmarshal(statusErr.Body, &dErr); err1 == nil && len(dErr) > 0 { + err = &formattedDockerError{dErr: dErr} + } + } return nil, nil, errors.Wrapf(err, "failed to push %v", targetName) } } @@ -542,3 +550,36 @@ func (d *descriptorReference) Descriptor() ocispecs.Descriptor { func (d *descriptorReference) Release() error { return d.release(context.TODO()) } + +type formattedDockerError struct { + dErr docker.Errors +} + +func (e *formattedDockerError) Error() string { + format := func(err error) string { + out := err.Error() + var dErr docker.Error + if errors.As(err, &dErr) { + if v, ok := dErr.Detail.(string); ok && v != "" { + out += " - " + v + } + } + return out + } + switch len(e.dErr) { + case 0: + return "" + case 1: + return format(e.dErr[0]) + default: + msg := "errors:\n" + for _, err := range e.dErr { + msg += format(err) + "\n" + } + return msg + } +} + +func (e *formattedDockerError) Unwrap() error { + return e.dErr +}