Files
LambdaMail/.gitea/workflows/release.yml
T
Lucas RayanandClaude Sonnet 5.5 a6719c6018
Release / Plano (reaproveitar as imagens?) (push) Successful in 4s
Release / ci (push) Skipped
Release / Namespace das imagens (push) Successful in 1s
Release / Build auth (linux/amd64) (push) Skipped
Release / Build migrations (linux/amd64) (push) Skipped
Release / Build protocols (linux/amd64) (push) Skipped
Release / Build rspamd (linux/amd64) (push) Skipped
Release / Build storage (linux/amd64) (push) Skipped
Release / Build webmail (linux/amd64) (push) Skipped
Release / Build auth (linux/arm64) (push) Skipped
Release / Build migrations (linux/arm64) (push) Skipped
Release / Build protocols (linux/arm64) (push) Skipped
Release / Build rspamd (linux/arm64) (push) Skipped
Release / Build storage (linux/arm64) (push) Skipped
Release / Build webmail (linux/arm64) (push) Skipped
Release / Publica migrations (push) Successful in 20s
Release / Publica auth (push) Successful in 20s
Release / Publica protocols (push) Successful in 20s
Release / Publica rspamd (push) Successful in 20s
Release / Publica storage (push) Successful in 21s
Release / Publica webmail (push) Successful in 21s
Release / Cria a release no Gitea (push) Skipped
ci: fix the release job being skipped, and take the notes from the changelog
The job inherited the implicit success() and was skipped whenever the build job
upstream was skipped (images reused). It now checks publish explicitly, like
publish does. Notes come from the matching CHANGELOG section through
scripts/release-notes.sh, not from the commit log of the whole history.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 12:27:08 -03:00

244 lines
10 KiB
YAML

