stages:
  - iac
  - build
  - test
  - security
  - tag
  - deploy

default:
  retry: 2

variables:
  BASE_IMAGE_NAME: $CI_REGISTRY_IMAGE
  TRIVY_CACHE_DIR: .cache/trivy
  TRIVY_NO_PROGRESS: "true"
  TRIVY_TIMEOUT: "10m0s"
  TRIVY_USERNAME: $CI_REGISTRY_USER
  TRIVY_PASSWORD: $CI_REGISTRY_PASSWORD
  TRIVY_REGISTRY: $CI_REGISTRY
  UV_PROJECT_ENVIRONMENT: .venv
  UV_CACHE_DIR: .cache/uv
  BUILDAH_ISOLATION: oci
  STORAGE_DRIVER: vfs
  DOCKER_HOST: "tcp://docker:2375"
  DOCKER_TLS_CERTDIR: ""

cache:
  key: "${CI_COMMIT_REF_SLUG}"
  paths:
    - $TRIVY_CACHE_DIR
    - $UV_CACHE_DIR
    - $UV_PROJECT_ENVIRONMENT
  policy: pull-push

.docker-job: &docker-job
  image: docker:28.5
  services:
    - docker:28.5-dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY

.buildah-job: &buildah-job
  image: quay.io/containers/buildah:latest
  variables:
    STORAGE_DRIVER: vfs
  before_script:
    - buildah login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY

.trivy-fs-template: &trivy-fs-scan
  stage: security
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  cache:
    paths:
      - $TRIVY_CACHE_DIR
    policy: pull-push
  before_script:
    - mkdir -p $TRIVY_CACHE_DIR
  script:
    - trivy filesystem --skip-files $TRIVY_CACHE_DIR --format cyclonedx --output fs-sbom.json .
    - trivy filesystem --skip-files $TRIVY_CACHE_DIR --format sarif --output gl-sast-fs-report.json .
  allow_failure: true
  artifacts:
    reports:
      sast: gl-sast-fs-report.json
    paths:
      - fs-sbom.json
      - gl-sast-fs-report.json
    expire_in: 1 week
    when: always
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $SAST_DISABLED
      when: never

.trivy-image-template: &trivy-image-scan
  stage: security
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  cache:
    paths:
      - $TRIVY_CACHE_DIR
    policy: pull-push
  before_script:
    - mkdir -p $TRIVY_CACHE_DIR
  script:
    - |
      trivy image \
        --scanners vuln \
        --format cyclonedx \
        --output image-sbom-${IMAGE_TYPE}.json \
        $IMAGE_NAME:$CI_COMMIT_SHA
    - |
      trivy image \
        --format sarif \
        --output gl-sast-image-${IMAGE_TYPE}-report.json \
        $IMAGE_NAME:$CI_COMMIT_SHA
  allow_failure: true
  artifacts:
    reports:
      sast: gl-sast-image-${IMAGE_TYPE}-report.json
    paths:
      - image-sbom-${IMAGE_TYPE}.json
      - gl-sast-image-${IMAGE_TYPE}-report.json
    expire_in: 1 week
    when: always
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $SAST_DISABLED
      when: never

.webhook-template: &webhook-config
  image: curlimages/curl:latest
  script:
    - |
      curl -sf -X POST \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $WEBHOOK_SECRET_TOKEN" \
        -H "Webhook-Identifier: $WEBHOOK_BYPASS_TOKEN" \
        "$WEBHOOK_URL"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: on_success

.build-template: &build-config
  <<: *buildah-job
  stage: build
  script:
    - |
      buildah bud \
        --tag $IMAGE_NAME:$CI_COMMIT_SHA \
        --file $CONTAINERFILE \
        --target $BUILDTARGET \
        --layers \
        --cache-from $IMAGE_NAME-cache \
        --cache-to $IMAGE_NAME-cache \
        .
    - buildah push $IMAGE_NAME:$CI_COMMIT_SHA
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: always
    - if: $CI_COMMIT_TAG
      when: always
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
      when: manual
      allow_failure: true

.tag-template: &tag-config
  <<: *buildah-job
  stage: tag
  script:
    - |
      set -euo pipefail

      buildah pull $IMAGE_NAME:$CI_COMMIT_SHA

      if [ -n "${CI_COMMIT_TAG:-}" ]; then
        buildah tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:$CI_COMMIT_TAG
        buildah push $IMAGE_NAME:$CI_COMMIT_TAG
      fi

      if [ -n "${CI_COMMIT_BRANCH:-}" ]; then
        buildah tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:$CI_COMMIT_REF_SLUG
        buildah push $IMAGE_NAME:$CI_COMMIT_REF_SLUG

        if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then
          buildah tag $IMAGE_NAME:$CI_COMMIT_SHA $IMAGE_NAME:latest
          buildah push $IMAGE_NAME:latest
        fi
      fi
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
      when: manual
      allow_failure: true

.uv-job: &uv-job
  image: debian:trixie-slim
  cache:
    key: "${CI_JOB_NAME}-${CI_COMMIT_REF_SLUG}"
    paths:
      - $UV_PROJECT_ENVIRONMENT
      - $UV_CACHE_DIR
    policy: pull-push
  before_script:
    - apt-get update
    - apt-get install -y --no-install-recommends ca-certificates curl just git
    - update-ca-certificates
    - curl -LsSf https://astral.sh/uv/install.sh | sh
    - export PATH="$HOME/.local/bin:$PATH"

