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
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>
244 lines
10 KiB
YAML
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
|