-
Notifications
You must be signed in to change notification settings - Fork 73
✨ graphql service endpoint for catalogd #2100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
grokspawn
wants to merge
9
commits into
operator-framework:main
Choose a base branch
from
grokspawn:catd-graphql-play
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e18639d
initial vibe
grokspawn 7b0a5ab
integration with existing http service endpoint
grokspawn f86037b
allow POST method only for graphql handler
grokspawn e91bdcf
functional, caching dynamic graphql server
grokspawn ad38b7e
claude-based storage/service interface division, missing preconditions
grokspawn 2f87dd2
caehe on unpack; fix image verification policy
grokspawn 15c817c
schema-agnostic approaches with working tests
grokspawn 6516cc1
asciicast demo
grokspawn 050236b
review updates
grokspawn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| # 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 array-nested objects: | ||
|
|
||
| ``` terminal | ||
| curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "query": "{ olmpackages { name channels { name entries { name } } } }" | ||
| }' | jq | ||
| ``` | ||
|
grokspawn marked this conversation as resolved.
|
||
|
|
||
| **Note:** Non-array nested fields (like `icon`) are returned as JSON strings. To access their data, request the field as a scalar and parse the JSON: | ||
|
|
||
| ``` terminal | ||
| curl -k -X POST 'https://localhost:8443/catalogs/operatorhubio/api/v1/graphql' \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "query": "{ olmpackages { name icon } }" | ||
| }' | jq '.data.olmpackages[] | .icon | fromjson' | ||
| ``` | ||
|
|
||
| ### 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 | | ||
|
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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| # | ||
| # Catalogd Dynamic GraphQL API Demo | ||
| # | ||
| # This demo showcases the dynamic GraphQL endpoint for querying | ||
| # File-Based Catalog (FBC) content. The schema is automatically | ||
| # discovered from catalog data -- no manual type definitions needed. | ||
| # | ||
|
|
||
| set -euo pipefail | ||
| trap cleanup SIGINT SIGTERM EXIT | ||
|
|
||
| SCRIPTPATH="$(cd -- "$(dirname "$0")" > /dev/null 2>&1; pwd -P)" | ||
| SERVER_PID="" | ||
| PORT=9376 | ||
| BASE="http://localhost:${PORT}/catalogs/example-catalog/api/v1/graphql" | ||
|
|
||
| cleanup() { | ||
| if [[ -n "${SERVER_PID}" ]]; then | ||
| kill "${SERVER_PID}" 2>/dev/null || true | ||
| wait "${SERVER_PID}" 2>/dev/null || true | ||
| fi | ||
| if [[ -n "${TMPBIN:-}" && -f "${TMPBIN}" ]]; then | ||
| rm -f "${TMPBIN}" | ||
| fi | ||
| } | ||
|
|
||
| gql() { | ||
| local query="$1" | ||
| curl -s -X POST "${BASE}" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d "{\"query\": \"$query\"}" | jq . | ||
| } | ||
|
|
||
| banner() { | ||
| echo "" | ||
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | ||
| echo " $1" | ||
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | ||
| } | ||
|
|
||
| # -- Build and start the demo server -- | ||
| banner "Building GraphQL demo server" | ||
| TMPBIN=$(mktemp /tmp/graphql-demo.XXXXXX) | ||
| REPOROOT="$(cd "${SCRIPTPATH}/../.."; pwd)" | ||
| (cd "${REPOROOT}" && go build -o "${TMPBIN}" ./hack/demo/graphql-demo-server/) | ||
| echo "Built successfully." | ||
|
|
||
| echo "" | ||
| echo "Starting server on port ${PORT}..." | ||
| "${TMPBIN}" 2>/dev/null & | ||
| SERVER_PID=$! | ||
| sleep 1 | ||
| echo "Server ready. Catalog loaded: example-catalog (5 packages, 11 bundles)" | ||
|
|
||
| # -- 1. Catalog Summary -- | ||
| banner "1. Discover catalog contents (summary query)" | ||
| echo '$ curl ... -d '\''{"query": "{ summary { totalSchemas schemas { name totalObjects totalFields } } }"}'\''' | ||
| echo "" | ||
| gql "{ summary { totalSchemas schemas { name totalObjects totalFields } } }" | ||
| sleep 2 | ||
|
|
||
| # -- 2. List packages -- | ||
| banner "2. List packages (field selection: name + defaultChannel only)" | ||
| echo '$ curl ... -d '\''{"query": "{ olmpackages { name defaultChannel } }"}'\''' | ||
| echo "" | ||
| gql "{ olmpackages { name defaultChannel } }" | ||
| sleep 2 | ||
|
|
||
| # -- 3. List bundles with pagination -- | ||
| banner "3. Browse bundles with pagination (offset 3, limit 4)" | ||
| echo '$ curl ... -d '\''{"query": "{ olmbundles(limit: 4, offset: 3) { name package } }"}'\''' | ||
| echo "" | ||
| gql "{ olmbundles(limit: 4, offset: 3) { name package } }" | ||
| sleep 2 | ||
|
|
||
| # -- 4. Nested properties -- | ||
| banner "4. Query bundle properties (nested objects)" | ||
| echo '$ curl ... -d '\''{"query": "{ olmbundles(limit: 2) { name package properties { type value } } }"}'\''' | ||
| echo "" | ||
| gql "{ olmbundles(limit: 2) { name package properties { type value } } }" | ||
| sleep 2 | ||
|
|
||
| # -- 5. Related images -- | ||
| banner "5. Query related images (nested array)" | ||
| echo '$ curl ... -d '\''{"query": "{ olmbundles(limit: 1) { name relatedImages { name image } } }"}'\''' | ||
| echo "" | ||
| gql "{ olmbundles(limit: 1) { name relatedImages { name image } } }" | ||
| sleep 2 | ||
|
|
||
| # -- 6. Channel upgrade graph -- | ||
| banner "6. Explore channel upgrade graph" | ||
| echo '$ curl ... -d '\''{"query": "{ olmchannels(limit: 2) { name package entries { name skipRange } } }"}'\''' | ||
| echo "" | ||
| gql "{ olmchannels(limit: 2) { name package entries { name skipRange } } }" | ||
| sleep 2 | ||
|
|
||
| # -- 7. Cross-schema query -- | ||
| banner "7. Multi-schema query in one request" | ||
| echo '$ curl ... -d '\''{"query": "{ olmpackages(limit: 3) { name defaultChannel } olmbundles(limit: 3) { name package } }"}'\''' | ||
| echo "" | ||
| gql "{ olmpackages(limit: 3) { name defaultChannel } olmbundles(limit: 3) { name package } }" | ||
| sleep 2 | ||
|
|
||
| # -- 8. GraphQL introspection -- | ||
| banner "8. Standard GraphQL introspection (auto-generated types)" | ||
| echo '$ curl ... -d '\''{"query": "{ __schema { types { name kind } } }"}'\'' | jq ...' | ||
| echo "" | ||
| curl -s -X POST "${BASE}" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"query": "{ __schema { types { name kind } } }"}' \ | ||
| | jq '[.data.__schema.types[] | select(.name | startswith("__") | not) | select(.kind == "OBJECT")]' | ||
| sleep 2 | ||
|
|
||
| # -- Done -- | ||
| banner "Demo complete" | ||
| echo "" | ||
| echo "Key takeaways:" | ||
| echo " - GraphQL schema is auto-discovered from FBC data" | ||
| echo " - No code changes needed when FBC schemas evolve" | ||
| echo " - Supports field selection, pagination, nested queries" | ||
| echo " - Full GraphQL introspection for tooling support" | ||
| echo "" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.