stages: - test - build - deploy variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" PYTHONUNBUFFERED: "1" DOCKER_TLS_CERTDIR: "/certs" IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" APP_IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" cache: paths: - .cache/pip .python_setup: &python_setup image: python:3.12-slim before_script: - python -V - python -m pip install --upgrade pip - pip install -r requirements-dev.txt lint: <<: *python_setup stage: test script: - ruff check app tests scripts - black --check app tests scripts unit_tests: <<: *python_setup stage: test script: - pytest -q tests type_check: <<: *python_setup stage: test script: - mypy app scripts --ignore-missing-imports build_image: stage: build image: docker:27 services: - docker:27-dind before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin script: - docker build --pull -t "$APP_IMAGE" -t "$CI_REGISTRY_IMAGE:latest" . - docker push "$APP_IMAGE" - | if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then docker push "$CI_REGISTRY_IMAGE:latest" fi - printf 'APP_IMAGE=%s\nIMAGE_TAG=%s\n' "$APP_IMAGE" "$IMAGE_TAG" > build.env artifacts: reports: dotenv: build.env security_scan: stage: build image: name: aquasec/trivy:0.61.1 entrypoint: [""] needs: ["build_image"] script: - trivy image --exit-code 1 --severity HIGH,CRITICAL --username "$CI_REGISTRY_USER" --password "$CI_REGISTRY_PASSWORD" "$APP_IMAGE" .deploy_setup: &deploy_setup image: python:3.12-alpine before_script: - apk add --no-cache bash openssh-client curl docker-cli docker-cli-compose - mkdir -p ~/.ssh - chmod 700 ~/.ssh - printf '%s' "$DEPLOY_SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - python -V .deploy_rules: &deploy_rules rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' deploy_production: <<: *deploy_setup stage: deploy needs: ["build_image", "security_scan"] environment: name: production variables: GIT_STRATEGY: fetch script: - test -n "$DEPLOY_HOST" || (echo "DEPLOY_HOST must be set to a VPN-reachable private address" && exit 1) - bash scripts/deploy.sh - | if [ -n "${EXTERNAL_HEALTHCHECK_URL:-}" ]; then python scripts/healthcheck.py "$EXTERNAL_HEALTHCHECK_URL" --timeout 120 --expect-status ok --expect-environment "${APP_ENV:-production}" fi <<: *deploy_rules