From 4bec302e48e1298d446ce6672a86781d0b03ab27 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 18 Sep 2023 09:58:28 -0400 Subject: [PATCH] ociarchive: Add new `ArchiveFileNotFoundError` This is for https://github.com/containers/skopeo/pull/2114 which is in turn a dependency of https://github.com/coreos/rpm-ostree/pull/4598 Basically I want to map ENOENT to a clear error, because the build tooling wants to treat "target image is not present" differently from "DNS lookup failed" or "we got EPERM". There's a bit of code motion here because we need to move the `os.Open()` call before creating a temporary directory. Signed-off-by: Colin Walters --- oci/archive/oci_src.go | 12 ++++++++++++ oci/archive/oci_src_test.go | 23 ++++++++++++++++++++++- oci/archive/oci_transport.go | 19 +++++++++++++------ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/oci/archive/oci_src.go b/oci/archive/oci_src.go index 6c9ee33402..ee8409896c 100644 --- a/oci/archive/oci_src.go +++ b/oci/archive/oci_src.go @@ -28,6 +28,18 @@ func (e ImageNotFoundError) Error() string { return fmt.Sprintf("no descriptor found for reference %q", e.ref.image) } +// ArchiveFileNotFoundError occurs when the archive file does not exist. +type ArchiveFileNotFoundError struct { + // ref is the image reference + ref ociArchiveReference + // path is the file path that was not present + path string +} + +func (e ArchiveFileNotFoundError) Error() string { + return fmt.Sprintf("archive file not found: %q", e.path) +} + type ociArchiveImageSource struct { impl.Compat diff --git a/oci/archive/oci_src_test.go b/oci/archive/oci_src_test.go index 6f00afc0cc..0d08d20043 100644 --- a/oci/archive/oci_src_test.go +++ b/oci/archive/oci_src_test.go @@ -1,5 +1,26 @@ package archive -import "github.com/containers/image/v5/internal/private" +import ( + "errors" + "path/filepath" + "testing" + + "github.com/containers/image/v5/internal/private" + "github.com/containers/image/v5/types" + "github.com/stretchr/testify/assert" +) var _ private.ImageSource = (*ociArchiveImageSource)(nil) + +func TestNewImageSourceNotFound(t *testing.T) { + emptyDir := t.TempDir() + sysctx := types.SystemContext{BlobInfoCacheDir: emptyDir} + archivePath := filepath.Join(emptyDir, "foo.ociarchive") + imgref, err := ParseReference(archivePath) + assert.Nil(t, err) + _, err = LoadManifestDescriptorWithContext(&sysctx, imgref) + assert.NotNil(t, err) + var aerr ArchiveFileNotFoundError + assert.True(t, errors.As(err, &aerr)) + assert.Equal(t, aerr.path, archivePath) +} diff --git a/oci/archive/oci_transport.go b/oci/archive/oci_transport.go index 2a03feeeac..d5fee36310 100644 --- a/oci/archive/oci_transport.go +++ b/oci/archive/oci_transport.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io/fs" "os" "strings" @@ -171,18 +172,24 @@ func createOCIRef(sys *types.SystemContext, image string) (tempDirOCIRef, error) // creates the temporary directory and copies the tarred content to it func createUntarTempDir(sys *types.SystemContext, ref ociArchiveReference) (tempDirOCIRef, error) { + src := ref.resolvedFile + arch, err := os.Open(src) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return tempDirOCIRef{}, ArchiveFileNotFoundError{ref: ref, path: src} + } else { + return tempDirOCIRef{}, err + } + } + defer arch.Close() + tempDirRef, err := createOCIRef(sys, ref.image) if err != nil { return tempDirOCIRef{}, fmt.Errorf("creating oci reference: %w", err) } - src := ref.resolvedFile dst := tempDirRef.tempDirectory + // TODO: This can take quite some time, and should ideally be cancellable using a context.Context. - arch, err := os.Open(src) - if err != nil { - return tempDirOCIRef{}, err - } - defer arch.Close() if err := archive.NewDefaultArchiver().Untar(arch, dst, &archive.TarOptions{NoLchown: true}); err != nil { if err := tempDirRef.deleteTempDir(); err != nil { return tempDirOCIRef{}, fmt.Errorf("deleting temp directory %q: %w", tempDirRef.tempDirectory, err)