ansible-initvm:
  stage: iac
  image: alpine/ansible:2.18.6
  variables:
    ANSIBLE_HOST_KEY_CHECKING: false
  before_script:
    - apk add openssl
    - echo $ENV_PRIVATE_KEY_BASE64 | base64 -d > /id.pem
    - chmod 0600 /id.pem
    - mv "$INVENTORY_ALPHA_VM" ./infrastructure/iac/ansible/inventory/host_vars/alpha.yaml
    - printf "[servers]\nalpha\n" > infrastructure/iac/ansible/inventory/hosts
  script:
    - cd ./infrastructure/iac/ansible
    - ansible-galaxy collection install -r requirements.yaml
    - ansible-galaxy install -r requirements.yaml
    - ansible-playbook -i inventory/hosts site.yaml
  when: manual

build-runtime:
  <<: *build-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend
    CONTAINERFILE: Containerfile
    BUILDTARGET: runtime

build-tests:
  <<: *build-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests
    CONTAINERFILE: Containerfile
    BUILDTARGET: tests

build-migrations:
  <<: *build-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations
    CONTAINERFILE: Containerfile
    BUILDTARGET: migrations

build-ml:
  <<: *build-config
  when: manual
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/ml
    CONTAINERFILE: Containerfile
    BUILDTARGET: ml

lint:
  <<: *uv-job
  stage: test
  script:
    - source $HOME/.local/bin/env
    - uv sync --group linters --frozen
    - source $UV_PROJECT_ENVIRONMENT/bin/activate
    - just lint
  allow_failure: true
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
    - if: $CI_COMMIT_TAG

test:
  <<: *docker-job
  stage: test
  tags:
    - beta
  variables:
    COMPOSE_PROFILES: |
      --profile migrations
      --profile tests
      --profile ml
  script:
    - apk add --no-cache docker-compose
    - export PROFILES="$(printf '%s ' $COMPOSE_PROFILES)"
    - cp "$TEST_STAGE_FIREBASE_CONF" ./infrastructure/configs/backend/firebase.json
    - |
      (
        while true; do
          docker compose -f compose.yaml $PROFILES logs -f 2>&1
          sleep 1
        done
      ) | tee -a compose.log &
    - LOGS_PID=$!
    - |
      REGISTRY_PREFIX=$CI_REGISTRY_IMAGE IMAGE_TAG=$CI_COMMIT_SHA \
      docker compose -f compose.yaml -f compose.prod.yaml \
      $PROFILES up -d --quiet-pull --quiet-build 2>&1 | tee compose.log
    - |
      TEST_CONTAINER_ID=$(docker compose -f compose.yaml $PROFILES ps -q tests -a)
      timeout 600 docker wait $TEST_CONTAINER_ID
      TEST_EXIT_CODE=$(docker inspect --format "{{.State.ExitCode}}" $TEST_CONTAINER_ID)

      if [ $TEST_EXIT_CODE -eq 0 ]; then
        echo "Tests passed."
      else
        echo "Tests failed with exit code $TEST_EXIT_CODE."
        exit 1
      fi
    - |
      docker compose -f compose.yaml $PROFILES down
    - cat .cov/coverage.txt
  artifacts:
    paths:
      - ./.cov
      - ./compose.log
    reports:
      coverage_report:
        coverage_format: cobertura
        path: .cov/coverage.xml
    expire_in: 1 week
    when: always
  coverage: /TOTAL.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
  dependencies:
    - build-runtime
    - build-tests
    - build-migrations

sast-filesystem:
  <<: *trivy-fs-scan

sast-image-runtime:
  <<: *trivy-image-scan
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend
    IMAGE_TYPE: runtime
  dependencies:
    - build-runtime

sast-image-tests:
  <<: *trivy-image-scan
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests
    IMAGE_TYPE: tests
  dependencies:
    - build-tests

sast-image-migrations:
  <<: *trivy-image-scan
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations
    IMAGE_TYPE: migrations
  dependencies:
    - build-migrations

# sast-image-ml:
#   <<: *trivy-image-scan
#   variables:
#     IMAGE_NAME: $BASE_IMAGE_NAME/ml
#     IMAGE_TYPE: ml
#   dependencies:
#     - build-ml

tag-runtime:
  <<: *tag-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend

tag-tests:
  <<: *tag-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-tests

tag-migrations:
  <<: *tag-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/backend-migrations

tag-ml:
  <<: *tag-config
  variables:
    IMAGE_NAME: $BASE_IMAGE_NAME/ml

webhook-migrations-deploy:
  <<: *webhook-config
  stage: deploy
  variables:
    WEBHOOK_URL: $WEBHOOK_URL_MIGRATIONS
  resource_group: staging
  dependencies:
    - build-migrations
    - sast-image-migrations

webhook-backend-deploy:
  <<: *webhook-config
  stage: deploy
  variables:
    WEBHOOK_URL: $WEBHOOK_URL_BACKEND
  environment:
    name: staging
    url: https://team-39-alpha-gm5qjkou.hack.prodcontest.ru
  resource_group: staging
  dependencies:
    - build-runtime
    - sast-image-runtime

webhook-ml-deploy:
  <<: *webhook-config
  stage: deploy
  variables:
    WEBHOOK_URL: $WEBHOOK_URL_ML
  resource_group: staging
  dependencies:
    - build-ml
    # - sast-image-ml

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
    - if: $CI_COMMIT_TAG
