#14: Gitea CI, registry composes, Node 22 image and changelog
- Gitea Actions: Trivy, Vitest in a Dockerfile test stage, arm64 image build, image scan and smoke test; releases reuse the image by content hash. - docker-compose.<edition>.registry.yaml run the stack from the published image. - Dockerfile: Node 22, a test stage, and no npm in the runtime image. - README, CLAUDE.md and CHANGELOG describe the CI, the image and the release flow. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
640e52c0c9
commit
a4a2584f37
12 files changed
+606
-9
No files matched your search
@@ -0,0 +1,95 @@
|
||||
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', '.claude/**', '.superpowers/**', '.wolf/**', 'docker-compose*.yaml', '.env.example', '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:
|
||||
# Trivy roda pela imagem oficial: o trivy-action clona o repositório do Trivy
|
||||
# do mesmo servidor do workflow, que aqui é o Gitea. O workspace do job não é
|
||||
# visível ao daemon, então os arquivos entram no container com `docker cp`.
|
||||
# O .trivyignore do repositório (DS-0002, documentado) é lido automaticamente.
|
||||
seguranca:
|
||||
name: Trivy (dependências, segredos e configuração)
|
||||
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"
|
||||
|
||||
imagem:
|
||||
name: Testes e imagem Docker (linux/arm64)
|
||||
needs: [seguranca]
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 40
|
||||
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"
|
||||
|
||||
# Vitest dentro do estágio `test` do Dockerfile: ele compila o addon nativo do
|
||||
# LevelDB uma vez, e as camadas ficam no daemon do runner para o build abaixo.
|
||||
- name: Testes (Vitest)
|
||||
if: ${{ steps.plano.outputs.reuse != 'true' }}
|
||||
run: docker build --target test -t craftdock:test .
|
||||
|
||||
- name: Build
|
||||
if: ${{ steps.plano.outputs.reuse != 'true' }}
|
||||
run: docker build -t craftdock: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 --ignore-unfixed --exit-code 1 craftdock:ci
|
||||
|
||||
- name: A imagem sobe e responde
|
||||
if: ${{ steps.plano.outputs.reuse != 'true' }}
|
||||
run: scripts/smoke-imagem.sh craftdock:ci
|
||||
|
||||
# O login fica por último: testes, build, Trivy e smoke não precisam de credencial.
|
||||
- if: ${{ steps.plano.outputs.reuse != 'true' }}
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
# 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 craftdock:ci "$tag"
|
||||
docker push "$tag"
|
||||
@@ -0,0 +1,129 @@
|
||||
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 REGISTRY_TOKEN no repositório (token do Gitea com write:package):
|
||||
# o registro recusa o token do job.
|
||||
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
|
||||
# Workflows reutilizáveis não herdam os secrets sozinhos: o login no registro precisa deles.
|
||||
secrets: inherit
|
||||
|
||||
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 "CraftDock ${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
|
||||
Reference in new issue
Block a user