name: Docker on: push: branches: [main] tags: ["v*.*.*"] pull_request: branches: [main] # Manual republish (e.g. base-image refresh without a version bump). # Must be dispatched FROM A TAG REF: metadata-action only derives semver # image tags from the git tag, so a branch dispatch is rejected below. workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: # Fail fast with a clear message instead of silently pushing a bare # `latest` (or nothing) when someone dispatches from a branch. guard-dispatch: if: github.event_name == 'workflow_dispatch' && !startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - run: | echo "::error::Manual runs must be dispatched from a v*.*.* tag ref (use the 'Run workflow' ref selector). Branch refs produce no semver image tags." exit 1 # Catch tag/code drift before anything is published (root.go has drifted # from tags before). verify-version: if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Check tag matches cmd/root.go version run: | TAG_VERSION="${GITHUB_REF_NAME#v}" CODE_VERSION=$(sed -nE 's/^[[:space:]]*version[[:space:]]*=[[:space:]]*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p' cmd/root.go) echo "tag=v${TAG_VERSION} code=${CODE_VERSION}" if [ "$TAG_VERSION" != "$CODE_VERSION" ]; then echo "::error::Git tag v${TAG_VERSION} does not match version \"${CODE_VERSION}\" in cmd/root.go. Bump cmd/root.go before tagging." exit 1 fi # PR / main validation: build only, single platform (multi-arch under QEMU # roughly doubles CI time for no extra signal; tag builds cover arm64). build: if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build (no push) uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64 push: false cache-from: type=gha cache-to: type=gha,mode=max # Publish on version tags (and manual dispatch from a tag ref). # No fork-secrets concern: this never runs for pull_request events. publish: if: startsWith(github.ref, 'refs/tags/v') needs: verify-version runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # `latest` is applied automatically on semver tags (latest=auto). # Caveat: it follows the most recently pushed tag, not the highest # version — we only tag forward, never backport. - name: Docker metadata id: meta uses: docker/metadata-action@v5 with: images: karust/openserp tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} # The pinned chromedp/headless-shell digest is a multi-arch OCI index # (amd64 + arm64/v8), and the builder stage cross-compiles via # $BUILDPLATFORM, so only the small final-stage apt layer runs under QEMU. - name: Build and push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max