name: CI on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: env: REGISTRY: forgejo.riotpiao.com IMAGE: forgejo.riotpiao.com/rock/api-gateway DOCKER_HOST: tcp://localhost:2375 jobs: ci: name: CI runs-on: golang steps: - name: Install dependencies run: | apt-get update apt-get install -y docker.io curl nodejs curl -sLO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl && mv kubectl /usr/local/bin/ kubectl version --client - name: Checkout code uses: actions/checkout@v4 - name: Go vet run: go vet ./... - name: Go test run: go test ./... - name: Get short SHA id: sha run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - name: Registry login if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \ --username "${REGISTRY_USER}" --password-stdin env: REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }} REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }} - name: Build Docker image if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | docker build --no-cache \ -t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \ -f Dockerfile . - name: Push image (SHA tag) if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}" # ── Deploy canary pod + integration test ──────────────── # Canary pod runs in-cluster with real secrets and real upstreams # (Temporal, Gotify). Tests run against its pod IP directly. # Zero downtime: production pods untouched until tests pass. - name: Setup kubeconfig if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | mkdir -p ~/.kube echo "${KUBECONFIG_B64}" | base64 -d > ~/.kube/config kubectl get nodes --no-headers | head -1 echo '✓ kubeconfig works' env: KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} - name: Deploy canary pod if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: canary run: | SHA="${{ steps.sha.outputs.short_sha }}" POD_NAME="api-gateway-canary-${SHA}" # Remove any stale canary kubectl delete pod "${POD_NAME}" -n api --ignore-not-found # Create canary pod with real secrets + config kubectl run "${POD_NAME}" \ --image="${IMAGE}:${SHA}" \ --restart=Never \ --namespace=api \ --labels="app=api-gateway-canary,sha=${SHA}" \ --overrides="$(cat <> $GITHUB_OUTPUT echo "pod_ip=${POD_IP}" >> $GITHUB_OUTPUT echo "✓ Canary ready at ${POD_IP}:8080" - name: Run integration tests against canary if: github.event_name == 'push' && github.ref == 'refs/heads/main' id: tests run: | POD_IP="${{ steps.canary.outputs.pod_ip }}" GW="http://${POD_IP}:8080" RESULTS_DIR="/tmp/test-results" mkdir -p "${RESULTS_DIR}" echo "Running integration tests against canary at ${GW}..." sh k8s/tekton/scripts/integration-test.sh RESULT=$(cat "${RESULTS_DIR}/result" 2>/dev/null || echo "fail") SUMMARY=$(cat "${RESULTS_DIR}/summary" 2>/dev/null || echo "unknown") echo "Result: ${RESULT}" echo "Summary: ${SUMMARY}" echo "result=${RESULT}" >> $GITHUB_OUTPUT - name: Cleanup canary pod if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main' run: | POD_NAME="${{ steps.canary.outputs.pod_name }}" kubectl delete pod "${POD_NAME}" -n api --ignore-not-found || true echo "✓ Canary pod removed" - name: Gate on test result if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.tests.outputs.result != 'pass' run: | echo "✗ Integration tests FAILED — production deployment unchanged" exit 1 # ── Zero-downtime promotion ────────────────────────────── # kubectl set image triggers a rolling update: new pods come up # before old pods are terminated. Production traffic uninterrupted. - name: Promote — rolling update if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | SHA="${{ steps.sha.outputs.short_sha }}" kubectl set image deployment/api-gateway \ api-gateway="${IMAGE}:${SHA}" -n api kubectl rollout status deployment/api-gateway -n api --timeout=120s echo "✓ Rolling update complete: ${IMAGE}:${SHA}" - name: Tag :latest (informational) if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | docker tag "${IMAGE}:${{ steps.sha.outputs.short_sha }}" "${IMAGE}:latest" docker push "${IMAGE}:latest" echo "✓ Tagged :latest" - name: Cleanup docker images if: always() run: docker image prune -af 2>&1 | tail -3 || true