From a5d65b4a85adf82a7b8f1ea41e9ff35ff4a038d4 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Sun, 17 May 2026 22:08:47 +0300 Subject: [PATCH] feat: validate action wrapping oasdiff validate Per-finding PR annotations via --format githubactions, plus a notice with a free review-page link when findings are reported. Mirrors the shape of the breaking action: text run for findings count + step output, githubactions run for the annotations, fail-on-finding toggle defaulting to true. allow-external-refs defaults to true (matches oasdiff's binary default); set to false when validating untrusted specs to prevent SSRF. Outputs `findings` (numeric) so downstream steps can branch on it. Co-Authored-By: Claude Opus 4.7 (1M context) --- validate/Dockerfile | 5 +++ validate/action.yml | 24 +++++++++++++++ validate/entrypoint.sh | 70 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 validate/Dockerfile create mode 100644 validate/action.yml create mode 100755 validate/entrypoint.sh diff --git a/validate/Dockerfile b/validate/Dockerfile new file mode 100644 index 0000000..3b3683d --- /dev/null +++ b/validate/Dockerfile @@ -0,0 +1,5 @@ +FROM tufin/oasdiff:v1.15.3 +RUN apk add --no-cache jq +ENV PLATFORM github-action +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/validate/action.yml b/validate/action.yml new file mode 100644 index 0000000..0c18d2b --- /dev/null +++ b/validate/action.yml @@ -0,0 +1,24 @@ +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-finding: + description: 'Fail with exit code 1 if any finding is reported (default: true)' + required: false + default: 'true' + 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: 'Number of findings reported by validate (0 if the spec is well-formed)' +runs: + using: 'docker' + image: 'Dockerfile' + args: + - ${{ inputs.spec }} + - ${{ inputs.fail-on-finding }} + - ${{ inputs.allow-external-refs }} diff --git a/validate/entrypoint.sh b/validate/entrypoint.sh new file mode 100755 index 0000000..e747366 --- /dev/null +++ b/validate/entrypoint.sh @@ -0,0 +1,70 @@ +#!/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_finding="$2" +readonly allow_external_refs="$3" + +echo "running oasdiff validate... spec: $spec, fail_on_finding: $fail_on_finding, allow_external_refs: $allow_external_refs" + +# Build flags. --allow-external-refs defaults to true in oasdiff so we +# only pass --allow-external-refs=false when the input explicitly opts +# out; otherwise rely on the binary's default. +flags="" +if [ "$allow_external_refs" = "false" ]; then + flags="$flags --allow-external-refs=false" +fi +echo "flags: $flags" + +# Run 1: capture the text-format findings count for GITHUB_OUTPUT and +# the user-facing step log. Tolerate non-zero exit — oasdiff returns 1 +# when any finding is reported, but we render annotations and the +# fail-on-finding decision below regardless. +validate_exit=0 +findings_text=$(oasdiff validate $flags "$spec") || validate_exit=$? + +# Run 2: render annotations to stdout via --format githubactions so +# GitHub parses them onto the PR's "Files changed" tab. Tolerate +# non-zero exit (same reason as Run 1). +oasdiff validate $flags --format githubactions "$spec" || true + +# *** GitHub Action step output *** + +# Extract the finding count from the first line of the text output: +# "N findings: N error, N warning, N info" +findings_count=0 +if [ -n "$findings_text" ]; then + header=$(printf '%s' "$findings_text" | head -n 1) + findings_count=$(printf '%s' "$header" | awk '{print $1}') + if ! printf '%s' "$findings_count" | grep -qE '^[0-9]+$'; then + findings_count=0 + fi +fi +echo "findings=$findings_count" >>"$GITHUB_OUTPUT" + +# Emit upgrade notice with a clickable summary link pointing at the +# free review surface. Same pattern as the breaking action: notice +# annotation + GITHUB_STEP_SUMMARY markdown link. +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 + +# Honour fail-on-finding (default true). When false, we report findings +# but the step still passes — useful for non-blocking visibility runs. +if [ "$fail_on_finding" = "false" ]; then + exit 0 +fi +exit "$validate_exit"