Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions cmd/catalogd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,27 @@ func run(ctx context.Context) error {
return err
}

localStorage = &storage.LocalDirV1{
RootDir: storeDir,
RootURL: baseStorageURL,
EnableMetasHandler: features.CatalogdFeatureGate.Enabled(features.APIV1MetasHandler),
var metasMode storage.MetasHandlerMode
if features.CatalogdFeatureGate.Enabled(features.APIV1MetasHandler) {
metasMode = storage.MetasHandlerEnabled
} else {
metasMode = storage.MetasHandlerDisabled
}

var graphqlMode storage.GraphQLQueriesMode
if features.CatalogdFeatureGate.Enabled(features.GraphQLCatalogQueries) {
graphqlMode = storage.GraphQLQueriesEnabled
} else {
graphqlMode = storage.GraphQLQueriesDisabled
}

localStorage = storage.NewLocalDirV1(
storeDir,
baseStorageURL,
metasMode,
graphqlMode,
)

// Config for the catalogd web server
catalogServerConfig := serverutil.CatalogServerConfig{
ExternalAddr: cfg.externalAddr,
Expand Down
202 changes: 202 additions & 0 deletions docs/draft/howto/catalog-queries-graphql-endpoint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Catalog queries using GraphQL

!!! warning "Alpha Feature"
The GraphQL endpoint is an **alpha feature** controlled by the `GraphQLCatalogQueries` feature gate.
The API and behavior may change in future releases.

After you [add a catalog of extensions](../../tutorials/add-catalog.md) to your cluster, you can query the catalog using GraphQL for flexible, structured queries with precise field selection.

## Prerequisites

* You have added a ClusterCatalog of extensions, such as [OperatorHub.io](https://operatorhub.io), to your cluster.
* The `GraphQLCatalogQueries` feature gate is enabled in catalogd.

!!! note
By default, Catalogd is installed with TLS enabled for the catalog webserver.
The following examples will show this default behavior, but for simplicity's sake will ignore TLS verification in the curl commands using the `-k` flag.

You also need to port forward the catalog server service:

``` terminal
kubectl -n olmv1-system port-forward svc/catalogd-service 8443:443
```

## GraphQL Endpoint

The GraphQL endpoint is available at:

```
https://localhost:8443/catalogs/<catalog-name>/api/v1/graphql
```

All queries must be sent as **HTTP POST** requests with a JSON body containing a `query` field.

## Understanding GraphQL Field Names

**IMPORTANT**: GraphQL field names are automatically generated from catalog schema names.

### Naming Convention

Schema names are converted to GraphQL field names using this process:

1. Remove dots and special characters: `olm.bundle` → `olmbundle`
2. Convert to lowercase: `OLM.Bundle` → `olmbundle`
3. Append 's' for pluralization: `olmbundle` → `olmbundles`

**Examples:**

| Schema Name | GraphQL Field Name |
|-------------|-------------------|
| `olm.bundle` | `olmbundles` |
| `olm.package` | `olmpackages` |
| `olm.channel` | `olmchannels` |
| `helm.chart` | `helmcharts` |

### Discovering Available Fields

To find the exact field names available for your catalog, use GraphQL introspection:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ __schema { queryType { fields { name description } } } }"
}' | jq
```

This returns all available query fields for the catalog, including the automatically generated schema-based fields.

!!! warning "Pluralization Limitations"
The current implementation appends 's' to schema names for pluralization. This may not produce grammatically correct English plurals in all cases (e.g., `index` → `indexs` instead of `indices`). When creating custom schemas, use singular nouns that pluralize well with a simple 's' suffix.

## Basic Queries

### Catalog Summary

Get an overview of schemas and object counts in the catalog:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ summary { totalSchemas schemas { name totalObjects totalFields } } }"
}' | jq
```

### Query Bundles

List bundles with specific fields:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmbundles(limit: 5, offset: 0) { name package image } }"
}' | jq
```

### Query Packages

List packages with metadata:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmpackages(limit: 10) { name description defaultChannel } }"
}' | jq
```

