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/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/models/common/model.tsp b/models/common/model.tsp index b509169..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; diff --git a/package-lock.json b/package-lock.json index d8911a5..2b420cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3681,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 54261b7..d865c3f 100644 --- a/schemas/core/openapi.yaml +++ b/schemas/core/openapi.yaml @@ -1317,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: diff --git a/schemas/core/swagger.yaml b/schemas/core/swagger.yaml index 1f13d75..8d510ec 100644 --- a/schemas/core/swagger.yaml +++ b/schemas/core/swagger.yaml @@ -1492,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 diff --git a/schemas/gcp/openapi.yaml b/schemas/gcp/openapi.yaml index 7be70ea..2770707 100644 --- a/schemas/gcp/openapi.yaml +++ b/schemas/gcp/openapi.yaml @@ -1267,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: diff --git a/schemas/gcp/swagger.yaml b/schemas/gcp/swagger.yaml index 1d36440..8879c90 100644 --- a/schemas/gcp/swagger.yaml +++ b/schemas/gcp/swagger.yaml @@ -1419,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 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