Skip to content

feat: consolidate production edge and checkout hardening #47

feat: consolidate production edge and checkout hardening

feat: consolidate production edge and checkout hardening #47

Workflow file for this run

name: CI e imagem Alpine
on:
push:
pull_request:
permissions:
contents: read
packages: write
concurrency:
group: ci-image-${{ github.sha }}
cancel-in-progress: false
env:
V_COMMIT: 45ae01d23168b6372f734eeb38a77360bbcf184a
VEEMARKER_COMMIT: 1510ef5a7cbf980f2e075f02baada7190748e3f7
DOTENV_COMMIT: 1d9477c8b1a3f5ca14b2eb042c4e6d52449b75d4
V_STRIPE_COMMIT: dca05be5fca093fe31f9e7d5f3b356fd84e3a690
LOCAL_IMAGE: tabua-mare-api:ci-${{ github.sha }}
GHCR_IMAGE: ghcr.io/ddiidev/tabua-mare-api:sha-${{ github.sha }}
DB_SQLITE_PATH: ${{ github.workspace }}/taubinha.sqlite
POSTGRESQL_CONN_STR: postgresql://postgres:postgres@127.0.0.1:5432/tabuamare_ci
RUN_POSTGRES_TEST: "1"
jobs:
test-build-smoke:
runs-on: ubuntu-latest
timeout-minutes: 45
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: tabuamare_ci
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d tabuamare_ci"
--health-interval 2s
--health-timeout 3s
--health-retries 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login GHCR
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Impedir reatribuicao da tag SHA
if: github.event_name == 'push'
run: |
set +e
manifest_error="$(docker manifest inspect "$GHCR_IMAGE" 2>&1 >/dev/null)"
manifest_status=$?
set -e
if [[ "$manifest_status" -eq 0 ]]; then
echo "::error::A tag imutavel $GHCR_IMAGE ja existe; reatribuicao recusada."
exit 1
fi
if ! grep -Eqi 'manifest unknown|not found' <<<"$manifest_error"; then
echo "::error::Falha ao consultar GHCR; publicacao recusada sem confirmar ausencia da tag."
exit 1
fi
- name: Dependencias nativas do V
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential libgc-dev libpq-dev libssl-dev libsqlite3-dev
- name: V 0.5.2 e modulos fixados
run: |
set -euo pipefail
git clone https://github.com/vlang/v.git /tmp/v
git -C /tmp/v checkout --detach "$V_COMMIT"
make -C /tmp/v
test "$(git -C /tmp/v rev-parse HEAD)" = "$V_COMMIT"
/tmp/v/v version | grep -F 'V 0.5.2'
mkdir -p "$HOME/.vmodules/leafscale" "$HOME/.vmodules/ken0x0a"
git clone https://github.com/leafscale/veemarker.git "$HOME/.vmodules/leafscale/veemarker"
git -C "$HOME/.vmodules/leafscale/veemarker" checkout --detach "$VEEMARKER_COMMIT"
git clone https://github.com/ken0x0a/v-dotenv.git "$HOME/.vmodules/ken0x0a/dotenv"
git -C "$HOME/.vmodules/ken0x0a/dotenv" checkout --detach "$DOTENV_COMMIT"
git clone https://github.com/Ddiidev/v-stripe.git "$HOME/.vmodules/v_stripe"
git -C "$HOME/.vmodules/v_stripe" checkout --detach "$V_STRIPE_COMMIT"
echo '/tmp/v' >> "$GITHUB_PATH"
- name: Testes V com PostgreSQL efemero
run: /tmp/v/v test tests/
- name: Contratos shell e ciclo de vida
run: |
set -euo pipefail
./scripts/test_seed_sqlite.sh
./scripts/test_alpine_image_static.sh
./scripts/test_coolify_deploy.sh
./scripts/test_health_lifecycle.sh
- name: Buildx
uses: docker/setup-buildx-action@v3
- name: Build unico linux/amd64
run: |
docker buildx build \
--platform linux/amd64 \
--load \
--tag "$LOCAL_IMAGE" \
--tag "$GHCR_IMAGE" \
.
- name: Verificar Alpine, arquitetura e bibliotecas
run: |
set -euo pipefail
test "$(docker image inspect "$LOCAL_IMAGE" --format '{{.Architecture}}')" = amd64
docker run --rm --entrypoint sh "$LOCAL_IMAGE" -eu -c \
"grep -Eq '^3[.]22[.]' /etc/alpine-release"
docker run --rm --entrypoint sh "$LOCAL_IMAGE" -eu -c '
apk add --no-cache pax-utils >/dev/null
scanelf --needed --nobanner /app/TabuaMareAPI
ldd /app/TabuaMareAPI | tee /tmp/ldd.txt
! grep -Fq "not found" /tmp/ldd.txt
'
- name: Ambiente isolado do smoke A/B
run: |
cat > "$RUNNER_TEMP/tabuamare-ci.env" <<'ENV'
POSTGRESQL_CONN_STR=postgresql://postgres:postgres@127.0.0.1:5432/tabuamare_ci
GOOGLE_CLIENT_ID=ci-client
GOOGLE_CLIENT_SECRET=ci-secret
GOOGLE_REDIRECT_URI=https://tabuamare.api.br/auth/google/callback
SESSION_SECRET=ci-only-session-secret-with-more-than-32-bytes
STRIPE_SECRET_KEY=sk_test_ci
STRIPE_WEBHOOK_SECRET=whsec_ci
URL_ENV=http://localhost:3330
ENV
- name: Smoke da imagem exata em A/B
run: |
set -euo pipefail
env_file="$RUNNER_TEMP/tabuamare-ci.env"
a_name="tabuamare-ci-a-${GITHUB_RUN_ID}"
b_name="tabuamare-ci-b-${GITHUB_RUN_ID}"
a_volume="tabuamare-ci-a-${GITHUB_RUN_ID}"
b_volume="tabuamare-ci-b-${GITHUB_RUN_ID}"
cleanup() { docker rm -f "$a_name" "$b_name" >/dev/null 2>&1 || true; docker volume rm "$a_volume" "$b_volume" >/dev/null 2>&1 || true; }
trap cleanup EXIT
docker volume create "$a_volume" >/dev/null
docker volume create "$b_volume" >/dev/null
wait_http() { local url="$1" expected="$2" code=000; for _ in $(seq 1 180); do code="$(curl -sS -o /dev/null -w '%{http_code}' "$url" 2>/dev/null || true)"; [[ "$code" == "$expected" ]] && return 0; sleep 1; done; echo "$url: recebido $code, esperado $expected" >&2; docker logs "$a_name" >&2 || true; docker logs "$b_name" >&2 || true; return 1; }
docker run -d --name "$a_name" --network host --env-file "$env_file" -e PORT=3330 -v "$a_volume:/app/data" "$LOCAL_IMAGE" 3330
wait_http http://127.0.0.1:3330/health/ready 204
docker run -d --name "$b_name" --network host --env-file "$env_file" -e PORT=3340 -v "$b_volume:/app/data" "$LOCAL_IMAGE" 3340
wait_http http://127.0.0.1:3340/health/ready 204
wait_http http://127.0.0.1:3330/api/v2/states 200
wait_http http://127.0.0.1:3340/api/v2/states 200
[[ "$(docker inspect --format '{{.State.Status}}' "$a_name")" == running ]]
[[ "$(docker inspect --format '{{.State.Status}}' "$b_name")" == running ]]
echo 'PASS: smoke direto A/B, readiness e API v2'
- name: Publicar tag imutavel
if: github.event_name == 'push'
run: docker push "$GHCR_IMAGE"
- name: Confirmar acesso publico anonimo
if: github.event_name == 'push'
run: |
docker logout ghcr.io
if ! docker manifest inspect "$GHCR_IMAGE" >/dev/null 2>&1; then
echo "::warning::Pacote GHCR ainda privado. Torne tabua-mare-api publico antes do deploy."
echo '## Acao obrigatoria' >> "$GITHUB_STEP_SUMMARY"
echo 'Tornar o pacote GHCR `tabua-mare-api` publico. O deploy permanece bloqueado enquanto o manifesto anonimo falhar.' >> "$GITHUB_STEP_SUMMARY"
fi