From 6c0c94373a9ca87899ee5d999451b98d93e26f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Thu, 17 Sep 2026 17:21:21 -0300 Subject: [PATCH] ci: publish DTF images and notify Portainer --- .gitea/workflows/deploy.yml | 112 ++++++++++++------------------------ deploy/Dockerfile.api | 2 +- deploy/Dockerfile.web | 4 +- 3 files changed, 39 insertions(+), 79 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index ec777fa..4b90ba2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Validate, publish and deploy +name: Build and deploy on: pull_request: @@ -8,102 +8,62 @@ on: jobs: validate: + name: Validate source runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - name: Validate source and deployment definitions + - name: Run fast regression checks run: | python3 -m py_compile local/*.py deploy/*.py - python3 -m unittest local.test_dependency_lock local.test_staging_readiness deploy.test_production_preflight local.test_pricing -v + python3 -m unittest \ + local.test_dependency_lock \ + local.test_staging_readiness \ + deploy.test_production_preflight \ + local.test_pricing -v sh -n local/lock_dependencies.sh - docker compose config --quiet - docker compose -f compose.staging.yaml config --quiet - set -a - . deploy/portainer.env.example - set +a - docker stack config --compose-file deploy/stack.yaml >/dev/null publish-and-deploy: + name: Publish images and notify Portainer needs: validate if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main' runs-on: ubuntu-latest - timeout-minutes: 75 - env: - COMPOSE_PROJECT_NAME: dtf-release-${{ gitea.run_number }} - REGISTRY_HOST: ${{ vars.REGISTRY_HOST }} - REGISTRY_OWNER: ${{ vars.REGISTRY_OWNER }} - PYTHON_BASE_IMAGE: ${{ vars.PYTHON_BASE_IMAGE }} - NGINX_BASE_IMAGE: ${{ vars.NGINX_BASE_IMAGE }} - TRIVY_IMAGE: ${{ vars.TRIVY_IMAGE }} + timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - name: Require production-capable application source - run: python3 deploy/production_preflight.py --source-only - - name: Validate immutable build inputs - run: | - for value in "$PYTHON_BASE_IMAGE" "$NGINX_BASE_IMAGE" "$TRIVY_IMAGE"; do - echo "$value" | grep -Eq '^[a-z0-9][a-z0-9._:/-]*@sha256:[0-9a-f]{64}$' - echo "$value" | grep -Ev '@sha256:0{64}$' >/dev/null - done - case "$REGISTRY_HOST/$REGISTRY_OWNER" in *[A-Z]*|*' '*|'/'*) exit 2;; esac - - name: Run complete isolated regression suite - run: | - docker compose up --build -d --wait - python3 -m local.security_test - python3 -m local.scanning_test - python3 -m local.smoke_test - python3 -m local.workflow_test - docker compose exec -T api python -m local.runtime_security_test - docker compose exec -T api python -m local.retention_test - node local/browser_test.mjs - python3 -m local.backup create-and-verify - - name: Build production images - run: | - api_sha="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-api:${{ gitea.sha }}" - api_latest="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-api:latest" - web_sha="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-web:${{ gitea.sha }}" - web_latest="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-web:latest" - docker build --pull --file deploy/Dockerfile.api \ - --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" \ - --build-arg VCS_REF="${{ gitea.sha }}" \ - --tag "$api_sha" --tag "$api_latest" . - docker build --pull --file deploy/Dockerfile.web \ - --build-arg PYTHON_BASE_IMAGE="$PYTHON_BASE_IMAGE" \ - --build-arg NGINX_BASE_IMAGE="$NGINX_BASE_IMAGE" \ - --build-arg VCS_REF="${{ gitea.sha }}" \ - --tag "$web_sha" --tag "$web_latest" . - - name: Block secret, configuration and image findings - run: | - api="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-api:${{ gitea.sha }}" - web="$REGISTRY_HOST/$REGISTRY_OWNER/dtf-web:${{ gitea.sha }}" - docker run --rm --volume "$PWD:/src:ro" --workdir /src "$TRIVY_IMAGE" \ - fs --scanners secret --exit-code 1 --severity HIGH,CRITICAL . - docker run --rm --volume "$PWD:/src:ro" --workdir /src "$TRIVY_IMAGE" \ - config --exit-code 1 --severity HIGH,CRITICAL deploy - docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock:ro "$TRIVY_IMAGE" \ - image --exit-code 1 --scanners vuln --severity HIGH,CRITICAL "$api" - docker run --rm --volume /var/run/docker.sock:/var/run/docker.sock:ro "$TRIVY_IMAGE" \ - image --exit-code 1 --scanners vuln --severity HIGH,CRITICAL "$web" - - name: Publish latest and rollback tags + - name: Sign in to the Gitea Container Registry env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | - trap 'docker logout "$REGISTRY_HOST" >/dev/null 2>&1 || true' EXIT - echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin - docker push "$REGISTRY_HOST/$REGISTRY_OWNER/dtf-api:${{ gitea.sha }}" - docker push "$REGISTRY_HOST/$REGISTRY_OWNER/dtf-api:latest" - docker push "$REGISTRY_HOST/$REGISTRY_OWNER/dtf-web:${{ gitea.sha }}" - docker push "$REGISTRY_HOST/$REGISTRY_OWNER/dtf-web:latest" - - name: Trigger the Portainer stack webhook + test -n "$REGISTRY_USERNAME" + test -n "$REGISTRY_TOKEN" + echo "$REGISTRY_TOKEN" | docker login gitea.blyzer.com.br \ + --username "$REGISTRY_USERNAME" --password-stdin + - name: Build and publish API + run: | + image="gitea.blyzer.com.br/blyzer/dtf-api" + docker build --pull --file deploy/Dockerfile.api \ + --build-arg VCS_REF="${{ gitea.sha }}" \ + --tag "$image:latest" --tag "$image:${{ gitea.sha }}" . + docker push "$image:latest" + docker push "$image:${{ gitea.sha }}" + - name: Build and publish web + run: | + image="gitea.blyzer.com.br/blyzer/dtf-web" + docker build --pull --file deploy/Dockerfile.web \ + --build-arg VCS_REF="${{ gitea.sha }}" \ + --tag "$image:latest" --tag "$image:${{ gitea.sha }}" . + docker push "$image:latest" + docker push "$image:${{ gitea.sha }}" + - name: Trigger Portainer redeployment env: PORTAINER_WEBHOOK: ${{ secrets.PORTAINER_WEBHOOK }} run: | - test -n "$PORTAINER_WEBHOOK" + if [ -z "$PORTAINER_WEBHOOK" ]; then + echo "PORTAINER_WEBHOOK is not configured; images were published but deployment was skipped." + exit 0 + fi curl --fail --silent --show-error --max-time 30 --request POST "$PORTAINER_WEBHOOK" - - name: Stop isolated test stack - if: always() - run: docker compose down --volumes --remove-orphans diff --git a/deploy/Dockerfile.api b/deploy/Dockerfile.api index 8978375..ba71b6b 100644 --- a/deploy/Dockerfile.api +++ b/deploy/Dockerfile.api @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -ARG PYTHON_BASE_IMAGE +ARG PYTHON_BASE_IMAGE=python:3.12-slim FROM ${PYTHON_BASE_IMAGE} ARG VCS_REF=unknown diff --git a/deploy/Dockerfile.web b/deploy/Dockerfile.web index f661d2b..616b502 100644 --- a/deploy/Dockerfile.web +++ b/deploy/Dockerfile.web @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG PYTHON_BASE_IMAGE -ARG NGINX_BASE_IMAGE +ARG PYTHON_BASE_IMAGE=python:3.12-slim +ARG NGINX_BASE_IMAGE=nginx:1.28-alpine FROM ${PYTHON_BASE_IMAGE} AS policy WORKDIR /build COPY dtf-site.html /build/dtf-site.html