--- kind: pipeline type: docker name: smartsoltech-ci platform: os: linux arch: amd64 services: - name: postgres image: postgres:17-alpine environment: POSTGRES_DB: smartsoltech_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: trust ports: - 5432 - name: redis image: redis:7-alpine ports: - 6379 steps: - name: code-quality image: python:3.10-slim environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/smartsoltech_test commands: - apt-get update && apt-get install -y git - pip install --upgrade pip - pip install flake8 black isort bandit safety - echo "Checking code quality..." - flake8 smartsoltech/ --max-line-length=88 --exclude=migrations,staticfiles --ignore=E203,W503 || true - echo "Checking code formatting..." - black --check smartsoltech/ --line-length=88 --target-version=py310 || true - echo "Checking imports..." - isort --check-only smartsoltech/ --profile=black || true - echo "Security scan..." - bandit -r smartsoltech/ -x "*/migrations/*,*/staticfiles/*" -ll || true - echo "Checking dependencies..." - safety check --file requirements.txt --ignore=70612 || true - name: install-dependencies image: python:3.10-slim commands: - apt-get update && apt-get install -y libpq-dev gcc git curl - pip install --upgrade pip - pip install -r requirements.txt - pip install coverage pytest-django pytest-cov - echo "Dependencies installed" depends_on: - code-quality - name: database-tests image: python:3.10-slim environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/smartsoltech_test SECRET_KEY: test-secret-key-for-ci-very-long-and-secure-key-12345 DEBUG: "False" ALLOWED_HOSTS: localhost,127.0.0.1,postgres DJANGO_SETTINGS_MODULE: smartsoltech.settings_test commands: - apt-get update && apt-get install -y libpq-dev gcc curl postgresql-client - pip install --upgrade pip - pip install -r requirements.txt - echo "Waiting for PostgreSQL..." - sleep 15 - echo "Checking database connection..." - until pg_isready -h postgres -p 5432 -U postgres; do echo "Waiting for postgres..."; sleep 2; done - echo "Checking migrations..." - cd smartsoltech - python manage.py check --settings=smartsoltech.settings_test - python manage.py makemigrations --check --dry-run --settings=smartsoltech.settings_test - python manage.py migrate --settings=smartsoltech.settings_test - echo "Database setup completed" depends_on: - install-dependencies - name: unit-tests image: python:3.10-slim environment: DATABASE_URL: postgresql://postgres:postgres@postgres:5432/smartsoltech_test SECRET_KEY: test-secret-key-for-ci-very-long-and-secure-key-12345 DEBUG: "False" ALLOWED_HOSTS: localhost,127.0.0.1,postgres TELEGRAM_BOT_TOKEN: test-token-for-ci DJANGO_SETTINGS_MODULE: smartsoltech.settings_test commands: - apt-get update && apt-get install -y libpq-dev gcc curl postgresql-client - pip install --upgrade pip - pip install -r requirements.txt - echo "Waiting for PostgreSQL..." - until pg_isready -h postgres -p 5432 -U postgres; do echo "Waiting for postgres..."; sleep 2; done - cd smartsoltech - echo "Running unit tests..." - python manage.py test --verbosity=2 --settings=smartsoltech.settings_test --keepdb - echo "Generating coverage report..." - coverage run --source='.' manage.py test --settings=smartsoltech.settings_test --keepdb - coverage report --show-missing - coverage xml - echo "Unit tests completed successfully" depends_on: - database-tests - name: build-docker-image image: docker:24-dind volumes: - name: docker-sock path: /var/run/docker.sock commands: - echo "Building Docker image..." - docker build -t smartsoltech:latest . - echo "Docker image built successfully" depends_on: - unit-tests - name: docker-compose-tests image: docker/compose:latest volumes: - name: docker-sock path: /var/run/docker.sock commands: - echo "Running Docker Compose tests..." - apk add --no-cache curl - docker-compose -f docker-compose.test.yml build - docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from django_test - docker-compose -f docker-compose.test.yml down -v - echo "Docker tests completed" depends_on: - build-docker-image - name: security-scan image: aquasec/trivy:latest volumes: - name: docker-sock path: /var/run/docker.sock commands: - echo "Security scanning Docker image..." - | if docker image inspect smartsoltech:latest >/dev/null 2>&1; then echo "Image found, starting security scan..." trivy image --exit-code 0 --severity HIGH,CRITICAL --no-progress smartsoltech:latest else echo "Image smartsoltech:latest not found, scanning base Python image instead..." trivy image --exit-code 0 --severity HIGH,CRITICAL --no-progress python:3.10-slim fi - echo "Security scan completed" depends_on: - docker-compose-tests - name: test-production-connectivity image: alpine:latest environment: PROD_HOST: from_secret: production_host commands: - echo "Testing production server connectivity..." - apk add --no-cache curl netcat-openbsd - | if [ -z "$PROD_HOST" ]; then echo "⚠️ Production host not configured, skipping connectivity test" exit 0 fi - echo "Testing SSH connectivity to $PROD_HOST..." - | if nc -z $PROD_HOST 22 2>/dev/null; then echo "✅ SSH port 22 is accessible on $PROD_HOST" else echo "⚠️ SSH port 22 is not accessible, but continuing CI" fi - echo "Testing HTTPS connectivity..." - | if curl -f -s --connect-timeout 10 https://smartsoltech.kr/health/ >/dev/null 2>&1; then echo "✅ Production HTTPS service is accessible" else echo "⚠️ Production HTTPS service check failed, but continuing CI" fi - echo "✅ Production connectivity test completed" depends_on: - security-scan when: branch: - master - main - name: deploy-to-staging image: alpine:latest environment: STAGING_HOST: from_secret: staging_host STAGING_USER: from_secret: staging_user STAGING_KEY: from_secret: staging_key commands: - echo "Checking staging environment configuration..." - apk add --no-cache openssh-client git curl netcat-openbsd - | if [ -z "$STAGING_HOST" ] || [ -z "$STAGING_USER" ]; then echo "⚠️ Staging credentials not configured" echo "ℹ️ Skipping staging deployment - this is normal for development CI" echo "✅ Continuing with integration tests on local environment" exit 0 fi - echo "Deploying to staging server:" $STAGING_HOST - mkdir -p ~/.ssh - echo "$STAGING_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H $STAGING_HOST >> ~/.ssh/known_hosts 2>/dev/null || true - echo "Testing staging server connectivity..." - | if ! nc -z $STAGING_HOST 22 2>/dev/null; then echo "❌ Cannot connect to staging server on port 22" echo "⚠️ Skipping staging deployment due to connectivity issues" exit 0 fi - echo "Deploying to staging environment..." - | ssh $STAGING_USER@$STAGING_HOST "cd /opt/smartsoltech-staging && echo 'Fetching latest changes...' && git fetch origin && git reset --hard origin/master && echo 'Restarting services...' && docker-compose down --timeout 30 && docker-compose pull && docker-compose up -d --build" || { echo "❌ Staging deployment failed" echo "⚠️ Continuing CI pipeline - staging failures are non-critical" } - echo "✅ Staging deployment step completed" depends_on: - test-production-connectivity when: branch: - master - main - name: integration-tests image: alpine:latest environment: STAGING_HOST: from_secret: staging_host commands: - echo "Starting comprehensive integration tests..." - apk add --no-cache curl jq netcat-openbsd - | # Определяем target для тестирования if [ -n "$STAGING_HOST" ]; then # Проверяем доступность staging сервера if nc -z -w5 $STAGING_HOST 80 2>/dev/null; then export TEST_TARGET="http://$STAGING_HOST" echo "🎯 Testing staging environment: $TEST_TARGET" else echo "⚠️ Staging server not accessible, falling back to local testing" export TEST_TARGET="http://localhost:8000" echo "🏠 Testing local environment: $TEST_TARGET" fi else export TEST_TARGET="http://localhost:8000" echo "🏠 Testing local environment: $TEST_TARGET" fi - echo "Waiting for services to be ready..." - sleep 30 - echo "Running endpoint availability tests..." - | test_endpoint() { local url="$1" local description="$2" echo "Testing $description ($url)..." local status_code=$(curl -o /dev/null -s -w "%{http_code}" -m 10 "$url" 2>/dev/null || echo "000") if [ "$status_code" = "200" ]; then echo "✅ $description - OK (HTTP $status_code)" return 0 elif [ "$status_code" = "404" ]; then echo "⚠️ $description - Not Found (HTTP $status_code)" return 1 elif [ "$status_code" = "000" ]; then echo "❌ $description - Connection Failed" return 1 else echo "⚠️ $description - Unexpected status (HTTP $status_code)" return 1 fi } - | # Счетчик ошибок errors=0 total_tests=0 # Основные страницы total_tests=$((total_tests + 1)) test_endpoint "$TEST_TARGET/" "Homepage" || ((errors++)) total_tests=$((total_tests + 1)) test_endpoint "$TEST_TARGET/services/" "Services page" || ((errors++)) total_tests=$((total_tests + 1)) test_endpoint "$TEST_TARGET/career/" "Career page" || ((errors++)) total_tests=$((total_tests + 1)) test_endpoint "$TEST_TARGET/contact/" "Contact page" || ((errors++)) total_tests=$((total_tests + 1)) test_endpoint "$TEST_TARGET/admin/" "Admin panel" || echo "ℹ️ Admin panel test failed (expected for production)" echo "" echo "📊 Integration Test Results:" echo " Total tests: $total_tests" echo " Failures: $errors" echo " Success rate: $(( (total_tests - errors) * 100 / total_tests ))%" if [ $errors -gt 2 ]; then echo "❌ Too many critical endpoint failures ($errors)" exit 1 elif [ $errors -gt 0 ]; then echo "⚠️ Some tests failed but within acceptable limits" else echo "✅ All integration tests passed successfully" fi - echo "✅ Integration testing phase completed" depends_on: - deploy-to-staging - name: notify-success image: plugins/webhook settings: urls: from_secret: telegram_webhook_url content_type: application/json template: | { "chat_id": "${TELEGRAM_CHAT_ID:-@smartsoltech_ci}", "text": "✅ *SmartSolTech CI/CD*\n\nBuild completed successfully!\n\n📝 *Commit:* `${DRONE_COMMIT_SHA:0:8}`\n👤 *Author:* ${DRONE_COMMIT_AUTHOR}\n🌿 *Branch:* ${DRONE_BRANCH}\n⏱ *Time:* ${DRONE_BUILD_FINISHED}\n\n🔗 [Details](${DRONE_BUILD_LINK})", "parse_mode": "Markdown" } environment: TELEGRAM_CHAT_ID: from_secret: telegram_chat_id when: status: - success # Отключаем до настройки секретов event: exclude: - '*' depends_on: - integration-tests - name: notify-failure image: plugins/webhook settings: urls: from_secret: telegram_webhook_url content_type: application/json template: | { "chat_id": "${TELEGRAM_CHAT_ID:-@smartsoltech_ci}", "text": "❌ *SmartSolTech CI/CD*\n\nBuild failed!\n\n📝 *Commit:* `${DRONE_COMMIT_SHA:0:8}`\n👤 *Author:* ${DRONE_COMMIT_AUTHOR}\n🌿 *Branch:* ${DRONE_BRANCH}\n⏱ *Time:* ${DRONE_BUILD_FINISHED}\n\n🔗 [Logs](${DRONE_BUILD_LINK})", "parse_mode": "Markdown" } environment: TELEGRAM_CHAT_ID: from_secret: telegram_chat_id when: status: - failure # Отключаем до настройки секретов event: exclude: - '*' depends_on: - integration-tests volumes: - name: docker-sock host: path: /var/run/docker.sock trigger: branch: - master - main - develop - feature/* event: - push - pull_request --- kind: pipeline type: docker name: production-deploy platform: os: linux arch: amd64 steps: - name: check-production-server image: alpine:latest environment: PROD_HOST: from_secret: production_host PROD_USER: from_secret: production_user commands: - echo "Checking production server connectivity..." - apk add --no-cache openssh-client curl netcat-openbsd - | if [ -z "$PROD_HOST" ] || [ -z "$PROD_USER" ]; then echo "❌ Production server credentials not configured" exit 1 fi - echo "Testing SSH connectivity to $PROD_HOST..." - | if ! nc -z $PROD_HOST 22; then echo "❌ SSH port 22 is not accessible on $PROD_HOST" exit 1 fi - echo "Testing HTTPS connectivity..." - | if curl -f -s --connect-timeout 10 https://smartsoltech.kr >/dev/null; then echo "✅ HTTPS service is accessible" else echo "⚠️ HTTPS service check failed, but continuing deployment" fi - echo "✅ Production server checks passed" - name: deploy-production image: docker:24-dind volumes: - name: docker-sock path: /var/run/docker.sock environment: PROD_HOST: from_secret: production_host PROD_USER: from_secret: production_user PROD_KEY: from_secret: production_ssh_key commands: - echo "Deploying to production..." - apk add --no-cache openssh-client git curl - mkdir -p ~/.ssh - echo "$PROD_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H $PROD_HOST >> ~/.ssh/known_hosts - echo "Creating backup before deployment..." - | ssh $PROD_USER@$PROD_HOST "cd /opt/smartsoltech && echo 'Creating backup...' && git stash push -m \"Pre-deployment backup \$(date)\" || true && docker-compose down --timeout 30 || true" - echo "Pulling latest changes..." - | ssh $PROD_USER@$PROD_HOST "cd /opt/smartsoltech && git fetch origin && git reset --hard origin/master && git clean -fd" - echo "Running deployment script..." - | ssh $PROD_USER@$PROD_HOST "cd /opt/smartsoltech && if [ -f ./bin/update ]; then chmod +x ./bin/update && ./bin/update else echo 'Update script not found, running manual deployment...' && docker-compose pull && docker-compose up -d --build fi" - echo "Verifying deployment..." - sleep 30 - | for i in 1 2 3; do if curl -f -s --connect-timeout 10 https://smartsoltech.kr >/dev/null; then echo "✅ Deployment verification successful" break else echo "⚠️ Deployment verification attempt $i failed, retrying..." sleep 15 fi if [ $i -eq 3 ]; then echo "❌ Deployment verification failed after 3 attempts" exit 1 fi done - echo "🎉 Production deployment completed successfully" depends_on: - check-production-server - name: notify-production-success image: plugins/webhook settings: urls: from_secret: telegram_webhook_url content_type: application/json template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "🎉 *SmartSolTech Production*\n\n✅ Production deployment completed!\n\n📝 *Commit:* \`${DRONE_COMMIT_SHA:0:8}\`\n👤 *Author:* ${DRONE_COMMIT_AUTHOR}\n🌿 *Branch:* ${DRONE_BRANCH}\n⏱ *Time:* ${DRONE_BUILD_FINISHED}\n\n🌐 [Website](https://smartsoltech.kr)\n🔧 [Admin](https://smartsoltech.kr/admin/)\n📊 [Status Check](https://smartsoltech.kr/health/)", "parse_mode": "Markdown" } environment: TELEGRAM_CHAT_ID: from_secret: telegram_chat_id depends_on: - deploy-production - name: notify-production-failure image: plugins/webhook settings: urls: from_secret: telegram_webhook_url content_type: application/json template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "🚨 *SmartSolTech Production*\n\n❌ Production deployment failed!\n\n📝 *Commit:* \`${DRONE_COMMIT_SHA:0:8}\`\n👤 *Author:* ${DRONE_COMMIT_AUTHOR}\n🌿 *Branch:* ${DRONE_BRANCH}\n⏱ *Time:* ${DRONE_BUILD_FINISHED}\n💥 *Step:* ${DRONE_FAILED_STEPS}\n\n🔗 [View Logs](${DRONE_BUILD_LINK})\n🛠 [Rollback Guide](https://smartsoltech.kr/docs/rollback)", "parse_mode": "Markdown" } environment: TELEGRAM_CHAT_ID: from_secret: telegram_chat_id when: status: - failure depends_on: - deploy-production volumes: - name: docker-sock host: path: /var/run/docker.sock trigger: event: - tag ref: - refs/tags/v* depends_on: - smartsoltech-ci --- kind: pipeline type: docker name: maintenance platform: os: linux arch: amd64 steps: - name: cleanup-docker image: docker:24-dind volumes: - name: docker-sock path: /var/run/docker.sock commands: - echo "Docker cleanup..." - docker system prune -af --volumes - docker image prune -af - echo "Docker cleanup completed" - name: backup-database image: postgres:17-alpine environment: PGHOST: from_secret: db_host PGUSER: from_secret: db_user PGPASSWORD: from_secret: db_password PGDATABASE: from_secret: db_name BACKUP_PATH: from_secret: backup_path commands: - echo "Creating database backup..." - mkdir -p /backups - pg_dump -h $PGHOST -U $PGUSER -d $PGDATABASE --no-password > /backups/backup_$(date +%Y%m%d_%H%M%S).sql - echo "Database backup created" - name: notify-maintenance image: plugins/webhook settings: urls: from_secret: telegram_webhook_url content_type: application/json template: | { "chat_id": "${TELEGRAM_CHAT_ID}", "text": "🛠 *SmartSolTech Maintenance*\n\n✅ Scheduled maintenance completed!\n\n🧹 Docker cleanup\n💾 Database backup\n⏱ *Time:* ${DRONE_BUILD_FINISHED}", "parse_mode": "Markdown" } environment: TELEGRAM_CHAT_ID: from_secret: telegram_chat_id depends_on: - cleanup-docker - backup-database volumes: - name: docker-sock host: path: /var/run/docker.sock trigger: event: - cron cron: - nightly_maintenance