diff --git a/pkg/catalog_next/pull.go b/pkg/catalog_next/pull.go index 391ca2d16..1f1165a96 100644 --- a/pkg/catalog_next/pull.go +++ b/pkg/catalog_next/pull.go @@ -2,10 +2,12 @@ package catalognext import ( "context" + "errors" "fmt" "time" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/docker/mcp-gateway/pkg/db" "github.com/docker/mcp-gateway/pkg/oci" @@ -41,6 +43,9 @@ func pullCatalog(ctx context.Context, dao db.DAO, ociService oci.Service, refStr catalogArtifact, err := oci.ReadArtifact[CatalogArtifact](refStr, MCPCatalogArtifactType) if err != nil { + if isNotFoundError(err) { + return nil, fmt.Errorf("catalog not found: %s", refStr) + } return nil, fmt.Errorf("failed to read OCI catalog: %w", err) } @@ -88,3 +93,18 @@ func pullCatalog(ctx context.Context, dao db.DAO, ociService oci.Service, refStr return &dbCatalog, nil } + +// isNotFoundError checks if the error is an OCI registry "not found" response +// (MANIFEST_UNKNOWN or NAME_UNKNOWN). +func isNotFoundError(err error) bool { + var transportErr *transport.Error + if !errors.As(err, &transportErr) { + return false + } + for _, diagnostic := range transportErr.Errors { + if diagnostic.Code == transport.ManifestUnknownErrorCode || diagnostic.Code == transport.NameUnknownErrorCode { + return true + } + } + return false +} diff --git a/pkg/catalog_next/pull_test.go b/pkg/catalog_next/pull_test.go new file mode 100644 index 000000000..08965c04e --- /dev/null +++ b/pkg/catalog_next/pull_test.go @@ -0,0 +1,63 @@ +package catalognext + +import ( + "fmt" + "net/http" + "testing" + + "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/stretchr/testify/assert" +) + +func TestIsNotFoundError(t *testing.T) { + tests := []struct { + name string + err error + expected bool + }{ + { + name: "manifest unknown", + err: &transport.Error{Errors: []transport.Diagnostic{{Code: transport.ManifestUnknownErrorCode}}}, + expected: true, + }, + { + name: "name unknown", + err: &transport.Error{Errors: []transport.Diagnostic{{Code: transport.NameUnknownErrorCode}}}, + expected: true, + }, + { + name: "wrapped manifest unknown", + err: fmt.Errorf("fetch failed: %w", &transport.Error{Errors: []transport.Diagnostic{{Code: transport.ManifestUnknownErrorCode}}}), + expected: true, + }, + { + name: "unauthorized error", + err: &transport.Error{Errors: []transport.Diagnostic{{Code: transport.UnauthorizedErrorCode}}}, + expected: false, + }, + { + name: "non-transport error", + err: fmt.Errorf("network timeout"), + expected: false, + }, + { + name: "transport error with no diagnostics", + err: &transport.Error{StatusCode: http.StatusNotFound}, + expected: false, + }, + { + name: "multiple diagnostics with one match", + err: &transport.Error{Errors: []transport.Diagnostic{ + {Code: transport.UnauthorizedErrorCode}, + {Code: transport.ManifestUnknownErrorCode}, + }}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, isNotFoundError(tt.err)) + }) + } +}