Skip to content

CI/CD Pipeline

CI/CD Pipeline #4

Workflow file for this run

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 "=========================================="