HyperFleet API - Simple REST API for cluster lifecycle management. Provides CRUD operations for clusters and status sub-resources. Pure data layer with PostgreSQL integration - no business logic or event creation. Stateless design enables horizontal scaling.
- Language: Go 1.24.9
- API Definition: TypeSpec → OpenAPI 3.0.3
- Code Generation: openapi-generator-cli v7.16.0
- Database: PostgreSQL with GORM ORM
- Container Runtime: Podman
- Testing: Gomega + Resty
- TypeSpec-based API specification
- OpenAPI 3.0 code generation workflow
- Cluster and NodePool lifecycle management
- Adapter-based status reporting with Kubernetes-style conditions
- Pagination and search capabilities
- Complete integration test coverage
- Database migrations with GORM
- Embedded OpenAPI specification using
//go:embed
hyperfleet-api/
├── cmd/hyperfleet/ # Application entry point
├── pkg/
│ ├── api/ # API models and handlers
│ │ ├── openapi/ # Generated Go models from OpenAPI
│ │ │ ├── api/ # Embedded OpenAPI specification
│ │ │ └── model_*.go # Generated model structs
│ │ └── openapi_embed.go # Go embed directive
│ ├── dao/ # Data access layer
│ ├── db/ # Database setup and migrations
│ ├── handlers/ # HTTP request handlers
│ ├── services/ # Business logic
│ └── server/ # Server configuration
├── openapi/ # API specification source
│ └── openapi.yaml # TypeSpec-generated OpenAPI spec (32KB)
├── test/
│ ├── integration/ # Integration tests
│ └── factories/ # Test data factories
└── Makefile # Build automation
Cluster resources represent Kubernetes clusters managed across different cloud providers.
Endpoints:
GET /api/hyperfleet/v1/clusters
POST /api/hyperfleet/v1/clusters
GET /api/hyperfleet/v1/clusters/{cluster_id}
GET /api/hyperfleet/v1/clusters/{cluster_id}/statuses
POST /api/hyperfleet/v1/clusters/{cluster_id}/statuses
Data Model:
{
"kind": "Cluster",
"id": "string",
"name": "string",
"generation": 1,
"spec": {
"region": "us-west-2",
"version": "4.15",
"nodes": 3
},
"labels": {
"env": "production"
},
"status": {
"phase": "Ready",
"observed_generation": 1,
"adapters": [...]
}
}Status Phases:
NotReady- Cluster is being provisioned or has failing conditionsReady- All adapter conditions report successFailed- Cluster provisioning or operation failed
NodePool resources represent groups of compute nodes within a cluster.
Endpoints:
GET /api/hyperfleet/v1/nodepools
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}
GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses
Data Model:
{
"kind": "NodePool",
"id": "string",
"name": "string",
"owner_references": {
"kind": "Cluster",
"id": "cluster_id"
},
"spec": {
"instance_type": "m5.2xlarge",
"replicas": 3,
"disk_size": 120
},
"labels": {},
"status": {
"phase": "Ready",
"adapters": [...]
}
}Resources report status through adapter-specific condition sets following Kubernetes conventions.
Structure:
{
"adapter": "dns-adapter",
"observed_generation": 1,
"conditions": [
{
"adapter": "dns-adapter",
"type": "Ready",
"status": "True",
"observed_generation": 1,
"reason": "ClusterProvisioned",
"message": "Cluster successfully provisioned",
"created_at": "2025-11-17T15:04:05Z",
"updated_at": "2025-11-17T15:04:05Z",
}
],
"data": {}
}Note: The created_at and updated_at fields in conditions are optional and typically set by the service.
Condition Types:
Ready- Resource is operationalAvailable- Resource is available for useProgressing- Resource is being modified- Custom types defined by adapters
All list endpoints return consistent pagination metadata:
{
"kind": "ClusterList",
"page": 1,
"size": 10,
"total": 100,
"items": [...]
}Pagination Parameters:
?page=N- Page number (default: 1)?pageSize=N- Items per page (default: 100)
Search Parameters (clusters only):
?search=name='cluster-name'- Filter by name
Before running hyperfleet-api, ensure these prerequisites are installed. See PREREQUISITES.md for details.
- Go 1.24 or higher
- Podman
- PostgreSQL 13+
- Make
# 1. Generate OpenAPI code (must run first as pkg/api/openapi is required by go.mod)
make generate
# 2. Install dependencies
go mod download
# 3. Build the binary
make binary
# 4. Setup PostgreSQL database
make db/setup
# 5. Run database migrations
./hyperfleet-api migrate
# 6. Verify database schema
make db/login
psql -h localhost -U hyperfleet hyperfleet
\dtNote: The pkg/api/openapi/ directory is not tracked in git. You must run make generate after cloning or pulling changes to the OpenAPI specification.
Local development (no authentication):
make run-no-authThe service starts on localhost:8000:
- REST API:
http://localhost:8000/api/hyperfleet/v1/ - OpenAPI spec:
http://localhost:8000/openapi - Swagger UI:
http://localhost:8000/openapi-ui - Health check:
http://localhost:8083/healthcheck - Metrics:
http://localhost:8080/metrics
Test the API:
# Check API compatibility
curl http://localhost:8000/api/hyperfleet/v1/compatibility | jq
# List clusters
curl http://localhost:8000/api/hyperfleet/v1/clusters | jq
# Create a cluster
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters \
-H "Content-Type: application/json" \
-d '{
"kind": "Cluster",
"name": "prod-cluster-1",
"spec": {
"region": "us-west-2",
"version": "4.15",
"nodes": 3
},
"labels": {
"env": "production"
}
}' | jqHyperFleet API can be configured via environment variables:
OPENAPI_SCHEMA_PATH
- Description: Path to the OpenAPI specification file used for validating cluster and nodepool spec fields
- Default:
openapi/openapi.yaml(repository base schema) - Required: No (service will start with default schema if not specified)
- Usage:
- Local development: Uses default repository schema
- Production: Set via Helm deployment to inject provider-specific schema from ConfigMap
Example:
# Local development (uses default)
./hyperfleet-api serve
# Custom schema path
export OPENAPI_SCHEMA_PATH=/path/to/custom/openapi.yaml
./hyperfleet-api serve
# Production (Helm sets this automatically)
# OPENAPI_SCHEMA_PATH=/etc/hyperfleet/schemas/openapi.yamlHow it works:
- The schema validator loads the OpenAPI specification at startup
- When POST/PATCH requests are made to create or update resources, the
specfield is validated against the schema - Invalid specs return HTTP 400 with detailed field-level error messages
- Unknown resource types or missing schemas are gracefully handled (validation skipped)
Provider-specific schemas: In production deployments, cloud providers can inject their own OpenAPI schemas via Helm:
helm install hyperfleet-api ./chart \
--set-file provider.schema=gcp-schema.yamlThe injected schema is mounted at /etc/hyperfleet/schemas/openapi.yaml and automatically used for validation.
# Unit tests
make test
# Integration tests (requires running database)
make test-integrationTest Coverage:
All 12 API endpoints have integration test coverage:
| Endpoint | Coverage |
|---|---|
| GET /compatibility | ✓ |
| GET /clusters | ✓ (list, pagination, search) |
| POST /clusters | ✓ |
| GET /clusters/{id} | ✓ |
| GET /clusters/{id}/statuses | ✓ |
| POST /clusters/{id}/statuses | ✓ |
| GET /nodepools | ✓ (list, pagination) |
| GET /clusters/{id}/nodepools | ✓ |
| POST /clusters/{id}/nodepools | ✓ |
| GET /clusters/{id}/nodepools/{nodepool_id} | ✓ |
| GET /clusters/{id}/nodepools/{nodepool_id}/statuses | ✓ |
| POST /clusters/{id}/nodepools/{nodepool_id}/statuses | ✓ |
The API specification is defined using TypeSpec and compiled to OpenAPI 3.0 from hyperfleet-api-spec:
TypeSpec definitions (.tsp files)
↓
tsp compile
↓
openapi/openapi.yaml (32KB, source specification)
Generated Go code is created via Docker-based workflow:
openapi/openapi.yaml
↓
make generate (podman + openapi-generator-cli v7.16.0)
↓
pkg/api/openapi/model_*.go (Go model structs)
pkg/api/openapi/api/openapi.yaml (44KB, fully resolved spec)
Generation process:
make generateremoves existing generated code- Builds Docker image with openapi-generator-cli
- Runs code generator inside container
- Copies generated files to host
Generated artifacts:
- Model structs with JSON tags
- Type definitions for all API resources
- Validation tags for required fields
- Fully resolved OpenAPI specification
Important: Generated files in pkg/api/openapi/ are not tracked in git. Developers must run make generate after cloning or pulling changes to the OpenAPI specification.
The fully resolved OpenAPI specification is embedded at compile time using Go 1.16+ //go:embed:
// pkg/api/openapi_embed.go
//go:embed openapi/api/openapi.yaml
var openapiFS embed.FS
func GetOpenAPISpec() ([]byte, error) {
return fs.ReadFile(openapiFS, "openapi/api/openapi.yaml")
}This embedded specification is:
- Compiled into the binary
- Served at
/openapiendpoint - Used by Swagger UI at
/openapi-ui - Zero runtime file I/O required
clusters
- Primary resources for cluster management
- Includes spec (region, version, nodes)
- Stores metadata (labels, generation)
- Tracks created_by, updated_by
node_pools
- Child resources owned by clusters
- Contains spec (instance_type, replicas, disk_size)
- Maintains owner_id foreign key to clusters
- Soft delete support
adapter_statuses
- Polymorphic status records
- owner_type: 'Cluster' or 'NodePool'
- owner_id: References clusters or node_pools
- Stores adapter name and conditions JSON
- Tracks observed_generation
labels
- Key-value pairs for resource categorization
- owner_type and owner_id for polymorphic relationships
- Supports filtering and search
Source file (openapi/openapi.yaml - 32KB):
- TypeSpec compilation output
- Uses
$reffor parameter reuse (78 references) - Compact, maintainable structure
- Input for code generation
Generated file (pkg/api/openapi/api/openapi.yaml - 44KB):
- openapi-generator output
- Fully resolved (no external
$ref) - Inline parameter definitions (54 references)
- Includes server configuration
- Embedded in Go binary
Key differences:
- Source file: Optimized for maintainability
- Generated file: Optimized for runtime serving
# Generate OpenAPI client code
make generate
# Build binary
make binary
# Run database migrations
./hyperfleet-api migrate
# Start server (no auth)
make run-no-auth
# Run tests
make test
make test-integration
# Database management
make db/setup # Create PostgreSQL container
make db/teardown # Remove PostgreSQL container
make db/login # Connect to database shellDevelopment mode (no auth):
make run-no-auth
curl http://localhost:8000/api/hyperfleet/v1/clustersProduction mode (OCM auth):
make run
ocm login --token=${OCM_ACCESS_TOKEN} --url=http://localhost:8000
ocm get /api/hyperfleet/v1/clusters# 1. Create cluster
CLUSTER=$(curl -s -X POST http://localhost:8000/api/hyperfleet/v1/clusters \
-H "Content-Type: application/json" \
-d '{
"kind": "Cluster",
"name": "production-cluster",
"spec": {
"region": "us-east-1",
"version": "4.16",
"nodes": 5
},
"labels": {
"env": "production",
"team": "platform"
}
}')
CLUSTER_ID=$(echo $CLUSTER | jq -r '.id')
# 2. Create node pool
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters/$CLUSTER_ID/nodepools \
-H "Content-Type: application/json" \
-d '{
"kind": "NodePool",
"name": "worker-pool",
"spec": {
"instance_type": "m5.2xlarge",
"replicas": 10,
"disk_size": 200
},
"labels": {
"pool_type": "worker"
}
}' | jq
# 3. Report adapter status
curl -X POST http://localhost:8000/api/hyperfleet/v1/clusters/$CLUSTER_ID/statuses \
-H "Content-Type: application/json" \
-d '{
"adapter": "dns-adapter",
"observed_generation": 1,
"conditions": [
{
"adapter": "dns-adapter",
"type": "Ready",
"status": "True",
"observed_generation": 1,
"reason": "ClusterProvisioned",
"message": "Cluster successfully provisioned",
"created_at": "2025-11-17T15:04:05Z",
"updated_at": "2025-11-17T15:04:05Z"
}
]
}' | jq
# 4. Get cluster with aggregated status
curl http://localhost:8000/api/hyperfleet/v1/clusters/$CLUSTER_ID | jq[License information to be added]
