From d983febe43b8fa27b60a08033e53ebd2d721da04 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 8 Sep 2026 18:46:20 +0100 Subject: [PATCH] ci: publish Docker images and the PyPI package from the release workflow backend-release creates the GitHub release with GITHUB_TOKEN, and GitHub never starts workflows from events that token produces, so the `release: published` triggers on the Docker and PyPI publish workflows only fired for releases made by hand. 0.18.0 got no Docker images for that reason, and 0.19.0 reached PyPI by a manual run. backend-release now calls both publish workflows after creating the release, passing the version it tagged. Both workflows gain a `workflow_call` trigger with a `version` input and read the tag from it or from the release event, so a release created by hand still publishes through the release trigger. The Docker Hub credentials are passed by name; the PyPI job authenticates through trusted publishing, which matches the called workflow's filename and environment, so the publisher configuration is unchanged. Permissions in backend-release move from the workflow to the jobs: the release job writes contents; the Docker call writes contents (the compose file attached to the release) and packages; the PyPI call gets an OIDC token. --- .github/workflows/backend-release.yml | 38 +++++++++++++++++++++++++-- .github/workflows/ci.yml | 33 +++++++++++++++++++---- .github/workflows/pypi-publish.yml | 28 ++++++++++++++------ 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/.github/workflows/backend-release.yml b/.github/workflows/backend-release.yml index 0c9e3f64..3d2576a8 100644 --- a/.github/workflows/backend-release.yml +++ b/.github/workflows/backend-release.yml @@ -1,5 +1,12 @@ name: Backend release +# A version bump on main tags the commit, creates the GitHub release, then +# publishes the Docker images and the PyPI package by calling those workflows. +# The release is created with GITHUB_TOKEN, and GitHub never starts workflows +# from events that token produces, so the `release: published` triggers on the +# publish workflows would not fire (0.18.0 got no images that way). Releases +# created by hand still publish through those triggers. + on: push: branches: [main] @@ -7,8 +14,7 @@ on: - 'docsgpt/version.py' workflow_dispatch: -permissions: - contents: write +permissions: {} concurrency: group: backend-release @@ -18,6 +24,11 @@ jobs: release: if: github.repository == 'arc53/DocsGPT' runs-on: ubuntu-latest + permissions: + contents: write + outputs: + version: ${{ steps.ver.outputs.version }} + created: ${{ steps.check.outputs.exists == 'false' }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: @@ -71,3 +82,26 @@ jobs: gh release create "$VERSION" \ --title "v$VERSION" \ --generate-notes + + docker: + needs: release + if: needs.release.outputs.created == 'true' + uses: $/.github/workflows/ci.yml + with: + version: ${{ needs.release.outputs.version }} + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + permissions: + contents: write # release-assets attaches the compose file to the release + packages: write + + pypi: + needs: release + if: needs.release.outputs.created == 'true' + uses: $/.github/workflows/pypi-publish.yml + with: + version: ${{ needs.release.outputs.version }} + permissions: + contents: read + id-token: write diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 187ac4df..dc780607 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,31 @@ name: Build and push DocsGPT Docker image +# Runs for a release created by hand (the release event), or called by the +# backend-release workflow with the version it just tagged: GitHub never starts +# workflows from events GITHUB_TOKEN produces, so a bot-created release does not +# fire the release trigger on its own. + on: release: types: [published] + workflow_call: + inputs: + version: + description: Release tag to build and push (the images are tagged with it) + type: string + required: true + secrets: + DOCKER_USERNAME: + required: true + DOCKER_PASSWORD: + required: true + +permissions: + contents: read + +env: + # The tag being published: passed in by the caller, or the release's own. + RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name }} jobs: build: @@ -58,7 +81,7 @@ jobs: ghcr.io/${{ github.repository_owner }}/docsgpt labels: | org.opencontainers.image.title=DocsGPT${{ matrix.variant }} - org.opencontainers.image.version=${{ github.event.release.tag_name }} + org.opencontainers.image.version=${{ env.RELEASE_TAG }} - name: Build and push platform-specific images uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 @@ -71,8 +94,8 @@ jobs: EXTRAS=${{ matrix.variant == '-docling' && 'docling' || '' }} INSTALL_TESSERACT=${{ matrix.variant == '-docling' && 'true' || 'false' }} tags: | - ${{ env.DOCKERHUB_NAMESPACE }}/docsgpt:${{ github.event.release.tag_name }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }} - ghcr.io/${{ github.repository_owner }}/docsgpt:${{ github.event.release.tag_name }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }} + ${{ env.DOCKERHUB_NAMESPACE }}/docsgpt:${{ env.RELEASE_TAG }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }} + ghcr.io/${{ github.repository_owner }}/docsgpt:${{ env.RELEASE_TAG }}${{ matrix.variant }}-${{ matrix.platform == 'linux/arm64' && 'arm64' || 'amd64' }} labels: ${{ steps.meta.outputs.labels }} provenance: false sbom: false @@ -118,7 +141,7 @@ jobs: - name: Create and push multi-arch manifests env: - TAG: ${{ github.event.release.tag_name }}${{ matrix.variant }} + TAG: ${{ env.RELEASE_TAG }}${{ matrix.variant }} LATEST: latest${{ matrix.variant }} run: | set -e @@ -145,6 +168,6 @@ jobs: - name: Attach the standalone compose file to the release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAG: ${{ github.event.release.tag_name }} + TAG: ${{ env.RELEASE_TAG }} run: | gh release upload "$TAG" deployment/docker-compose-standalone.yaml --clobber diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 8e977c56..c5c559cc 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -1,13 +1,21 @@ name: Publish to PyPI # Trusted publishing: PyPI trusts this workflow file in arc53/DocsGPT running -# in the `pypi` environment, so no API token is stored. A published release -# (which the backend-release workflow creates when docsgpt/version.py changes -# on main) publishes to PyPI; a manual run publishes to TestPyPI by default. +# in the `pypi` environment, so no API token is stored. The backend-release +# workflow calls this one after it tags a version bump on main (a release it +# creates with GITHUB_TOKEN never fires the release trigger); a release created +# by hand publishes through that trigger; a manual run publishes to TestPyPI +# by default. on: release: types: [published] + workflow_call: + inputs: + version: + description: Version the archives must carry (the release tag) + type: string + required: true workflow_dispatch: inputs: target: @@ -19,6 +27,11 @@ on: permissions: contents: read +env: + # The version being released: passed in by the caller, or the release's tag; + # empty for a manual run, which publishes whatever main builds. + RELEASE_VERSION: ${{ inputs.version || github.event.release.tag_name }} + jobs: build: if: github.repository == 'arc53/DocsGPT' @@ -37,11 +50,9 @@ jobs: run: uv build - name: The archives carry the released version - if: github.event_name == 'release' - env: - TAG: ${{ github.event.release.tag_name }} + if: env.RELEASE_VERSION != '' run: | - ls "dist/docsgpt-${TAG}.tar.gz" "dist/docsgpt-${TAG}-py3-none-any.whl" + ls "dist/docsgpt-${RELEASE_VERSION}.tar.gz" "dist/docsgpt-${RELEASE_VERSION}-py3-none-any.whl" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -50,7 +61,8 @@ jobs: if-no-files-found: error publish-pypi: - if: github.event_name == 'release' || inputs.target == 'pypi' + # A release event, a call from backend-release, or a manual run aimed at PyPI. + if: github.event_name == 'release' || inputs.version != '' || inputs.target == 'pypi' needs: build runs-on: ubuntu-latest environment: