CI/CD Pipeline #4
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: CI/CD Pipeline | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tutorial_url: | |
| description: 'Tutorial URL to validate' | |
| required: true | |
| default: 'https://abp.io/docs/latest/tutorials/book-store?UI=MVC&DB=EF' | |
| persona: | |
| description: 'Developer persona' | |
| required: false | |
| default: 'senior' | |
| type: choice | |
| options: [junior, mid, senior] | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Restore dependencies | |
| run: dotnet restore | |
| - name: Build solution | |
| run: dotnet build --configuration Release --no-restore | |
| - name: Run unit tests | |
| run: dotnet test --configuration Release --no-build --verbosity normal | |
| validate-tutorial: | |
| needs: build-and-test | |
| runs-on: ubuntu-latest | |
| environment: protected-validation | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Set up Docker | |
| run: | | |
| sudo systemctl start docker | |
| sudo systemctl enable docker | |
| - name: Install ABP CLI | |
| run: dotnet tool install -g Volo.Abp.Studio.Cli | |
| - name: Create output directory | |
| run: mkdir -p output | |
| - name: Run validation | |
| env: | |
| AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} | |
| AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| run: | | |
| dotnet run --project src/Validator.Orchestrator -- run \ | |
| --url "${{ github.event.inputs.tutorial_url || 'https://abp.io/docs/latest/tutorials/book-store?UI=MVC&DB=EF' }}" \ | |
| --persona "${{ github.event.inputs.persona || 'senior' }}" \ | |
| --output ./output | |
| - name: Upload results | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: validation-results | |
| path: output/ | |
| - name: Notify abp.io of validation result | |
| if: always() | |
| env: | |
| ABPIO_VALIDATION_API_KEY: ${{ secrets.ABPIO_VALIDATION_API_KEY }} | |
| ABPIO_REPORT_BASE_URL: ${{ secrets.ABPIO_REPORT_BASE_URL }} | |
| DEFAULT_TUTORIAL_URL: ${{ github.event.inputs.tutorial_url || 'https://abp.io/docs/latest/tutorials/book-store?UI=MVC&DB=EF' }} | |
| run: | | |
| SUMMARY_FILE="./output/summary.json" | |
| BASE_URL="${ABPIO_REPORT_BASE_URL:-https://abp.io}" | |
| BASE_URL="${BASE_URL%/}" | |
| RESPONSE_FILE="$(mktemp)" | |
| echo "==========================================" | |
| echo " Tutorial Validation Notification" | |
| echo "==========================================" | |
| if [ ! -f "$SUMMARY_FILE" ]; then | |
| echo "No summary.json found at $SUMMARY_FILE" | |
| ls -la ./output/ 2>/dev/null || echo "Directory not found" | |
| exit 0 | |
| fi | |
| if [ -z "$ABPIO_VALIDATION_API_KEY" ]; then | |
| echo "ABPIO_VALIDATION_API_KEY not set, skipping notification" | |
| exit 0 | |
| fi | |
| if ! jq empty "$SUMMARY_FILE" >/dev/null 2>&1; then | |
| echo "summary.json is not valid JSON" | |
| cat "$SUMMARY_FILE" | |
| exit 0 | |
| fi | |
| OVERALL_STATUS=$(jq -r '.overallStatus // .OverallStatus // empty' "$SUMMARY_FILE") | |
| TUTORIAL_URL=$(jq -r '.tutorialUrl // .TutorialUrl // empty' "$SUMMARY_FILE") | |
| TUTORIAL_NAME=$(jq -r '.tutorialName // .TutorialName // "Unknown Tutorial"' "$SUMMARY_FILE") | |
| if [ -z "$TUTORIAL_URL" ] || [ "$TUTORIAL_URL" = "null" ]; then | |
| TUTORIAL_URL="$DEFAULT_TUTORIAL_URL" | |
| echo "summary.json had no tutorialUrl; using workflow default URL" | |
| fi | |
| if [ -z "$OVERALL_STATUS" ] || [ "$OVERALL_STATUS" = "null" ]; then | |
| echo "summary.json missing overall status field" | |
| exit 0 | |
| fi | |
| OS_LOWER=$(echo "$OVERALL_STATUS" | tr '[:upper:]' '[:lower:]') | |
| PASSED=false | |
| if [ "$OS_LOWER" = "passed" ]; then | |
| PASSED=true | |
| fi | |
| echo "Tutorial: $TUTORIAL_NAME" | |
| echo "URL: $TUTORIAL_URL" | |
| echo "Status: $OVERALL_STATUS" | |
| echo "Passed (query): $PASSED" | |
| REPORT_QUERY=$(python3 -c "import sys, urllib.parse; print(urllib.parse.urlencode({'tutorialUrl': sys.argv[1], 'passed': sys.argv[2]}))" "$TUTORIAL_URL" "$PASSED") | |
| REPORT_URL="${BASE_URL}/api/tutorial-validation/report?${REPORT_QUERY}" | |
| MAX_RETRIES=3 | |
| RETRY_DELAY=5 | |
| SUCCESS=false | |
| for i in $(seq 1 $MAX_RETRIES); do | |
| echo "" | |
| echo "Attempt $i/$MAX_RETRIES: POST ${BASE_URL}/api/tutorial-validation/report" | |
| HTTP_CODE=$(curl -sS \ | |
| --connect-timeout 10 \ | |
| --max-time 30 \ | |
| -o "$RESPONSE_FILE" \ | |
| -w "%{http_code}" \ | |
| -X POST "$REPORT_URL" \ | |
| -H "X-Api-Key: ${ABPIO_VALIDATION_API_KEY}") | |
| if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then | |
| echo "Successfully reported to abp.io (HTTP $HTTP_CODE)" | |
| SUCCESS=true | |
| break | |
| elif [ "$HTTP_CODE" = "401" ]; then | |
| echo "Authentication failed - check ABPIO_VALIDATION_API_KEY" | |
| cat "$RESPONSE_FILE" 2>/dev/null || true | |
| break | |
| elif [ "$HTTP_CODE" = "400" ]; then | |
| echo "Bad request (invalid tutorialUrl or unsupported host?)" | |
| cat "$RESPONSE_FILE" 2>/dev/null || true | |
| break | |
| elif [ "$HTTP_CODE" = "409" ]; then | |
| echo "Report lock contention (HTTP 409)" | |
| cat "$RESPONSE_FILE" 2>/dev/null || true | |
| if [ $i -lt $MAX_RETRIES ]; then | |
| echo "Retrying in ${RETRY_DELAY}s..." | |
| sleep $RETRY_DELAY | |
| fi | |
| else | |
| echo "Attempt $i/$MAX_RETRIES failed (HTTP $HTTP_CODE)" | |
| cat "$RESPONSE_FILE" 2>/dev/null || true | |
| if [ $i -lt $MAX_RETRIES ]; then | |
| echo "Retrying in ${RETRY_DELAY}s..." | |
| sleep $RETRY_DELAY | |
| fi | |
| fi | |
| done | |
| rm -f "$RESPONSE_FILE" | |
| if [ "$SUCCESS" = false ]; then | |
| echo "" | |
| echo "Failed to report to abp.io after $MAX_RETRIES attempts" | |
| echo "Final HTTP Code: $HTTP_CODE" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "==========================================" | |
| echo " Notification Complete" | |
| echo "==========================================" |