-
Notifications
You must be signed in to change notification settings - Fork 13
HYPERFLEET-299 - docs: Add search and filtering documentation #63
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,165 @@ | ||||||||||
| # API Search and Filtering | ||||||||||
|
|
||||||||||
| This document describes how to search and filter resources in the HyperFleet API using TSL (Tree Search Language) queries. | ||||||||||
|
|
||||||||||
| ## Overview | ||||||||||
|
|
||||||||||
| The HyperFleet API supports search and filtering capabilities through the `search` query parameter. All list endpoints (`GET /clusters`, `GET /nodepools`, etc.) accept TSL (Tree Search Language) queries that allow you to filter results using field comparisons, logical operators, and complex nested conditions. | ||||||||||
|
|
||||||||||
| ## TSL Language Reference | ||||||||||
|
|
||||||||||
| The HyperFleet API uses the [Tree Search Language (TSL)](https://github.com/yaacov/tree-search-language) library for parsing search queries. | ||||||||||
|
|
||||||||||
| ### Supported Operators | ||||||||||
|
|
||||||||||
| | Operator | Description | Example | | ||||||||||
| |----------|-------------|---------| | ||||||||||
| | `=` | Equal | `name='test'` | | ||||||||||
| | `!=` | Not equal | `name!='old'` | | ||||||||||
| | `<` | Less than | `generation<10` | | ||||||||||
| | `<=` | Less than or equal | `generation<=5` | | ||||||||||
| | `>` | Greater than | `generation>1` | | ||||||||||
| | `>=` | Greater than or equal | `generation>=1` | | ||||||||||
| | `in` | In list | `name in ('c1','c2')` | | ||||||||||
| | `and` | Logical AND | `a='1' and b='2'` | | ||||||||||
| | `or` | Logical OR | `a='1' or a='2'` | | ||||||||||
| | `not` | Logical NOT | `not name='test'` | | ||||||||||
rafabene marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| ### Query Value Syntax | ||||||||||
|
|
||||||||||
| - **String values**: Must be enclosed in single quotes: `name='my-cluster'` | ||||||||||
| - **Numeric values**: No quotes required: `generation>5` | ||||||||||
| - **Lists**: Comma-separated values in parentheses: `id in ('2abc123', '2def456')` | ||||||||||
|
|
||||||||||
| ## Searchable Fields | ||||||||||
|
|
||||||||||
| ### Clusters | ||||||||||
|
|
||||||||||
| | Field | Type | Description | Example | | ||||||||||
| |-------|------|-------------|---------| | ||||||||||
| | `id` | string | Cluster ID | `id='2abc123'` | | ||||||||||
| | `name` | string | Cluster name | `name='my-cluster'` | | ||||||||||
| | `generation` | integer | Spec version counter | `generation>1` | | ||||||||||
| | `created_by` | string | Creator email | `created_by='user@example.com'` | | ||||||||||
| | `updated_by` | string | Last updater email | `updated_by='user@example.com'` | | ||||||||||
| | `labels.<key>` | string | Label value | `labels.environment='production'` | | ||||||||||
| | `status.conditions.<Type>` | string | Condition status | `status.conditions.Ready='True'` | | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find cluster by name | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=name='my-cluster'" | ||||||||||
|
|
||||||||||
| # Find clusters by multiple names | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=name in ('cluster1', 'cluster2', 'cluster3')" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### NodePools | ||||||||||
|
|
||||||||||
| NodePools support the same searchable fields as Clusters, plus: | ||||||||||
|
|
||||||||||
| | Field | Type | Description | Example | | ||||||||||
| |-------|------|-------------|---------| | ||||||||||
| | `owner_id` | string | Parent cluster ID | `owner_id='2abc123'` | | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find nodepools by parent cluster ID | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/nodepools" \ | ||||||||||
| --data-urlencode "search=owner_id='2abc123'" | ||||||||||
|
|
||||||||||
| # Find ready nodepools | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/nodepools" \ | ||||||||||
| --data-urlencode "search=status.conditions.Ready='True'" | ||||||||||
|
|
||||||||||
| # Find nodepools by label | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/nodepools" \ | ||||||||||
| --data-urlencode "search=labels.role='worker'" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## Labels Queries | ||||||||||
|
|
||||||||||
| Use `labels.<key>` syntax to filter by label values: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find production clusters | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=labels.environment='production'" | ||||||||||
|
|
||||||||||
| # Find clusters in a specific region | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=labels.region='us-east'" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Label keys must contain only lowercase letters (a-z), digits (0-9), and underscores (_). | ||||||||||
|
|
||||||||||
| ## Status Condition Queries | ||||||||||
|
|
||||||||||
| Query resources by status conditions: `status.conditions.<Type>='<Status>'` | ||||||||||
|
|
||||||||||
| Condition types must be PascalCase (`Ready`, `Available`) and status must be `True` or `False` for resource conditions. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code in conditionsNodeConverter explicitly restricts status.conditions. fields to only the = operator: if n.Func != tsl.EqOp {
return nil, errors.BadRequest("only equality operator (=) is supported for condition queries")
}This means:
But the document mentions !=, <, <=, >, >=, not, in as supported operators. The "Status Condition Queries" section should mention that Suggested addition after line 100:
Suggested change
Anyway, besides the note, I've opened a jira ticket to fix the bug: https://issues.redhat.com/browse/HYPERFLEET-709 HYPERFLEET-709 will also update this document when it gets resolved. |
||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find available clusters | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=status.conditions.Available='True'" | ||||||||||
|
|
||||||||||
| # Find clusters that are not ready | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=status.conditions.Ready='False'" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## Complex Queries | ||||||||||
|
|
||||||||||
| Combine multiple conditions using `and`, `or`, `not`, and parentheses `()`: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find ready production clusters | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=status.conditions.Ready='True' and labels.environment='production'" | ||||||||||
|
|
||||||||||
| # Find clusters in dev or staging | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=labels.environment in ('dev', 'staging')" | ||||||||||
|
|
||||||||||
| # Find ready clusters in production or staging | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=status.conditions.Ready='True' and (labels.environment='production' or labels.environment='staging')" | ||||||||||
|
|
||||||||||
| # Find clusters that are not in production | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=not labels.environment='production'" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Operator precedence: `()` > comparisons > `not` > `and` > `or` | ||||||||||
|
|
||||||||||
| ## Other Common Use Cases | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| # Find non-production clusters | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=labels.environment in ('dev', 'staging', 'test')" | ||||||||||
|
|
||||||||||
| # Find clusters created by specific user | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=created_by='user@example.com'" | ||||||||||
|
|
||||||||||
| # Find clusters by multiple IDs | ||||||||||
| curl -G "http://localhost:8000/api/hyperfleet/v1/clusters" \ | ||||||||||
| --data-urlencode "search=id in ('2abc123', '2def456', '2ghi789')" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## Error Handling | ||||||||||
|
|
||||||||||
| Invalid queries return `400 Bad Request` with error details: | ||||||||||
|
|
||||||||||
| ```json | ||||||||||
| { | ||||||||||
rafabene marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| "type": "https://api.hyperfleet.io/errors/bad-request", | ||||||||||
| "title": "Bad Request", | ||||||||||
| "status": 400, | ||||||||||
| "detail": "Failed to parse search query: invalid-query", | ||||||||||
| "code": "HYPERFLEET-VAL-005", | ||||||||||
| "timestamp": "2025-01-15T10:30:00Z" | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.