Skip to content
Open
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,15 @@ curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters \

# Search clusters
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=labels.env='production'" | jq
--data-urlencode "search=labels.environment='production'" | jq

# Search ready clusters in a specific region
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=status.conditions.Ready='True' and labels.region='us-east'" | jq
```

See [docs/search.md](docs/search.md) for search and filtering documentation.

## Development

### Common Commands
Expand Down Expand Up @@ -161,6 +167,7 @@ This project uses [pre-commit](https://pre-commit.io/) for code quality checks.
### Additional Resources

- **[PREREQUISITES.md](PREREQUISITES.md)** - Prerequisite installation
- **[Search and Filtering](docs/search.md)** - Guide to TSL query syntax, operators, and examples
- **[docs/continuous-delivery-migration.md](docs/continuous-delivery-migration.md)** - CD migration guide
- **[docs/dao.md](docs/dao.md)** - Data access patterns
- **[docs/testcontainers.md](docs/testcontainers.md)** - Testcontainers usage
Expand Down
30 changes: 2 additions & 28 deletions docs/api-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,40 +408,14 @@ GET /api/hyperfleet/v1/clusters?page=1&pageSize=10

### Search

Search using TSL (Tree Search Language) query syntax:
All list endpoints support filtering using TSL (Tree Search Language) query syntax. Example:

```bash
# Simple equality
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=name='my-cluster'"

# AND query with condition-based status
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=status.conditions.Ready='True' and labels.environment='production'"

# OR query
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=labels.environment='dev' or labels.environment='staging'"

# Query for available resources
curl -G http://localhost:8000/api/hyperfleet/v1/clusters \
--data-urlencode "search=status.conditions.Available='True'"
```

**Supported fields:**

- `name` - Resource name
- `status.conditions.<Type>` - Condition status (True, False). Examples:
- `status.conditions.Ready='True'` - Resources that are ready
- `status.conditions.Available='True'` - Resources that are available
- `labels.<key>` - Label values

**Supported operators:**

- `=` - Equality
- `in` - In list
- `and` - Logical AND
- `or` - Logical OR
See **[search.md](search.md)** for complete documentation.

## Field Descriptions

Expand Down
165 changes: 165 additions & 0 deletions docs/search.md
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'` |

### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

  • status.conditions.Ready!='False' → returns a 400 error
  • not status.conditions.Ready='True' → silently returns empty results (the condition extraction replaces the node with 1 = 1, so not (1 = 1) is always false)

But the document mentions !=, <, <=, >, >=, not, in as supported operators.

The "Status Condition Queries" section should mention that
status.conditions. fields only support the = operator. Other operators
(!=, <, >, in, not, etc.) will either return a 400 error or silently produce
incorrect results.

Suggested addition after line 100:

Suggested change
Condition types must be PascalCase (`Ready`, `Available`) and status must be `True` or `False` for resource conditions.
Condition types must be PascalCase (`Ready`, `Available`) and status must be `True` or `False` for resource conditions.
**Note:** Only the `=` operator is supported for condition queries. Other operators (`!=`, `<`, `>`, `in`, etc.) will return an error. Use `not` with conditions cautiously — `not status.conditions.Ready='True'` does not work as expected.

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
{
"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"
}
```