name: Test and Build on: push: branches: [main, develop] pull_request: branches: [main, develop] env: REGISTRY: registry.vshn.net NAMESPACE: vshn-servalafe-prod jobs: # Test job - runs Django tests including pricing tests test: name: Run Django Tests runs-on: ubuntu-latest services: # Use PostgreSQL service for more realistic testing postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres POSTGRES_DB: servala_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.13" - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: Install dependencies run: | uv sync --extra dev - name: Run pricing model tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/servala_test DJANGO_SETTINGS_MODULE: hub.settings run: | echo "::group::Running pricing model tests" uv run --extra dev manage.py test hub.services.tests.test_pricing --verbosity=2 echo "::endgroup::" - name: Run pricing edge case tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/servala_test DJANGO_SETTINGS_MODULE: hub.settings run: | echo "::group::Running pricing edge case tests" uv run --extra dev manage.py test hub.services.tests.test_pricing_edge_cases --verbosity=2 echo "::endgroup::" - name: Run pricing integration tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/servala_test DJANGO_SETTINGS_MODULE: hub.settings run: | echo "::group::Running pricing integration tests" uv run --extra dev manage.py test hub.services.tests.test_pricing_integration --verbosity=2 echo "::endgroup::" - name: Run all Django tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/servala_test DJANGO_SETTINGS_MODULE: hub.settings run: | echo "::group::Running all Django tests" uv run --extra dev manage.py test --verbosity=2 echo "::endgroup::" - name: Run Django system checks env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/servala_test DJANGO_SETTINGS_MODULE: hub.settings run: | echo "::group::Running Django system checks" uv run --extra dev manage.py check --verbosity=2 echo "::endgroup::" # Lint job - code quality checks lint: name: Code Quality Checks runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.13" - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: Install dependencies run: | uv sync --extra dev - name: Run ruff linting run: | echo "::group::Running ruff linting" uv run ruff check . --output-format=github || true echo "::endgroup::" - name: Run ruff formatting check run: | echo "::group::Checking code formatting" uv run ruff format --check . || true echo "::endgroup::" # Security checks security: name: Security Checks runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.13" - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: "uv.lock" - name: Install dependencies run: | uv sync --extra dev - name: Run safety check for known vulnerabilities run: | echo "::group::Running safety check" uv run safety check || true echo "::endgroup::" - name: Run bandit security linter run: | echo "::group::Running bandit security scan" uv run bandit -r hub/ -f json -o bandit-report.json || true if [ -f bandit-report.json ]; then echo "Bandit security scan results:" cat bandit-report.json fi echo "::endgroup::" # Build job - only runs if tests pass build: name: Build Docker Image runs-on: ubuntu-latest needs: [test, lint, security] if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/servala tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # Deploy job - only runs on main branch after successful build deploy: name: Deploy to Production runs-on: ubuntu-latest needs: [build] if: github.ref == 'refs/heads/main' environment: name: production url: https://servala.com/ steps: - name: Checkout code uses: actions/checkout@v4 - name: Deploy to OpenShift env: OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }} OPENSHIFT_TOKEN: ${{ secrets.OPENSHIFT_TOKEN }} run: | # Install OpenShift CLI curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/openshift-client-linux.tar.gz tar -xzf openshift-client-linux.tar.gz sudo mv oc /usr/local/bin/ # Login to OpenShift oc login --token=$OPENSHIFT_TOKEN --server=$OPENSHIFT_SERVER # Apply deployment configuration oc -n ${{ env.NAMESPACE }} apply --overwrite -f deployment/ # Restart deployment to pick up new image oc -n ${{ env.NAMESPACE }} rollout restart deployment/servala # Wait for deployment to complete oc -n ${{ env.NAMESPACE }} rollout status deployment/servala --timeout=300s