ci: imagem construída do código, esteira para os runners do Gitea e changelog
Release / Plano (reaproveitar a imagem?) (push) Failing after 4s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Skipped
Release / Cria a release no Gitea (push) Skipped

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-11-01 21:35:00 -03:00
1 parent 47586bcc7b
commit ab7f532456
18 files changed
+520 -45

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
name: CI
# Documentação e configuração de ferramenta não mudam nada que a suíte teste.
# Na main não roda por push: o release.yml é quem a chama, e só quando a imagem
# precisa ser reconstruída; rodar nos dois repetiria a mesma suíte no mesmo commit.
# Sem gatilho de pull_request: o push da branch já valida o mesmo commit, e os
# dois juntos rodariam tudo duas vezes (o repositório não recebe PR de fork).
#
# A imagem é construída aqui uma única vez, em arm64 (a arquitetura do servidor),
# testada e publicada como inputs-<hash>. Ao fazer o merge, o release encontra
# essa tag e só aplica os nomes novos, sem rodar nem construir nada de novo.
on:
push:
branches-ignore: [main]
paths-ignore: ['**.md', 'LICENSE', 'CITATION.cff', 'paper.md', 'references.bib', 'docs/**', '.claude/**', '.wolf/**', '.superset/**', 'scripts/release-notes.sh']
schedule:
- cron: '0 9 * * 1' # toda segunda: reavalia com a base de vulnerabilidades nova
workflow_call:
permissions:
contents: read
concurrency:
group: ci-${{ gitea.ref }}
cancel-in-progress: true
jobs:
verificar:
name: Tipos e testes
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
# tsc -b é o mesmo que o build faz antes do Vite; aqui falha antes de gastar a imagem.
- run: npx tsc -b
- run: npm test
# Trivy roda pela imagem oficial: o trivy-action clona o repositório do Trivy
# do mesmo servidor do workflow, que aqui é o Gitea, e não o GitHub.
# O workspace do job é um volume que o daemon não enxerga, então os arquivos
# entram no container com `docker cp` em vez de `-v`.
seguranca:
name: Trivy (dependências e segredos)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Repositório
run: |
cid=$(docker create --network host -v trivy-cache:/root/.cache/ -w /src aquasec/trivy:0.70.0 \
fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 .)
docker cp . "$cid:/src"
docker start -a "$cid"
status=$(docker inspect -f '{{.State.ExitCode}}' "$cid")
docker rm "$cid" >/dev/null
exit "$status"
# Só começa depois das verificações baratas: um teste quebrado não gasta um
# build de imagem. Roda no runner arm64, o mesmo tipo do servidor, então o
# que é escaneado e testado é a imagem que vai ao ar.
imagem:
name: Imagem Docker (linux/arm64)
needs: [verificar, seguranca]
runs-on: ubuntu-24.04-arm
env:
REGISTRY: git.lucasrguerra.dev.br
steps:
- uses: actions/checkout@v4
# Conteúdo já publicado (re-push, tag num commit já validado): nada a fazer.
- id: plano
run: scripts/plano-imagem.sh "$REGISTRY" "${{ gitea.repository }}" >> "$GITHUB_OUTPUT"
- if: ${{ steps.plano.outputs.reuse != 'true' }}
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
# `docker build` simples, no daemon persistente do runner: as camadas e o
# cache mount do npm sobrevivem entre execuções. Um buildx criaria um
# BuildKit novo, e vazio, a cada job.
- name: Build
if: ${{ steps.plano.outputs.reuse != 'true' }}
run: docker build -t pss:ci .
- name: Trivy na imagem
if: ${{ steps.plano.outputs.reuse != 'true' }}
run: |
docker run --rm --network host -v trivy-cache:/root/.cache/ -e DOCKER_HOST=tcp://127.0.0.1:2375 aquasec/trivy:0.70.0 \
image --severity HIGH,CRITICAL --exit-code 1 pss:ci
# A SPA sobe, cai no index nas rotas internas e entrega os assets com
# cache imutável (scripts/smoke-imagem.sh).
- name: A imagem sobe e responde
if: ${{ steps.plano.outputs.reuse != 'true' }}
run: |
docker run -d --name pss-ci -p 127.0.0.1:3998:80 pss:ci
status=0
scripts/smoke-imagem.sh http://127.0.0.1:3998 || status=1
docker rm -f pss-ci >/dev/null
exit "$status"
# Passou em tudo: publica a mesma imagem que foi testada, sem reconstruir.
- name: Publica inputs-<hash>
if: ${{ steps.plano.outputs.reuse != 'true' }}
run: |
tag="${{ steps.plano.outputs.image }}:inputs-${{ steps.plano.outputs.inputs }}"
docker tag pss:ci "$tag"
docker push "$tag"
+126
View File
@@ -0,0 +1,126 @@
name: Release
# Publica a imagem no registro de containers do Gitea depois de um merge em main,
# e de novo com tag semver quando uma tag v* é enviada. O servidor só faz pull.
# Precisa do secret de usuário REGISTRY_TOKEN (token do Gitea com write:package).
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
env:
REGISTRY: git.lucasrguerra.dev.br
jobs:
# A imagem só depende dos arquivos que entram no build, não da documentação nem
# da configuração de ferramentas. O push da branch já a construiu, testou e
# publicou como inputs-<hash>; quando essa tag existe (o caso normal de um
# merge, uma tag num commit validado, um merge só de docs), não há o que
# construir: as tags novas apontam para o manifesto existente.
plano:
name: Plano (reaproveitar a imagem?)
runs-on: ubuntu-latest
outputs:
inputs: ${{ steps.plano.outputs.inputs }}
reuse: ${{ steps.plano.outputs.reuse }}
image: ${{ steps.plano.outputs.image }}
steps:
- uses: actions/checkout@v4
- id: plano
run: scripts/plano-imagem.sh "$REGISTRY" "${{ gitea.repository }}" >> "$GITHUB_OUTPUT"
# Nada é publicado sem a suíte completa passar antes. Pulada quando a imagem é
# reaproveitada: o mesmo conteúdo já passou por ela.
ci:
needs: plano
if: ${{ needs.plano.outputs.reuse != 'true' }}
uses: ./.gitea/workflows/ci.yml
publicar:
name: Publica a imagem (linux/arm64)
needs: [plano, ci]
# ci é pulado, e não bem-sucedido, quando a imagem é reaproveitada.
if: ${{ !cancelled() && needs.plano.result == 'success' && (needs.ci.result == 'success' || needs.plano.outputs.reuse == 'true') }}
# O único runner é arm64 e o servidor de destino (Oracle) também, então a
# imagem é construída nativamente, sem emulação.
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
# Se o ci chamado acima acabou de publicar a imagem (push direto na main),
# o plano lá de cima está desatualizado: confere de novo antes de construir.
- id: replano
run: scripts/plano-imagem.sh "$REGISTRY" "${{ gitea.repository }}" >> "$GITHUB_OUTPUT"
- 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: ${{ needs.plano.outputs.image }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
type=sha,format=long
- name: Build e push
if: ${{ steps.replano.outputs.reuse != 'true' }}
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/arm64
push: true
tags: |
${{ steps.meta.outputs.tags }}
${{ needs.plano.outputs.image }}:inputs-${{ needs.plano.outputs.inputs }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ needs.plano.outputs.image }}:buildcache
cache-to: type=registry,ref=${{ needs.plano.outputs.image }}:buildcache,mode=max
# Mesmo manifesto, nomes novos: nenhuma camada é lida nem escrita.
- name: Aplica as tags à imagem existente
if: ${{ steps.replano.outputs.reuse == 'true' }}
run: |
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}')
docker buildx imagetools create $tags "${{ needs.plano.outputs.image }}:inputs-${{ needs.plano.outputs.inputs }}"
# Cria a entrada em "Releases" quando a execução é de uma tag v*. Roda depois
# de publicar a imagem, com as notas da seção do CHANGELOG.md.
release:
name: Cria a release no Gitea
needs: [plano, publicar]
# Explícito, como o publicar: o ci acima é pulado quando a imagem é
# reaproveitada, e o success() implícito pularia este job também.
if: ${{ !cancelled() && needs.publicar.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
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.plano.outputs.image }}
run: |
scripts/release-notes.sh "$TAG" "$IMAGE" > notes.md
pre=false; case "$TAG" in *-*) pre=true ;; esac
jq -n --arg tag "$TAG" --arg name "PSS ${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