From b80b2221df73d18ddc1291946f84142ec721541f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 15 Sep 2023 08:53:45 -0400 Subject: [PATCH] proxy: Make `OpenImageOptional` work with `oci-archive:` By adding the standard ENOENT to our list of errors. I hit this while working on https://github.com/coreos/rpm-ostree/pull/4598 which is a tool that builds base images and wants to query if the image exists beforehand. Signed-off-by: Colin Walters --- cmd/skopeo/proxy.go | 12 +++++++++++- integration/proxy_test.go | 19 +++++++++++++++++++ .../image/v5/oci/archive/oci_transport.go | 19 +++++++++++++------ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/cmd/skopeo/proxy.go b/cmd/skopeo/proxy.go index aab85365b1..40c8e85fc0 100644 --- a/cmd/skopeo/proxy.go +++ b/cmd/skopeo/proxy.go @@ -73,6 +73,7 @@ import ( "github.com/containers/image/v5/image" "github.com/containers/image/v5/manifest" + ociarchive "github.com/containers/image/v5/oci/archive" ocilayout "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/pkg/blobinfocache" "github.com/containers/image/v5/transports" @@ -229,13 +230,22 @@ func isDockerManifestUnknownError(err error) bool { return ec.ErrorCode() == dockerdistributionapi.ErrorCodeManifestUnknown } +func isOciImageNotFound(err error) bool { + var lerr ocilayout.ImageNotFoundError + if errors.As(err, &lerr) { + return true + } + var aerr ociarchive.ImageNotFoundError + return errors.As(err, &aerr) +} + // isNotFoundImageError heuristically attempts to determine whether an error // is saying the remote source couldn't find the image (as opposed to an // authentication error, an I/O error etc.) // TODO drive this into containers/image properly func isNotFoundImageError(err error) bool { return isDockerManifestUnknownError(err) || - errors.Is(err, ocilayout.ImageNotFoundError{}) + isOciImageNotFound(err) } func (h *proxyHandler) openImageImpl(args []any, allowNotFound bool) (retReplyBuf replyBuf, retErr error) { diff --git a/integration/proxy_test.go b/integration/proxy_test.go index 826128da64..0907b9be5e 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -332,6 +332,15 @@ func runTestOpenImageOptionalNotFound(p *proxy, img string) error { return nil } +// Verify that we do see an error if the parent directory doesn't exist +func runTestOpenImageOptionalNoParentDirectory(p *proxy) error { + _, err := p.callNoFd("OpenImageOptional", []any{"oci-archive:/no/such/parent/directory/foo.ociarchive"}) + if err == nil { + return fmt.Errorf("Successfully opened nonexistent parent directory") + } + return nil +} + func (s *proxySuite) TestProxy() { t := s.T() p, err := newProxy() @@ -354,4 +363,14 @@ func (s *proxySuite) TestProxy() { err = fmt.Errorf("Testing optional image %s: %v", knownNotExtantImage, err) } assert.NoError(t, err) + + nonExistentArchive := "oci-archive:/enoent" + err = runTestOpenImageOptionalNotFound(p, nonExistentArchive) + if err != nil { + err = fmt.Errorf("Testing optional image %s: %v", nonExistentArchive, err) + } + assert.NoError(t, err) + + err = runTestOpenImageOptionalNoParentDirectory(p) + assert.NoError(t, err) } diff --git a/vendor/github.com/containers/image/v5/oci/archive/oci_transport.go b/vendor/github.com/containers/image/v5/oci/archive/oci_transport.go index 2a03feeeac..6d25383a3c 100644 --- a/vendor/github.com/containers/image/v5/oci/archive/oci_transport.go +++ b/vendor/github.com/containers/image/v5/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) { - 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 + if errors.Is(err, fs.ErrNotExist) { + return tempDirOCIRef{}, ImageNotFoundError{ref: ref} + } 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) + } + dst := tempDirRef.tempDirectory + 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)