Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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"
69 changes: 53 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
1 change: 1 addition & 0 deletions .spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends: ["spectral:oas"]
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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?**

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/openshift-hyperfleet/hyperfleet-api-spec

go 1.23
2 changes: 1 addition & 1 deletion main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 24 additions & 4 deletions models-core/cluster/example_cluster.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
28 changes: 24 additions & 4 deletions models-core/nodepool/example_nodepool.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
28 changes: 24 additions & 4 deletions models-gcp/cluster/example_cluster.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
Loading