name: build-and-deploy on: push: branches: [main] tags: ["v*"] pull_request: branches: [main] workflow_dispatch: env: REGISTRY: ${{ vars.REGISTRY || 'git.ngbackend.cloud' }} IMAGE: ${{ vars.IMAGE_NAME || 'parham.monfared/cx-ui' }} jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: { python-version: "3.12" } - name: Install backend dependencies run: pip install -r backend/requirements.txt - name: Runbook and screening tests # These are pure-logic tests: no Prometheus, no CX-Tools, no network. run: | cd backend python tests/test_runbooks.py python tests/test_screening.py python tests/test_api.py - uses: actions/setup-node@v4 with: { node-version: "22" } - name: Build frontend run: | cd frontend npm ci --no-audit --no-fund || npm install --no-audit --no-fund npm run typecheck npm run build image: needs: test if: github.event_name != 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_TOKEN }} - uses: docker/metadata-action@v5 id: meta with: images: ${{ env.REGISTRY }}/${{ env.IMAGE }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=sha,prefix=,format=short - uses: docker/build-push-action@v6 with: context: . file: backend/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy: needs: image if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest environment: ${{ vars.DEPLOY_ENVIRONMENT || 'production' }} steps: - uses: actions/checkout@v4 - name: Write kubeconfig run: | mkdir -p "$HOME/.kube" echo "${{ secrets.KUBECONFIG }}" | base64 -d > "$HOME/.kube/config" chmod 600 "$HOME/.kube/config" - name: Sync secrets # Applied imperatively so nothing sensitive is ever committed. Every # value comes from the repository/environment secret store. run: | kubectl -n "${{ vars.K8S_NAMESPACE || 'cx-triage' }}" create secret generic cx-triage \ --from-literal=CX_SECRET_KEY='${{ secrets.CX_SECRET_KEY }}' \ --from-literal=CX_DATABASE_URL='${{ secrets.CX_DATABASE_URL }}' \ --from-literal=CX_OIDC_CLIENT_ID='${{ secrets.CX_OIDC_CLIENT_ID }}' \ --from-literal=CX_OIDC_CLIENT_SECRET='${{ secrets.CX_OIDC_CLIENT_SECRET }}' \ --from-literal=CX_ZENDESK_SUBDOMAIN='${{ secrets.CX_ZENDESK_SUBDOMAIN }}' \ --from-literal=CX_ZENDESK_EMAIL='${{ secrets.CX_ZENDESK_EMAIL }}' \ --from-literal=CX_ZENDESK_TOKEN='${{ secrets.CX_ZENDESK_TOKEN }}' \ --from-literal=CX_JIRA_BASE='${{ vars.CX_JIRA_BASE }}' \ --from-literal=CX_JIRA_EMAIL='${{ secrets.CX_JIRA_EMAIL }}' \ --from-literal=CX_JIRA_TOKEN='${{ secrets.CX_JIRA_TOKEN }}' \ --from-literal=CX_BOOTSTRAP_ADMIN_PASSWORD='${{ secrets.CX_BOOTSTRAP_ADMIN_PASSWORD }}' \ --dry-run=client -o yaml | kubectl apply -f - - name: Render and apply manifests env: IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} NAMESPACE: ${{ vars.K8S_NAMESPACE || 'cx-triage' }} DOMAIN: ${{ vars.CX_DOMAIN }} BASE_URL: https://${{ vars.CX_DOMAIN }} OIDC_ENABLED: ${{ vars.CX_OIDC_ENABLED || 'true' }} OIDC_ISSUER: ${{ vars.CX_OIDC_ISSUER }} OIDC_ADMIN_GROUP: ${{ vars.CX_OIDC_ADMIN_GROUP || 'cx-triage-admins' }} PROMETHEUS_BASE: ${{ vars.CX_PROMETHEUS_BASE }} FEATURE_SEND_ENABLED: ${{ vars.CX_FEATURE_SEND_ENABLED || 'false' }} FEATURE_ZENDESK: ${{ vars.CX_FEATURE_ZENDESK || 'false' }} FEATURE_JIRA: ${{ vars.CX_FEATURE_JIRA || 'false' }} FEATURE_LINKAGE_SCAN: ${{ vars.CX_FEATURE_LINKAGE_SCAN || 'true' }} SEND_DAILY_CAP: ${{ vars.CX_SEND_DAILY_CAP || '25' }} JIRA_PROJECT: ${{ vars.CX_JIRA_PROJECT || 'INFRA' }} INGRESS_CLASS: ${{ vars.INGRESS_CLASS || 'nginx' }} TLS_ISSUER: ${{ vars.TLS_ISSUER || 'letsencrypt-prod' }} REPLICAS: ${{ vars.REPLICAS || '1' }} run: | for f in deploy/k8s/*.yaml; do envsubst < "$f" done > /tmp/rendered.yaml kubectl apply -f /tmp/rendered.yaml kubectl -n "$NAMESPACE" rollout status deployment/cx-triage --timeout=5m