# =========================================== # Domain Check API - GitLab CI/CD Pipeline # =========================================== # Registry: (GitLab Container Registry) # Runner: Main Docker Runner (10.11.10.102) - tags: docker, ci, cd # =========================================== stages: - lint - security - test - build - release - deploy variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" # GitLab CI provides these automatically: # CI_REGISTRY = # CI_REGISTRY_IMAGE = didiai-domain-check # CI_REGISTRY_USER / CI_REGISTRY_PASSWORD = auto-generated JWT # Cache pip packages cache: key: ${CI_COMMIT_REF_SLUG} paths: - .cache/pip # =========================================== # LINT Stage # =========================================== lint:python: stage: lint image: python:3.10-slim tags: - docker before_script: - pip install --cache-dir .cache/pip flake8 black isort script: - flake8 api/app --max-line-length=120 --ignore=E501,W503 || true - black --check api/app || true - isort --check-only api/app || true allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH lint:dockerfile: stage: lint image: hadolint/hadolint:latest-debian tags: - docker script: - hadolint api/Dockerfile || true allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH # =========================================== # SECURITY Stage (SAST) # =========================================== sast:bandit: stage: security image: python:3.10-slim tags: - docker before_script: - pip install --cache-dir .cache/pip bandit script: - bandit -r api/app -f json -o bandit-report.json || true - bandit -r api/app -f txt || true artifacts: paths: - bandit-report.json when: always expire_in: 1 week allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH sast:safety: stage: security image: python:3.10-slim tags: - docker before_script: - pip install --cache-dir .cache/pip safety script: - cd api && safety check -r requirements.txt --json > ../safety-report.json || true - cd api && safety check -r requirements.txt || true artifacts: paths: - safety-report.json when: always expire_in: 1 week allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH sast:trivy: stage: security image: name: aquasec/trivy:latest entrypoint: [""] tags: - docker script: - trivy fs --exit-code 0 --severity HIGH,CRITICAL --format json -o trivy-report.json . || true - trivy fs --exit-code 0 --severity HIGH,CRITICAL . || true artifacts: paths: - trivy-report.json when: always expire_in: 1 week allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH # =========================================== # TEST Stage # =========================================== test:api: stage: test image: python:3.10-slim tags: - docker services: - postgres:15-alpine - redis:7-alpine variables: POSTGRES_DB: test_domain_check POSTGRES_USER: test_user POSTGRES_PASSWORD: test_password DATABASE_URL: postgresql://test_user:test_password@postgres:5432/test_domain_check REDIS_URL: redis://redis:6379/0 FLASK_ENV: testing before_script: - cd api - pip install --cache-dir ../.cache/pip -r requirements.txt script: - python -c "from app import create_app; app = create_app(); print('App created successfully')" allow_failure: true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH # =========================================== # BUILD Stage - Push to GitLab Container Registry # =========================================== build:docker: stage: build image: docker:24-dind tags: - docker - ci services: - docker:24-dind before_script: # Login to GitLab Container Registry (auto credentials) - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - echo "Building image $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:latest ./api - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - docker push $CI_REGISTRY_IMAGE:latest - echo "Image pushed to $CI_REGISTRY_IMAGE" rules: - if: $CI_COMMIT_BRANCH == "main" - if: $CI_COMMIT_BRANCH == "develop" # =========================================== # RELEASE Stage - Create deployment bundle # =========================================== release:bundle: stage: release image: alpine:latest tags: - docker script: - apk add --no-cache tar gzip - mkdir -p release-bundle/deploy # Copy deployment files - cp docker-compose.yml release-bundle/deploy/ - cp deploy.sh release-bundle/deploy/ 2>/dev/null || echo "#!/bin/bash" > release-bundle/deploy/deploy.sh - cp .env.example release-bundle/deploy/ 2>/dev/null || cp .env release-bundle/deploy/env.example 2>/dev/null || true # Create IMAGE.txt with registry path - echo "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" > release-bundle/IMAGE.txt - echo "$CI_REGISTRY_IMAGE:latest" >> release-bundle/IMAGE.txt # Create restore script - | cat > release-bundle/restore.sh << 'RESTORE_EOF' #!/bin/bash set -e echo "=== Domain Check API - Restore ===" IMAGE=$(head -1 IMAGE.txt) echo "Pulling image: $IMAGE" docker pull $IMAGE echo "Starting services..." cd deploy docker compose up -d echo "Done! Check: docker compose ps" RESTORE_EOF - chmod +x release-bundle/restore.sh release-bundle/deploy/deploy.sh # Create tarball - tar -czvf release-bundle-${CI_COMMIT_SHORT_SHA}.tgz release-bundle/ - ls -la release-bundle-*.tgz artifacts: paths: - release-bundle-*.tgz expire_in: 30 days rules: - if: $CI_COMMIT_BRANCH == "main" # =========================================== # DEPLOY Stage - Deploy to server via SSH # =========================================== deploy:dev: stage: deploy image: alpine:latest tags: - docker - cd before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts script: - | ssh $DEPLOY_USER@$DEPLOY_HOST << ENDSSH set -e echo "=== Deploying Domain Check API ===" cd /home/admin365/domain-check # Pull latest code git pull origin main # Login to GitLab registry docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY # Pull latest image docker pull $CI_REGISTRY_IMAGE:latest || echo "Pull failed, building locally" # Restart services docker compose down docker compose up -d --build # Health check sleep 10 curl -s http://localhost:51000/health || echo "Health check pending..." docker compose ps echo "=== Deploy complete ===" ENDSSH environment: name: development url: http://domain-check-api:11000 rules: - if: $CI_COMMIT_BRANCH == "main" when: manual allow_failure: true deploy:auto: stage: deploy image: alpine:latest tags: - docker - cd before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - mkdir -p ~/.ssh - chmod 700 ~/.ssh - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts script: - | ssh $DEPLOY_USER@$DEPLOY_HOST << ENDSSH set -e cd /home/admin365/domain-check git pull origin main docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY || true docker pull $CI_REGISTRY_IMAGE:latest || true docker compose up -d --build docker compose ps ENDSSH environment: name: development url: http://domain-check-api:11000 rules: - if: $CI_COMMIT_BRANCH == "main" allow_failure: true # =========================================== # Cleanup # =========================================== cleanup:images: stage: .post image: docker:24-dind tags: - docker services: - docker:24-dind script: - docker image prune -f when: always allow_failure: true rules: - if: $CI_COMMIT_BRANCH == "main"