name: Release # Publishes the multi-arch Acauã image (linux/amd64 + linux/arm64) to the Gitea # container registry. A merge into main publishes `edge`; a v* tag re-points the # image already built for the same inputs to X.Y.Z, X.Y and latest (no rebuild) # and creates the Gitea Release. Needs the secret REGISTRY_TOKEN (Gitea token # with write:package) visible to this repository. on: push: branches: [main] tags: ['v*'] workflow_dispatch: concurrency: group: release-${{ gitea.ref }} cancel-in-progress: false env: REGISTRY: git.lucasrguerra.dev.br jobs: # The image is built only from these paths, so their tree hashes identify it. # When inputs- already exists (docs-only merge, workflow change, or a tag # on a commit main already published), nothing is built or tested again. plan: name: Plan (reuse the published image?) runs-on: linux-arm64 outputs: inputs: ${{ steps.plan.outputs.inputs }} reuse: ${{ steps.plan.outputs.reuse }} image: ${{ steps.plan.outputs.image }} steps: - uses: actions/checkout@v7 - id: plan env: REPO: ${{ gitea.repository }} run: | inputs=$(git rev-parse HEAD:Dockerfile HEAD:.dockerignore HEAD:Cargo.toml HEAD:Cargo.lock \ HEAD:crates HEAD:apps/gui/ui HEAD:logo.png HEAD:scripts/fetch-libduckdb.sh | sha256sum | cut -c1-16) ns="${REPO,,}" token=$(curl -fsS "https://$REGISTRY/v2/token?service=container_registry&scope=repository:$ns:pull" | jq -r .token) code=$(curl -s -o /dev/null -w '%{http_code}' -I \ -H "Authorization: Bearer $token" \ -H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json' \ "https://$REGISTRY/v2/$ns/manifests/inputs-$inputs") echo "inputs-$inputs -> HTTP $code" reuse=false; [ "$code" = 200 ] && reuse=true echo "inputs=$inputs" >> "$GITHUB_OUTPUT" echo "reuse=$reuse" >> "$GITHUB_OUTPUT" echo "image=$REGISTRY/$ns" >> "$GITHUB_OUTPUT" ci: needs: plan if: ${{ needs.plan.outputs.reuse != 'true' }} uses: ./.gitea/workflows/ci.yml # Each architecture is built on a native runner (no QEMU) and pushed under a # per-architecture tag; publish joins them into one manifest list. build: name: Build image (${{ matrix.arch }}) needs: [plan, ci] if: ${{ needs.plan.outputs.reuse != 'true' }} runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - { arch: amd64, platform: linux/amd64, runner: linux-amd64 } - { arch: arm64, platform: linux/arm64, runner: linux-arm64 } steps: - uses: actions/checkout@v7 - uses: docker/setup-buildx-action@v4 - uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Build and push (per architecture) uses: docker/build-push-action@v7 with: context: . platforms: ${{ matrix.platform }} push: true tags: ${{ needs.plan.outputs.image }}:build-${{ gitea.sha }}-${{ matrix.arch }} cache-from: type=registry,ref=${{ needs.plan.outputs.image }}:buildcache-${{ matrix.arch }} cache-to: type=registry,ref=${{ needs.plan.outputs.image }}:buildcache-${{ matrix.arch }},mode=max # Runs the image that was just pushed, on its own architecture. - name: Smoke test run: scripts/smoke-image.sh "${{ needs.plan.outputs.image }}:build-${{ gitea.sha }}-${{ matrix.arch }}" publish: name: Publish tags needs: [plan, build] # build is skipped, not successful, when the image is reused. if: ${{ !cancelled() && needs.plan.result == 'success' && (needs.build.result == 'success' || needs.plan.outputs.reuse == 'true') }} runs-on: linux-arm64 steps: - uses: docker/setup-buildx-action@v4 - uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Tags id: meta uses: docker/metadata-action@v6 with: images: ${{ needs.plan.outputs.image }} flavor: latest=false tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest,enable=${{ startsWith(gitea.ref, 'refs/tags/v') && !contains(gitea.ref_name, '-') }} type=raw,value=edge,enable=${{ gitea.ref == 'refs/heads/main' }} type=sha,format=long - name: Apply tags run: | image='${{ needs.plan.outputs.image }}' tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') inputs='${{ needs.plan.outputs.inputs }}' if [ '${{ needs.plan.outputs.reuse }}' = true ]; then docker buildx imagetools create $tags "$image:inputs-$inputs" else docker buildx imagetools create $tags -t "$image:inputs-$inputs" \ "$image:build-${{ gitea.sha }}-amd64" "$image:build-${{ gitea.sha }}-arm64" fi - name: Check architectures run: | docker buildx imagetools inspect "${{ needs.plan.outputs.image }}:inputs-${{ needs.plan.outputs.inputs }}" \ --format '{{json .Manifest}}' \ | jq -r '.manifests[] | select(.platform.os == "linux") | "\(.platform.os)/\(.platform.architecture)"' \ | tee platforms.txt grep -qx linux/amd64 platforms.txt && grep -qx linux/arm64 platforms.txt - name: Summary run: | { echo "### Acauã image published (linux/amd64, linux/arm64)"; echo; echo '```' echo "${{ steps.meta.outputs.tags }}"; echo '```'; } >> "$GITHUB_STEP_SUMMARY" gitea-release: name: Gitea release needs: [plan, publish] if: ${{ !cancelled() && needs.publish.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }} runs-on: linux-arm64 steps: - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Release env: TAG: ${{ gitea.ref_name }} TOKEN: ${{ gitea.token }} API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} IMAGE: ${{ needs.plan.outputs.image }} run: | if curl -fsS -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" >/dev/null 2>&1; then echo "release $TAG already exists"; exit 0 fi { scripts/release-notes.sh "$TAG"; echo; echo '```bash'; echo "docker pull $IMAGE:${TAG#v}"; echo '```'; } > notes.md pre=false; case "$TAG" in *-*) pre=true ;; esac jq -n --arg tag "$TAG" --arg name "Acauã ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \ '{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \ -H 'Content-Type: application/json' -d @release.json >/dev/null