From 2ce53e87538426f59de967935028d674ad15c8ff Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Tue, 16 Dec 2025 13:08:36 +0100 Subject: [PATCH 1/8] feat: introduce generation field for NodePool management --- openapi/openapi.yaml | 28 ++++++++++++++++------------ pkg/api/node_pool_types.go | 13 ++++++++++--- pkg/api/presenters/node_pool.go | 13 +++++++------ pkg/dao/node_pool.go | 18 ++++++++++++++++++ pkg/services/node_pool.go | 3 +-- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 08665c8..44cc17f 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -361,11 +361,6 @@ paths: schema: $ref: '#/components/schemas/Error' components: - securitySchemes: - BearerAuth: - type: http - scheme: bearer - parameters: QueryParams.order: name: order @@ -407,10 +402,6 @@ components: schema: type: string explode: false - description: | - Filter results using TSL (Tree Search Language) query syntax. - - Examples: `status.phase='NotReady'`, `name in ('c1','c2')`, `labels.region='us-east'` schemas: APIResource: type: object @@ -713,9 +704,9 @@ components: default: Cluster name: type: string - minLength: 3 + minLength: 1 maxLength: 63 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + pattern: ^[a-z0-9-]+$ description: Cluster name (unique) spec: allOf: @@ -913,6 +904,7 @@ components: - updated_time - created_by - updated_by + - generation - owner_references - status properties: @@ -928,6 +920,11 @@ components: updated_by: type: string format: email + generation: + type: integer + format: int32 + minimum: 1 + description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer owner_references: $ref: '#/components/schemas/ObjectReference' status: @@ -943,6 +940,7 @@ components: environment: production pooltype: worker spec: {} + generation: 1 created_time: '2021-01-01T00:00:00Z' updated_time: '2021-01-01T00:00:00Z' created_by: user-123@example.com @@ -1046,6 +1044,7 @@ components: - updated_time - created_by - updated_by + - generation - owner_references - status - name @@ -1063,6 +1062,11 @@ components: updated_by: type: string format: email + generation: + type: integer + format: int32 + minimum: 1 + description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer owner_references: $ref: '#/components/schemas/ObjectReference' status: @@ -1206,6 +1210,6 @@ components: - Failed description: Phase of a resource (Cluster or NodePool) servers: - - url: https://api.hyperfleet.redhat.com + - url: https://hyperfleet.redhat.com description: Production variables: {} diff --git a/pkg/api/node_pool_types.go b/pkg/api/node_pool_types.go index b966052..5a90871 100644 --- a/pkg/api/node_pool_types.go +++ b/pkg/api/node_pool_types.go @@ -19,6 +19,9 @@ type NodePool struct { Labels datatypes.JSON `json:"labels,omitempty" gorm:"type:jsonb"` Href string `json:"href,omitempty" gorm:"size:500"` + // Version control + Generation int32 `json:"generation" gorm:"default:1;not null"` + // Owner references (expanded) OwnerID string `json:"owner_id" gorm:"size:255;not null;index"` OwnerKind string `json:"owner_kind" gorm:"size:50;not null"` @@ -55,6 +58,9 @@ func (np *NodePool) BeforeCreate(tx *gorm.DB) error { np.ID = NewID() np.CreatedTime = now np.UpdatedTime = now + if np.Generation == 0 { + np.Generation = 1 + } if np.OwnerKind == "" { np.OwnerKind = "Cluster" } @@ -78,7 +84,8 @@ func (np *NodePool) BeforeUpdate(tx *gorm.DB) error { } type NodePoolPatchRequest struct { - Name *string `json:"name,omitempty"` - Spec *map[string]interface{} `json:"spec,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` + Name *string `json:"name,omitempty"` + Spec *map[string]interface{} `json:"spec,omitempty"` + Generation *int32 `json:"generation,omitempty"` + Labels *map[string]string `json:"labels,omitempty"` } diff --git a/pkg/api/presenters/node_pool.go b/pkg/api/presenters/node_pool.go index f9f005f..dd954b8 100644 --- a/pkg/api/presenters/node_pool.go +++ b/pkg/api/presenters/node_pool.go @@ -87,12 +87,13 @@ func PresentNodePool(nodePool *api.NodePool) openapi.NodePool { kind := nodePool.Kind result := openapi.NodePool{ - Id: &nodePool.ID, - Kind: &kind, - Href: &href, - Name: nodePool.Name, - Spec: spec, - Labels: &labels, + Id: &nodePool.ID, + Kind: &kind, + Href: &href, + Name: nodePool.Name, + Spec: spec, + Labels: &labels, + Generation: nodePool.Generation, OwnerReferences: openapi.ObjectReference{ Id: &nodePool.OwnerID, Kind: &nodePool.OwnerKind, diff --git a/pkg/dao/node_pool.go b/pkg/dao/node_pool.go index e62ddd2..e48c03e 100644 --- a/pkg/dao/node_pool.go +++ b/pkg/dao/node_pool.go @@ -1,6 +1,7 @@ package dao import ( + "bytes" "context" "gorm.io/gorm/clause" @@ -48,6 +49,23 @@ func (d *sqlNodePoolDao) Create(ctx context.Context, nodePool *api.NodePool) (*a func (d *sqlNodePoolDao) Replace(ctx context.Context, nodePool *api.NodePool) (*api.NodePool, error) { g2 := (*d.sessionFactory).New(ctx) + + // Get the existing nodePool to compare spec + existing, err := d.Get(ctx, nodePool.ID) + if err != nil { + db.MarkForRollback(ctx, err) + return nil, err + } + + // Compare spec: if changed, increment generation + if !bytes.Equal(existing.Spec, nodePool.Spec) { + nodePool.Generation = existing.Generation + 1 + } else { + // Spec unchanged, preserve generation + nodePool.Generation = existing.Generation + } + + // Save the nodePool if err := g2.Omit(clause.Associations).Save(nodePool).Error; err != nil { db.MarkForRollback(ctx, err) return nil, err diff --git a/pkg/services/node_pool.go b/pkg/services/node_pool.go index 43bb74d..0b949c4 100644 --- a/pkg/services/node_pool.go +++ b/pkg/services/node_pool.go @@ -186,8 +186,7 @@ func (s *sqlNodePoolService) UpdateNodePoolStatusFromAdapters(ctx context.Contex } // Compute overall phase using required adapters - // NodePool doesn't have a generation field, so we pass 0 which means "don't check generation" - newPhase := ComputePhase(ctx, adapterStatuses, requiredNodePoolAdapters, 0) + newPhase := ComputePhase(ctx, adapterStatuses, requiredNodePoolAdapters, nodePool.Generation) // Calculate min(adapters[].last_report_time) for nodepool.status.last_updated_time // This uses the OLDEST adapter timestamp to ensure Sentinel can detect stale adapters From 62b5484373e114b80a6a9ee419649b1ec5c14a75 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Tue, 16 Dec 2025 19:31:59 +0100 Subject: [PATCH 2/8] update openapi to v1.0.1 --- openapi/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 44cc17f..d4b6879 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: HyperFleet API - version: 1.0.0 + version: 1.0.1 contact: name: HyperFleet Team license: From d63747b83593e1ea87e33e4a78ff432abe694b99 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Tue, 16 Dec 2025 19:48:31 +0100 Subject: [PATCH 3/8] updated openapi.yaml --- openapi.yaml | 1218 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1218 insertions(+) create mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..522a8af --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,1218 @@ +openapi: 3.0.0 +info: + title: HyperFleet API + version: 1.0.1 + contact: + name: HyperFleet Team + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + description: |- + HyperFleet API provides simple CRUD operations for managing cluster resources and their status history. + + **Architecture**: Simple CRUD only, no business logic, no event creation. + Sentinel operator handles all orchestration logic. + Adapters handle the specifics of managing spec +tags: [] +paths: + /api/hyperfleet/v1/clusters: + get: + operationId: getClusters + summary: List clusters + parameters: + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + - $ref: '#/components/parameters/SearchParams' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/ClusterList' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + operationId: postCluster + summary: Create cluster + description: |- + Create a new cluster resource. + + **Note**: The `status` object in the response is read-only and computed by the service. + It is NOT part of the request body. Initially, status.phase will be "NotReady" and + status.conditions will be empty until status conditions are POSTed. + parameters: [] + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/Cluster' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ClusterCreateRequest' + /api/hyperfleet/v1/clusters/{cluster_id}: + get: + operationId: getClusterById + summary: Get cluster by ID + parameters: + - $ref: '#/components/parameters/SearchParams' + - name: cluster_id + in: path + required: true + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/Cluster' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /api/hyperfleet/v1/clusters/{cluster_id}/nodepools: + get: + operationId: getNodePoolsByClusterId + summary: List all nodepools for cluster + description: Returns the list of all nodepools for a cluster + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + - $ref: '#/components/parameters/SearchParams' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/NodePoolList' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + post: + operationId: createNodePool + summary: Create nodepool + description: Create a NodePool for a cluster + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/NodePoolCreateResponse' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NodePoolCreateRequest' + /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}: + get: + operationId: getNodePoolById + summary: Get nodepool by ID + description: Returns specific nodepool + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + - name: nodepool_id + in: path + required: true + description: NodePool ID + schema: + type: string + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/NodePool' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses: + post: + operationId: postNodePoolStatuses + summary: Create or update adapter status + description: |- + Adapter creates or updates its status report for this nodepool. + If adapter already has a status, it will be updated (upsert by adapter name). + + Response includes the full adapter status with all conditions. + Adapter should call this endpoint every time it evaluates the nodepool. + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + - name: nodepool_id + in: path + required: true + schema: + type: string + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatus' + '400': + description: The server could not understand the request due to invalid syntax. + '404': + description: The server cannot find the requested resource. + '409': + description: The request conflicts with the current state of the server. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatusCreateRequest' + get: + operationId: getNodePoolsStatuses + summary: List all adapter statuses for nodepools + description: Returns adapter status reports for this nodepool + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + - name: nodepool_id + in: path + required: true + schema: + type: string + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + - $ref: '#/components/parameters/SearchParams' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatusList' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /api/hyperfleet/v1/clusters/{cluster_id}/statuses: + post: + operationId: postClusterStatuses + summary: Create or update adapter status + description: |- + Adapter creates or updates its status report for this cluster. + If adapter already has a status, it will be updated (upsert by adapter name). + + Response includes the full adapter status with all conditions. + Adapter should call this endpoint every time it evaluates the cluster. + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + responses: + '201': + description: The request has succeeded and a new resource has been created as a result. + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatus' + '400': + description: The server could not understand the request due to invalid syntax. + '404': + description: The server cannot find the requested resource. + '409': + description: The request conflicts with the current state of the server. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatusCreateRequest' + get: + operationId: getClusterStatuses + summary: List all adapter statuses for cluster + description: Returns adapter status reports for this cluster + parameters: + - name: cluster_id + in: path + required: true + description: Cluster ID + schema: + type: string + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + - $ref: '#/components/parameters/SearchParams' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatusList' + '400': + description: The server could not understand the request due to invalid syntax. + '404': + description: The server cannot find the requested resource. + /api/hyperfleet/v1/nodepools: + get: + operationId: getNodePools + summary: List all nodepools for cluster + description: Returns the list of all nodepools + parameters: + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + - $ref: '#/components/parameters/SearchParams' + responses: + '200': + description: The request has succeeded. + content: + application/json: + schema: + $ref: '#/components/schemas/NodePoolList' + '400': + description: The server could not understand the request due to invalid syntax. + default: + description: An unexpected error response. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +components: + parameters: + QueryParams.order: + name: order + in: query + required: false + schema: + $ref: '#/components/schemas/OrderDirection' + explode: false + QueryParams.orderBy: + name: orderBy + in: query + required: false + schema: + type: string + default: created_time + explode: false + QueryParams.page: + name: page + in: query + required: false + schema: + type: integer + format: int32 + default: 1 + explode: false + QueryParams.pageSize: + name: pageSize + in: query + required: false + schema: + type: integer + format: int32 + default: 20 + explode: false + SearchParams: + name: search + in: query + required: false + description: |- + Filter results using TSL (Tree Search Language) query syntax. + Examples: `status.phase='NotReady'`, `name in ('c1','c2')`, `labels.region='us-east'` + schema: + type: string + explode: false + schemas: + APIResource: + type: object + properties: + labels: + type: object + additionalProperties: + type: string + description: labels for the API resource as pairs of name:value strings + allOf: + - $ref: '#/components/schemas/ObjectReference' + AdapterCondition: + type: object + allOf: + - $ref: '#/components/schemas/ConditionBase' + description: |- + Condition in AdapterStatus + Used for standard Kubernetes condition types: "Available", "Applied", "Health" + Note: observed_generation is at AdapterStatus level, not per-condition, + since all conditions in one AdapterStatus share the same observed generation + AdapterStatus: + type: object + required: + - conditions + - created_time + - last_report_time + properties: + conditions: + type: array + items: + $ref: '#/components/schemas/AdapterCondition' + description: |- + Kubernetes-style conditions tracking adapter state + Typically includes: Available, Applied, Health + created_time: + type: string + format: date-time + description: When this adapter status was first created (API-managed) + last_report_time: + type: string + format: date-time + description: |- + When this adapter last reported its status (API-managed) + Updated every time the adapter POSTs, even if conditions haven't changed + Used by Sentinel to detect adapter liveness + allOf: + - $ref: '#/components/schemas/AdapterStatusBase' + description: |- + AdapterStatus represents the complete status report from an adapter + Contains multiple conditions, job metadata, and adapter-specific data + example: + adapter: validator + observed_generation: 1 + conditions: + - type: Available + status: 'True' + reason: All validations passed + message: All 30 validation tests passed + last_transition_time: '2021-01-01T10:00:00Z' + - type: Applied + status: 'True' + reason: Validation job applied + message: Validation job applied successfully + last_transition_time: '2021-01-01T10:00:00Z' + - type: Health + status: 'True' + reason: All adapter operations completed successfully + message: All adapter runtime operations completed successfully + last_transition_time: '2021-01-01T10:00:00Z' + metadata: + job_name: validator-job-abc123 + job_namespace: hyperfleet-system + attempt: 1 + started_time: '2021-01-01T10:00:00Z' + completed_time: '2021-01-01T10:02:00Z' + duration: 2m + data: + validation_results: + total_tests: 30 + passed: 30 + failed: 0 + created_time: '2021-01-01T10:00:00Z' + last_report_time: '2021-01-01T10:02:00Z' + AdapterStatusBase: + type: object + required: + - adapter + - observed_generation + properties: + adapter: + type: string + description: Adapter name (e.g., "validator", "dns", "provisioner") + observed_generation: + type: integer + format: int32 + description: Which generation of the resource this status reflects + metadata: + type: object + properties: + job_name: + type: string + job_namespace: + type: string + attempt: + type: integer + format: int32 + started_time: + type: string + format: date-time + completed_time: + type: string + format: date-time + duration: + type: string + description: Job execution metadata + data: + type: object + additionalProperties: {} + description: Adapter-specific data (structure varies by adapter type) + description: Base fields shared by AdapterStatus and AdapterStatusCreateRequest + AdapterStatusCreateRequest: + type: object + required: + - observed_time + - conditions + properties: + observed_time: + type: string + format: date-time + description: |- + When the adapter observed this resource state + API will use this to set AdapterStatus.last_report_time + conditions: + type: array + items: + $ref: '#/components/schemas/ConditionRequest' + allOf: + - $ref: '#/components/schemas/AdapterStatusBase' + description: Request payload for creating/updating adapter status + example: + adapter: validator + observed_generation: 1 + observed_time: '2021-01-01T10:00:00Z' + conditions: + - type: Available + status: 'True' + reason: All validations passed + message: All 30 validation tests passed + - type: Applied + status: 'True' + reason: Validation job applied + message: Validation job applied successfully + - type: Health + status: 'True' + reason: All adapter operations completed successfully + message: All adapter runtime operations completed successfully + metadata: + job_name: validator-job-abc123 + job_namespace: hyperfleet-system + attempt: 1 + started_time: '2021-01-01T10:00:00Z' + completed_time: '2021-01-01T10:02:00Z' + duration: 2m + data: + validation_results: + total_tests: 30 + passed: 30 + failed: 0 + AdapterStatusList: + type: object + required: + - items + properties: + items: + type: array + items: + $ref: '#/components/schemas/AdapterStatus' + allOf: + - $ref: '#/components/schemas/List' + description: List of adapter statuses with pagination metadata + example: + kind: AdapterStatusList + page: 1 + size: 2 + total: 2 + items: + - adapter: validator + observed_generation: 1 + conditions: + - type: Available + status: 'True' + reason: All validations passed + message: All 30 validation tests passed + last_transition_time: '2021-01-01T10:00:00Z' + metadata: + job_name: validator-job-abc123 + duration: 2m + created_time: '2021-01-01T10:00:00Z' + last_report_time: '2021-01-01T10:02:00Z' + - adapter: dns + observed_generation: 1 + conditions: + - type: Available + status: 'True' + reason: DNS configured + message: DNS records created + last_transition_time: '2021-01-01T10:01:00Z' + created_time: '2021-01-01T10:01:00Z' + last_report_time: '2021-01-01T10:01:30Z' + BearerAuth: + type: object + required: + - type + - scheme + properties: + type: + type: string + enum: + - http + scheme: + type: string + enum: + - bearer + Cluster: + type: object + required: + - created_time + - updated_time + - created_by + - updated_by + - generation + - status + properties: + created_time: + type: string + format: date-time + updated_time: + type: string + format: date-time + created_by: + type: string + format: email + updated_by: + type: string + format: email + generation: + type: integer + format: int32 + minimum: 1 + description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer + status: + $ref: '#/components/schemas/ClusterStatus' + allOf: + - $ref: '#/components/schemas/ClusterBase' + example: + kind: Cluster + id: cluster-123 + href: https://api.hyperfleet.com/v1/clusters/cluster-123 + name: cluster-123 + labels: + environment: production + team: platform + spec: {} + created_time: '2021-01-01T00:00:00Z' + updated_time: '2021-01-01T00:00:00Z' + generation: 1 + status: + phase: Ready + last_transition_time: '2021-01-01T00:00:00Z' + observed_generation: 1 + last_updated_time: '2021-01-01T00:00:00Z' + conditions: + - type: ValidationSuccessful + status: 'True' + reason: All validations passed + message: All 30 validation tests passed + observed_generation: 1 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: DNSSuccessful + status: 'True' + reason: DNS configured + message: DNS records created for custom.domain.com + observed_generation: 1 + created_time: '2021-01-01T10:01:00Z' + last_updated_time: '2021-01-01T10:01:00Z' + last_transition_time: '2021-01-01T10:01:00Z' + created_by: user-123@example.com + updated_by: user-123@example.com + ClusterBase: + type: object + required: + - kind + - name + - spec + properties: + kind: + type: string + default: Cluster + name: + type: string + minLength: 1 + maxLength: 63 + pattern: ^[a-z0-9-]+$ + description: Cluster name (unique) + spec: + allOf: + - $ref: '#/components/schemas/ClusterSpec' + description: |- + Cluster specification + CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job + But CLM will validate the schema before accepting the request + allOf: + - $ref: '#/components/schemas/APIResource' + ClusterCreateRequest: + type: object + allOf: + - $ref: '#/components/schemas/ClusterBase' + example: + kind: Cluster + name: cluster-123 + labels: + environment: production + team: platform + spec: {} + ClusterList: + type: object + required: + - items + properties: + items: + type: array + items: + $ref: '#/components/schemas/Cluster' + allOf: + - $ref: '#/components/schemas/List' + ClusterSpec: + type: object + description: |- + Core cluster specification. + Accepts any properties as the spec is provider-agnostic. + This is represented as a simple object to allow flexibility. + ClusterStatus: + type: object + required: + - phase + - last_transition_time + - observed_generation + - last_updated_time + - conditions + properties: + phase: + allOf: + - $ref: '#/components/schemas/ResourcePhase' + description: |- + Current cluster phase (native database column). + Updated when conditions are reported. + Note: status.phase provides aggregated view from all conditions. + last_transition_time: + type: string + format: date-time + description: |- + When cluster last transitioned (used by Sentinel for backoff) + Updated when conditions are reported if the phase changes + observed_generation: + type: integer + format: int32 + description: |- + Last generation processed + Updated when conditions are reported. + This will be the lowest value of each condition's observed_generation values + The phase value is based on this generation + last_updated_time: + type: string + format: date-time + description: |- + Time of the last update + Updated when conditions are reported. + Computed as min(conditions[].last_updated_time) to detect stale adapters. + Uses earliest (not latest) timestamp to ensure Sentinel can detect if any adapter has stopped reporting. + conditions: + type: array + items: + $ref: '#/components/schemas/ResourceCondition' + description: |- + Cluster status computed from all status conditions. + + This object is computed by the service and CANNOT be modified directly. + It is aggregated from condition updates posted to `/clusters/{id}/statuses`. + + Provides quick overview of all reported conditions and aggregated phase. + ConditionBase: + type: object + required: + - type + - status + - last_transition_time + properties: + type: + type: string + description: Condition type + status: + allOf: + - $ref: '#/components/schemas/ConditionStatus' + description: Condition status + reason: + type: string + description: Machine-readable reason code + message: + type: string + description: Human-readable message + last_transition_time: + type: string + format: date-time + description: |- + When this condition last transitioned status (API-managed) + Only updated when status changes (True/False/Unknown), not when reason/message changes + description: Base condition fields shared by all condition types + ConditionRequest: + type: object + required: + - type + - status + properties: + type: + type: string + status: + $ref: '#/components/schemas/ConditionStatus' + reason: + type: string + message: + type: string + description: |- + Condition data for create/update requests (from adapters) + observed_generation and observed_time are now at AdapterStatusCreateRequest level + ConditionStatus: + type: string + enum: + - 'True' + - 'False' + - Unknown + description: Status value for conditions + Error: + type: object + properties: + id: + type: string + kind: + type: string + description: Resource kind + href: + type: string + description: Resource URI + code: + type: string + reason: + type: string + operation_id: + type: string + details: + type: array + items: + type: object + properties: + field: + type: string + description: Field path that failed validation + error: + type: string + description: Validation error message for this field + description: Field-level validation errors (optional) + List: + type: object + required: + - kind + - page + - size + - total + - items + properties: + kind: + type: string + page: + type: integer + format: int32 + size: + type: integer + format: int32 + total: + type: integer + format: int32 + items: + type: array + items: {} + NodePool: + type: object + required: + - created_time + - updated_time + - created_by + - updated_by + - generation + - owner_references + - status + properties: + created_time: + type: string + format: date-time + updated_time: + type: string + format: date-time + created_by: + type: string + format: email + updated_by: + type: string + format: email + generation: + type: integer + format: int32 + minimum: 1 + description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer + owner_references: + $ref: '#/components/schemas/ObjectReference' + status: + $ref: '#/components/schemas/NodePoolStatus' + allOf: + - $ref: '#/components/schemas/NodePoolBase' + example: + kind: NodePool + id: nodepool-123 + href: https://api.hyperfleet.com/v1/nodepools/nodepool-123 + name: worker-pool-1 + labels: + environment: production + pooltype: worker + spec: {} + generation: 1 + created_time: '2021-01-01T00:00:00Z' + updated_time: '2021-01-01T00:00:00Z' + created_by: user-123@example.com + updated_by: user-123@example.com + owner_references: + id: cluster-123 + kind: Cluster + href: https://api.hyperfleet.com/v1/clusters/cluster-123 + status: + phase: Ready + last_transition_time: '2021-01-01T10:00:00Z' + observed_generation: 1 + last_updated_time: '2021-01-01T10:02:00Z' + conditions: + - type: ValidationSuccessful + status: 'True' + reason: All validations passed + message: NodePool validation passed + observed_generation: 1 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: NodePoolSuccessful + status: 'True' + reason: NodePool provisioned successfully + message: NodePool has been scaled to desired count + observed_generation: 1 + created_time: '2021-01-01T10:01:00Z' + last_updated_time: '2021-01-01T10:01:00Z' + last_transition_time: '2021-01-01T10:01:00Z' + NodePoolBase: + type: object + required: + - name + - spec + properties: + labels: + type: object + additionalProperties: + type: string + description: labels for the API resource as pairs of name:value strings + id: + type: string + description: Resource identifier + kind: + type: string + description: Resource kind + href: + type: string + description: Resource URI + name: + type: string + description: NodePool name (unique in a cluster) + spec: + allOf: + - $ref: '#/components/schemas/NodePoolSpec' + description: |- + Cluster specification + CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job + But CLM will validate the schema before accepting the request + NodePoolCreateRequest: + type: object + required: + - name + - spec + properties: + labels: + type: object + additionalProperties: + type: string + description: labels for the API resource as pairs of name:value strings + id: + type: string + description: Resource identifier + kind: + type: string + description: Resource kind + href: + type: string + description: Resource URI + name: + type: string + description: NodePool name (unique in a cluster) + spec: + allOf: + - $ref: '#/components/schemas/NodePoolSpec' + description: |- + Cluster specification + CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job + But CLM will validate the schema before accepting the request + example: + name: worker-pool-1 + labels: + environment: production + pooltype: worker + spec: {} + NodePoolCreateResponse: + type: object + required: + - created_time + - updated_time + - created_by + - updated_by + - generation + - owner_references + - status + - name + - spec + properties: + created_time: + type: string + format: date-time + updated_time: + type: string + format: date-time + created_by: + type: string + format: email + updated_by: + type: string + format: email + generation: + type: integer + format: int32 + minimum: 1 + description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer + owner_references: + $ref: '#/components/schemas/ObjectReference' + status: + $ref: '#/components/schemas/NodePoolStatus' + labels: + type: object + additionalProperties: + type: string + description: labels for the API resource as pairs of name:value strings + id: + type: string + description: Resource identifier + kind: + type: string + description: Resource kind + href: + type: string + description: Resource URI + name: + type: string + description: NodePool name (unique in a cluster) + spec: + allOf: + - $ref: '#/components/schemas/NodePoolSpec' + description: |- + Cluster specification + CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job + But CLM will validate the schema before accepting the request + NodePoolList: + type: object + required: + - items + properties: + items: + type: array + items: + $ref: '#/components/schemas/NodePool' + allOf: + - $ref: '#/components/schemas/List' + NodePoolSpec: + type: object + description: |- + Core nodepool specification. + Accepts any properties as the spec is provider-agnostic. + This is represented as a simple object to allow flexibility. + NodePoolStatus: + type: object + required: + - phase + - observed_generation + - last_transition_time + - last_updated_time + - conditions + properties: + phase: + allOf: + - $ref: '#/components/schemas/ResourcePhase' + description: |- + Current NodePool phase (native database column). + Updated when conditions are reported. + Note: status.phase provides aggregated view from all conditions. + observed_generation: + type: integer + format: int32 + minimum: 1 + description: |- + Last generation processed + Updated when conditions are reported. + This will be the lowest value of each condition's observed_generation values + The phase value is based on this generation + last_transition_time: + type: string + format: date-time + description: When NodePool last transitioned (used by Sentinel for backoff) + last_updated_time: + type: string + format: date-time + description: |- + Time of the last update + Updated when conditions are reported. + Computed as min(conditions[].last_updated_time) to detect stale adapters. + Uses earliest (not latest) timestamp to ensure Sentinel can detect if any adapter has stopped reporting. + conditions: + type: array + items: + $ref: '#/components/schemas/ResourceCondition' + description: |- + NodePool status computed from all status conditions. + + This object is computed by the service and CANNOT be modified directly. + ObjectReference: + type: object + properties: + id: + type: string + description: Resource identifier + kind: + type: string + description: Resource kind + href: + type: string + description: Resource URI + OrderDirection: + type: string + enum: + - asc + - desc + ResourceCondition: + type: object + required: + - observed_generation + - created_time + - last_updated_time + properties: + observed_generation: + type: integer + format: int32 + description: Generation of the spec that this condition reflects + created_time: + type: string + format: date-time + description: When this condition was first created (API-managed) + last_updated_time: + type: string + format: date-time + description: |- + When the corresponding adapter last reported (API-managed) + Updated every time the adapter POSTs, even if condition status hasn't changed + Copied from AdapterStatus.last_report_time + allOf: + - $ref: '#/components/schemas/ConditionBase' + description: |- + Condition in Cluster/NodePool status + Used for semantic condition types: "ValidationSuccessful", "DNSSuccessful", "NodePoolSuccessful", etc. + Includes observed_generation and last_updated_time to track adapter-specific state + ResourcePhase: + type: string + enum: + - NotReady + - Ready + - Failed + description: Phase of a resource (Cluster or NodePool) +servers: + - url: https://hyperfleet.redhat.com + description: Production + variables: {} From 2bc2453fce9d975ede98cb448c3bd4a49d3fd4f9 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Tue, 16 Dec 2025 23:19:10 +0100 Subject: [PATCH 4/8] fix openapi location in repository --- openapi.yaml | 1218 ------------------------------------------ openapi/openapi.yaml | 3 + 2 files changed, 3 insertions(+), 1218 deletions(-) delete mode 100644 openapi.yaml diff --git a/openapi.yaml b/openapi.yaml deleted file mode 100644 index 522a8af..0000000 --- a/openapi.yaml +++ /dev/null @@ -1,1218 +0,0 @@ -openapi: 3.0.0 -info: - title: HyperFleet API - version: 1.0.1 - contact: - name: HyperFleet Team - license: - name: Apache 2.0 - url: https://www.apache.org/licenses/LICENSE-2.0 - description: |- - HyperFleet API provides simple CRUD operations for managing cluster resources and their status history. - - **Architecture**: Simple CRUD only, no business logic, no event creation. - Sentinel operator handles all orchestration logic. - Adapters handle the specifics of managing spec -tags: [] -paths: - /api/hyperfleet/v1/clusters: - get: - operationId: getClusters - summary: List clusters - parameters: - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - - $ref: '#/components/parameters/SearchParams' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/ClusterList' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - post: - operationId: postCluster - summary: Create cluster - description: |- - Create a new cluster resource. - - **Note**: The `status` object in the response is read-only and computed by the service. - It is NOT part of the request body. Initially, status.phase will be "NotReady" and - status.conditions will be empty until status conditions are POSTed. - parameters: [] - responses: - '201': - description: The request has succeeded and a new resource has been created as a result. - content: - application/json: - schema: - $ref: '#/components/schemas/Cluster' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ClusterCreateRequest' - /api/hyperfleet/v1/clusters/{cluster_id}: - get: - operationId: getClusterById - summary: Get cluster by ID - parameters: - - $ref: '#/components/parameters/SearchParams' - - name: cluster_id - in: path - required: true - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/Cluster' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /api/hyperfleet/v1/clusters/{cluster_id}/nodepools: - get: - operationId: getNodePoolsByClusterId - summary: List all nodepools for cluster - description: Returns the list of all nodepools for a cluster - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - - $ref: '#/components/parameters/SearchParams' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/NodePoolList' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - post: - operationId: createNodePool - summary: Create nodepool - description: Create a NodePool for a cluster - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - responses: - '201': - description: The request has succeeded and a new resource has been created as a result. - content: - application/json: - schema: - $ref: '#/components/schemas/NodePoolCreateResponse' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/NodePoolCreateRequest' - /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}: - get: - operationId: getNodePoolById - summary: Get nodepool by ID - description: Returns specific nodepool - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - - name: nodepool_id - in: path - required: true - description: NodePool ID - schema: - type: string - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/NodePool' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses: - post: - operationId: postNodePoolStatuses - summary: Create or update adapter status - description: |- - Adapter creates or updates its status report for this nodepool. - If adapter already has a status, it will be updated (upsert by adapter name). - - Response includes the full adapter status with all conditions. - Adapter should call this endpoint every time it evaluates the nodepool. - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - - name: nodepool_id - in: path - required: true - schema: - type: string - responses: - '201': - description: The request has succeeded and a new resource has been created as a result. - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatus' - '400': - description: The server could not understand the request due to invalid syntax. - '404': - description: The server cannot find the requested resource. - '409': - description: The request conflicts with the current state of the server. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatusCreateRequest' - get: - operationId: getNodePoolsStatuses - summary: List all adapter statuses for nodepools - description: Returns adapter status reports for this nodepool - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - - name: nodepool_id - in: path - required: true - schema: - type: string - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - - $ref: '#/components/parameters/SearchParams' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatusList' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /api/hyperfleet/v1/clusters/{cluster_id}/statuses: - post: - operationId: postClusterStatuses - summary: Create or update adapter status - description: |- - Adapter creates or updates its status report for this cluster. - If adapter already has a status, it will be updated (upsert by adapter name). - - Response includes the full adapter status with all conditions. - Adapter should call this endpoint every time it evaluates the cluster. - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - responses: - '201': - description: The request has succeeded and a new resource has been created as a result. - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatus' - '400': - description: The server could not understand the request due to invalid syntax. - '404': - description: The server cannot find the requested resource. - '409': - description: The request conflicts with the current state of the server. - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatusCreateRequest' - get: - operationId: getClusterStatuses - summary: List all adapter statuses for cluster - description: Returns adapter status reports for this cluster - parameters: - - name: cluster_id - in: path - required: true - description: Cluster ID - schema: - type: string - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - - $ref: '#/components/parameters/SearchParams' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatusList' - '400': - description: The server could not understand the request due to invalid syntax. - '404': - description: The server cannot find the requested resource. - /api/hyperfleet/v1/nodepools: - get: - operationId: getNodePools - summary: List all nodepools for cluster - description: Returns the list of all nodepools - parameters: - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - - $ref: '#/components/parameters/SearchParams' - responses: - '200': - description: The request has succeeded. - content: - application/json: - schema: - $ref: '#/components/schemas/NodePoolList' - '400': - description: The server could not understand the request due to invalid syntax. - default: - description: An unexpected error response. - content: - application/json: - schema: - $ref: '#/components/schemas/Error' -components: - parameters: - QueryParams.order: - name: order - in: query - required: false - schema: - $ref: '#/components/schemas/OrderDirection' - explode: false - QueryParams.orderBy: - name: orderBy - in: query - required: false - schema: - type: string - default: created_time - explode: false - QueryParams.page: - name: page - in: query - required: false - schema: - type: integer - format: int32 - default: 1 - explode: false - QueryParams.pageSize: - name: pageSize - in: query - required: false - schema: - type: integer - format: int32 - default: 20 - explode: false - SearchParams: - name: search - in: query - required: false - description: |- - Filter results using TSL (Tree Search Language) query syntax. - Examples: `status.phase='NotReady'`, `name in ('c1','c2')`, `labels.region='us-east'` - schema: - type: string - explode: false - schemas: - APIResource: - type: object - properties: - labels: - type: object - additionalProperties: - type: string - description: labels for the API resource as pairs of name:value strings - allOf: - - $ref: '#/components/schemas/ObjectReference' - AdapterCondition: - type: object - allOf: - - $ref: '#/components/schemas/ConditionBase' - description: |- - Condition in AdapterStatus - Used for standard Kubernetes condition types: "Available", "Applied", "Health" - Note: observed_generation is at AdapterStatus level, not per-condition, - since all conditions in one AdapterStatus share the same observed generation - AdapterStatus: - type: object - required: - - conditions - - created_time - - last_report_time - properties: - conditions: - type: array - items: - $ref: '#/components/schemas/AdapterCondition' - description: |- - Kubernetes-style conditions tracking adapter state - Typically includes: Available, Applied, Health - created_time: - type: string - format: date-time - description: When this adapter status was first created (API-managed) - last_report_time: - type: string - format: date-time - description: |- - When this adapter last reported its status (API-managed) - Updated every time the adapter POSTs, even if conditions haven't changed - Used by Sentinel to detect adapter liveness - allOf: - - $ref: '#/components/schemas/AdapterStatusBase' - description: |- - AdapterStatus represents the complete status report from an adapter - Contains multiple conditions, job metadata, and adapter-specific data - example: - adapter: validator - observed_generation: 1 - conditions: - - type: Available - status: 'True' - reason: All validations passed - message: All 30 validation tests passed - last_transition_time: '2021-01-01T10:00:00Z' - - type: Applied - status: 'True' - reason: Validation job applied - message: Validation job applied successfully - last_transition_time: '2021-01-01T10:00:00Z' - - type: Health - status: 'True' - reason: All adapter operations completed successfully - message: All adapter runtime operations completed successfully - last_transition_time: '2021-01-01T10:00:00Z' - metadata: - job_name: validator-job-abc123 - job_namespace: hyperfleet-system - attempt: 1 - started_time: '2021-01-01T10:00:00Z' - completed_time: '2021-01-01T10:02:00Z' - duration: 2m - data: - validation_results: - total_tests: 30 - passed: 30 - failed: 0 - created_time: '2021-01-01T10:00:00Z' - last_report_time: '2021-01-01T10:02:00Z' - AdapterStatusBase: - type: object - required: - - adapter - - observed_generation - properties: - adapter: - type: string - description: Adapter name (e.g., "validator", "dns", "provisioner") - observed_generation: - type: integer - format: int32 - description: Which generation of the resource this status reflects - metadata: - type: object - properties: - job_name: - type: string - job_namespace: - type: string - attempt: - type: integer - format: int32 - started_time: - type: string - format: date-time - completed_time: - type: string - format: date-time - duration: - type: string - description: Job execution metadata - data: - type: object - additionalProperties: {} - description: Adapter-specific data (structure varies by adapter type) - description: Base fields shared by AdapterStatus and AdapterStatusCreateRequest - AdapterStatusCreateRequest: - type: object - required: - - observed_time - - conditions - properties: - observed_time: - type: string - format: date-time - description: |- - When the adapter observed this resource state - API will use this to set AdapterStatus.last_report_time - conditions: - type: array - items: - $ref: '#/components/schemas/ConditionRequest' - allOf: - - $ref: '#/components/schemas/AdapterStatusBase' - description: Request payload for creating/updating adapter status - example: - adapter: validator - observed_generation: 1 - observed_time: '2021-01-01T10:00:00Z' - conditions: - - type: Available - status: 'True' - reason: All validations passed - message: All 30 validation tests passed - - type: Applied - status: 'True' - reason: Validation job applied - message: Validation job applied successfully - - type: Health - status: 'True' - reason: All adapter operations completed successfully - message: All adapter runtime operations completed successfully - metadata: - job_name: validator-job-abc123 - job_namespace: hyperfleet-system - attempt: 1 - started_time: '2021-01-01T10:00:00Z' - completed_time: '2021-01-01T10:02:00Z' - duration: 2m - data: - validation_results: - total_tests: 30 - passed: 30 - failed: 0 - AdapterStatusList: - type: object - required: - - items - properties: - items: - type: array - items: - $ref: '#/components/schemas/AdapterStatus' - allOf: - - $ref: '#/components/schemas/List' - description: List of adapter statuses with pagination metadata - example: - kind: AdapterStatusList - page: 1 - size: 2 - total: 2 - items: - - adapter: validator - observed_generation: 1 - conditions: - - type: Available - status: 'True' - reason: All validations passed - message: All 30 validation tests passed - last_transition_time: '2021-01-01T10:00:00Z' - metadata: - job_name: validator-job-abc123 - duration: 2m - created_time: '2021-01-01T10:00:00Z' - last_report_time: '2021-01-01T10:02:00Z' - - adapter: dns - observed_generation: 1 - conditions: - - type: Available - status: 'True' - reason: DNS configured - message: DNS records created - last_transition_time: '2021-01-01T10:01:00Z' - created_time: '2021-01-01T10:01:00Z' - last_report_time: '2021-01-01T10:01:30Z' - BearerAuth: - type: object - required: - - type - - scheme - properties: - type: - type: string - enum: - - http - scheme: - type: string - enum: - - bearer - Cluster: - type: object - required: - - created_time - - updated_time - - created_by - - updated_by - - generation - - status - properties: - created_time: - type: string - format: date-time - updated_time: - type: string - format: date-time - created_by: - type: string - format: email - updated_by: - type: string - format: email - generation: - type: integer - format: int32 - minimum: 1 - description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer - status: - $ref: '#/components/schemas/ClusterStatus' - allOf: - - $ref: '#/components/schemas/ClusterBase' - example: - kind: Cluster - id: cluster-123 - href: https://api.hyperfleet.com/v1/clusters/cluster-123 - name: cluster-123 - labels: - environment: production - team: platform - spec: {} - created_time: '2021-01-01T00:00:00Z' - updated_time: '2021-01-01T00:00:00Z' - generation: 1 - status: - phase: Ready - last_transition_time: '2021-01-01T00:00:00Z' - observed_generation: 1 - last_updated_time: '2021-01-01T00:00:00Z' - conditions: - - type: ValidationSuccessful - status: 'True' - reason: All validations passed - message: All 30 validation tests passed - observed_generation: 1 - created_time: '2021-01-01T10:00:00Z' - last_updated_time: '2021-01-01T10:00:00Z' - last_transition_time: '2021-01-01T10:00:00Z' - - type: DNSSuccessful - status: 'True' - reason: DNS configured - message: DNS records created for custom.domain.com - observed_generation: 1 - created_time: '2021-01-01T10:01:00Z' - last_updated_time: '2021-01-01T10:01:00Z' - last_transition_time: '2021-01-01T10:01:00Z' - created_by: user-123@example.com - updated_by: user-123@example.com - ClusterBase: - type: object - required: - - kind - - name - - spec - properties: - kind: - type: string - default: Cluster - name: - type: string - minLength: 1 - maxLength: 63 - pattern: ^[a-z0-9-]+$ - description: Cluster name (unique) - spec: - allOf: - - $ref: '#/components/schemas/ClusterSpec' - description: |- - Cluster specification - CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job - But CLM will validate the schema before accepting the request - allOf: - - $ref: '#/components/schemas/APIResource' - ClusterCreateRequest: - type: object - allOf: - - $ref: '#/components/schemas/ClusterBase' - example: - kind: Cluster - name: cluster-123 - labels: - environment: production - team: platform - spec: {} - ClusterList: - type: object - required: - - items - properties: - items: - type: array - items: - $ref: '#/components/schemas/Cluster' - allOf: - - $ref: '#/components/schemas/List' - ClusterSpec: - type: object - description: |- - Core cluster specification. - Accepts any properties as the spec is provider-agnostic. - This is represented as a simple object to allow flexibility. - ClusterStatus: - type: object - required: - - phase - - last_transition_time - - observed_generation - - last_updated_time - - conditions - properties: - phase: - allOf: - - $ref: '#/components/schemas/ResourcePhase' - description: |- - Current cluster phase (native database column). - Updated when conditions are reported. - Note: status.phase provides aggregated view from all conditions. - last_transition_time: - type: string - format: date-time - description: |- - When cluster last transitioned (used by Sentinel for backoff) - Updated when conditions are reported if the phase changes - observed_generation: - type: integer - format: int32 - description: |- - Last generation processed - Updated when conditions are reported. - This will be the lowest value of each condition's observed_generation values - The phase value is based on this generation - last_updated_time: - type: string - format: date-time - description: |- - Time of the last update - Updated when conditions are reported. - Computed as min(conditions[].last_updated_time) to detect stale adapters. - Uses earliest (not latest) timestamp to ensure Sentinel can detect if any adapter has stopped reporting. - conditions: - type: array - items: - $ref: '#/components/schemas/ResourceCondition' - description: |- - Cluster status computed from all status conditions. - - This object is computed by the service and CANNOT be modified directly. - It is aggregated from condition updates posted to `/clusters/{id}/statuses`. - - Provides quick overview of all reported conditions and aggregated phase. - ConditionBase: - type: object - required: - - type - - status - - last_transition_time - properties: - type: - type: string - description: Condition type - status: - allOf: - - $ref: '#/components/schemas/ConditionStatus' - description: Condition status - reason: - type: string - description: Machine-readable reason code - message: - type: string - description: Human-readable message - last_transition_time: - type: string - format: date-time - description: |- - When this condition last transitioned status (API-managed) - Only updated when status changes (True/False/Unknown), not when reason/message changes - description: Base condition fields shared by all condition types - ConditionRequest: - type: object - required: - - type - - status - properties: - type: - type: string - status: - $ref: '#/components/schemas/ConditionStatus' - reason: - type: string - message: - type: string - description: |- - Condition data for create/update requests (from adapters) - observed_generation and observed_time are now at AdapterStatusCreateRequest level - ConditionStatus: - type: string - enum: - - 'True' - - 'False' - - Unknown - description: Status value for conditions - Error: - type: object - properties: - id: - type: string - kind: - type: string - description: Resource kind - href: - type: string - description: Resource URI - code: - type: string - reason: - type: string - operation_id: - type: string - details: - type: array - items: - type: object - properties: - field: - type: string - description: Field path that failed validation - error: - type: string - description: Validation error message for this field - description: Field-level validation errors (optional) - List: - type: object - required: - - kind - - page - - size - - total - - items - properties: - kind: - type: string - page: - type: integer - format: int32 - size: - type: integer - format: int32 - total: - type: integer - format: int32 - items: - type: array - items: {} - NodePool: - type: object - required: - - created_time - - updated_time - - created_by - - updated_by - - generation - - owner_references - - status - properties: - created_time: - type: string - format: date-time - updated_time: - type: string - format: date-time - created_by: - type: string - format: email - updated_by: - type: string - format: email - generation: - type: integer - format: int32 - minimum: 1 - description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer - owner_references: - $ref: '#/components/schemas/ObjectReference' - status: - $ref: '#/components/schemas/NodePoolStatus' - allOf: - - $ref: '#/components/schemas/NodePoolBase' - example: - kind: NodePool - id: nodepool-123 - href: https://api.hyperfleet.com/v1/nodepools/nodepool-123 - name: worker-pool-1 - labels: - environment: production - pooltype: worker - spec: {} - generation: 1 - created_time: '2021-01-01T00:00:00Z' - updated_time: '2021-01-01T00:00:00Z' - created_by: user-123@example.com - updated_by: user-123@example.com - owner_references: - id: cluster-123 - kind: Cluster - href: https://api.hyperfleet.com/v1/clusters/cluster-123 - status: - phase: Ready - last_transition_time: '2021-01-01T10:00:00Z' - observed_generation: 1 - last_updated_time: '2021-01-01T10:02:00Z' - conditions: - - type: ValidationSuccessful - status: 'True' - reason: All validations passed - message: NodePool validation passed - observed_generation: 1 - created_time: '2021-01-01T10:00:00Z' - last_updated_time: '2021-01-01T10:00:00Z' - last_transition_time: '2021-01-01T10:00:00Z' - - type: NodePoolSuccessful - status: 'True' - reason: NodePool provisioned successfully - message: NodePool has been scaled to desired count - observed_generation: 1 - created_time: '2021-01-01T10:01:00Z' - last_updated_time: '2021-01-01T10:01:00Z' - last_transition_time: '2021-01-01T10:01:00Z' - NodePoolBase: - type: object - required: - - name - - spec - properties: - labels: - type: object - additionalProperties: - type: string - description: labels for the API resource as pairs of name:value strings - id: - type: string - description: Resource identifier - kind: - type: string - description: Resource kind - href: - type: string - description: Resource URI - name: - type: string - description: NodePool name (unique in a cluster) - spec: - allOf: - - $ref: '#/components/schemas/NodePoolSpec' - description: |- - Cluster specification - CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job - But CLM will validate the schema before accepting the request - NodePoolCreateRequest: - type: object - required: - - name - - spec - properties: - labels: - type: object - additionalProperties: - type: string - description: labels for the API resource as pairs of name:value strings - id: - type: string - description: Resource identifier - kind: - type: string - description: Resource kind - href: - type: string - description: Resource URI - name: - type: string - description: NodePool name (unique in a cluster) - spec: - allOf: - - $ref: '#/components/schemas/NodePoolSpec' - description: |- - Cluster specification - CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job - But CLM will validate the schema before accepting the request - example: - name: worker-pool-1 - labels: - environment: production - pooltype: worker - spec: {} - NodePoolCreateResponse: - type: object - required: - - created_time - - updated_time - - created_by - - updated_by - - generation - - owner_references - - status - - name - - spec - properties: - created_time: - type: string - format: date-time - updated_time: - type: string - format: date-time - created_by: - type: string - format: email - updated_by: - type: string - format: email - generation: - type: integer - format: int32 - minimum: 1 - description: Generation field is updated on customer updates, reflecting the version of the "intent" of the customer - owner_references: - $ref: '#/components/schemas/ObjectReference' - status: - $ref: '#/components/schemas/NodePoolStatus' - labels: - type: object - additionalProperties: - type: string - description: labels for the API resource as pairs of name:value strings - id: - type: string - description: Resource identifier - kind: - type: string - description: Resource kind - href: - type: string - description: Resource URI - name: - type: string - description: NodePool name (unique in a cluster) - spec: - allOf: - - $ref: '#/components/schemas/NodePoolSpec' - description: |- - Cluster specification - CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job - But CLM will validate the schema before accepting the request - NodePoolList: - type: object - required: - - items - properties: - items: - type: array - items: - $ref: '#/components/schemas/NodePool' - allOf: - - $ref: '#/components/schemas/List' - NodePoolSpec: - type: object - description: |- - Core nodepool specification. - Accepts any properties as the spec is provider-agnostic. - This is represented as a simple object to allow flexibility. - NodePoolStatus: - type: object - required: - - phase - - observed_generation - - last_transition_time - - last_updated_time - - conditions - properties: - phase: - allOf: - - $ref: '#/components/schemas/ResourcePhase' - description: |- - Current NodePool phase (native database column). - Updated when conditions are reported. - Note: status.phase provides aggregated view from all conditions. - observed_generation: - type: integer - format: int32 - minimum: 1 - description: |- - Last generation processed - Updated when conditions are reported. - This will be the lowest value of each condition's observed_generation values - The phase value is based on this generation - last_transition_time: - type: string - format: date-time - description: When NodePool last transitioned (used by Sentinel for backoff) - last_updated_time: - type: string - format: date-time - description: |- - Time of the last update - Updated when conditions are reported. - Computed as min(conditions[].last_updated_time) to detect stale adapters. - Uses earliest (not latest) timestamp to ensure Sentinel can detect if any adapter has stopped reporting. - conditions: - type: array - items: - $ref: '#/components/schemas/ResourceCondition' - description: |- - NodePool status computed from all status conditions. - - This object is computed by the service and CANNOT be modified directly. - ObjectReference: - type: object - properties: - id: - type: string - description: Resource identifier - kind: - type: string - description: Resource kind - href: - type: string - description: Resource URI - OrderDirection: - type: string - enum: - - asc - - desc - ResourceCondition: - type: object - required: - - observed_generation - - created_time - - last_updated_time - properties: - observed_generation: - type: integer - format: int32 - description: Generation of the spec that this condition reflects - created_time: - type: string - format: date-time - description: When this condition was first created (API-managed) - last_updated_time: - type: string - format: date-time - description: |- - When the corresponding adapter last reported (API-managed) - Updated every time the adapter POSTs, even if condition status hasn't changed - Copied from AdapterStatus.last_report_time - allOf: - - $ref: '#/components/schemas/ConditionBase' - description: |- - Condition in Cluster/NodePool status - Used for semantic condition types: "ValidationSuccessful", "DNSSuccessful", "NodePoolSuccessful", etc. - Includes observed_generation and last_updated_time to track adapter-specific state - ResourcePhase: - type: string - enum: - - NotReady - - Ready - - Failed - description: Phase of a resource (Cluster or NodePool) -servers: - - url: https://hyperfleet.redhat.com - description: Production - variables: {} diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d4b6879..522a8af 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -399,6 +399,9 @@ components: name: search in: query required: false + description: |- + Filter results using TSL (Tree Search Language) query syntax. + Examples: `status.phase='NotReady'`, `name in ('c1','c2')`, `labels.region='us-east'` schema: type: string explode: false From f8e2dc54988bc1e6d8150d8eb3edbafd45f88060 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 17 Dec 2025 12:54:28 +0100 Subject: [PATCH 5/8] remove name from patch requests --- openapi/openapi.yaml | 10 ++++++++++ pkg/api/cluster_types.go | 6 ++---- pkg/api/node_pool_types.go | 6 ++---- pkg/handlers/cluster.go | 7 ------- pkg/handlers/node_pool.go | 4 ---- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 522a8af..11a56fe 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -40,6 +40,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - BearerAuth: [] post: operationId: postCluster summary: Create cluster @@ -71,6 +73,8 @@ paths: application/json: schema: $ref: '#/components/schemas/ClusterCreateRequest' + security: + - BearerAuth: [] /api/hyperfleet/v1/clusters/{cluster_id}: get: operationId: getClusterById @@ -97,6 +101,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - BearerAuth: [] /api/hyperfleet/v1/clusters/{cluster_id}/nodepools: get: operationId: getNodePoolsByClusterId @@ -1212,6 +1218,10 @@ components: - Ready - Failed description: Phase of a resource (Cluster or NodePool) + securitySchemes: + BearerAuth: + type: http + scheme: bearer servers: - url: https://hyperfleet.redhat.com description: Production diff --git a/pkg/api/cluster_types.go b/pkg/api/cluster_types.go index 72fb468..aa68980 100644 --- a/pkg/api/cluster_types.go +++ b/pkg/api/cluster_types.go @@ -68,8 +68,6 @@ func (c *Cluster) BeforeUpdate(tx *gorm.DB) error { } type ClusterPatchRequest struct { - Name *string `json:"name,omitempty"` - Spec *map[string]interface{} `json:"spec,omitempty"` - Generation *int32 `json:"generation,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` + Spec *map[string]interface{} `json:"spec,omitempty"` + Labels *map[string]string `json:"labels,omitempty"` } diff --git a/pkg/api/node_pool_types.go b/pkg/api/node_pool_types.go index 5a90871..1f04bea 100644 --- a/pkg/api/node_pool_types.go +++ b/pkg/api/node_pool_types.go @@ -84,8 +84,6 @@ func (np *NodePool) BeforeUpdate(tx *gorm.DB) error { } type NodePoolPatchRequest struct { - Name *string `json:"name,omitempty"` - Spec *map[string]interface{} `json:"spec,omitempty"` - Generation *int32 `json:"generation,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` + Spec *map[string]interface{} `json:"spec,omitempty"` + Labels *map[string]string `json:"labels,omitempty"` } diff --git a/pkg/handlers/cluster.go b/pkg/handlers/cluster.go index 3668488..fbd846d 100644 --- a/pkg/handlers/cluster.go +++ b/pkg/handlers/cluster.go @@ -69,10 +69,6 @@ func (h clusterHandler) Patch(w http.ResponseWriter, r *http.Request) { return nil, err } - //patch a field - if patch.Name != nil { - found.Name = *patch.Name - } if patch.Spec != nil { specJSON, err := json.Marshal(*patch.Spec) if err != nil { @@ -80,9 +76,6 @@ func (h clusterHandler) Patch(w http.ResponseWriter, r *http.Request) { } found.Spec = specJSON } - if patch.Generation != nil { - found.Generation = *patch.Generation - } clusterModel, err := h.cluster.Replace(ctx, found) if err != nil { diff --git a/pkg/handlers/node_pool.go b/pkg/handlers/node_pool.go index 8fe51ce..a57d88a 100644 --- a/pkg/handlers/node_pool.go +++ b/pkg/handlers/node_pool.go @@ -68,10 +68,6 @@ func (h nodePoolHandler) Patch(w http.ResponseWriter, r *http.Request) { return nil, err } - //patch a field - if patch.Name != nil { - found.Name = *patch.Name - } if patch.Spec != nil { specJSON, err := json.Marshal(*patch.Spec) if err != nil { From c9330981e49f7ed9fd20af57233c7f9064f506d1 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 17 Dec 2025 13:02:17 +0100 Subject: [PATCH 6/8] updated openapi.yaml --- openapi/openapi.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 11a56fe..78f4670 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -135,6 +135,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - BearerAuth: [] post: operationId: createNodePool summary: Create nodepool @@ -167,6 +169,8 @@ paths: application/json: schema: $ref: '#/components/schemas/NodePoolCreateRequest' + security: + - BearerAuth: [] /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}: get: operationId: getNodePoolById @@ -200,6 +204,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - BearerAuth: [] /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses: post: operationId: postNodePoolStatuses @@ -313,6 +319,8 @@ paths: application/json: schema: $ref: '#/components/schemas/AdapterStatusCreateRequest' + security: + - BearerAuth: [] get: operationId: getClusterStatuses summary: List all adapter statuses for cluster @@ -340,6 +348,8 @@ paths: description: The server could not understand the request due to invalid syntax. '404': description: The server cannot find the requested resource. + security: + - BearerAuth: [] /api/hyperfleet/v1/nodepools: get: operationId: getNodePools @@ -366,6 +376,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + security: + - BearerAuth: [] components: parameters: QueryParams.order: From a08ed786c7ed33e6e3b4c4c8af8f1bb6f97a15f7 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 17 Dec 2025 13:22:33 +0100 Subject: [PATCH 7/8] updated openapi.yaml --- openapi/openapi.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 78f4670..ca549db 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -725,9 +725,9 @@ components: default: Cluster name: type: string - minLength: 1 + minLength: 3 maxLength: 63 - pattern: ^[a-z0-9-]+$ + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ description: Cluster name (unique) spec: allOf: @@ -1014,6 +1014,9 @@ components: description: Resource URI name: type: string + minLength: 3 + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ description: NodePool name (unique in a cluster) spec: allOf: @@ -1044,6 +1047,9 @@ components: description: Resource URI name: type: string + minLength: 3 + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ description: NodePool name (unique in a cluster) spec: allOf: @@ -1108,6 +1114,9 @@ components: description: Resource URI name: type: string + minLength: 3 + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ description: NodePool name (unique in a cluster) spec: allOf: From 13b3f1200051bd810117a8208bd720f46aaa5680 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 17 Dec 2025 13:25:41 +0100 Subject: [PATCH 8/8] updated openapi.yaml --- openapi/openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ca549db..7b20a9f 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1022,7 +1022,7 @@ components: allOf: - $ref: '#/components/schemas/NodePoolSpec' description: |- - Cluster specification + NodePool specification CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job But CLM will validate the schema before accepting the request NodePoolCreateRequest: @@ -1055,7 +1055,7 @@ components: allOf: - $ref: '#/components/schemas/NodePoolSpec' description: |- - Cluster specification + NodePool specification CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job But CLM will validate the schema before accepting the request example: @@ -1122,7 +1122,7 @@ components: allOf: - $ref: '#/components/schemas/NodePoolSpec' description: |- - Cluster specification + NodePool specification CLM doesn't know how to unmarshall the spec, it only stores and forwards to adapters to do their job But CLM will validate the schema before accepting the request NodePoolList: