Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ jobs:
dist/*.tar.gz
dist/*.tar.gz.sha256

update-action-tag:
needs: [version, release]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Update major version tag
run: |
version="${{ needs.version.outputs.value }}"
major="v$(echo "$version" | cut -d. -f1)"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git configuration is missing before executing git commands. The update-formula job (lines 168-169) sets git user.name and user.email before using git commands, but this job does not. Without git config, the git tag and git push commands may fail depending on the runner environment.

Suggested change
major="v$(echo "$version" | cut -d. -f1)"
major="v$(echo "$version" | cut -d. -f1)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

Copilot uses AI. Check for mistakes.
git tag -f "$major"
git push origin "$major" --force

update-formula:
needs: [version, release]
runs-on: ubuntu-latest
Expand Down
76 changes: 76 additions & 0 deletions .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: test-action

on:
push:
branches: [main]
paths:
- "action/**"
- "tests/fixtures/**"
- ".github/workflows/test-action.yml"
pull_request:
branches: [main]
paths:
- "action/**"
- "tests/fixtures/**"
- ".github/workflows/test-action.yml"

jobs:
test-setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Run setup action
uses: ./action/setup

- name: Verify oav is on PATH
run: oav --version

test-validate-pass:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup oav
uses: ./action/setup

- name: Run validate (should pass)
id: validate
uses: ./action/validate
with:
spec: tests/fixtures/valid.yml
skip-generate: "true"
skip-compile: "true"
upload-reports: "false"

- name: Assert pass
run: |
if [[ "${{ steps.validate.outputs.result }}" != "pass" ]]; then
echo "::error::Expected result=pass, got ${{ steps.validate.outputs.result }}"
exit 1
fi

test-validate-failure:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup oav
uses: ./action/setup

- name: Run validate (should fail)
id: validate
uses: ./action/validate
continue-on-error: true
with:
spec: tests/fixtures/invalid.yml
skip-generate: "true"
skip-compile: "true"
upload-reports: "false"

- name: Assert failure
run: |
if [[ "${{ steps.validate.outputs.result }}" != "fail" ]]; then
echo "::error::Expected result=fail, got ${{ steps.validate.outputs.result }}"
exit 1
fi
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,85 @@ cargo install --git https://github.com/entur/openapi-validator-cli
- Cargo: `cargo uninstall oav`
- Curl/manual: `rm /usr/local/bin/oav` (or wherever you installed it)

## GitHub Action

Use oav directly in GitHub Actions workflows. The validate action requires Docker, so it must run on `ubuntu-latest` (or other Linux runners).

### Basic usage

```yaml
- uses: entur/openapi-validator-cli/action/setup@v0

- uses: entur/openapi-validator-cli/action/validate@v0
with:
spec: openapi/api.yaml
```

### Full example

```yaml
name: Validate OpenAPI
on: [push, pull_request]

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup oav
uses: entur/openapi-validator-cli/action/setup@v0

- name: Validate OpenAPI spec
id: oav
uses: entur/openapi-validator-cli/action/validate@v0
with:
spec: openapi/api.yaml
mode: both
skip-compile: "true"
```

### Setup only

If you want to use oav commands directly:

```yaml
- uses: entur/openapi-validator-cli/action/setup@v0
with:
version: "0.3.0"

- run: |
oav init --spec openapi/api.yaml
oav validate --color never
```

### Inputs (validate action)

| Input | Default | Description |
|---------------------|-----------|------------------------------------------|
| `spec` | — | Path to OpenAPI spec file |
| `mode` | — | Validation mode (server, client, both) |
| `server-generators` | — | Comma-separated server generators |
| `client-generators` | — | Comma-separated client generators |
| `skip-lint` | `false` | Skip the lint step |
| `skip-generate` | `false` | Skip the generate step |
| `skip-compile` | `false` | Skip the compile step |
| `linter` | — | Linter to use (spectral, redocly, none) |
| `ruleset` | — | Path to custom Spectral ruleset |
| `docker-timeout` | — | Docker timeout in seconds |
| `search-depth` | — | Max directory depth for spec search |
| `working-directory` | `.` | Working directory for oav commands |
| `upload-reports` | `true` | Upload reports as workflow artifact |

### Outputs

| Output | Description |
|----------|------------------------------------------|
| `result` | `pass`, `fail`, or `error` |
| `total` | Total number of validation targets |
| `passed` | Number of passed targets |
| `failed` | Number of failed targets |

## Commands

- `oav init` — create `.oav/`, scaffold `.oavc`, and add gitignore entries
Expand Down
70 changes: 70 additions & 0 deletions action/setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Setup oav
description: Download and install the oav OpenAPI validator CLI

inputs:
version:
description: Version to install (e.g. 0.3.0). Use "latest" for the most recent release.
required: false
default: latest
token:
description: GitHub token for API access and artifact downloads
required: false
default: ${{ github.token }}

outputs:
version:
description: Resolved version that was installed
value: ${{ steps.install.outputs.version }}

runs:
using: composite
steps:
- name: Resolve version and target
id: resolve
shell: bash
run: |
version="${{ inputs.version }}"
if [[ "$version" == "latest" ]]; then
json="$(curl -fsSL -H "Authorization: token ${{ inputs.token }}" \
"https://api.github.com/repos/entur/openapi-validator-cli/releases/latest")"
tag="$(printf '%s' "$json" | awk -F\" '/"tag_name":/ {print $4; exit}')"
if [[ -z "$tag" ]]; then
echo "::error::Unable to determine latest release tag"
exit 1
fi
version="${tag#v}"
fi

os="$(uname -s)"
arch="$(uname -m)"
case "$os" in
Darwin) platform="apple-darwin" ;;
Linux) platform="unknown-linux-gnu" ;;
*) echo "::error::Unsupported OS: $os"; exit 1 ;;
esac
case "$arch" in
x86_64) arch="x86_64" ;;
arm64|aarch64) arch="aarch64" ;;
*) echo "::error::Unsupported architecture: $arch"; exit 1 ;;
esac

target="${arch}-${platform}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "target=${target}" >> "$GITHUB_OUTPUT"

- name: Cache oav binary
id: cache
uses: actions/cache@v4
with:
path: ${{ runner.tool_cache }}/oav/${{ steps.resolve.outputs.version }}
key: oav-${{ steps.resolve.outputs.version }}-${{ steps.resolve.outputs.target }}

- name: Install oav
id: install
shell: bash
run: bash "${{ github.action_path }}/install.sh"
env:
INPUT_VERSION: ${{ steps.resolve.outputs.version }}
INPUT_TARGET: ${{ steps.resolve.outputs.target }}
INPUT_TOKEN: ${{ inputs.token }}
CACHE_HIT: ${{ steps.cache.outputs.cache-hit }}
37 changes: 37 additions & 0 deletions action/setup/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail

version="${INPUT_VERSION}"
target="${INPUT_TARGET}"
token="${INPUT_TOKEN}"
install_dir="${RUNNER_TOOL_CACHE:-$HOME/.oav/bin}/oav/${version}"

mkdir -p "$install_dir"

if [[ "${CACHE_HIT}" != "true" ]]; then
base_url="https://github.com/entur/openapi-validator-cli/releases/download/v${version}"
asset="oav-${version}-${target}.tar.gz"
sha_asset="${asset}.sha256"

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT

echo "Downloading oav v${version} for ${target}..."
curl -fsSL -H "Authorization: token ${token}" \
"${base_url}/${asset}" -o "${tmpdir}/${asset}"
curl -fsSL -H "Authorization: token ${token}" \
"${base_url}/${sha_asset}" -o "${tmpdir}/${sha_asset}"

echo "Verifying checksum..."
(cd "$tmpdir" && sha256sum -c "$sha_asset")

tar -xzf "${tmpdir}/${asset}" -C "$tmpdir"
install -m 0755 "${tmpdir}/oav" "${install_dir}/oav"

echo "Installed oav v${version} to ${install_dir}"
else
echo "Using cached oav v${version}"
fi

echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "${install_dir}" >> "$GITHUB_PATH"
102 changes: 102 additions & 0 deletions action/validate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Validate OpenAPI specs
description: Run oav to lint, generate, and compile OpenAPI specs

inputs:
spec:
description: Path to OpenAPI spec file
required: false
mode:
description: Validation mode (server, client, both)
required: false
server-generators:
description: Comma-separated list of server generators
required: false
client-generators:
description: Comma-separated list of client generators
required: false
skip-lint:
description: Skip the lint step
required: false
default: "false"
skip-generate:
description: Skip the generate step
required: false
default: "false"
skip-compile:
description: Skip the compile step
required: false
default: "false"
linter:
description: Linter to use (spectral, redocly, none)
required: false
ruleset:
description: Path to custom Spectral ruleset
required: false
docker-timeout:
description: Docker operation timeout in seconds
required: false
search-depth:
description: Max directory depth when searching for spec files
required: false
working-directory:
description: Working directory for oav commands
required: false
default: "."
upload-reports:
description: Upload .oav/reports/ as a workflow artifact
required: false
default: "true"

outputs:
result:
description: "Validation result: pass, fail, or error"
value: ${{ steps.validate.outputs.result }}
total:
description: Total number of validation targets
value: ${{ steps.validate.outputs.total }}
passed:
description: Number of passed targets
value: ${{ steps.validate.outputs.passed }}
failed:
description: Number of failed targets
value: ${{ steps.validate.outputs.failed }}

runs:
using: composite
steps:
- name: Run oav validate
id: validate
shell: bash
working-directory: ${{ inputs.working-directory }}
run: bash "${{ github.action_path }}/validate.sh"
env:
INPUT_SPEC: ${{ inputs.spec }}
INPUT_MODE: ${{ inputs.mode }}
INPUT_SERVER_GENERATORS: ${{ inputs.server-generators }}
INPUT_CLIENT_GENERATORS: ${{ inputs.client-generators }}
INPUT_SKIP_LINT: ${{ inputs.skip-lint }}
INPUT_SKIP_GENERATE: ${{ inputs.skip-generate }}
INPUT_SKIP_COMPILE: ${{ inputs.skip-compile }}
INPUT_LINTER: ${{ inputs.linter }}
INPUT_RULESET: ${{ inputs.ruleset }}
INPUT_DOCKER_TIMEOUT: ${{ inputs.docker-timeout }}
INPUT_SEARCH_DEPTH: ${{ inputs.search-depth }}

- name: Generate step summary
if: always()
shell: bash
working-directory: ${{ inputs.working-directory }}
run: bash "${{ github.action_path }}/summary.sh"

- name: Upload reports
if: always() && inputs.upload-reports == 'true'
uses: actions/upload-artifact@v4
with:
name: oav-reports
path: ${{ inputs.working-directory }}/.oav/reports/
if-no-files-found: ignore

- name: Set exit code
if: always()
shell: bash
run: exit ${{ steps.validate.outputs.exit_code }}
Loading
Loading