diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 3126395..64d50d2 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -21,7 +21,7 @@ jobs: # Stage 1: Validation validate: name: Validation - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw steps: - name: Checkout code uses: actions/checkout@v4 @@ -30,7 +30,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - cache-dependency-path: server/go.sum + cache: false - name: Verify Go version run: | @@ -61,8 +61,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: web/package-lock.json - name: Run npm audit working-directory: web @@ -72,7 +70,7 @@ jobs: # Stage 2: Test Matrix test-server: name: Server Tests - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -82,10 +80,13 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - cache-dependency-path: server/go.sum + cache: false - name: Run server tests working-directory: server + env: + KUBERNETES_SERVICE_HOST: '' + KUBERNETES_SERVICE_PORT: '' run: | go test -v -race -coverprofile=coverage.out -covermode=atomic ./... @@ -106,10 +107,11 @@ jobs: with: name: server-coverage path: server/coverage.out + retention-days: 3 test-web: name: Web Tests - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -119,8 +121,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: web/package-lock.json - name: Install dependencies working-directory: web @@ -137,7 +137,7 @@ jobs: # Stage 3: Build Docker Image build: name: Build Docker Image - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: [test-server, test-web] permissions: contents: read @@ -201,8 +201,6 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max build-args: | GO_VERSION=1.24.3 NODE_MAJOR=20 @@ -217,7 +215,7 @@ jobs: # resolution happens before `if` is evaluated. An in-line action has no such problem. grype-scan: name: Security Scan (Grype) - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: [build] if: false # Disabled - KubeTTY image too large for Grype to scan in CI (same reasoning as the prior Trivy job) permissions: @@ -236,7 +234,7 @@ jobs: # Stage 5: Helm Chart Validation helm-validate: name: Helm Chart Validation - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -278,11 +276,12 @@ jobs: with: name: helm-charts path: helm-packages/*.tgz + retention-days: 3 # Stage 6: Deploy to Production deploy-production: name: Deploy to Production - runs-on: self-hosted-linux + runs-on: self-hosted-linux-dfw needs: [build, helm-validate] if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') permissions: diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index f9019bd..b8a84d3 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -13,15 +13,14 @@ on: workflow_dispatch: env: - GO_VERSION: '1.23' NODE_VERSION: '20' - COVERAGE_THRESHOLD: 60 + COVERAGE_THRESHOLD: 35 jobs: # Fast validation checks validate: name: Quick Validation - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw steps: - name: Checkout code uses: actions/checkout@v4 @@ -29,13 +28,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} - cache-dependency-path: server/go.sum - - - name: Verify Go version - run: | - go version - go version | grep -q "go1.23" || (echo "ERROR: Must use Go 1.23.x" && exit 1) + go-version-file: server/go.mod + cache: false - name: Check Go formatting working-directory: server @@ -68,8 +62,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: web/package-lock.json - name: Check for package-lock changes working-directory: web @@ -85,7 +77,7 @@ jobs: # Server tests with coverage (unit tests only) test-server: name: Server Tests - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -94,19 +86,32 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} - cache-dependency-path: server/go.sum + go-version-file: server/go.mod + cache: false - name: Run server tests working-directory: server + env: + KUBERNETES_SERVICE_HOST: '' + KUBERNETES_SERVICE_PORT: '' run: | go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + - name: Upload server coverage report + uses: actions/upload-artifact@v4 + with: + name: server-coverage + path: server/coverage.out + retention-days: 3 + # Integration tests with PostgreSQL test-integration: name: Integration Tests - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate + permissions: + contents: read + issues: write services: postgres: image: postgres:16-alpine @@ -128,12 +133,20 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} - cache-dependency-path: server/go.sum + go-version-file: server/go.mod + cache: false - name: Initialize test database run: | - PGPASSWORD=kubetty_test psql -h localhost -U kubetty_test -d kubetty_test -f server/scripts/init-test-db.sql + for migration in server/migrations/*.up.sql; do + echo "Applying ${migration}" + PGPASSWORD=kubetty_test psql \ + -v ON_ERROR_STOP=1 \ + -h localhost \ + -U kubetty_test \ + -d kubetty_test \ + -f "${migration}" + done - name: Run integration tests working-directory: server @@ -144,6 +157,8 @@ jobs: CNPG_PASSWORD: kubetty_test CNPG_DATABASE: kubetty_test KUBETTY_TEST_DB: "true" + KUBERNETES_SERVICE_HOST: '' + KUBERNETES_SERVICE_PORT: '' run: | go test -v -race -run Integration ./... @@ -156,6 +171,8 @@ jobs: CNPG_PASSWORD: kubetty_test CNPG_DATABASE: kubetty_test KUBETTY_TEST_DB: "true" + KUBERNETES_SERVICE_HOST: '' + KUBERNETES_SERVICE_PORT: '' run: | go test -v -race -coverprofile=coverage-integration.out -covermode=atomic ./... @@ -164,12 +181,14 @@ jobs: with: name: integration-coverage path: server/coverage-integration.out - retention-days: 7 + retention-days: 3 - name: Check test coverage + id: coverage working-directory: server run: | - coverage=$(go tool cover -func=coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}') + coverage=$(go tool cover -func=coverage-integration.out | grep total | awk '{print substr($3, 1, length($3)-1)}') + echo "percent=${coverage}" >> "$GITHUB_OUTPUT" echo "### Test Coverage: ${coverage}%" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY @@ -180,25 +199,13 @@ jobs: fi echo "✅ Coverage ${coverage}% meets threshold ${threshold}%" >> $GITHUB_STEP_SUMMARY - - name: Upload coverage report - uses: actions/upload-artifact@v4 - with: - name: server-coverage - path: server/coverage.out - retention-days: 7 - - name: Comment coverage on PR if: github.event_name == 'pull_request' + continue-on-error: true uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const coverage = fs.readFileSync('server/coverage.out', 'utf8'); - const lines = coverage.trim().split('\n'); - const totalLine = lines[lines.length - 1]; - const match = totalLine.match(/(\d+\.\d+)%/); - const coveragePercent = match ? match[1] : 'unknown'; - + const coveragePercent = ${{ toJSON(steps.coverage.outputs.percent) }}; const threshold = process.env.COVERAGE_THRESHOLD; const emoji = parseFloat(coveragePercent) >= parseFloat(threshold) ? '✅' : '❌'; @@ -207,7 +214,7 @@ jobs: `- **Threshold**: ${threshold}%\n` + `- **Status**: ${parseFloat(coveragePercent) >= parseFloat(threshold) ? 'PASS' : 'FAIL'}`; - github.rest.issues.createComment({ + await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, @@ -217,7 +224,7 @@ jobs: # Web tests test-web: name: Web Tests - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -227,8 +234,6 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: web/package-lock.json - name: Install dependencies working-directory: web @@ -248,15 +253,15 @@ jobs: echo "### Frontend Build Size" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - du -sh dist/ >> $GITHUB_STEP_SUMMARY + du -sh ../server/cmd/gateway/ui/dist/ >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - ls -lh dist/assets/*.js dist/assets/*.css 2>/dev/null | awk '{print $5, $9}' >> $GITHUB_STEP_SUMMARY || echo "No asset files found" >> $GITHUB_STEP_SUMMARY + ls -lh ../server/cmd/gateway/ui/dist/assets/*.js ../server/cmd/gateway/ui/dist/assets/*.css 2>/dev/null | awk '{print $5, $9}' >> $GITHUB_STEP_SUMMARY || echo "No asset files found" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY # Helm chart validation helm-validate: name: Helm Validation - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: validate steps: - name: Checkout code @@ -323,9 +328,12 @@ jobs: # Summary job that all others depend on pr-validation-summary: name: PR Validation Summary - runs-on: ubuntu-latest + runs-on: self-hosted-linux-dfw needs: [validate, test-server, test-integration, test-web, helm-validate] if: always() + permissions: + contents: read + issues: write steps: - name: Check validation results run: | @@ -354,10 +362,11 @@ jobs: - name: Update PR status if: github.event_name == 'pull_request' && needs.validate.result == 'success' && needs.test-server.result == 'success' && needs.test-integration.result == 'success' && needs.test-web.result == 'success' && needs.helm-validate.result == 'success' + continue-on-error: true uses: actions/github-script@v7 with: script: | - github.rest.issues.createComment({ + await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo,