name: GoChat CI/CD Pipeline # Reference: Chatwoot uses CircleCI for CI/CD; this is the Go equivalent using GitHub Actions # Multi-stage pipeline: test → build → security → deploy on: push: branches: [main, develop, 'release/**'] pull_request: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: gochat/gochat GOPROXY: https://goproxy.cn,direct jobs: # ---- Stage 1: Lint + Test ---- test: name: Test & Lint runs-on: ubuntu-latest strategy: matrix: db-mode: [postgres, sqlite] services: postgres: image: pgvector/pgvector:pg16 env: POSTGRES_DB: gochat_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis: image: redis:7-alpine ports: - 6379:6379 options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.25' # Lint - name: Run golangci-lint uses: golangci/golangci-lint-action@v6 with: version: v1.64 working-directory: backend # Vet - name: Run go vet working-directory: backend run: go vet ./... # Unit + Integration Tests - name: Run unit & integration tests working-directory: backend env: GOCHAT_ENV: test GOCHAT_TEST_DB: ${{ matrix.db-mode }} POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: gochat_test REDIS_HOST: localhost REDIS_PORT: 6379 GOPROXY: https://goproxy.cn,direct run: go test -v -race -coverprofile=coverage.out -timeout 180s ./internal/... ./pkg/... ./cmd/... # E2E Tests (only on postgres matrix) - name: Run e2e tests if: matrix.db-mode == 'postgres' working-directory: backend env: GOCHAT_ENV: test POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: gochat_test REDIS_HOST: localhost REDIS_PORT: 6379 run: go test -v -timeout 120s ./tests/e2e/... # Benchmark (quick sanity check, not full bench) - name: Run benchmarks working-directory: backend env: GOCHAT_ENV: test GOCHAT_TEST_DB: ${{ matrix.db-mode }} POSTGRES_HOST: localhost POSTGRES_PORT: 5432 POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: gochat_test run: go test -bench=. -benchtime=1s -run=^$ -timeout 60s ./internal/service/... ./pkg/crypto/... # Coverage report - name: Upload coverage if: matrix.db-mode == 'postgres' uses: codecov/codecov-action@v4 with: files: backend/coverage.out # ---- Stage 2: Security Scan ---- security: name: Security Scan needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.25' # Gosec — Go security scanner - name: Run gosec uses: securego/gosec@master with: args: '-no-fail ./...' working-directory: backend # Dependency vulnerability scan - name: Run govulncheck working-directory: backend run: go install golang.org/x/vuln/cmd/govulncheck@latest && govulncheck ./... # Trivy filesystem scan - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'table' exit-code: '1' severity: 'CRITICAL,HIGH' # ---- Stage 3: Build Docker Image ---- build: name: Build Docker Image needs: [test, security] runs-on: ubuntu-latest if: github.event_name == 'push' # Only build on push, not PRs permissions: contents: read packages: write outputs: image_tag: ${{ steps.meta.outputs.tags }} image_digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@v4 # Set up Docker Buildx for multi-platform builds - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # Login to GHCR - name: Login to GHCR uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # Extract metadata (tags, labels) - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha # Build and push - name: Build and push id: build uses: docker/build-push-action@v5 with: context: . file: ./deploy/docker/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | VERSION=${{ github.ref_name }} COMMIT_SHA=${{ github.sha }} BUILD_DATE=${{ github.event.head_commit.timestamp }} cache-from: type=gha cache-to: type=gha,mode=max # Scan Docker image with Trivy - name: Scan Docker image uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'table' exit-code: '1' severity: 'CRITICAL,HIGH' # ---- Stage 4: Validate Helm Chart ---- helm-validate: name: Validate Helm Chart needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Helm uses: azure/setup-helm@v3 with: version: 'v3.14.0' - name: Set up kubeval run: | curl -sfL https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-linux-amd64.tar.gz | tar -xz chmod +x kubeconform && mv kubeconform /usr/local/bin/ - name: Lint Helm chart run: helm lint deploy/helm/gochat/ - name: Template and validate Helm chart run: | helm template gochat deploy/helm/gochat/ \ --values deploy/helm/gochat/values.yaml \ --values deploy/helm/gochat/values-production.yaml \ | kubeconform -summary -kubernetes-version 1.29.0 # ---- Stage 5: Deploy to Staging ---- deploy-staging: name: Deploy to Staging needs: [build, helm-validate] runs-on: ubuntu-latest if: github.ref == 'refs/heads/develop' environment: staging steps: - uses: actions/checkout@v4 - name: Deploy to staging uses: stefanprodan/helm-gh-action@v1.7.0 with: context: ./deploy/helm/gochat chart-ref: ./deploy/helm/gochat cluster-config: staging-k8s-config kube-config: ${{ secrets.KUBE_CONFIG_STAGING }} namespace: gochat-staging values: values.yaml value-files: >- values-staging.yaml release-name: gochat-staging atomic: true # Post-deploy health check - name: Health check run: | chmod +x backend/scripts/health_check.sh GOCHAT_HOST=gochat-staging GOCHAT_PORT=3000 ./backend/scripts/health_check.sh --full --timeout 30 # ---- Stage 6: Deploy to Production ---- deploy-production: name: Deploy to Production needs: [build, helm-validate] runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/heads/release/') environment: production steps: - uses: actions/checkout@v4 - name: Deploy to production uses: stefanprodan/helm-gh-action@v1.7.0 with: context: ./deploy/helm/gochat chart-ref: ./deploy/helm/gochat cluster-config: production-k8s-config kube-config: ${{ secrets.KUBE_CONFIG_PRODUCTION }} namespace: gochat-production values: values.yaml value-files: >- values-production.yaml release-name: gochat-production atomic: true # Post-deploy health check - name: Health check run: | chmod +x backend/scripts/health_check.sh GOCHAT_HOST=gochat-production GOCHAT_PORT=3000 ./backend/scripts/health_check.sh --full --timeout 30