### Query Channels

List channels:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmchannels { name package entries } }"
}' | jq
```

## Advanced Queries

### Pagination

All schema-based queries support pagination via `limit` and `offset` arguments:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmbundles(limit: 10, offset: 20) { name } }"
}' | jq
```

### Nested Field Selection

Select only the fields you need, including nested objects:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmpackages { name icon { mediatype base64data } } }"
}' | jq
```

### Complex Bundle Properties

Query bundle properties with their type and value fields:

``` terminal
curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \
-H "Content-Type: application/json" \
-d '{
"query": "{ olmbundles(limit: 5) { name properties { type value } } }"
}' | jq
```

**Note:** The `properties` field contains an array of objects, each with a `type` string and a `value` field that can contain complex nested data. GraphQL will return the full JSON structure for the `value` field.

## Comparing GraphQL vs Metas Endpoint

| Feature | GraphQL (`/api/v1/graphql`) | Metas (`/api/v1/metas`) |
|---------|---------------------------|------------------------|
| Field selection | Precise - request only needed fields | All fields always returned |
| Query complexity | Rich queries with nested objects | Simple parameter-based filtering |
| Response size | Minimal - only requested data | Full objects always returned |
| Schema discovery | Introspection built-in | External documentation needed |
| Pagination | Built-in `limit` and `offset` | Manual implementation required |
| HTTP Method | POST only | GET supported |
| Feature status | Alpha (feature gate required) | Stable |
Comment thread
grokspawn marked this conversation as resolved.

**When to use GraphQL:**
- You need specific fields from large objects
- You want to query related data in a single request
- You need structured, typed responses
- You're building a UI or client that benefits from precise data fetching

**When to use Metas endpoint:**
- You need simple, stable API
- You're doing basic filtering by schema/package/name
- You want to use GET requests for caching
- You need guaranteed API stability

## Limitations

1. **Pluralization**: Schema names are pluralized by appending 's', which may not be grammatically correct for all words
2. **Schema naming**: Full schema names (including namespace/prefix) are preserved in field names (`olm.bundle` → `olmbundles`, not `bundles`)
3. **POST only**: GraphQL endpoint only accepts POST requests, unlike the metas endpoint which supports GET
4. **Alpha stability**: API may change in future releases while in alpha

## Enabling the GraphQL Feature

The GraphQL endpoint is controlled by the `GraphQLCatalogQueries` feature gate. To enable it:

``` yaml
args:
- --feature-gates=GraphQLCatalogQueries=true
```

See [enable webhook support](enable-webhook-support.md) for more details on configuring feature gates.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/google/go-containerregistry v0.21.4
github.com/google/renameio/v2 v2.0.2
github.com/gorilla/handlers v1.5.2
github.com/graphql-go/graphql v0.8.1
github.com/klauspost/compress v1.18.5
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU=
Expand Down
1 change: 1 addition & 0 deletions helm/experimental.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ options:
features:
enabled:
- APIV1MetasHandler
- GraphQLCatalogQueries
# This can be one of: standard or experimental
featureSet: experimental
6 changes: 4 additions & 2 deletions internal/catalogd/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
)

const (
APIV1MetasHandler = featuregate.Feature("APIV1MetasHandler")
APIV1MetasHandler = featuregate.Feature("APIV1MetasHandler")
GraphQLCatalogQueries = featuregate.Feature("GraphQLCatalogQueries")
)

var catalogdFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
APIV1MetasHandler: {Default: false, PreRelease: featuregate.Alpha},
APIV1MetasHandler: {Default: false, PreRelease: featuregate.Alpha, LockToDefault: false},
GraphQLCatalogQueries: {Default: false, PreRelease: featuregate.Alpha, LockToDefault: false},
}

var CatalogdFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
Expand Down
Loading
Loading