name: Release
# Publishes the service images to the Gitea container registry after a merge
# into main, and again under semver tags when a v* tag is pushed. The deploy
# host only pulls, so nothing has to be built on the server. Needs the user
# secret REGISTRY_TOKEN (a Gitea token with the write:package scope).
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
env:
REGISTRY: git.lucasrguerra.dev.br
jobs:
# The images are built from services/, migrations/ and config/ and nothing
# else, so those three trees identify them. When an image for the same trees
# is already in the registry (a docs-only merge, a workflow change, or a tag on
# a commit that was just released), there is nothing to build: the new tags
# are pointed at the existing manifest. This is what keeps tagging a release
# from compiling the webmail a second time.
plan:
name: Plano (reaproveitar as imagens?)
runs-on: ubuntu-latest
outputs:
inputs: ${{ steps.plan.outputs.inputs }}
reuse: ${{ steps.plan.outputs.reuse }}
steps:
- uses: actions/checkout@v7
- id: plan
env:
REGISTRY: ${{ env.REGISTRY }}
REPO: ${{ gitea.repository }}
run: |
inputs=$(git rev-parse HEAD:services HEAD:migrations HEAD:config | sha256sum | cut -c1-16)
ns="${REPO,,}"
reuse=true
for s in protocols auth storage webmail migrations rspamd; do
token=$(curl -fsS "https://$REGISTRY/v2/token?service=container_registry&scope=repository:$ns/$s: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/$s/manifests/inputs-$inputs")
echo "$s: inputs-$inputs -> HTTP $code"
[ "$code" = 200 ] || reuse=false
done
echo "inputs=$inputs" >> "$GITHUB_OUTPUT"
echo "reuse=$reuse" >> "$GITHUB_OUTPUT"
echo "reuse the published images: $reuse"
# Nothing is published unless the full suite passes first. Skipped when the
# images are reused: the same trees already went through it.
ci:
needs: plan
if: ${{ needs.plan.outputs.reuse != 'true' }}
uses: ./.gitea/workflows/ci.yml
# Registry paths must be lowercase, and this repository is "lucasrguerra/LambdaMail".
# Lowercasing gitea.repository here keeps the workflow fork-safe instead of
# hardcoding the owner.
prepare:
name: Namespace das imagens
runs-on: ubuntu-latest
outputs:
namespace: ${{ steps.namespace.outputs.value }}
steps:
- id: namespace
# GITHUB_OUTPUT is the runner's own interface: Gitea sets it under that name.
run: |
repo='${{ gitea.repository }}'
echo "value=${repo,,}" >> "$GITHUB_OUTPUT"
# Each architecture is built on a runner of its own kind. QEMU emulation
# would also work, but it makes the Go and Next builds roughly an order of
# magnitude slower.
build:
name: Build ${{ matrix.service }} (${{ matrix.platform }})
needs: [plan, ci, prepare]
if: ${{ needs.plan.outputs.reuse != 'true' }}
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
# Both are axes, so this is a cross product: four services on two
# architectures is eight jobs. Declaring platform only under `include`
# would not multiply anything - include can add keys to existing
# combinations, but it cannot create the combinations themselves.
service: [protocols, auth, storage, webmail, migrations, rspamd]
platform: [linux/amd64, linux/arm64]
include:
- platform: linux/amd64
runner: linux-amd64
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v7
# The only runner is arm64; linux/amd64 is built under QEMU emulation
# (label linux-amd64 points at the same runner), so it is slower.
- uses: docker/setup-qemu-action@v3
if: matrix.arch == 'amd64'
- uses: docker/setup-buildx-action@v4
- name: Autentica no registro
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Metadados
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}
# Each architecture is pushed under its own tag (no digest artifacts: they
# are not portable to Gitea). The final tags are attached later, once both
# exist and can be joined into a single manifest list.
- name: Build e push por arquitetura
id: build
uses: docker/build-push-action@v7
with:
context: ${{ (matrix.service == 'migrations' || matrix.service == 'rspamd') && '.' || format('services/{0}', matrix.service) }}
file: services/${{ matrix.service }}/Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
push: true
tags: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:build-${{ gitea.sha }}-${{ matrix.arch }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:buildcache-${{ matrix.arch }}
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:buildcache-${{ matrix.arch }},mode=max
publish:
name: Publica ${{ matrix.service }}
needs: [plan, build, prepare]
# build is skipped, not successful, when the images are reused.
if: ${{ !cancelled() && needs.plan.result == 'success' && needs.prepare.result == 'success' && (needs.build.result == 'success' || needs.plan.outputs.reuse == 'true') }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
service: [protocols, auth, storage, webmail, migrations, rspamd]
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v4
- name: Autentica no registro
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
# `latest` is what compose pulls. The semver tags only materialise on a
# v* tag push and make it possible to pin or roll back to an exact
# release; the sha tag does the same for any main build.
- name: Metadados
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest
type=sha,format=long
# Joins both architectures under one manifest list, so `docker pull`
# resolves to the right one automatically. The inputs-<hash> tag is what
# lets a later run recognise these images and skip the build.
- name: Aplica as tags
run: |
image='${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}'
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}')
inputs='${{ needs.plan.outputs.inputs }}'
if [ '${{ needs.plan.outputs.reuse }}' = true ]; then
# Same manifest under new names: no layer is read or written.
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: Confere o resultado
run: |
docker buildx imagetools inspect \
"${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}:latest" \
--format '{{json .Manifest}}' \
| jq -r '.manifests[] | select(.platform.os == "linux") | "\(.platform.os)/\(.platform.architecture)"'
- name: Resumo
run: |
{
echo "### ${{ matrix.service }} publicado (linux/amd64 e linux/arm64)"
echo
echo '```'
echo "${{ steps.meta.outputs.tags }}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# Cria a entrada em "Releases" do repositório. Só roda quando uma tag v* é
# enviada, depois que todas as imagens foram publicadas. Usa o token do próprio
# Actions, então não precisa de segredo extra.
gitea-release:
name: Cria a release no Gitea
needs: [publish, prepare]
# Explicit, like publish: the build job upstream is skipped when the images
# are reused, and the implicit success() would then skip this job too.
if: ${{ !cancelled() && needs.publish.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
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 }}
NAMESPACE: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}
run: |
scripts/release-notes.sh "$TAG" "$NAMESPACE" > notes.md
pre=false; case "$TAG" in *-*) pre=true ;; esac
jq -n --arg tag "$TAG" --arg name "LambdaMail ${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