django-ninjaやFastAPIなど、OpenAPI定義を自動で生成してくれる便利なライブラリを使っているとき、OpenAPIの定義をGithub Pagesでブランチごと
共有できたら便利だな、と思い調べてやってみたの会。
プロジェクト構成は以前のdemoのall-authがないものに、django-ninjaの設定を加えたものとなります。
以前のはこちら
django-ninjaの設定は以下になります。
# demo/api.py from ninja import Router router = Router() @router.get("/") def test(request): return "test"
# config/api.py from demo.api import router from ninja import NinjaAPI api = NinjaAPI() api.add_router("", router)
# config/urls.py from .api import api urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls) ]
また、ドキュメントには記載がない(と思うのですが)のですが、django-ninjaにはOpenAPIの定義をexportするコマンド
があるためそちらを利用します。
コマンド利用のため、INSTALLED_APPS
にninja
を追加します。
INSTALLED_APPS = [ ... 'ninja', ]
コマンドでexportされるか確認します。
$ (.env) > python manage.py export_openapi_schema --api config.urls.api {"openapi": "3.1.0", "info": {"title": "NinjaAPI", "version": "1.0.0", "description": ""}, "paths": {"/api/": {"get": {"operationId": "demo_api_test", "summary": "Test", "parameters": [], "responses": {"200": {"description": "OK"}}}}}, "components": {"schemas": {}}, "servers": []}
よさそう。
Github Actionsを用いてGithub Pagesをデプロイする方法は、以下公式を真似て定義していきます。
今回はdjango-ninjaでOpenAPIの定義を出力するpython設定と、出力された定義からRedocのHTMLに変換するnodeの設定が必要になる為、setupを以下を参考に定義します。
また、ブランチ名にある/
等は適当に置換してurl-safeな形にします。
完成したymlファイルは以下となります。
on: [push] jobs: build: runs-on: ubuntu-latest outputs: SAFE_REF_NAME: ${{ steps.safe-url.outputs.SAFE_REF_NAME }} steps: - name: Convert Branch Name To Safe Url id: safe-url run: | SAFE_REF_NAME=$(echo "${{ github.ref_name }}" | tr '/' '-' | tr -d '#') echo "SAFE_REF_NAME=${SAFE_REF_NAME}" >> $GITHUB_OUTPUT - name: Checkout Repo uses: actions/checkout@v4 - name: Configure GitHub Pages uses: actions/configure-pages@v5 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt - name: Generate OpenAPI run: python manage.py export_openapi_schema --api config.urls.api > open-api.json - name: Use Node.js 20 uses: actions/setup-node@v4 with: node-version: '20' - name: Parse To HTML run: | npm install -g redoc-cli redoc-cli bundle open-api.json -o .output/${{ steps.safe-url.outputs.SAFE_REF_NAME }}/index.html continue-on-error: true - name: Upload static files as artifact id: deployment uses: actions/upload-pages-artifact@v3 with: path: .output deploy: # https://docs.github.com/ja/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages environment: name: github-pages url: ${{steps.deployment.outputs.page_url}}${{ needs.build.outputs.SAFE_REF_NAME }} permissions: pages: write id-token: write runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4
今回はBranchベースではなくGitHub Actionsを用いてPagesをデプロイするので、Settings>Pages>Build and deployment
から設定をGitHub Actionsに変更します。
また、mainブランチ以外もデプロイできるようにDeployment branches and tags
はNo restriction
に変更します。
mainを適当にプッシュし、CIを走らせた後https://takap-sandbox.github.io/django-ninja-openapi-ci/main/
にアクセスします。
次に、別ブランチ(feature/test)を切り、Redocが別物だとわかるようにdjango-ninjaの値を変更した後、プッシュします。
# demo/api.py from ninja import Router router = Router() @router.get("/") def test(request): return "test" @router.get("/change") def change(request): return "change"
https://takap-sandbox.github.io/django-ninja-openapi-ci/feature-test/
にアクセスします。
一件これでうまくいっているように見えますが、再度main側のRedocを見ようとすると404エラー
となります。
これは、actions/upload-pages-artifact@v3
が前回の成果物を上書きしてしまい結果、最新のRedocだけとなるため、404となっています。
(actions/upload-pages-artifact@v3
はtar形式でアップロードする為、差分更新的なオプションもなさそうでした。)
一度Pagesの成果物をダウンロードできれば良いのですが、そのようなワークフローやオプション等もなさそうでしたので、何か良い方法はないかなぁと以下模索してみました。
actions/cache
を用いて誤魔化そうと思いましたが結果としてダメでした。
これはブランチ間で異なるキャッシュが生成される
為、今回ケースのような場合にはマッチしませんでした。
(まぁそもそもそんな用途でcache使わないでね...はそれはそう)
また、もし上記がクリアできたとしても、制約として7日間以上アクセスがないキャッシュは削除される
ようなので、運用しようとなるとスケジュールで適宜再生成する等のワークフローが別途必要になるのであまりよくはないですね。
一応上記cacheを用いて頑張っていた時のymlおいておきます。
Pagesの成果物は行えませんが、普通に成果物をupload-artifact
でアップロードし、次回そのartifactをdownloadすれば、上記を満たせるのではと思いました。
こちらは、結果として実現できたのですがdownload-artifact
で異なるワークフロー間のartifactの取得
する場合、対象のワークフローrun_idの指定が必要となり、そのrun_idの取得が少し大変でした。
結果としては以下のようなymlとなりました。
on: [push] env: API: 'https://api.github.com/repos/takap-sandbox/django-ninja-openapi-ci/actions/workflows/test.yml/runs?status=success' jobs: build: runs-on: ubuntu-latest outputs: SAFE_REF_NAME: ${{ steps.safe-url.outputs.SAFE_REF_NAME }} steps: - name: Convert Branch Name To Safe Url id: safe-url run: | SAFE_REF_NAME=$(echo "${{ github.ref_name }}" | tr '/' '-' | tr -d '#') echo "SAFE_REF_NAME=${SAFE_REF_NAME}" >> $GITHUB_OUTPUT - name: Checkout Repo uses: actions/checkout@v4 - name: Fetch Prev Run Id run: | RUN_ID=$(curl -L \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ ${{env.API}} | jq -r '.workflow_runs[0] | select(.name == ".github/workflows/test.yml") | .id') echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV - name: Download Prev Artifact continue-on-error: true uses: actions/download-artifact@v4 with: name: output-artifact path: .output github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ env.RUN_ID }} - name: Configure GitHub Pages uses: actions/configure-pages@v5 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt - name: Generate OpenAPI run: python manage.py export_openapi_schema --api config.urls.api > open-api.json - name: Use Node.js 20 uses: actions/setup-node@v4 with: node-version: '20' - name: Parse To HTML run: | npm install -g redoc-cli redoc-cli bundle open-api.json -o .output/${{ steps.safe-url.outputs.SAFE_REF_NAME }}/index.html continue-on-error: true - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: output-artifact path: .output compression-level: 0 include-hidden-files: true - name: Upload static files as artifact id: deployment uses: actions/upload-pages-artifact@v3 with: path: .output deploy: # https://docs.github.com/ja/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages environment: name: github-pages url: ${{steps.deployment.outputs.page_url}}${{ needs.build.outputs.SAFE_REF_NAME }} permissions: pages: write id-token: write runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4
異なるワークフロー間のartifact取得は以下を参考に行いました。
また、successをクエリパラメータに付与することで、成功したworkflowのみを対象にしています。
再度これをもとに各ブランチをプッシュすると、output-artifact
をもとにactions内でmergeした.outputでPagesをデプロイしてくれるため、複数ブランチに対応したRedocを表示することが出来ました。
ただ単純にgithub pagesにデプロイするだけなのであればすごく快適なのですが、こういったケースを行おうとすると途端に大変になるなと思いました...
またもしブランチごとに行うのだとしても、deploy from a branch
で行うほうがまだ快適に行えそうかなと思いました まる
今回のリポジトリはこちらに置いておきました。