stages: - test - build - deploy variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" CI_PYTHON_PACKAGES_DIR: "$CI_PROJECT_DIR/.ci-packages" APP_IMAGE_REPOSITORY: "$CI_REGISTRY_IMAGE/app" ASTERISK_IMAGE_REPOSITORY: "$CI_REGISTRY_IMAGE/asterisk" APP_IMAGE_TAG: "$CI_COMMIT_SHA" ASTERISK_IMAGE_TAG: "$CI_COMMIT_SHA" APP_IMAGE: "$CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA" ASTERISK_IMAGE: "$CI_REGISTRY_IMAGE/asterisk:$CI_COMMIT_SHA" DOCKER_TLS_CERTDIR: "/certs" DOCKER_HOST: "tcp://docker:2376" DOCKER_TLS_VERIFY: "1" DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" PYTHON_BIN: "python3" CI_POSTGRES_ADMIN_URL: "" .python_job: tags: - crm-shell before_script: - rm -rf "$CI_PYTHON_PACKAGES_DIR" - mkdir -p "$CI_PYTHON_PACKAGES_DIR" - $PYTHON_BIN -m pip install --target "$CI_PYTHON_PACKAGES_DIR" -r requirements.txt - export PYTHONPATH="$CI_PYTHON_PACKAGES_DIR${PYTHONPATH:+:$PYTHONPATH}" test: extends: .python_job stage: test script: - $PYTHON_BIN -m pytest -q postgres-readiness: extends: .python_job stage: test rules: - if: '$ENABLE_POSTGRES_READINESS == "1" && $CI_POSTGRES_ADMIN_URL' - when: never variables: SCHEMA_MANAGEMENT_MODE: migrations CC_DATA_DIR: .testdata_pg EVENT_BUS_ENABLED: "0" APP_TOKEN_SECRET: ci-secret ALLOW_LEGACY_HEADER_AUTH: "1" TELEGRAM_BOT_ENABLED: "0" WHATSAPP_OUTBOUND_ENABLED: "0" AI_PROVIDER: stub AI_TELEGRAM_ENABLED: "0" AI_WHATSAPP_ENABLED: "0" AI_VOICE_ENABLED: "0" script: - | export CI_PG_DB="mvpcc_ci_${CI_PIPELINE_IID}_${CI_JOB_ID}" $PYTHON_BIN - <<'PY' import os import psycopg admin_url = os.environ["CI_POSTGRES_ADMIN_URL"] db_name = os.environ["CI_PG_DB"] with psycopg.connect(admin_url, autocommit=True) as conn: with conn.cursor() as cur: cur.execute(f'DROP DATABASE IF EXISTS "{db_name}" WITH (FORCE)') cur.execute(f'CREATE DATABASE "{db_name}"') PY - export DATABASE_URL="postgresql://postgres@127.0.0.1:5433/${CI_PG_DB}" - $PYTHON_BIN scripts/migrate_core_db.py - $PYTHON_BIN -m pytest -q tests/test_postgres_readiness.py after_script: - | export CI_PG_DB="mvpcc_ci_${CI_PIPELINE_IID}_${CI_JOB_ID}" $PYTHON_BIN - <<'PY' import os import psycopg admin_url = os.environ["CI_POSTGRES_ADMIN_URL"] db_name = os.environ["CI_PG_DB"] with psycopg.connect(admin_url, autocommit=True) as conn: with conn.cursor() as cur: cur.execute(f'DROP DATABASE IF EXISTS "{db_name}" WITH (FORCE)') PY .docker_build: tags: - call-center-prod variables: DOCKER_HOST: "" DOCKER_TLS_VERIFY: "" DOCKER_CERT_PATH: "" DOCKER_TLS_CERTDIR: "" image: docker:27 services: - name: docker:27-dind alias: docker before_script: - command -v docker >/dev/null - echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin rules: - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_TAG' - when: never build-app-image: extends: .docker_build stage: build needs: - test - job: postgres-readiness optional: true script: - docker build -t "$APP_IMAGE" . - docker push "$APP_IMAGE" - | if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then docker tag "$APP_IMAGE" "$APP_IMAGE_REPOSITORY:latest" docker push "$APP_IMAGE_REPOSITORY:latest" fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker tag "$APP_IMAGE" "$APP_IMAGE_REPOSITORY:$CI_COMMIT_TAG" docker push "$APP_IMAGE_REPOSITORY:$CI_COMMIT_TAG" fi build-asterisk-image: extends: .docker_build stage: build needs: - test - job: postgres-readiness optional: true script: - docker build -f deployment/asterisk/Dockerfile -t "$ASTERISK_IMAGE" . - docker push "$ASTERISK_IMAGE" - | if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then docker tag "$ASTERISK_IMAGE" "$ASTERISK_IMAGE_REPOSITORY:latest" docker push "$ASTERISK_IMAGE_REPOSITORY:latest" fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker tag "$ASTERISK_IMAGE" "$ASTERISK_IMAGE_REPOSITORY:$CI_COMMIT_TAG" docker push "$ASTERISK_IMAGE_REPOSITORY:$CI_COMMIT_TAG" fi deploy-production: stage: deploy tags: - call-center-prod image: alpine:3.21 needs: - build-app-image - build-asterisk-image environment: name: production before_script: - command -v ssh >/dev/null - command -v rsync >/dev/null - mkdir -p ~/.ssh - chmod 700 ~/.ssh - printf '%s\n' "$DEPLOY_SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts script: - test -n "$DEPLOY_HOST" - test -n "$DEPLOY_USER" - test -n "$DEPLOY_PATH" - ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_PATH/deployment'" - rsync -av deployment/ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/deployment/" - | ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "mkdir -p '$DEPLOY_PATH/.data_local/generated_ivr_yandex/ivr' '$DEPLOY_PATH/.data_local/recordings' '$DEPLOY_PATH/.asterisk_assets'" - | ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "printf 'APP_IMAGE=%s\nASTERISK_IMAGE=%s\n' '$APP_IMAGE' '$ASTERISK_IMAGE' > '$DEPLOY_PATH/deployment/.env.images'" - | ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "echo '$CI_REGISTRY_PASSWORD' | docker login '$CI_REGISTRY' -u '$CI_REGISTRY_USER' --password-stdin && \ cd '$DEPLOY_PATH/deployment' && \ docker compose --env-file .env.images -f docker-compose.server.registry.yml pull && \ docker compose --env-file .env.images -f docker-compose.server.registry.yml up -d && \ docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml pull && \ docker compose --env-file .env.images -f docker-compose.asterisk.server.registry.yml up -d" rules: - if: '$ENABLE_REGISTRY_BUILD == "1" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' when: manual - when: never allow_failure: false deploy-production-source: stage: deploy tags: - call-center-prod needs: - test environment: name: production before_script: - command -v rsync >/dev/null - command -v docker >/dev/null - | if [ -n "$DEPLOY_HOST" ] && [ -n "$DEPLOY_USER" ]; then command -v ssh >/dev/null mkdir -p ~/.ssh chmod 700 ~/.ssh printf '%s\n' "$DEPLOY_SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts fi script: - export DEPLOY_TARGET_PATH="${DEPLOY_PATH:-/home/mvpcc/call-center}" - | if [ -n "$DEPLOY_HOST" ] && [ -n "$DEPLOY_USER" ]; then ssh "$DEPLOY_USER@$DEPLOY_HOST" "mkdir -p '$DEPLOY_TARGET_PATH'" rsync -av --exclude '.git/' --exclude '.tmp/' --exclude '.codex_tmp/' --exclude '.local_stack/' --exclude '.data_pg/' ./ "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_TARGET_PATH/" ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "mkdir -p '$DEPLOY_TARGET_PATH/.data_local/generated_ivr_yandex/ivr' '$DEPLOY_TARGET_PATH/.data_local/recordings' '$DEPLOY_TARGET_PATH/.asterisk_assets'" ssh "$DEPLOY_USER@$DEPLOY_HOST" \ "cd '$DEPLOY_TARGET_PATH/deployment' && \ docker compose -f docker-compose.server.yml up -d --build && \ docker compose -f docker-compose.asterisk.server.yml up -d --build" else run_local() { if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then sudo -n "$@" else "$@" fi } run_local mkdir -p "$DEPLOY_TARGET_PATH" run_local rsync -av --exclude '.git/' --exclude '.tmp/' --exclude '.codex_tmp/' --exclude '.local_stack/' --exclude '.data_pg/' ./ "$DEPLOY_TARGET_PATH/" run_local mkdir -p "$DEPLOY_TARGET_PATH/.data_local/generated_ivr_yandex/ivr" "$DEPLOY_TARGET_PATH/.data_local/recordings" "$DEPLOY_TARGET_PATH/.asterisk_assets" cd "$DEPLOY_TARGET_PATH/deployment" run_local docker compose -f docker-compose.server.yml up -d --build run_local docker compose -f docker-compose.asterisk.server.yml up -d --build fi rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' when: manual - when: never allow_failure: false