From bc795ac24a5df0118da58ca4a07ffd2af23979df Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 2 Aug 2026 23:25:17 +0000 Subject: [PATCH] Fix the swagger update cron job (#481) The scheduled `update swagger files` job has failed on **every** run since it was added (I checked the run list on gitea.com, ~250 consecutive failures). Two reasons: 1. Upstream moved the file: `go-gitea/gitea` main no longer has `templates/swagger/v1_json.tmpl`, it is now `templates/swagger/v1-swagger.generated.json`, and the placeholders changed from `{{.SwaggerAppVer}}` / `{{.SwaggerAppSubUrl}}` to `0.0.0+GITEA-API-APP-VERSION` / `/GITEA-API-APP-SUBURL/api/v1`. Because the script used `curl --silent` without `--fail`, the 404 body was written into `static/swagger-latest.json`. 2. `main` is protected (`required_approvals=1`), so the job's `git push` to main could never succeed. Changes: - `update_api_docs.sh`: `set -euo pipefail`, download with `--fail` (a broken download now aborts instead of committing garbage), try the new generated json first and fall back to the old template for released tags, handle the 1.28+/1.24+/<1.24 placeholder variants, and add a `--latest-only` flag. - `Makefile`: new `update-api-docs-latest` target for the cron job; `clean` no longer deletes the committed `static/swagger-*.json` files (deleting them breaks `make build`). - `.gitea/workflows/update_swagger.yaml`: every 12h (plus `workflow_dispatch`) it regenerates only `static/swagger-latest.json`, verifies no placeholder is left and the file is valid JSON, force pushes to `bot/update-swagger-latest` and opens a pull request via the API (HTTP 409 = a PR is already open, it just now points at the new commit). It skips entirely when the bot branch already carries the same file, so no PR churn. - `README.md`: document the two make targets and the automation. `secrets.DEPLOY_TOKEN` must belong to a real user with `write:repository` so the PR checks are triggered; the job fails with a clear message if it is empty. Tested locally in a scratch clone: `make update-api-docs-latest` produces a valid `dev` spec with no placeholders left, `make update-api-docs` still regenerates all released versions, and the workflow shell logic was simulated against a local bare repo for all three paths (open PR / bot branch already up to date / main already up to date), including a shallow clone to confirm `fetch-depth: 1` force pushes work. Fixes #454 Reviewed-on: https://gitea.com/gitea/docs/pulls/481 Reviewed-by: techknowlogick <9+techknowlogick@noreply.gitea.com> --- .gitea/workflows/update_swagger.yaml | 97 ++++++++++++++++++++++++---- Makefile | 13 ++-- README.md | 14 ++++ update_api_docs.sh | 70 ++++++++++++++++---- 4 files changed, 163 insertions(+), 31 deletions(-) diff --git a/.gitea/workflows/update_swagger.yaml b/.gitea/workflows/update_swagger.yaml index a2a09098..bf2b1dfc 100644 --- a/.gitea/workflows/update_swagger.yaml +++ b/.gitea/workflows/update_swagger.yaml @@ -5,24 +5,95 @@ on: - cron: '0 */12 * * *' # every 12 hours on the hour workflow_dispatch: +env: + # main is protected, so the update is proposed as a pull request from this branch + BOT_BRANCH: bot/update-swagger-latest + BASE_BRANCH: main + jobs: update-swagger: + if: github.repository == 'gitea/docs' runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - run: | + with: + ref: main + # pushing uses DEPLOY_TOKEN, keep the ephemeral job token out of .git/config + persist-credentials: false + - name: regenerate swagger-latest.json + run: make update-api-docs-latest + - name: verify the generated file + run: | + set -euo pipefail + # placeholders must have been replaced, otherwise upstream changed them again + if grep -q -e 'GITEA-API-APP' -e '{{' static/swagger-latest.json; then + echo "static/swagger-latest.json still contains template placeholders" + exit 1 + fi + if command -v python3 >/dev/null 2>&1; then + python3 -c "import json; json.load(open('static/swagger-latest.json'))" + fi + - name: push bot branch + id: bot_branch + env: + DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} + run: | set -euo pipefail - make update-api-docs - - git config --global user.name "Gitea Bot" - git config --global user.email "teabot@gitea.io" - git remote set-url origin https://x-access-token:${{ secrets.DEPLOY_TOKEN }}@gitea.com/gitea/docs.git - - if git status --porcelain | grep -q .; then - git add --all - git commit -m "[skip ci] Updated swagger files" - git push - else - echo "No API doc changes detected; skipping commit" + if git diff --quiet -- static/swagger-latest.json; then + echo "swagger-latest.json is already up to date" + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 fi + + if [ -z "$DEPLOY_TOKEN" ]; then + echo "secrets.DEPLOY_TOKEN is missing, cannot push the update" + exit 1 + fi + remote="https://x-access-token:$DEPLOY_TOKEN@${GITHUB_SERVER_URL#*://}/$GITHUB_REPOSITORY.git" + + # skip if an open bot branch already carries exactly this file + if git fetch --quiet --depth=1 "$remote" "refs/heads/$BOT_BRANCH" 2>/dev/null; then + old_blob="$(git rev-parse --quiet --verify "FETCH_HEAD:static/swagger-latest.json" || true)" + if [ "$old_blob" = "$(git hash-object static/swagger-latest.json)" ]; then + echo "$BOT_BRANCH already proposes this swagger-latest.json" + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + fi + + git config user.name "Gitea Bot" + git config user.email "teabot@gitea.io" + git switch --create "$BOT_BRANCH" + git add static/swagger-latest.json + git commit -m "Update swagger-latest.json" + # force push: the branch is always rebuilt on top of the current main + git push --force "$remote" "HEAD:refs/heads/$BOT_BRANCH" + echo "changed=true" >> "$GITHUB_OUTPUT" + - name: create pull request + if: steps.bot_branch.outputs.changed == 'true' + env: + DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} + run: | + set -euo pipefail + + cat > pull.json <.json` (released versions). + +```shell +make update-api-docs # refresh latest + every released version +make update-api-docs-latest # refresh only static/swagger-latest.json +``` + +`static/swagger-latest.json` is refreshed automatically: the `update swagger files` +workflow runs every 12 hours and opens a pull request whenever gitea main changed. +Released versions are updated by hand when a new gitea version is documented. diff --git a/update_api_docs.sh b/update_api_docs.sh index 1f11651d..3a2ad1dd 100755 --- a/update_api_docs.sh +++ b/update_api_docs.sh @@ -1,4 +1,19 @@ #!/bin/bash +# +# Regenerates the swagger definitions served by this site. +# +# Usage: ./update_api_docs.sh [--latest-only] [sed -i suffix] [extra sed args...] +# +# --latest-only only refresh static/swagger-latest.json from gitea main, +# leaving the released static/swagger-.json untouched. + +set -euo pipefail + +LATEST_ONLY=0 +if [ "${1:-}" = "--latest-only" ]; then + LATEST_ONLY=1 + shift +fi SED_INPLACE=(-i) EXTRA_SED_ARGS=() @@ -17,21 +32,54 @@ fi EXTRA_SED_ARGS=("$@") inplace_sed() { - sed "${SED_INPLACE[@]}" "${EXTRA_SED_ARGS[@]}" "$@" + # ${arr[@]+...} keeps `set -u` happy with empty arrays on bash 3.2 (macOS) + sed "${SED_INPLACE[@]}" ${EXTRA_SED_ARGS[@]+"${EXTRA_SED_ARGS[@]}"} "$@" } -curl --silent --output v1_json.tmpl https://raw.githubusercontent.com/go-gitea/gitea/refs/heads/main/templates/swagger/v1_json.tmpl -inplace_sed 's|"version": "{{.SwaggerAppVer}}"|"version": "dev"|' v1_json.tmpl -inplace_sed 's|"basePath": "{{.SwaggerAppSubUrl}}/api/v1"|"basePath": "https://gitea.com/api/v1"|' v1_json.tmpl +# gitea >= 1.28 ships a pre-generated json, older versions ship a go template +SWAGGER_PATHS=( + 'templates/swagger/v1-swagger.generated.json' + 'templates/swagger/v1_json.tmpl' +) + +# download_swagger +download_swagger() { + local ref="$1" output="$2" path + for path in "${SWAGGER_PATHS[@]}"; do + if curl --silent --fail --location --output "$output" \ + "https://raw.githubusercontent.com/go-gitea/gitea/${ref}/${path}"; then + return 0 + fi + done + echo "unable to download the swagger definition of ${ref}" >&2 + return 1 +} + +# rewrite_swagger +rewrite_swagger() { + local file="$1" version="$2" + # gitea >= 1.28 + inplace_sed "s|\"version\": \"0.0.0+GITEA-API-APP-VERSION\"|\"version\": \"${version}\"|" "$file" + inplace_sed 's|"basePath": "/GITEA-API-APP-SUBURL/api/v1"|"basePath": "https://gitea.com/api/v1"|' "$file" + # gitea >= 1.24 + inplace_sed "s|\"version\": \"{{.SwaggerAppVer}}\"|\"version\": \"${version}\"|" "$file" + inplace_sed 's|"basePath": "{{.SwaggerAppSubUrl}}/api/v1"|"basePath": "https://gitea.com/api/v1"|' "$file" + # gitea < 1.24 + inplace_sed "s|\"version\": \"{{AppVer \| JSEscape}}\"|\"version\": \"${version}\"|" "$file" + inplace_sed "s#\"basePath\": \"{{AppSubUrl | JSEscape}}/api/v1\"#\"basePath\": \"https://gitea.com/api/v1\"#" "$file" +} + +download_swagger 'refs/heads/main' v1_json.tmpl +rewrite_swagger v1_json.tmpl 'dev' mv v1_json.tmpl static/swagger-latest.json +if [ "$LATEST_ONLY" -eq 1 ]; then + exit 0 +fi + for ver in '1.27.1' '1.26.4' '1.25.5' '1.24.7' '1.23.8' '1.22.6'; do - curl --silent --output v1_json.tmpl https://raw.githubusercontent.com/go-gitea/gitea/refs/tags/v${ver}/templates/swagger/v1_json.tmpl - inplace_sed "s|\"version\": \"{{.SwaggerAppVer}}\"|\"version\": \"${ver}\"|" v1_json.tmpl - inplace_sed 's|"basePath": "{{.SwaggerAppSubUrl}}/api/v1"|"basePath": "https://gitea.com/api/v1"|' v1_json.tmpl - # for versions < 1.24 - inplace_sed "s|\"version\": \"{{AppVer \| JSEscape}}\"|\"version\": \"${ver}\"|" v1_json.tmpl - inplace_sed "s#\"basePath\": \"{{AppSubUrl | JSEscape}}/api/v1\"#\"basePath\": \"https://gitea.com/api/v1\"#" v1_json.tmpl + download_swagger "refs/tags/v${ver}" v1_json.tmpl + rewrite_swagger v1_json.tmpl "${ver}" minor=$(echo "$ver" | cut -d '.' -f 2) - mv v1_json.tmpl static/swagger-$minor.json + mv v1_json.tmpl "static/swagger-$minor.json" done