diff --git a/oci/oci.go b/oci/oci.go index 8425453..925d0b4 100644 --- a/oci/oci.go +++ b/oci/oci.go @@ -15,6 +15,8 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/types" + + "github.com/cocoonstack/cocoon-common/snapshot" ) const maxRegistryConnsPerHost = 32 @@ -59,6 +61,9 @@ func (r *OCIRegistry) GetManifest(ctx context.Context, repo, tag string) ([]byte } desc, err := remote.Get(ref, r.callOpts(ctx)...) if err != nil { + if isNotFound(err) { + return nil, "", fmt.Errorf("get manifest %s:%s: %w", repo, tag, snapshot.ErrManifestNotFound) + } return nil, "", fmt.Errorf("get manifest %s:%s: %w", repo, tag, err) } return desc.Manifest, string(desc.MediaType), nil @@ -176,13 +181,17 @@ func bulkTransport() *http.Transport { // ignoreNotFound maps a registry 404 to a nil error (absent, not failed) and // wraps anything else. func ignoreNotFound(err error, action string) error { - var terr *transport.Error - if errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { + if isNotFound(err) { return nil } return fmt.Errorf("%s: %w", action, err) } +func isNotFound(err error) bool { + var terr *transport.Error + return errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound +} + // streamLayer is a v1.Layer over a body with a known digest and size, so PutBlob // streams a raw blob without buffering it (WriteLayer reads only Compressed()). // body is single-use: a retried upload fails the digest check, not corrupts. diff --git a/oci/oci_test.go b/oci/oci_test.go index 4375065..1b7dbbd 100644 --- a/oci/oci_test.go +++ b/oci/oci_test.go @@ -201,6 +201,17 @@ func TestStreamResolvesIndexChildByDigest(t *testing.T) { } } +func TestGetManifestNotFoundIsTyped(t *testing.T) { + srv := httptest.NewServer(registry.New()) + t.Cleanup(srv.Close) + r := NewOCIRegistry(strings.TrimPrefix(srv.URL, "http://")+"/cocoon", authn.DefaultKeychain) + + _, _, err := r.GetManifest(t.Context(), "ghost/repo", "missing") + if !errors.Is(err, snapshot.ErrManifestNotFound) { + t.Fatalf("err = %v, want errors.Is ErrManifestNotFound", err) + } +} + func digestOf(b []byte) string { sum := sha256.Sum256(b) return "sha256:" + hex.EncodeToString(sum[:]) diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go index 33936ad..4d5a54e 100644 --- a/snapshot/snapshot.go +++ b/snapshot/snapshot.go @@ -21,6 +21,8 @@ const ( ) var ( + ErrManifestNotFound = errors.New("manifest not found") + errMissingSnapshotJSON = errors.New("snapshot.json not found in export stream") nowFunc = time.Now // tests override