Skip to content

Check KSeF OpenAPI changes (all environments) #170

Check KSeF OpenAPI changes (all environments)

Check KSeF OpenAPI changes (all environments) #170

name: "Check KSeF OpenAPI changes (all environments)"
on:
schedule:
- cron: "0 22 * * *"
workflow_dispatch:
permissions:
contents: read
issues: write
concurrency:
group: check-ksef-openapi
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- env_name: test
url: "https://api-test.ksef.mf.gov.pl/docs/v2/openapi.json"
local_file: "spec/openapi-test.json"
- env_name: demo
url: "https://api-demo.ksef.mf.gov.pl/docs/v2/openapi.json"
local_file: "spec/openapi-demo.json"
- env_name: production
url: "https://api.ksef.mf.gov.pl/docs/v2/openapi.json"
local_file: "spec/openapi.json"
steps:
- name: Checkout test branch
uses: actions/checkout@v4
with:
ref: test
- name: Fetch remote OpenAPI spec (${{ matrix.env_name }})
id: fetch
shell: bash
run: |
set -euo pipefail
HTTP_CODE=$(curl -fsSL -w '%{http_code}' "${{ matrix.url }}" -o remote-openapi.json 2>/dev/null || true)
if [ ! -s remote-openapi.json ]; then
echo "fetch_ok=false" >> "$GITHUB_OUTPUT"
echo "http_code=$HTTP_CODE" >> "$GITHUB_OUTPUT"
echo "::warning::Failed to fetch OpenAPI spec from ${{ matrix.env_name }} (HTTP $HTTP_CODE)"
exit 0
fi
echo "fetch_ok=true" >> "$GITHUB_OUTPUT"
echo "remote_sha=$(sha256sum remote-openapi.json | awk '{print $1}')" >> "$GITHUB_OUTPUT"
# Canonical hash ignores info.description (KSeF embeds a redeploy build tag
# there — e.g. "2.5.0-te-20260512.2+0beff2cf..." — which churns on every deploy
# even when the API surface is unchanged, triggering false-positive drift alerts).
# Sorting keys makes the hash order-independent.
echo "remote_canonical_sha=$(jq -cS 'del(.info.description)' remote-openapi.json | sha256sum | awk '{print $1}')" >> "$GITHUB_OUTPUT"
# Extract version info if available
VERSION=$(python3 -c "import json; d=json.load(open('remote-openapi.json')); print(d.get('info',{}).get('version','unknown'))" 2>/dev/null || echo "unknown")
echo "remote_version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Compute local openapi.json hash
id: local
if: steps.fetch.outputs.fetch_ok == 'true'
shell: bash
run: |
set -euo pipefail
FILE="${{ matrix.local_file }}"
if [ ! -f "$FILE" ]; then
echo "local_exists=false" >> "$GITHUB_OUTPUT"
echo "local_sha=" >> "$GITHUB_OUTPUT"
echo "local_canonical_sha=" >> "$GITHUB_OUTPUT"
echo "local_version=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "local_exists=true" >> "$GITHUB_OUTPUT"
echo "local_sha=$(sha256sum "$FILE" | awk '{print $1}')" >> "$GITHUB_OUTPUT"
echo "local_canonical_sha=$(jq -cS 'del(.info.description)' "$FILE" | sha256sum | awk '{print $1}')" >> "$GITHUB_OUTPUT"
VERSION=$(python3 -c "import json; d=json.load(open('$FILE')); print(d.get('info',{}).get('version','unknown'))" 2>/dev/null || echo "unknown")
echo "local_version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Compare
id: cmp
if: steps.fetch.outputs.fetch_ok == 'true'
shell: bash
run: |
set -euo pipefail
REMOTE="${{ steps.fetch.outputs.remote_canonical_sha }}"
LOCAL_EXISTS="${{ steps.local.outputs.local_exists }}"
LOCAL="${{ steps.local.outputs.local_canonical_sha }}"
if [ "$LOCAL_EXISTS" != "true" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "reason=local_missing" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$REMOTE" != "$LOCAL" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "reason=hash_differs" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "reason=same" >> "$GITHUB_OUTPUT"
fi
- name: Create or update issue if changed
if: steps.cmp.outputs.changed == 'true'
uses: actions/github-script@v7
env:
ENV_NAME: ${{ matrix.env_name }}
ENV_URL: ${{ matrix.url }}
REMOTE_SHA: ${{ steps.fetch.outputs.remote_sha }}
REMOTE_VERSION: ${{ steps.fetch.outputs.remote_version }}
LOCAL_EXISTS: ${{ steps.local.outputs.local_exists }}
LOCAL_SHA: ${{ steps.local.outputs.local_sha }}
LOCAL_VERSION: ${{ steps.local.outputs.local_version }}
REASON: ${{ steps.cmp.outputs.reason }}
LOCAL_FILE: ${{ matrix.local_file }}
with:
script: |
const envName = process.env.ENV_NAME;
const envUrl = process.env.ENV_URL;
const remoteSha = process.env.REMOTE_SHA || "";
const remoteVersion = process.env.REMOTE_VERSION || "";
const localExists = process.env.LOCAL_EXISTS || "";
const localSha = process.env.LOCAL_SHA || "";
const localVersion = process.env.LOCAL_VERSION || "";
const reason = process.env.REASON || "";
const title = `KSeF OpenAPI changed — ${envName} environment`;
const body = [
`Wykryto zmianę w specu OpenAPI KSeF na środowisku **${envName}** względem \`${process.env.LOCAL_FILE}\`.`,
"",
`| | Local | Remote (${envName}) |`,
"|---|---|---|",
`| **Version** | ${localVersion || "(brak pliku)"} | ${remoteVersion} |`,
`| **SHA256** | \`${localSha || "(brak pliku)"}\` | \`${remoteSha}\` |`,
"",
`- **URL:** ${envUrl}`,
`- **Powód:** ${reason}`,
"",
`**Changelog:** https://github.com/CIRFMF/ksef-docs/blob/main/api-changelog.md`,
"",
"Sugestia:",
`- pobierz aktualny spec i zaktualizuj \`${process.env.LOCAL_FILE}\`.`,
`- jeśli zmiana na **${envName}** — przygotuj się na update produkcji.`
].join("\n");
// Search for existing open issue for this environment
const searchTitle = `KSeF OpenAPI changed — ${envName}`;
const { data: issues } = await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open in:title "${searchTitle}"`
});
if (issues.items.length > 0) {
const issue_number = issues.items[0].number;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
body
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ["ksef-api"]
});
}
- name: Send Pushover notification if changed
if: steps.cmp.outputs.changed == 'true'
shell: bash
env:
PUSHOVER_TOKEN: ${{ secrets.PUSHOVER_APP_TOKEN }}
PUSHOVER_USER: ${{ secrets.PUSHOVER_USER_KEY }}
run: |
if [ -z "$PUSHOVER_TOKEN" ] || [ -z "$PUSHOVER_USER" ]; then
echo "::warning::Pushover secrets not configured — skipping notification"
exit 0
fi
ENV_NAME="${{ matrix.env_name }}"
LOCAL_VER="${{ steps.local.outputs.local_version }}"
REMOTE_VER="${{ steps.fetch.outputs.remote_version }}"
TITLE="KSeF OpenAPI changed — ${ENV_NAME}"
MESSAGE="Spec OpenAPI KSeF zmienił się na środowisku ${ENV_NAME}. Local: ${LOCAL_VER:-brak} -> Remote: ${REMOTE_VER:-unknown}. Powód: ${{ steps.cmp.outputs.reason }}"
curl -s \
--form-string "token=$PUSHOVER_TOKEN" \
--form-string "user=$PUSHOVER_USER" \
--form-string "title=$TITLE" \
--form-string "message=$MESSAGE" \
--form-string "priority=0" \
--form-string "url=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--form-string "url_title=GitHub Actions run" \
https://api.pushover.net/1/messages.json
- name: Summary
if: always()
run: |
ENV="${{ matrix.env_name }}"
FETCH_OK="${{ steps.fetch.outputs.fetch_ok }}"
if [ "$FETCH_OK" != "true" ]; then
echo "### ⚠️ $ENV — fetch failed (HTTP ${{ steps.fetch.outputs.http_code }})" >> $GITHUB_STEP_SUMMARY
else
CHANGED="${{ steps.cmp.outputs.changed }}"
REASON="${{ steps.cmp.outputs.reason }}"
LOCAL_VER="${{ steps.local.outputs.local_version }}"
REMOTE_VER="${{ steps.fetch.outputs.remote_version }}"
echo "### $ENV" >> $GITHUB_STEP_SUMMARY
echo "- Changed: $CHANGED" >> $GITHUB_STEP_SUMMARY
echo "- Reason: $REASON" >> $GITHUB_STEP_SUMMARY
echo "- Local version: $LOCAL_VER" >> $GITHUB_STEP_SUMMARY
echo "- Remote version: $REMOTE_VER" >> $GITHUB_STEP_SUMMARY
fi