Argocd gitops dev phase 1 #30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Platform Validation & E2E Tests | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - argocd-gitops-dev | |
| pull_request: | |
| branches: | |
| - main | |
| - argocd-gitops-dev | |
| workflow_dispatch: | |
| inputs: | |
| cluster_mode: | |
| description: 'Cluster mode (kind/existing)' | |
| required: false | |
| default: 'kind' | |
| type: choice | |
| options: | |
| - kind | |
| - existing | |
| exclude_apps: | |
| description: 'Comma-separated apps to exclude' | |
| required: false | |
| default: '' | |
| only_critical: | |
| description: 'Only validate critical apps' | |
| required: false | |
| default: false | |
| type: boolean | |
| env: | |
| PYTHON_VERSION: '3.11' | |
| KIND_VERSION: 'v0.20.0' | |
| KUBECTL_VERSION: 'v1.28.0' | |
| ARGOCD_VERSION: 'v2.9.3' | |
| jobs: | |
| validate-app-state: | |
| name: Validate ArgoCD Application State | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: 'pip' | |
| - name: Install Python dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install kubernetes>=28.1.0 pytest>=8.0.0 pytest-html>=4.1.0 \ | |
| pytest-json-report>=1.5.0 tenacity>=8.2.3 rich>=13.7.0 | |
| - name: Determine cluster mode | |
| id: cluster_mode | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "mode=${{ inputs.cluster_mode }}" >> $GITHUB_OUTPUT | |
| else | |
| echo "mode=kind" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Set up Docker (for Kind) | |
| if: steps.cluster_mode.outputs.mode == 'kind' | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Install Kind | |
| if: steps.cluster_mode.outputs.mode == 'kind' | |
| run: | | |
| curl -Lo ./kind https://kind.sigs.k8s.io/dl/${{ env.KIND_VERSION }}/kind-linux-amd64 | |
| chmod +x ./kind | |
| sudo mv ./kind /usr/local/bin/kind | |
| kind version | |
| - name: Install kubectl | |
| run: | | |
| curl -LO "https://dl.k8s.io/release/${{ env.KUBECTL_VERSION }}/bin/linux/amd64/kubectl" | |
| chmod +x kubectl | |
| sudo mv kubectl /usr/local/bin/ | |
| kubectl version --client | |
| - name: Install ArgoCD CLI | |
| run: | | |
| cd /tmp | |
| curl -fsSL -o argocd "https://github.com/argoproj/argo-cd/releases/download/${{ env.ARGOCD_VERSION }}/argocd-linux-amd64" | |
| chmod +x argocd | |
| sudo mv argocd /usr/local/bin/argocd | |
| argocd version --client | |
| - name: Deploy Platform with quick-redeploy.sh | |
| if: steps.cluster_mode.outputs.mode == 'kind' | |
| run: | | |
| chmod +x ./scripts/quick-redeploy.sh | |
| # CI mode skips interactive prompts automatically | |
| ./scripts/quick-redeploy.sh | |
| - name: Wait for ArgoCD applications to sync and become healthy | |
| run: | | |
| echo "Waiting for ArgoCD applications to sync and become healthy..." | |
| echo "Monitoring ArgoCD application status via kubectl..." | |
| TIMEOUT=1800 # 30 minutes maximum | |
| INTERVAL=10 # Check every 10 seconds | |
| ELAPSED=0 | |
| # List of critical applications to monitor (excluding agents and observability as per test params) | |
| CRITICAL_APPS="infrastructure gateway-api cert-manager istio-base istiod istio-config keycloak kagenti-operator kagenti-platform-operator platform" | |
| while [ $ELAPSED -lt $TIMEOUT ]; do | |
| echo "" | |
| echo "=== Check at ${ELAPSED}s / ${TIMEOUT}s ===" | |
| # Get all applications | |
| ALL_APPS=$(kubectl get applications -n argocd -o json | jq -r '.items[].metadata.name') | |
| # Count apps by status | |
| TOTAL=0 | |
| SYNCED=0 | |
| HEALTHY=0 | |
| for app in $ALL_APPS; do | |
| # Skip agents and observability (excluded in test params) | |
| if [[ "$app" == *"agent"* ]] || [[ "$app" == "observability" ]]; then | |
| continue | |
| fi | |
| TOTAL=$((TOTAL + 1)) | |
| # Get app status | |
| APP_JSON=$(kubectl get application "$app" -n argocd -o json) | |
| SYNC_STATUS=$(echo "$APP_JSON" | jq -r '.status.sync.status // "Unknown"') | |
| HEALTH_STATUS=$(echo "$APP_JSON" | jq -r '.status.health.status // "Unknown"') | |
| echo " $app: sync=$SYNC_STATUS, health=$HEALTH_STATUS" | |
| if [ "$SYNC_STATUS" = "Synced" ]; then | |
| SYNCED=$((SYNCED + 1)) | |
| fi | |
| if [ "$HEALTH_STATUS" = "Healthy" ]; then | |
| HEALTHY=$((HEALTHY + 1)) | |
| fi | |
| done | |
| echo "" | |
| echo "Summary: $HEALTHY/$TOTAL healthy, $SYNCED/$TOTAL synced" | |
| # Check if all apps are healthy and synced | |
| if [ $TOTAL -gt 0 ] && [ $HEALTHY -eq $TOTAL ] && [ $SYNCED -eq $TOTAL ]; then | |
| echo "" | |
| echo "✅ All applications are healthy and synced!" | |
| echo "Total time waited: ${ELAPSED}s" | |
| exit 0 | |
| fi | |
| # Wait before next check | |
| sleep $INTERVAL | |
| ELAPSED=$((ELAPSED + INTERVAL)) | |
| done | |
| echo "" | |
| echo "⚠️ Timeout reached after ${TIMEOUT}s" | |
| echo "Final status: $HEALTHY/$TOTAL healthy, $SYNCED/$TOTAL synced" | |
| echo "Proceeding with tests anyway..." | |
| - name: Set test parameters | |
| id: test_params | |
| run: | | |
| EXCLUDE_APPS="${{ inputs.exclude_apps }}" | |
| ONLY_CRITICAL="${{ inputs.only_critical }}" | |
| if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then | |
| # For automated runs, be more lenient | |
| EXCLUDE_APPS="agents,observability" | |
| ONLY_CRITICAL="false" | |
| fi | |
| echo "exclude_apps=${EXCLUDE_APPS}" >> $GITHUB_OUTPUT | |
| echo "only_critical=${ONLY_CRITICAL}" >> $GITHUB_OUTPUT | |
| - name: Run app state validation | |
| id: validation | |
| run: | | |
| PYTEST_ARGS="-v --html=app-state-report.html --self-contained-html --json-report --json-report-file=app-state-report.json" | |
| if [[ -n "${{ steps.test_params.outputs.exclude_apps }}" ]]; then | |
| PYTEST_ARGS="$PYTEST_ARGS --exclude-app=${{ steps.test_params.outputs.exclude_apps }}" | |
| fi | |
| if [[ "${{ steps.test_params.outputs.only_critical }}" == "true" ]]; then | |
| PYTEST_ARGS="$PYTEST_ARGS --only-critical" | |
| fi | |
| pytest tests/validation/test_app_state.py $PYTEST_ARGS | |
| - name: Run E2E platform tests | |
| id: e2e_tests | |
| continue-on-error: true | |
| run: | | |
| echo "Running E2E platform tests..." | |
| pytest tests/e2e/test_platform_e2e.py \ | |
| -v \ | |
| --tb=short \ | |
| --html=e2e-report.html \ | |
| --self-contained-html \ | |
| --json-report \ | |
| --json-report-file=e2e-report.json \ | |
| --continue-on-collection-errors | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: validation-results | |
| path: | | |
| app-state-report.html | |
| app-state-report.json | |
| e2e-report.html | |
| e2e-report.json | |
| retention-days: 30 | |
| - name: Parse test results | |
| if: always() | |
| id: parse_results | |
| run: | | |
| # Parse app state validation results | |
| if [[ -f app-state-report.json ]]; then | |
| APP_TOTAL=$(jq -r '.summary.total // 0' app-state-report.json) | |
| APP_PASSED=$(jq -r '.summary.passed // 0' app-state-report.json) | |
| APP_FAILED=$(jq -r '.summary.failed // 0' app-state-report.json) | |
| else | |
| APP_TOTAL=0 | |
| APP_PASSED=0 | |
| APP_FAILED=0 | |
| fi | |
| # Parse E2E test results | |
| if [[ -f e2e-report.json ]]; then | |
| E2E_TOTAL=$(jq -r '.summary.total // 0' e2e-report.json) | |
| E2E_PASSED=$(jq -r '.summary.passed // 0' e2e-report.json) | |
| E2E_FAILED=$(jq -r '.summary.failed // 0' e2e-report.json) | |
| E2E_XFAILED=$(jq -r '.summary.xfailed // 0' e2e-report.json) | |
| else | |
| E2E_TOTAL=0 | |
| E2E_PASSED=0 | |
| E2E_FAILED=0 | |
| E2E_XFAILED=0 | |
| fi | |
| # Combined totals | |
| TOTAL=$((APP_TOTAL + E2E_TOTAL)) | |
| PASSED=$((APP_PASSED + E2E_PASSED)) | |
| FAILED=$((APP_FAILED + E2E_FAILED)) | |
| echo "total=${TOTAL}" >> $GITHUB_OUTPUT | |
| echo "passed=${PASSED}" >> $GITHUB_OUTPUT | |
| echo "failed=${FAILED}" >> $GITHUB_OUTPUT | |
| echo "app_total=${APP_TOTAL}" >> $GITHUB_OUTPUT | |
| echo "app_passed=${APP_PASSED}" >> $GITHUB_OUTPUT | |
| echo "app_failed=${APP_FAILED}" >> $GITHUB_OUTPUT | |
| echo "e2e_total=${E2E_TOTAL}" >> $GITHUB_OUTPUT | |
| echo "e2e_passed=${E2E_PASSED}" >> $GITHUB_OUTPUT | |
| echo "e2e_failed=${E2E_FAILED}" >> $GITHUB_OUTPUT | |
| echo "e2e_xfailed=${E2E_XFAILED}" >> $GITHUB_OUTPUT | |
| - name: Comment on PR | |
| if: github.event_name == 'pull_request' && always() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const total = '${{ steps.parse_results.outputs.total }}'; | |
| const passed = '${{ steps.parse_results.outputs.passed }}'; | |
| const failed = '${{ steps.parse_results.outputs.failed }}'; | |
| const appTotal = '${{ steps.parse_results.outputs.app_total }}'; | |
| const appPassed = '${{ steps.parse_results.outputs.app_passed }}'; | |
| const appFailed = '${{ steps.parse_results.outputs.app_failed }}'; | |
| const e2eTotal = '${{ steps.parse_results.outputs.e2e_total }}'; | |
| const e2ePassed = '${{ steps.parse_results.outputs.e2e_passed }}'; | |
| const e2eFailed = '${{ steps.parse_results.outputs.e2e_failed }}'; | |
| const e2eXFailed = '${{ steps.parse_results.outputs.e2e_xfailed }}'; | |
| const status = failed === '0' ? '✅ PASSED' : '❌ FAILED'; | |
| const color = failed === '0' ? '🟢' : '🔴'; | |
| const comment = `## ${color} Platform Validation & E2E Tests ${status} | |
| ### 📋 App State Validation | |
| - Total: ${appTotal} | |
| - Passed: ✅ ${appPassed} | |
| - Failed: ❌ ${appFailed} | |
| ### 🧪 E2E Platform Tests | |
| - Total: ${e2eTotal} | |
| - Passed: ✅ ${e2ePassed} | |
| - Failed: ❌ ${e2eFailed} | |
| - Expected Failures: ⚠️ ${e2eXFailed} | |
| ### 📊 Combined Results | |
| - Total Tests: ${total} | |
| - Passed: ✅ ${passed} | |
| - Failed: ❌ ${failed} | |
| **Cluster Mode:** ${{ steps.cluster_mode.outputs.mode }} | |
| **Excluded Apps:** ${{ steps.test_params.outputs.exclude_apps || 'None' }} | |
| **Only Critical:** ${{ steps.test_params.outputs.only_critical }} | |
| 📊 [View detailed report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| `; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment | |
| }); | |
| - name: Generate summary | |
| if: always() | |
| run: | | |
| echo "## Platform Validation & E2E Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📋 App State Validation" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ steps.validation.outcome }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Total:** ${{ steps.parse_results.outputs.app_total }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Passed:** ${{ steps.parse_results.outputs.app_passed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Failed:** ${{ steps.parse_results.outputs.app_failed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 🧪 E2E Platform Tests" >> $GITHUB_STEP_SUMMARY | |
| echo "**Status:** ${{ steps.e2e_tests.outcome }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Total:** ${{ steps.parse_results.outputs.e2e_total }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Passed:** ${{ steps.parse_results.outputs.e2e_passed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Failed:** ${{ steps.parse_results.outputs.e2e_failed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Expected Failures:** ${{ steps.parse_results.outputs.e2e_xfailed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### 📊 Combined Results" >> $GITHUB_STEP_SUMMARY | |
| echo "**Total Tests:** ${{ steps.parse_results.outputs.total }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Passed:** ${{ steps.parse_results.outputs.passed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Failed:** ${{ steps.parse_results.outputs.failed }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Cluster Mode:** ${{ steps.cluster_mode.outputs.mode }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Excluded Apps:** ${{ steps.test_params.outputs.exclude_apps || 'None' }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Only Critical:** ${{ steps.test_params.outputs.only_critical }}" >> $GITHUB_STEP_SUMMARY | |
| - name: Cleanup Kind cluster | |
| if: always() && steps.cluster_mode.outputs.mode == 'kind' | |
| run: | | |
| kind delete cluster --name kagenti-demo || true | |
| - name: Fail job if validation failed | |
| if: steps.validation.outcome == 'failure' | |
| run: exit 1 |