diff --git a/README.md b/README.md index 4e7a6b3..249ad3f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository supports the development of the Hyperfleet OpenAPI contract, but is not the source-of-truth for the OpenAPI contract. -This project hosts the TypeSpec files to generate the HyperFleet core OpenAPI specification. TypeSpec is an implementation detail providing better ergonomics than writing contracts in plain YAML. The repository generates the core provider contract; the GCP-specific contract lives in [hyperfleet-api-spec-gcp](https://github.com/openshift-hyperfleet/hyperfleet-api-spec-gcp). +This project hosts the TypeSpec files to generate the HyperFleet core OpenAPI specification. TypeSpec is an implementation detail providing better ergonomics than writing contracts in plain YAML. The repository generates the core provider contract; the provider-specific contract lives in [hyperfleet-api-spec-template](https://github.com/openshift-hyperfleet/hyperfleet-api-spec-template). Access to the OpenAPI contract source of truth in hyperfleet-api repository: @@ -68,7 +68,7 @@ The repository is organized with root-level configuration files and two main dir ### `/shared` -Contains models and services shared across providers (also published as an npm package for consumption by provider-specific repos like `hyperfleet-api-spec-gcp`): +Contains models and services shared across providers (also published as an npm package for consumption by provider-specific repos like `hyperfleet-api-spec-template`): - **`shared/models/clusters/`** - Cluster resource definitions (interfaces and base models) - **`shared/models/statuses/`** - Status resource definitions for clusters and nodepools @@ -89,10 +89,10 @@ Contains core-specific models and internal services: The status endpoints are split into two files to support different API consumers: -| File | Operations | Audience | Included in Build | -|------|------------|----------|-------------------| -| `shared/services/statuses.tsp` | GET (read) | External clients | ✅ Yes (default) | -| `core/services/statuses-internal.tsp` | PUT (write) | Internal adapters | ❌ No (opt-in) | +| File | Operations | Audience | Included in Build | +| ------------------------------------- | ----------- | ----------------- | ----------------- | +| `shared/services/statuses.tsp` | GET (read) | External clients | ✅ Yes (default) | +| `core/services/statuses-internal.tsp` | PUT (write) | Internal adapters | ❌ No (opt-in) | **Why the split?** @@ -149,7 +149,7 @@ The HyperFleet API provides simple CRUD operations for managing cluster resource ## Adding a New Provider -Provider-specific contracts live in their own repository and consume this repo as an npm package (the `hyperfleet` package). See [hyperfleet-api-spec-gcp](https://github.com/openshift-hyperfleet/hyperfleet-api-spec-gcp) for a reference implementation. +Provider-specific contracts live in their own repository and consume this repo as an npm package (the `hyperfleet` package). See [hyperfleet-api-spec-template](https://github.com/openshift-hyperfleet/hyperfleet-api-spec-template) for a reference implementation. ## Adding a New Service @@ -162,7 +162,7 @@ To add a new service (e.g., with additional endpoints): import "@typespec/openapi"; import "../models/common/model.tsp"; // ... other imports as needed - + namespace HyperFleet; @route("/new-resource") interface NewService { diff --git a/shared/services/resources.tsp b/core/services/resources-internal.tsp similarity index 56% rename from shared/services/resources.tsp rename to core/services/resources-internal.tsp index 285c4a0..c2810d0 100644 --- a/shared/services/resources.tsp +++ b/core/services/resources-internal.tsp @@ -2,9 +2,9 @@ import "@typespec/http"; import "@typespec/openapi"; import "@typespec/openapi3"; -import "../models/resource/model.tsp"; -import "../models/common/model.tsp"; -import "../models/statuses/model.tsp"; +import "../../shared/models/resource/model.tsp"; +import "../../shared/models/common/model.tsp"; +import "../../shared/models/statuses/model.tsp"; using Http; using OpenAPI; @@ -90,4 +90,55 @@ interface Resources { | Error | BadRequestResponse; + /** + * Permanently removes the resource record from the database for a resource stuck in Finalizing state. + * This is a database-only operation. Requires a reason for audit purposes. + */ + @route("/{resource_id}/force-delete") + @post + @summary("Force-delete a resource") + @operationId("forceDeleteResource") + forceDeleteResource( + @path resource_id: string, + @body body: ForceDeleteRequest, + ): { + @statusCode statusCode: 204; + } | Error + | NotFoundResponse + | BadRequestResponse + | ConflictResponse; +} + +@tag("Resource statuses") +@route("/resources/{resource_id}/statuses") +@useAuth(HyperFleet.BearerAuth) +interface ResourceStatuses { + /** + * Returns adapter statuses for a resource. + */ + @route("") + @get + @summary("List resource statuses") + @operationId("getResourceStatuses") + getResourceStatuses( + @path resource_id: string, + ...QueryParams, + ): Body + | Error + | NotFoundResponse + | BadRequestResponse; + + @route("") + @put + @summary("Adapter creates or updates resource status") + @operationId("putResourceStatuses") + @doc("Adapters call this endpoint to report status for a resource after each evaluation. The adapter's status entry is created if it doesn't exist, or updated if it does (upserted by adapter name).") + putResourceStatuses( + @path resource_id: string, + @body body: AdapterStatusCreateRequest, + ): + | (CreatedResponse & AdapterStatus) + | BadRequestResponse + | NotFoundResponse + | ConflictResponse; } diff --git a/core/services/statuses-internal.tsp b/core/services/statuses-internal.tsp index b692086..d180702 100644 --- a/core/services/statuses-internal.tsp +++ b/core/services/statuses-internal.tsp @@ -14,7 +14,25 @@ namespace HyperFleet; @route("/clusters/{cluster_id}/statuses") @useAuth(HyperFleet.BearerAuth) @tag("Cluster statuses") -interface ClusterStatusesInternal { +interface ClusterStatuses { + /** + * Returns adapter status reports for this cluster + */ + @route("") + @get + @summary("List all adapter statuses for cluster") + @operationId("getClusterStatuses") + getClusterStatuses( + /** + * Cluster ID + */ + @path cluster_id: string, + ...QueryParams + ): Body + | Error + | NotFoundResponse + | BadRequestResponse; + @route("") @put @summary("Adapter creates or updates cluster status") @@ -37,7 +55,26 @@ interface ClusterStatusesInternal { @tag("NodePool statuses") @route("/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses") @useAuth(HyperFleet.BearerAuth) -interface NodePoolStatusesInternal { +interface NodePoolStatuses { + /** + * Returns adapter status reports for this nodepool + */ + @route("") + @get + @summary("List all adapter statuses for nodepools") + @operationId("getNodePoolsStatuses") + getNodePoolsStatuses( + /** + * Cluster ID + */ + @path cluster_id: string, + @path nodepool_id: string, + ...QueryParams + ): Body + | Error + | NotFoundResponse + | BadRequestResponse; + @route("") @put @summary("Adapter creates or updates nodepool status") @@ -61,45 +98,3 @@ interface NodePoolStatusesInternal { | NotFoundResponse | ConflictResponse; } - -@tag("Resource statuses") -@route("/resources/{resource_id}/statuses") -@useAuth(HyperFleet.BearerAuth) -interface ResourceStatusesInternal { - @route("") - @put - @summary("Adapter creates or updates resource status") - @operationId("putResourceStatuses") - @doc("Adapters call this endpoint to report status for a resource after each evaluation. The adapter's status entry is created if it doesn't exist, or updated if it does (upserted by adapter name).") - putResourceStatuses( - @path resource_id: string, - @body body: AdapterStatusCreateRequest, - ): - | (CreatedResponse & AdapterStatus) - | BadRequestResponse - | NotFoundResponse - | ConflictResponse; -} - -@tag("Resources") -@route("/resources") -@useAuth(HyperFleet.BearerAuth) -interface ResourcesForceDelete { - /** - * Permanently removes the resource record from the database for a resource stuck in Finalizing state. - * This is a database-only operation. Requires a reason for audit purposes. - */ - @route("/{resource_id}/force-delete") - @post - @summary("Force-delete a resource") - @operationId("forceDeleteResource") - forceDeleteResource( - @path resource_id: string, - @body body: ForceDeleteRequest, - ): { - @statusCode statusCode: 204; - } | Error - | NotFoundResponse - | BadRequestResponse - | ConflictResponse; -} diff --git a/main.tsp b/main.tsp index 092cfe7..f5f2e99 100644 --- a/main.tsp +++ b/main.tsp @@ -11,11 +11,10 @@ import "./core/models/nodepool/example_nodepool.tsp"; import "./core/models/nodepool/example_post.tsp"; import "./core/models/nodepool/example_patch.tsp"; import "./core/services/statuses-internal.tsp"; +import "./core/services/resources-internal.tsp"; import "./shared/services/clusters.tsp"; -import "./shared/services/statuses.tsp"; import "./shared/services/nodepools.tsp"; -import "./shared/services/resources.tsp"; import "./core/services/force-delete-internal.tsp"; using Http; @@ -31,7 +30,7 @@ using OpenAPI; */ @service(#{ title: "HyperFleet API" }) @info(#{ - version: "1.0.18", + version: "1.0.19", contact: #{ name: "HyperFleet Team", url: "https://github.com/openshift-hyperfleet", diff --git a/package-lock.json b/package-lock.json index 2b5941e..00b0888 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hyperfleet", - "version": "1.0.18", + "version": "1.0.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hyperfleet", - "version": "1.0.18", + "version": "1.0.19", "devDependencies": { "@stoplight/spectral-cli": "6.15.1", "@typespec/compiler": "^1.6.0", diff --git a/package.json b/package.json index 1069cf4..66e86ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hyperfleet", - "version": "1.0.18", + "version": "1.0.19", "type": "module", "exports": { "./*": "./*" diff --git a/schemas/core/openapi.yaml b/schemas/core/openapi.yaml index e62782f..8c9a92f 100644 --- a/schemas/core/openapi.yaml +++ b/schemas/core/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: HyperFleet API - version: 1.0.18 + version: 1.0.19 contact: name: HyperFleet Team url: https://github.com/openshift-hyperfleet @@ -17,8 +17,8 @@ info: tags: - name: Cluster statuses - name: NodePool statuses - - name: Resource statuses - name: Resources + - name: Resource statuses - name: Clusters - name: NodePools paths: @@ -578,6 +578,48 @@ paths: security: - BearerAuth: [] /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses: + 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/SearchParams' + - $ref: '#/components/parameters/QueryParams.page' + - $ref: '#/components/parameters/QueryParams.pageSize' + - $ref: '#/components/parameters/QueryParams.orderBy' + - $ref: '#/components/parameters/QueryParams.order' + 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. + default: + description: An unexpected error response. + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + tags: + - NodePool statuses + security: + - BearerAuth: [] put: operationId: putNodePoolStatuses summary: Adapter creates or updates nodepool status @@ -618,10 +660,11 @@ paths: $ref: '#/components/schemas/AdapterStatusCreateRequest' security: - BearerAuth: [] + /api/hyperfleet/v1/clusters/{cluster_id}/statuses: get: - operationId: getNodePoolsStatuses - summary: List all adapter statuses for nodepools - description: Returns adapter status reports for this nodepool + operationId: getClusterStatuses + summary: List all adapter statuses for cluster + description: Returns adapter status reports for this cluster parameters: - name: cluster_id in: path @@ -629,11 +672,6 @@ paths: description: Cluster ID schema: type: string - - name: nodepool_id - in: path - required: true - schema: - type: string - $ref: '#/components/parameters/SearchParams' - $ref: '#/components/parameters/QueryParams.page' - $ref: '#/components/parameters/QueryParams.pageSize' @@ -648,6 +686,8 @@ paths: $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. default: description: An unexpected error response. content: @@ -655,10 +695,9 @@ paths: schema: $ref: '#/components/schemas/Error' tags: - - NodePool statuses + - Cluster statuses security: - BearerAuth: [] - /api/hyperfleet/v1/clusters/{cluster_id}/statuses: put: operationId: putClusterStatuses summary: Adapter creates or updates cluster status @@ -693,37 +732,6 @@ paths: $ref: '#/components/schemas/AdapterStatusCreateRequest' security: - BearerAuth: [] - 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/SearchParams' - - $ref: '#/components/parameters/QueryParams.page' - - $ref: '#/components/parameters/QueryParams.pageSize' - - $ref: '#/components/parameters/QueryParams.orderBy' - - $ref: '#/components/parameters/QueryParams.order' - 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. - tags: - - Cluster statuses - security: - - BearerAuth: [] /api/hyperfleet/v1/nodepools: get: operationId: getNodePools @@ -963,39 +971,6 @@ paths: security: - BearerAuth: [] /api/hyperfleet/v1/resources/{resource_id}/statuses: - put: - operationId: putResourceStatuses - summary: Adapter creates or updates resource status - description: Adapters call this endpoint to report status for a resource after each evaluation. The adapter's status entry is created if it doesn't exist, or updated if it does (upserted by adapter name). - parameters: - - name: resource_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. - tags: - - Resource statuses - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AdapterStatusCreateRequest' - security: - - BearerAuth: [] get: operationId: getResourceStatuses summary: List resource statuses @@ -1032,6 +1007,39 @@ paths: - Resource statuses security: - BearerAuth: [] + put: + operationId: putResourceStatuses + summary: Adapter creates or updates resource status + description: Adapters call this endpoint to report status for a resource after each evaluation. The adapter's status entry is created if it doesn't exist, or updated if it does (upserted by adapter name). + parameters: + - name: resource_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. + tags: + - Resource statuses + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AdapterStatusCreateRequest' + security: + - BearerAuth: [] components: parameters: QueryParams.order: diff --git a/shared/services/statuses.tsp b/shared/services/statuses.tsp deleted file mode 100644 index ac88b6f..0000000 --- a/shared/services/statuses.tsp +++ /dev/null @@ -1,79 +0,0 @@ -import "@typespec/http"; -import "@typespec/openapi"; -import "@typespec/openapi3"; - -import "../models/statuses/model.tsp"; -import "../models/common/model.tsp"; -import "../models/nodepools/model.tsp"; - -using Http; -using OpenAPI; - -namespace HyperFleet; -@route("/clusters/{cluster_id}/statuses") -@useAuth(HyperFleet.BearerAuth) -@tag("Cluster statuses") -interface ClusterStatuses{ - /** - * Returns adapter status reports for this cluster - */ - @route("") - @get - @summary("List all adapter statuses for cluster") - @operationId("getClusterStatuses") - getClusterStatuses( - /** - * Cluster ID - */ - @path cluster_id: string, - ...QueryParams - ): Body - | NotFoundResponse - | BadRequestResponse; - - -} - -@tag("NodePool statuses") -@route("/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses") -@useAuth(HyperFleet.BearerAuth) -interface NodePoolStatuses{ - /** - * Returns adapter status reports for this nodepool - */ - @route("") - @get - @summary("List all adapter statuses for nodepools") - @operationId("getNodePoolsStatuses") - getNodePoolsStatuses( - /** - * Cluster ID - */ - @path cluster_id: string, - @path nodepool_id: string, - ...QueryParams - ): Body - | Error - | BadRequestResponse; - -} - -@tag("Resource statuses") -@route("/resources/{resource_id}/statuses") -@useAuth(HyperFleet.BearerAuth) -interface ResourceStatuses { - /** - * Returns adapter statuses for a resource. - */ - @route("") - @get - @summary("List resource statuses") - @operationId("getResourceStatuses") - getResourceStatuses( - @path resource_id: string, - ...QueryParams, - ): Body - | Error - | NotFoundResponse - | BadRequestResponse; -}