diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..75caf23 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + validate: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Install TypeSpec compiler + run: npm install -g @typespec/compiler@1.11 + + - name: Install Spectral CLI + run: npm install -g @stoplight/spectral-cli + + - name: Build all schemas + run: | + ./build-schema.sh core + ./build-schema.sh core --swagger + ./build-schema.sh gcp + ./build-schema.sh gcp --swagger + + - name: Check schema consistency + run: | + if ! git diff --exit-code schemas/; then + echo "Committed schemas are out of sync with TypeSpec sources." + echo "Run './build-schema.sh core --swagger && ./build-schema.sh gcp --swagger' and commit the results." + exit 1 + fi + + - name: Lint OpenAPI schemas + run: | + spectral lint schemas/core/openapi.yaml schemas/gcp/openapi.yaml --format github-actions + + - name: Check version bump + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CURRENT=$(grep -oP '(?<=version: ")[^"]+' main.tsp) + LATEST=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null | sed 's/^v//' || echo "") + if [ -z "$LATEST" ]; then + echo "No previous releases found — version check skipped" + exit 0 + fi + if [ "$CURRENT" = "$LATEST" ]; then + echo "Version '$CURRENT' matches latest release tag 'v$LATEST' — bump the version in main.tsp before merging." + exit 1 + fi + echo "Version bump OK: $LATEST → $CURRENT" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e5bed41..af7d078 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,50 +2,87 @@ name: Create Release on: push: - tags: - - 'v*' + branches: + - main-test + workflow_dispatch: jobs: release: runs-on: ubuntu-latest - permissions: contents: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' - name: Install dependencies - run: npm install + run: npm ci - - name: Install tsp - run: npm install -g @typespec/compiler@1.6 + - name: Install TypeSpec compiler + run: npm install -g @typespec/compiler@1.11 - - name: Build Core schema - run: ./build-schema.sh core + - name: Extract version + id: version + run: | + VERSION=$(grep -oP '(?<=version: ")[^"]+' main.tsp) + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" - - name: Build GCP schema - run: ./build-schema.sh gcp + - name: Check if release already exists + id: check_tag + run: | + git fetch --tags + if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "Tag ${{ steps.version.outputs.tag }} already exists — skipping release" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Build all schemas + if: steps.check_tag.outputs.skip == 'false' + run: | + ./build-schema.sh core + ./build-schema.sh core --swagger + ./build-schema.sh gcp + ./build-schema.sh gcp --swagger - name: Prepare release assets + if: steps.check_tag.outputs.skip == 'false' run: | cp schemas/core/openapi.yaml core-openapi.yaml + cp schemas/core/swagger.yaml core-swagger.yaml cp schemas/gcp/openapi.yaml gcp-openapi.yaml + cp schemas/gcp/swagger.yaml gcp-swagger.yaml + + - name: Create release tag + if: steps.check_tag.outputs.skip == 'false' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.tag }}" + git push origin "${{ steps.version.outputs.tag }}" - - name: Create Release + - name: Create GitHub Release + if: steps.check_tag.outputs.skip == 'false' uses: softprops/action-gh-release@v2 with: + tag_name: ${{ steps.version.outputs.tag }} + generate_release_notes: true + draft: false + prerelease: false files: | core-openapi.yaml + core-swagger.yaml gcp-openapi.yaml - draft: false - prerelease: false - generate_release_notes: true + gcp-swagger.yaml env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.spectral.yaml b/.spectral.yaml new file mode 100644 index 0000000..d47c47d --- /dev/null +++ b/.spectral.yaml @@ -0,0 +1 @@ +extends: ["spectral:oas"] diff --git a/README.md b/README.md index 413f8e1..e556a3e 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Contains service definitions that generate the OpenAPI specifications: - **`services/clusters.tsp`** - Cluster resource endpoints - **`services/statuses.tsp`** - Status resource endpoints (GET only - public API) -- **`services/statuses-internal.tsp`** - Status write endpoints (POST - internal API, see below) +- **`services/statuses-internal.tsp`** - Status write endpoints (POST/PUT - internal API, see below) - **`services/nodepools.tsp`** - NodePool resource endpoints #### Public vs Internal API Split @@ -121,6 +121,7 @@ The status endpoints are split into two files to support different API consumers |------|------------|----------|-------------------| | `statuses.tsp` | GET (read) | External clients | ✅ Yes (default) | | `statuses-internal.tsp` | POST (write) | Internal adapters | ❌ No (opt-in) | +| `statuses-internal.tsp` | PUT (write) | Internal adapters | ❌ No (opt-in) | **Why the split?** diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e21a7e6 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/openshift-hyperfleet/hyperfleet-api-spec + +go 1.23 diff --git a/main.tsp b/main.tsp index 6dfad14..9ac4066 100644 --- a/main.tsp +++ b/main.tsp @@ -20,7 +20,7 @@ using OpenAPI; * */ @service(#{ title: "HyperFleet API" }) -@info(#{ version: "1.0.7", contact: #{ name: "HyperFleet Team" }, license: #{ name: "Apache 2.0" ,url: "https://www.apache.org/licenses/LICENSE-2.0"} }) +@info(#{ version: "1.0.9", contact: #{ name: "HyperFleet Team" }, license: #{ name: "Apache 2.0" ,url: "https://www.apache.org/licenses/LICENSE-2.0"} }) @server("https://hyperfleet.redhat.com", "Production") @route("/api/hyperfleet/v1") namespace HyperFleet; diff --git a/models-core/cluster/example_cluster.tsp b/models-core/cluster/example_cluster.tsp index 59591c3..1e15469 100644 --- a/models-core/cluster/example_cluster.tsp +++ b/models-core/cluster/example_cluster.tsp @@ -22,6 +22,16 @@ const exampleCluster: Cluster = #{ last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + 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: ConditionType.Available, status: ResourceConditionStatus.True, @@ -75,7 +85,17 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleReadyReason, message: ExampleReadyMessage, - observed_generation: 1, + observed_generation: 2, + created_time: "2021-01-01T10:00:00Z", + last_updated_time: "2021-01-01T10:00:00Z", + last_transition_time: "2021-01-01T10:00:00Z", + }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -85,7 +105,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAvailableReason, message: ExampleAvailableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -95,7 +115,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter1AvaliableReason, message: ExampleAdapter1AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -105,7 +125,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter2AvaliableReason, message: ExampleAdapter2AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:01:00Z", last_updated_time: "2021-01-01T10:01:00Z", last_transition_time: "2021-01-01T10:01:00Z", diff --git a/models-core/nodepool/example_nodepool.tsp b/models-core/nodepool/example_nodepool.tsp index 2746900..289db7c 100644 --- a/models-core/nodepool/example_nodepool.tsp +++ b/models-core/nodepool/example_nodepool.tsp @@ -27,6 +27,16 @@ const exampleNodePool: NodePool = #{ last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + 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: ConditionType.Available, status: ResourceConditionStatus.True, @@ -85,7 +95,17 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: ExampleReadyReason, message: ExampleReadyMessage, - observed_generation: 1, + observed_generation: 2, + created_time: "2021-01-01T10:00:00Z", + last_updated_time: "2021-01-01T10:00:00Z", + last_transition_time: "2021-01-01T10:00:00Z", + }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -95,7 +115,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: ExampleAvailableReason, message: ExampleAvailableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -105,7 +125,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter1AvaliableReason, message: ExampleAdapter1AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -115,7 +135,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter2AvaliableReason, message: ExampleAdapter2AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:01:00Z", last_updated_time: "2021-01-01T10:01:00Z", last_transition_time: "2021-01-01T10:01:00Z", diff --git a/models-gcp/cluster/example_cluster.tsp b/models-gcp/cluster/example_cluster.tsp index e23fc11..501d762 100644 --- a/models-gcp/cluster/example_cluster.tsp +++ b/models-gcp/cluster/example_cluster.tsp @@ -44,6 +44,16 @@ const exampleCluster: Cluster = #{ last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + 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: ConditionType.Available, status: ResourceConditionStatus.True, @@ -119,7 +129,17 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleReadyReason, message: ExampleReadyMessage, - observed_generation: 1, + observed_generation: 2, + created_time: "2021-01-01T10:00:00Z", + last_updated_time: "2021-01-01T10:00:00Z", + last_transition_time: "2021-01-01T10:00:00Z", + }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -129,7 +149,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAvailableReason, message: ExampleAvailableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -139,7 +159,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter1AvaliableReason, message: ExampleAdapter1AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -149,7 +169,7 @@ const exampleDeletedCluster: Cluster = #{ status: ResourceConditionStatus.True, reason: ExampleAdapter2AvaliableReason, message: ExampleAdapter2AvaliableMessage, - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:01:00Z", last_updated_time: "2021-01-01T10:01:00Z", last_transition_time: "2021-01-01T10:01:00Z", diff --git a/models-gcp/nodepool/example_nodepool.tsp b/models-gcp/nodepool/example_nodepool.tsp index a2a81e3..0dfd02a 100644 --- a/models-gcp/nodepool/example_nodepool.tsp +++ b/models-gcp/nodepool/example_nodepool.tsp @@ -48,6 +48,16 @@ const exampleNodePool: NodePool = #{ last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + 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: ConditionType.Available, status: ResourceConditionStatus.True, @@ -128,7 +138,17 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: "ResourceReady", message: "All conditions successful for current spec generation", - observed_generation: 1, + observed_generation: 2, + created_time: "2021-01-01T10:00:00Z", + last_updated_time: "2021-01-01T10:00:00Z", + last_transition_time: "2021-01-01T10:00:00Z", + }, + #{ + type: ConditionType.Reconciled, + status: ResourceConditionStatus.True, + reason: ExampleReconciledReason, + message: ExampleReconciledMessage, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -138,7 +158,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: "ResourceAvailable", message: "All conditions successful for observed_generation", - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -148,7 +168,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: "All validations passed", message: "NodePool validation passed", - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:00:00Z", last_updated_time: "2021-01-01T10:00:00Z", last_transition_time: "2021-01-01T10:00:00Z", @@ -158,7 +178,7 @@ const exampleDeletedNodePool: NodePool = #{ status: ResourceConditionStatus.True, reason: "NodePool provisioned successfully", message: "NodePool has 3 nodes running", - observed_generation: 1, + observed_generation: 2, created_time: "2021-01-01T10:01:00Z", last_updated_time: "2021-01-01T10:01:00Z", last_transition_time: "2021-01-01T10:01:00Z", diff --git a/models/clusters/model.tsp b/models/clusters/model.tsp index 15aef10..b7b1226 100644 --- a/models/clusters/model.tsp +++ b/models/clusters/model.tsp @@ -33,7 +33,8 @@ model ClusterStatus { * List of status conditions for the cluster. * * **Mandatory conditions**: - * - `type: "Ready"`: Whether all adapters report successfully at the current generation. + * - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + * - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. * - `type: "Available"`: Aggregated adapter result for a common observed_generation. * * These conditions are present immediately upon resource creation. diff --git a/models/common/model.tsp b/models/common/model.tsp index 84f8014..10d9a35 100644 --- a/models/common/model.tsp +++ b/models/common/model.tsp @@ -79,7 +79,7 @@ model Error { detail?: string; /** URI reference for this specific occurrence */ - @format("uri") + @format("uri-reference") @example("/api/hyperfleet/v1/clusters") instance?: string; @@ -176,7 +176,8 @@ model List { union ConditionType { string, Available: "Available", - Ready: "Ready" + Ready: "Ready", + Reconciled: "Reconciled" } /** @@ -246,6 +247,8 @@ model ResourceCondition { const ExampleAvailableMessage: string = "All adapters reported Available True for the same generation"; const ExampleReadyReason: string = "All adapters reported Ready True for the current generation"; const ExampleReadyMessage: string = "All adapters reported Ready True for the current generation"; + const ExampleReconciledReason: string = "All required adapters reported Available=True or Finalized=True at the current generation"; + const ExampleReconciledMessage: string = "All required adapters reported Available=True or Finalized=True at the current generation"; const ExampleAdapter1: string = "adapter1"; const ExampleAdapter2: string = "adapter2"; const ExampleAdapter1AppliedReason: string = "Validation job applied"; @@ -254,6 +257,8 @@ model ResourceCondition { const ExampleAdapter1HealthMessage: string = "All adapter1 runtime operations completed successfully"; const ExampleAdapter1AvaliableReason: string = "This adapter1 is available"; const ExampleAdapter1AvaliableMessage: string = "This adapter1 is available"; + const ExampleAdapter1FinalizedReason: string = "All resources deleted; cleanup confirmed"; + const ExampleAdapter1FinalizedMessage: string = "All resources deleted; cleanup confirmed"; const ExampleAdapter2AppliedReason: string = "Validation job applied"; const ExampleAdapter2AppliedMessage: string = "Adapter2 validation job applied successfully"; const ExampleAdapter2HealthReason: string = "All adapter2 operations completed successfully"; diff --git a/models/nodepools/model.tsp b/models/nodepools/model.tsp index 5d74a0b..c614622 100644 --- a/models/nodepools/model.tsp +++ b/models/nodepools/model.tsp @@ -30,7 +30,8 @@ model NodePoolStatus { * List of status conditions for the nodepool. * * **Mandatory conditions**: - * - `type: "Ready"`: Whether all adapters report successfully at the current generation. + * - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + * - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. * - `type: "Available"`: Aggregated adapter result for a common observed_generation. * * These conditions are present immediately upon resource creation. diff --git a/models/statuses/example_adapter_status.tsp b/models/statuses/example_adapter_status.tsp index 5e47d72..598e9fe 100644 --- a/models/statuses/example_adapter_status.tsp +++ b/models/statuses/example_adapter_status.tsp @@ -26,6 +26,13 @@ const exampleAdapterStatus: AdapterStatus = (#{ reason: ExampleAdapter1HealthReason, message: ExampleAdapter1HealthMessage, last_transition_time: "2021-01-01T10:00:00Z", + }, + #{ + type: "Finalized", + status: AdapterConditionStatus.True, + reason: ExampleAdapter1FinalizedReason, + message: ExampleAdapter1FinalizedMessage, + last_transition_time: "2021-01-01T10:00:00Z", } ], metadata: #{ @@ -70,6 +77,12 @@ const exampleAdapterStatusCreateRequest: AdapterStatusCreateRequest = (#{ status: AdapterConditionStatus.True, reason: ExampleAdapter1HealthReason, message: ExampleAdapter1HealthMessage, + }, + #{ + type: "Finalized", + status: AdapterConditionStatus.True, + reason: ExampleAdapter1FinalizedReason, + message: ExampleAdapter1FinalizedMessage, } ], metadata: #{ diff --git a/models/statuses/model.tsp b/models/statuses/model.tsp index d95061b..2df792e 100644 --- a/models/statuses/model.tsp +++ b/models/statuses/model.tsp @@ -11,7 +11,7 @@ enum ResourceConditionStatus { /** * Condition in AdapterStatus - * Used for standard Kubernetes condition types: "Available", "Applied", "Health" + * Used for standard Kubernetes condition types: "Available", "Applied", "Health", "Finalized" * Note: observed_generation is at AdapterStatus level, not per-condition, * since all conditions in one AdapterStatus share the same observed generation */ @@ -74,7 +74,7 @@ model AdapterStatus { ...AdapterStatusBase; /** * Kubernetes-style conditions tracking adapter state - * Typically includes: Available, Applied, Health + * Typically includes: Available, Applied, Health, Finalized */ conditions: AdapterCondition[]; diff --git a/package-lock.json b/package-lock.json index a95801b..2b420cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -655,7 +655,6 @@ "integrity": "sha512-yxyV+ch8tnqiuU2gClv/mQEESoFwpkjo6177UkYfV0nVA9PzTg4zVVc7+WIMZk04wiLRRT3H1uc11FB1cwLY3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "~7.27.1", "@inquirer/prompts": "^7.4.0", @@ -689,7 +688,6 @@ "integrity": "sha512-q/JwVw21CF4buE3ZS+xSoy2TKAOwyhZ7g3kdNqCgm69BI5p5GGu+3ZlUA+4Blk8hkt0G8XcIN8fhJP+a4O6KAw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -709,7 +707,6 @@ "integrity": "sha512-KuxYAzfP5ljM0PUhSGclNZgTG0H+kyTQcwn6cf4TKhO72R2QMQmiMtN2plqvzsfkL+TLwad1iZhMWTCAMFAQ4w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -788,7 +785,6 @@ "integrity": "sha512-dguO/B+mwlCyenWGG+M+16cMQuGHSTJbU5Z0pyUou1uyWrB1px//s4pW7PKD14S+fPutJE0wTMQm+CctOq6quA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20.0.0" }, @@ -810,7 +806,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3387,7 +3382,6 @@ "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10.16.0" } @@ -3687,9 +3681,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, diff --git a/schemas/core/openapi.yaml b/schemas/core/openapi.yaml index 38d522a..d865c3f 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.7 + version: 1.0.9 contact: name: HyperFleet Team license: @@ -50,7 +50,7 @@ paths: **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.conditions will include mandatory "Available" and "Ready" conditions. + status.conditions will include mandatory "Available", "Ready" and "Reconciled" conditions. parameters: [] responses: '201': @@ -175,7 +175,15 @@ paths: status: 'True' reason: All adapters reported Ready True for the current generation message: All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -183,7 +191,7 @@ paths: status: 'True' reason: All adapters reported Available True for the same generation message: All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -191,7 +199,7 @@ paths: status: 'True' reason: This adapter1 is available message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -199,7 +207,7 @@ paths: status: 'True' reason: This adapter2 is available message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' last_transition_time: '2021-01-01T10:01:00Z' @@ -370,7 +378,15 @@ paths: status: 'True' reason: All adapters reported Ready True for the current generation message: All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -378,7 +394,7 @@ paths: status: 'True' reason: All adapters reported Available True for the same generation message: All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -386,7 +402,7 @@ paths: status: 'True' reason: This adapter1 is available message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -394,7 +410,7 @@ paths: status: 'True' reason: This adapter2 is available message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' last_transition_time: '2021-01-01T10:01:00Z' @@ -478,6 +494,7 @@ paths: - name: nodepool_id in: path required: true + description: Nodepool ID schema: type: string responses: @@ -499,6 +516,45 @@ paths: application/json: schema: $ref: '#/components/schemas/AdapterStatusCreateRequest' + security: + - BearerAuth: [] + put: + operationId: putNodePoolStatuses + summary: Adapter creates or updates resource nodepool status + 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: + '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' + security: + - BearerAuth: [] get: operationId: getNodePoolsStatuses summary: List all adapter statuses for nodepools @@ -573,6 +629,37 @@ paths: $ref: '#/components/schemas/AdapterStatusCreateRequest' security: - BearerAuth: [] + put: + operationId: putClusterStatuses + summary: Adapter creates or updates resource cluster status + 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' + security: + - BearerAuth: [] get: operationId: getClusterStatuses summary: List all adapter statuses for cluster @@ -702,7 +789,7 @@ components: $ref: '#/components/schemas/AdapterConditionStatus' description: |- Condition in AdapterStatus - Used for standard Kubernetes condition types: "Available", "Applied", "Health" + Used for standard Kubernetes condition types: "Available", "Applied", "Health", "Finalized" Note: observed_generation is at AdapterStatus level, not per-condition, since all conditions in one AdapterStatus share the same observed generation AdapterConditionStatus: @@ -757,7 +844,7 @@ components: $ref: '#/components/schemas/AdapterCondition' description: |- Kubernetes-style conditions tracking adapter state - Typically includes: Available, Applied, Health + Typically includes: Available, Applied, Health, Finalized created_time: type: string format: date-time @@ -791,6 +878,11 @@ components: reason: All adapter1 operations completed successfully message: All adapter1 runtime operations completed successfully last_transition_time: '2021-01-01T10:00:00Z' + - type: Finalized + status: 'True' + reason: All resources deleted; cleanup confirmed + message: All resources deleted; cleanup confirmed + last_transition_time: '2021-01-01T10:00:00Z' metadata: job_name: validator-job-abc123 job_namespace: hyperfleet-system @@ -871,6 +963,10 @@ components: status: 'True' reason: All adapter1 operations completed successfully message: All adapter1 runtime operations completed successfully + - type: Finalized + status: 'True' + reason: All resources deleted; cleanup confirmed + message: All resources deleted; cleanup confirmed metadata: job_name: validator-job-abc123 job_namespace: hyperfleet-system @@ -1036,6 +1132,14 @@ components: created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + 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: Available status: 'True' reason: All adapters reported Available True for the same generation @@ -1158,7 +1262,8 @@ components: List of status conditions for the cluster. **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. These conditions are present immediately upon resource creation. @@ -1212,7 +1317,7 @@ components: example: The cluster name field is required instance: type: string - format: uri + format: uri-reference description: URI reference for this specific occurrence example: /api/hyperfleet/v1/clusters code: @@ -1326,6 +1431,14 @@ components: created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + 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: Available status: 'True' reason: All adapters reported Available True for the same generation @@ -1515,7 +1628,8 @@ components: List of status conditions for the nodepool. **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. These conditions are present immediately upon resource creation. diff --git a/schemas/core/swagger.yaml b/schemas/core/swagger.yaml index fea0aec..8d510ec 100644 --- a/schemas/core/swagger.yaml +++ b/schemas/core/swagger.yaml @@ -16,7 +16,7 @@ info: name: Apache 2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0' title: HyperFleet API - version: 1.0.7 + version: 1.0.9 host: hyperfleet.redhat.com basePath: / schemes: @@ -112,8 +112,8 @@ paths: It is NOT part of the request body. Initially, - status.conditions will include mandatory "Available" and "Ready" - conditions. + status.conditions will include mandatory "Available", "Ready" and + "Reconciled" conditions. operationId: postCluster summary: Create cluster '/api/hyperfleet/v1/clusters/{cluster_id}': @@ -155,19 +155,31 @@ paths: message: >- All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or + Finalized=True at the current generation + observed_generation: 2 + reason: >- + All required adapters reported Available=True or + Finalized=True at the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: >- All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Available True for the same generation @@ -177,7 +189,7 @@ paths: last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter1 is available status: 'True' type: Adapter1Successful @@ -185,7 +197,7 @@ paths: last_transition_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter2 is available status: 'True' type: Adapter2Successful @@ -425,19 +437,31 @@ paths: message: >- All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or + Finalized=True at the current generation + observed_generation: 2 + reason: >- + All required adapters reported Available=True or + Finalized=True at the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: >- All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Available True for the same generation @@ -447,7 +471,7 @@ paths: last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter1 is available status: 'True' type: Adapter1Successful @@ -455,7 +479,7 @@ paths: last_transition_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter2 is available status: 'True' type: Adapter2Successful @@ -624,7 +648,8 @@ paths: name: cluster_id required: true type: string - - in: path + - description: Nodepool ID + in: path name: nodepool_id required: true type: string @@ -646,6 +671,8 @@ paths: description: The server cannot find the requested resource. '409': description: The request conflicts with the current state of the server. + security: + - BearerAuth: [] description: >- Adapter creates or updates its status report for this nodepool. @@ -658,6 +685,44 @@ paths: Adapter should call this endpoint every time it evaluates the nodepool. operationId: postNodePoolStatuses summary: Create or update adapter status + put: + consumes: + - application/json + produces: + - application/json + parameters: + - description: Cluster ID + in: path + name: cluster_id + required: true + type: string + - description: Nodepool ID + in: path + name: nodepool_id + required: true + type: string + - in: body + name: body + required: true + schema: + $ref: '#/definitions/AdapterStatusCreateRequest' + responses: + '201': + description: >- + The request has succeeded and a new resource has been created as a + result. + schema: + $ref: '#/definitions/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. + security: + - BearerAuth: [] + operationId: putNodePoolStatuses + summary: Adapter creates or updates resource nodepool status '/api/hyperfleet/v1/clusters/{cluster_id}/statuses': get: produces: @@ -758,6 +823,39 @@ paths: Adapter should call this endpoint every time it evaluates the cluster. operationId: postClusterStatuses summary: Create or update adapter status + put: + consumes: + - application/json + produces: + - application/json + parameters: + - description: Cluster ID + in: path + name: cluster_id + required: true + type: string + - in: body + name: body + required: true + schema: + $ref: '#/definitions/AdapterStatusCreateRequest' + responses: + '201': + description: >- + The request has succeeded and a new resource has been created as a + result. + schema: + $ref: '#/definitions/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. + security: + - BearerAuth: [] + operationId: putClusterStatuses + summary: Adapter creates or updates resource cluster status /api/hyperfleet/v1/nodepools: get: produces: @@ -819,7 +917,7 @@ definitions: Condition in AdapterStatus Used for standard Kubernetes condition types: "Available", "Applied", - "Health" + "Health", "Finalized" Note: observed_generation is at AdapterStatus level, not per-condition, @@ -879,6 +977,11 @@ definitions: reason: All adapter1 operations completed successfully status: 'True' type: Health + - last_transition_time: '2021-01-01T10:00:00Z' + message: All resources deleted; cleanup confirmed + reason: All resources deleted; cleanup confirmed + status: 'True' + type: Finalized created_time: '2021-01-01T10:00:00Z' data: validation_results: @@ -901,7 +1004,7 @@ definitions: conditions: description: |- Kubernetes-style conditions tracking adapter state - Typically includes: Available, Applied, Health + Typically includes: Available, Applied, Health, Finalized items: $ref: '#/definitions/AdapterCondition' type: array @@ -970,6 +1073,10 @@ definitions: reason: All adapter1 operations completed successfully status: 'True' type: Health + - message: All resources deleted; cleanup confirmed + reason: All resources deleted; cleanup confirmed + status: 'True' + type: Finalized data: validation_results: failed: 0 @@ -1123,6 +1230,18 @@ definitions: reason: All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or Finalized=True at + the current generation + observed_generation: 1 + reason: >- + All required adapters reported Available=True or Finalized=True at + the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' @@ -1317,8 +1436,11 @@ definitions: **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the - current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all + adapters report successfully at the current generation. + + - `type: "Reconciled"`: Whether the resource's desired state has been + fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. @@ -1370,7 +1492,7 @@ definitions: instance: description: URI reference for this specific occurrence example: /api/hyperfleet/v1/clusters - format: uri + format: uri-reference type: string status: description: HTTP status code @@ -1428,6 +1550,18 @@ definitions: reason: All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or Finalized=True at + the current generation + observed_generation: 1 + reason: >- + All required adapters reported Available=True or Finalized=True at + the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' @@ -1689,8 +1823,11 @@ definitions: **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the - current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all + adapters report successfully at the current generation. + + - `type: "Reconciled"`: Whether the resource's desired state has been + fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. diff --git a/schemas/gcp/openapi.yaml b/schemas/gcp/openapi.yaml index 1315b5e..2770707 100644 --- a/schemas/gcp/openapi.yaml +++ b/schemas/gcp/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: HyperFleet API - version: 1.0.7 + version: 1.0.9 contact: name: HyperFleet Team license: @@ -50,7 +50,7 @@ paths: **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.conditions will include mandatory "Available" and "Ready" conditions. + status.conditions will include mandatory "Available", "Ready" and "Reconciled" conditions. parameters: [] responses: '201': @@ -194,7 +194,15 @@ paths: status: 'True' reason: All adapters reported Ready True for the current generation message: All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -202,7 +210,7 @@ paths: status: 'True' reason: All adapters reported Available True for the same generation message: All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -210,7 +218,7 @@ paths: status: 'True' reason: This adapter1 is available message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -218,7 +226,7 @@ paths: status: 'True' reason: This adapter2 is available message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' last_transition_time: '2021-01-01T10:01:00Z' @@ -408,7 +416,15 @@ paths: status: 'True' reason: ResourceReady message: All conditions successful for current spec generation - observed_generation: 1 + observed_generation: 2 + created_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -416,7 +432,7 @@ paths: status: 'True' reason: ResourceAvailable message: All conditions successful for observed_generation - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -424,7 +440,7 @@ paths: status: 'True' reason: All validations passed message: NodePool validation passed - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' @@ -432,7 +448,7 @@ paths: status: 'True' reason: NodePool provisioned successfully message: NodePool has 3 nodes running - observed_generation: 1 + observed_generation: 2 created_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' last_transition_time: '2021-01-01T10:01:00Z' @@ -674,7 +690,7 @@ components: $ref: '#/components/schemas/AdapterConditionStatus' description: |- Condition in AdapterStatus - Used for standard Kubernetes condition types: "Available", "Applied", "Health" + Used for standard Kubernetes condition types: "Available", "Applied", "Health", "Finalized" Note: observed_generation is at AdapterStatus level, not per-condition, since all conditions in one AdapterStatus share the same observed generation AdapterConditionStatus: @@ -729,7 +745,7 @@ components: $ref: '#/components/schemas/AdapterCondition' description: |- Kubernetes-style conditions tracking adapter state - Typically includes: Available, Applied, Health + Typically includes: Available, Applied, Health, Finalized created_time: type: string format: date-time @@ -763,6 +779,11 @@ components: reason: All adapter1 operations completed successfully message: All adapter1 runtime operations completed successfully last_transition_time: '2021-01-01T10:00:00Z' + - type: Finalized + status: 'True' + reason: All resources deleted; cleanup confirmed + message: All resources deleted; cleanup confirmed + last_transition_time: '2021-01-01T10:00:00Z' metadata: job_name: validator-job-abc123 job_namespace: hyperfleet-system @@ -972,6 +993,14 @@ components: created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + 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: Available status: 'True' reason: All adapters reported Available True for the same generation @@ -1195,7 +1224,8 @@ components: List of status conditions for the cluster. **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. These conditions are present immediately upon resource creation. @@ -1237,7 +1267,7 @@ components: example: The cluster name field is required instance: type: string - format: uri + format: uri-reference description: URI reference for this specific occurrence example: /api/hyperfleet/v1/clusters code: @@ -1381,6 +1411,14 @@ components: created_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' + - type: Reconciled + status: 'True' + reason: All required adapters reported Available=True or Finalized=True at the current generation + message: All required adapters reported Available=True or Finalized=True at the current generation + 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: Available status: 'True' reason: ResourceAvailable @@ -1677,7 +1715,8 @@ components: List of status conditions for the nodepool. **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all adapters report successfully at the current generation. + - `type: "Reconciled"`: Whether the resource's desired state has been fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. These conditions are present immediately upon resource creation. diff --git a/schemas/gcp/swagger.yaml b/schemas/gcp/swagger.yaml index e1f0d9b..8879c90 100644 --- a/schemas/gcp/swagger.yaml +++ b/schemas/gcp/swagger.yaml @@ -16,7 +16,7 @@ info: name: Apache 2.0 url: 'https://www.apache.org/licenses/LICENSE-2.0' title: HyperFleet API - version: 1.0.7 + version: 1.0.9 host: hyperfleet.redhat.com basePath: / schemes: @@ -112,8 +112,8 @@ paths: It is NOT part of the request body. Initially, - status.conditions will include mandatory "Available" and "Ready" - conditions. + status.conditions will include mandatory "Available", "Ready" and + "Reconciled" conditions. operationId: postCluster summary: Create cluster '/api/hyperfleet/v1/clusters/{cluster_id}': @@ -175,19 +175,31 @@ paths: message: >- All adapters reported Ready True for the current generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or + Finalized=True at the current generation + observed_generation: 2 + reason: >- + All required adapters reported Available=True or + Finalized=True at the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: >- All adapters reported Available True for the same generation - observed_generation: 1 + observed_generation: 2 reason: >- All adapters reported Available True for the same generation @@ -197,7 +209,7 @@ paths: last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: This adapter1 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter1 is available status: 'True' type: Adapter1Successful @@ -205,7 +217,7 @@ paths: last_transition_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' message: This adapter2 is available - observed_generation: 1 + observed_generation: 2 reason: This adapter2 is available status: 'True' type: Adapter2Successful @@ -462,15 +474,27 @@ paths: last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: All conditions successful for current spec generation - observed_generation: 1 + observed_generation: 2 reason: ResourceReady status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or + Finalized=True at the current generation + observed_generation: 2 + reason: >- + All required adapters reported Available=True or + Finalized=True at the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: All conditions successful for observed_generation - observed_generation: 1 + observed_generation: 2 reason: ResourceAvailable status: 'True' type: Available @@ -478,7 +502,7 @@ paths: last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' message: NodePool validation passed - observed_generation: 1 + observed_generation: 2 reason: All validations passed status: 'True' type: ValidationSuccessful @@ -486,7 +510,7 @@ paths: last_transition_time: '2021-01-01T10:01:00Z' last_updated_time: '2021-01-01T10:01:00Z' message: NodePool has 3 nodes running - observed_generation: 1 + observed_generation: 2 reason: NodePool provisioned successfully status: 'True' type: NodePoolSuccessful @@ -773,7 +797,7 @@ definitions: Condition in AdapterStatus Used for standard Kubernetes condition types: "Available", "Applied", - "Health" + "Health", "Finalized" Note: observed_generation is at AdapterStatus level, not per-condition, @@ -833,6 +857,11 @@ definitions: reason: All adapter1 operations completed successfully status: 'True' type: Health + - last_transition_time: '2021-01-01T10:00:00Z' + message: All resources deleted; cleanup confirmed + reason: All resources deleted; cleanup confirmed + status: 'True' + type: Finalized created_time: '2021-01-01T10:00:00Z' data: validation_results: @@ -855,7 +884,7 @@ definitions: conditions: description: |- Kubernetes-style conditions tracking adapter state - Typically includes: Available, Applied, Health + Typically includes: Available, Applied, Health, Finalized items: $ref: '#/definitions/AdapterCondition' type: array @@ -1041,6 +1070,18 @@ definitions: reason: All adapters reported Ready True for the current generation status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or Finalized=True at + the current generation + observed_generation: 1 + reason: >- + All required adapters reported Available=True or Finalized=True at + the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' @@ -1336,8 +1377,11 @@ definitions: **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the - current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all + adapters report successfully at the current generation. + + - `type: "Reconciled"`: Whether the resource's desired state has been + fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. @@ -1375,7 +1419,7 @@ definitions: instance: description: URI reference for this specific occurrence example: /api/hyperfleet/v1/clusters - format: uri + format: uri-reference type: string status: description: HTTP status code @@ -1463,6 +1507,18 @@ definitions: reason: ResourceReady status: 'True' type: Ready + - created_time: '2021-01-01T10:00:00Z' + last_transition_time: '2021-01-01T10:00:00Z' + last_updated_time: '2021-01-01T10:00:00Z' + message: >- + All required adapters reported Available=True or Finalized=True at + the current generation + observed_generation: 1 + reason: >- + All required adapters reported Available=True or Finalized=True at + the current generation + status: 'True' + type: Reconciled - created_time: '2021-01-01T10:00:00Z' last_transition_time: '2021-01-01T10:00:00Z' last_updated_time: '2021-01-01T10:00:00Z' @@ -1831,8 +1887,11 @@ definitions: **Mandatory conditions**: - - `type: "Ready"`: Whether all adapters report successfully at the - current generation. + - `type: "Ready"` *(deprecated — use Reconciled)*: Whether all + adapters report successfully at the current generation. + + - `type: "Reconciled"`: Whether the resource's desired state has been + fully reconciled by all adapters at the current generation. - `type: "Available"`: Aggregated adapter result for a common observed_generation. diff --git a/schemas/schemas.go b/schemas/schemas.go new file mode 100644 index 0000000..2ced3a4 --- /dev/null +++ b/schemas/schemas.go @@ -0,0 +1,14 @@ +// Package schemas exposes the generated HyperFleet OpenAPI schema files as an embedded filesystem. +// Consumers can import this package to access versioned schema content without vendoring local copies. +// +// Usage: +// +// import specschemas "github.com/openshift-hyperfleet/hyperfleet-api-spec/schemas" +// +// data, err := specschemas.FS.ReadFile("gcp/openapi.yaml") +package schemas + +import "embed" + +//go:embed core/openapi.yaml core/swagger.yaml gcp/openapi.yaml gcp/swagger.yaml +var FS embed.FS diff --git a/services/clusters.tsp b/services/clusters.tsp index 4e203fa..8aae519 100644 --- a/services/clusters.tsp +++ b/services/clusters.tsp @@ -38,7 +38,7 @@ interface Clusters { * * **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.conditions will include mandatory "Available" and "Ready" conditions. + * status.conditions will include mandatory "Available", "Ready" and "Reconciled" conditions. * */ @route("") diff --git a/services/statuses-internal.tsp b/services/statuses-internal.tsp index 8a19862..1aeb605 100644 --- a/services/statuses-internal.tsp +++ b/services/statuses-internal.tsp @@ -10,18 +10,17 @@ using Http; using OpenAPI; namespace HyperFleet; + @route("/clusters/{cluster_id}/statuses") @useAuth(HyperFleet.BearerAuth) //@tag("Cluster statuses") -interface ClusterStatusesInternal{ - +interface ClusterStatusesInternal { /** * 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. - * */ @route("") @post @@ -31,29 +30,43 @@ interface ClusterStatusesInternal{ /** * Cluster ID */ - @path cluster_id: string, + @path cluster_id: string, @body body: AdapterStatusCreateRequest, ): | (CreatedResponse & AdapterStatus) - | BadRequestResponse - | NotFoundResponse - | ConflictResponse - | BadRequestResponse; + | BadRequestResponse + | NotFoundResponse + | ConflictResponse; + + @route("") + @put + @summary("Adapter creates or updates resource cluster status") + @operationId("putClusterStatuses") + putClusterStatuses( + /** + * Cluster ID + */ + @path cluster_id: string, + @body body: AdapterStatusCreateRequest, + ): + | (CreatedResponse & AdapterStatus) + | BadRequestResponse + | NotFoundResponse + | ConflictResponse; } //@tag("NodePool statuses") @route("/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses") -interface NodePoolStatusesInternal{ - +@useAuth(HyperFleet.BearerAuth) +interface NodePoolStatusesInternal { /** * 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. - * */ @route("") @post @@ -63,15 +76,39 @@ interface NodePoolStatusesInternal{ /** * Cluster ID */ - @path cluster_id: string, - @path nodepool_id: string, + @path cluster_id: string, + + /** + * Nodepool ID + */ + @path nodepool_id: string, @body body: AdapterStatusCreateRequest, ): | (CreatedResponse & AdapterStatus) - | BadRequestResponse - | NotFoundResponse - | ConflictResponse - | BadRequestResponse; + | BadRequestResponse + | NotFoundResponse + | ConflictResponse; + + @route("") + @put + @summary("Adapter creates or updates resource nodepool status") + @operationId("putNodePoolStatuses") + putNodePoolStatuses( + /** + * Cluster ID + */ + @path cluster_id: string, + + /** + * Nodepool ID + */ + @path nodepool_id: string, + @body body: AdapterStatusCreateRequest, + ): + | (CreatedResponse & AdapterStatus) + | BadRequestResponse + | NotFoundResponse + | ConflictResponse; }