Skip to content
Draft
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
76 changes: 76 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -581,3 +581,79 @@ jobs:
exit 1
fi
echo "PASS"
oasdiff_validate_valid:
runs-on: ubuntu-latest
name: Test validate on a valid spec
steps:
- name: checkout
uses: actions/checkout@v6
- name: Running validate action on a valid spec
id: test_validate_valid
uses: ./validate
with:
spec: 'specs/valid.yaml'
- name: Test validate reports zero findings
run: |
findings="${{ steps.test_validate_valid.outputs.findings }}"
if [ "$findings" != "0" ]; then
echo "Expected 0 findings, got '$findings'" >&2
exit 1
fi
oasdiff_validate_findings:
runs-on: ubuntu-latest
name: Test validate fails on an invalid spec
steps:
- name: checkout
uses: actions/checkout@v6
- name: Running validate action on an invalid spec
id: test_validate_findings
continue-on-error: true
uses: ./validate
with:
spec: 'specs/invalid.yaml'
- name: Test validate failed and reported findings
run: |
if [ "${{ steps.test_validate_findings.outcome }}" != "failure" ]; then
echo "Expected the validate step to fail on error-level findings" >&2
exit 1
fi
findings="${{ steps.test_validate_findings.outputs.findings }}"
if [ "$findings" = "0" ] || [ -z "$findings" ]; then
echo "Expected findings > 0, got '$findings'" >&2
exit 1
fi
oasdiff_validate_fail_on:
runs-on: ubuntu-latest
name: Test validate severity threshold (--fail-on)
steps:
- name: checkout
uses: actions/checkout@v6
- name: Validate a warning-only spec with the default threshold
id: test_validate_warn_default
uses: ./validate
with:
spec: 'specs/validate-warning.yaml'
- name: Default threshold reports the warning but passes
run: |
if [ "${{ steps.test_validate_warn_default.outcome }}" != "success" ]; then
echo "Expected the step to pass (warnings don't fail by default)" >&2
exit 1
fi
warnings="${{ steps.test_validate_warn_default.outputs.warning_count }}"
if [ "$warnings" != "1" ]; then
echo "Expected warning_count 1, got '$warnings'" >&2
exit 1
fi
- name: Validate the same spec with fail-on WARN
id: test_validate_warn_failon
continue-on-error: true
uses: ./validate
with:
spec: 'specs/validate-warning.yaml'
fail-on: 'WARN'
- name: fail-on WARN escalates the warning to a failure
run: |
if [ "${{ steps.test_validate_warn_failon.outcome }}" != "failure" ]; then
echo "Expected the step to fail with fail-on WARN" >&2
exit 1
fi
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GitHub Actions for comparing OpenAPI specs and detecting breaking changes, based
- [Check for breaking changes](#check-for-breaking-changes)
- [Generate a changelog](#generate-a-changelog)
- [Generate a diff report](#generate-a-diff-report)
- [Validate a single spec](#validate-a-single-spec)
- [Configuring with `.oasdiff.yaml`](#configuring-with-oasdiffyaml)
- [Spec paths](#spec-paths)
- [Pro: Rich PR comment](#pro-rich-pr-comment)
Expand Down Expand Up @@ -156,6 +157,33 @@ jobs:
| `flatten-allof` | `false` | Merge allOf subschemas into a single schema before diff | `true`, `false` |
| `output-to-file` | `''` | Write output to this file path instead of stdout | file path |

### Validate a single spec

Validates one OpenAPI spec against the OpenAPI and JSON Schema rules and writes an inline GitHub annotation for each finding. Unlike the other actions it takes a single spec, not a base/revision pair. Findings are classified by severity (error, warning, info); by default the workflow fails only on errors.

```yaml
name: oasdiff
on:
pull_request:
branches: [ "main" ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oasdiff/oasdiff-action/validate@v0.0.47
with:
spec: 'openapi.yaml'
```

| Input | Default | Description | Accepted values |
|---|---|---|---|
| `spec` | — (required) | Path to the OpenAPI spec to validate | file path, URL, git ref |
| `fail-on` | `''` | Fail with exit code 1 when a finding is at or above this severity (empty uses the oasdiff default, `ERR`) | `ERR`, `WARN`, `INFO` |
| `allow-external-refs` | `true` | Resolve external `$ref`s; set `false` to prevent SSRF when validating untrusted specs | `true`, `false` |

For a non-blocking, report-only run, leave `fail-on` and set `continue-on-error: true` on the step. Outputs: `findings` (total), `error_count`, `warning_count`, `info_count`.

---

## Configuring with `.oasdiff.yaml`
Expand Down
2 changes: 1 addition & 1 deletion release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ set -e

REPO_DIR="$(cd "$(dirname "$0")" && pwd)"

DOCKERFILES="breaking/Dockerfile changelog/Dockerfile diff/Dockerfile pr-comment/Dockerfile"
DOCKERFILES="breaking/Dockerfile changelog/Dockerfile diff/Dockerfile validate/Dockerfile pr-comment/Dockerfile"

# ── Resolve action version ───────────────────────────────────────────────────

Expand Down
4 changes: 4 additions & 0 deletions specs/invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
openapi: 3.0.0
info:
title: invalid
paths: {}
10 changes: 10 additions & 0 deletions specs/valid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
openapi: 3.0.0
info:
title: valid
version: "1.0.0"
paths:
/things:
get:
responses:
'200':
description: ok
8 changes: 8 additions & 0 deletions specs/validate-warning.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
openapi: 3.0.0
info:
title: warning
version: "1.0.0"
license:
name: MIT
identifier: MIT
paths: {}
5 changes: 5 additions & 0 deletions validate/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM tufin/oasdiff:v1.16.0
RUN apk add --no-cache jq
ENV PLATFORM github-action
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
30 changes: 30 additions & 0 deletions validate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: 'Validate an OpenAPI spec'
description: 'Validate an OpenAPI spec against the spec, with per-finding PR annotations'
inputs:
spec:
description: 'Path of the OpenAPI spec in YAML or JSON format'
required: true
fail-on:
description: 'Fail with exit code 1 when a finding has this severity or higher: ERR, WARN, or INFO. Defaults to ERR (errors fail the build; warnings and info are reported but do not). For a non-blocking, report-only run, set continue-on-error on the step.'
required: false
default: ''
allow-external-refs:
description: 'Allow external $refs in the spec; disable to prevent SSRF when validating untrusted specs'
required: false
default: 'true'
outputs:
findings:
description: 'Total number of findings reported by validate (0 if the spec is valid)'
error_count:
description: 'Number of error-level findings'
warning_count:
description: 'Number of warning-level findings'
info_count:
description: 'Number of info-level findings'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.spec }}
- ${{ inputs.fail-on }}
- ${{ inputs.allow-external-refs }}
80 changes: 80 additions & 0 deletions validate/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/sh
set -e

if [ -n "$GITHUB_WORKSPACE" ]; then
git config --global --get-all safe.directory | grep -q "$GITHUB_WORKSPACE" || \
git config --global --add safe.directory "$GITHUB_WORKSPACE"
fi

readonly spec="$1"
readonly fail_on="$2"
readonly allow_external_refs="$3"

echo "running oasdiff validate... spec: $spec, fail_on: $fail_on, allow_external_refs: $allow_external_refs"

# Build flags. --allow-external-refs defaults to true in oasdiff, so only
# pass it when the input opts out. --fail-on defaults to ERR in oasdiff
# (errors fail the build; warnings and info are reported but don't), so only
# pass it when the input overrides the threshold.
flags=""
if [ "$allow_external_refs" = "false" ]; then
flags="$flags --allow-external-refs=false"
fi
if [ -n "$fail_on" ]; then
flags="$flags --fail-on $fail_on"
fi
echo "flags: $flags"

# Run 1: render annotations to stdout via --format githubactions so GitHub
# parses them onto the PR's "Files changed" tab. This is the authoritative
# run: its exit code honours --fail-on (1 when a finding is at or above the
# threshold, 0 otherwise). Tolerate non-zero so we can still set the output
# and emit the notice below; the exit code is reapplied at the end.
exit_code=0
oasdiff validate $flags --format githubactions "$spec" || exit_code=$?

# Run 2: text format, captured for the finding count. Tolerate non-zero
# exit (the authoritative decision is already captured above).
findings_text=$(oasdiff validate $flags "$spec") || true

# *** GitHub Action step output ***

# Total finding count from the header "N findings: N error, N warning, N info".
# A valid spec prints nothing, so the count stays 0.
findings_count=0
if [ -n "$findings_text" ]; then
header=$(printf '%s' "$findings_text" | head -n 1)
n=$(printf '%s' "$header" | awk '{print $1}')
if printf '%s' "$n" | grep -qE '^[0-9]+$'; then
findings_count="$n"
fi
fi
echo "findings=$findings_count" >>"$GITHUB_OUTPUT"

# The --format githubactions run above writes error_count/warning_count/
# info_count to GITHUB_OUTPUT, but only when there are findings. Emit zeros
# for a valid spec so those outputs are always present for callers.
if [ "$findings_count" -eq 0 ]; then
{
echo "error_count=0"
echo "warning_count=0"
echo "info_count=0"
} >>"$GITHUB_OUTPUT"
fi

# When there are findings, point the user at oasdiff.com, the same way the
# breaking action does (notice annotation + step-summary link). The precise
# location of each finding is already on the annotations above.
if [ "$findings_count" -gt 0 ]; then
notice_url="https://www.oasdiff.com/review?owner=$(printf '%s' "${GITHUB_REPOSITORY%%/*}" | jq -sRr @uri)&repo=$(printf '%s' "${GITHUB_REPOSITORY#*/}" | jq -sRr @uri)"
echo "::notice::🔎 ${findings_count} OpenAPI validation finding(s). See annotations above. oasdiff.com → ${notice_url}"
{
echo "### 🔎 oasdiff validate found ${findings_count} OpenAPI spec issue(s)"
echo ""
echo "See annotations on the Files Changed tab for the precise line and column of each finding."
echo ""
echo "[Learn more about oasdiff →](${notice_url})"
} >> "$GITHUB_STEP_SUMMARY"
fi

exit "$exit_code"
Loading