Update Proxmox API Specifications #48
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: Update Proxmox API Specifications | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| on: | |
| schedule: | |
| # Run weekly on Sundays at 2 AM UTC | |
| - cron: "0 2 * * 0" | |
| workflow_dispatch: | |
| # Allow manual triggering | |
| inputs: | |
| api_type: | |
| description: "API type to update" | |
| required: true | |
| default: "both" | |
| type: choice | |
| options: | |
| - both | |
| - pve | |
| - pbs | |
| create_pr: | |
| description: "Create PR for changes" | |
| required: false | |
| default: true | |
| type: boolean | |
| jobs: | |
| update-pve-specs: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.inputs.api_type == 'both' || github.event.inputs.api_type == 'pve' || github.event_name == 'schedule' }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.14" | |
| - name: Install UV | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| with: | |
| enable-cache: true | |
| - name: Install Python dependencies | |
| run: uv sync | |
| - name: Download latest PVE API documentation | |
| run: | | |
| # Download the latest PVE API viewer page | |
| curl -L "https://pve.proxmox.com/pve-docs/api-viewer/apidoc.js" \ | |
| -o proxmox-virtual-environment/apidoc.js | |
| - name: Generate PVE OpenAPI specification | |
| run: | | |
| cd scripts/pve | |
| echo "🔍 Current directory: $(pwd)" | |
| echo "📁 Files before generation:" | |
| ls -la | |
| echo "🚀 Running PVE OpenAPI generation..." | |
| START_TIME=$(date +%s) | |
| uv run python generate_openapi.py | |
| END_TIME=$(date +%s) | |
| DURATION=$((END_TIME - START_TIME)) | |
| echo "⏱️ Generation completed in ${DURATION} seconds" | |
| echo "PVE_GENERATION_TIME=${DURATION}" >> $GITHUB_ENV | |
| echo "📁 Verifying generated files:" | |
| ls -la ../../proxmox-virtual-environment/*.json ../../proxmox-virtual-environment/*.yaml | |
| - name: Validate PVE OpenAPI specification | |
| run: | | |
| echo "🔍 Validating PVE OpenAPI specifications..." | |
| # Use the dedicated validation script | |
| uv run python scripts/validate_openapi.py proxmox-virtual-environment/pve-api.json proxmox-virtual-environment/pve-api.yaml | |
| - name: Update PVE README with stats | |
| run: | | |
| cd proxmox-virtual-environment | |
| uv run python << 'EOF' | |
| import json | |
| import re | |
| # Load the API spec | |
| with open('pve-api.json', 'r') as f: | |
| spec = json.load(f) | |
| # Calculate statistics | |
| total_paths = len(spec.get('paths', {})) | |
| total_operations = sum(len([k for k in path.keys() if k in ['get', 'post', 'put', 'delete', 'patch']]) | |
| for path in spec['paths'].values()) | |
| # Update README | |
| with open('README.md', 'r') as f: | |
| content = f.read() | |
| # Update statistics in README | |
| content = re.sub(r'- \*\*Total Endpoints\*\*: \d+ unique API paths', | |
| f'- **Total Endpoints**: {total_paths} unique API paths', content) | |
| content = re.sub(r'- \*\*Total Operations\*\*: \d+ HTTP operations', | |
| f'- **Total Operations**: {total_operations} HTTP operations', content) | |
| with open('README.md', 'w') as f: | |
| f.write(content) | |
| EOF | |
| update-pbs-specs: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.inputs.api_type == 'both' || github.event.inputs.api_type == 'pbs' || github.event_name == 'schedule' }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.14" | |
| - name: Install UV | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| with: | |
| enable-cache: true | |
| - name: Install Python dependencies | |
| run: uv sync | |
| - name: Download latest PBS API documentation | |
| run: | | |
| # Download the latest PBS API viewer page | |
| curl -L "https://pbs.proxmox.com/docs/api-viewer/apidoc.js" \ | |
| -o proxmox-backup-server/apidoc.js | |
| - name: Generate PBS OpenAPI specification | |
| run: | | |
| cd scripts/pbs | |
| echo "🔍 Current directory: $(pwd)" | |
| echo "📁 Files before generation:" | |
| ls -la | |
| echo "🚀 Running PBS OpenAPI generation..." | |
| START_TIME=$(date +%s) | |
| uv run python generate_openapi.py | |
| END_TIME=$(date +%s) | |
| DURATION=$((END_TIME - START_TIME)) | |
| echo "⏱️ Generation completed in ${DURATION} seconds" | |
| echo "PBS_GENERATION_TIME=${DURATION}" >> $GITHUB_ENV | |
| echo "📁 Verifying generated files:" | |
| ls -la ../../proxmox-backup-server/*.json ../../proxmox-backup-server/*.yaml | |
| - name: Validate PBS OpenAPI specification | |
| run: | | |
| echo "🔍 Validating PBS OpenAPI specifications..." | |
| # Use the dedicated validation script | |
| uv run python scripts/validate_openapi.py proxmox-backup-server/pbs-api.json proxmox-backup-server/pbs-api.yaml | |
| - name: Update PBS README with stats | |
| run: | | |
| cd proxmox-backup-server | |
| uv run python << 'EOF' | |
| import json | |
| import re | |
| import os | |
| # Load the API spec | |
| with open('pbs-api.json', 'r') as f: | |
| spec = json.load(f) | |
| # Calculate statistics | |
| total_paths = len(spec.get('paths', {})) | |
| total_operations = sum(len([k for k in path.keys() if k in ['get', 'post', 'put', 'delete', 'patch']]) | |
| for path in spec['paths'].values()) | |
| # Get file sizes | |
| json_size = os.path.getsize('pbs-api.json') | |
| yaml_size = os.path.getsize('pbs-api.yaml') | |
| # Update README | |
| with open('README.md', 'r') as f: | |
| content = f.read() | |
| # Update statistics in README | |
| content = re.sub(r'- \*\*Total Endpoints\*\*: \d+ unique API paths', | |
| f'- **Total Endpoints**: {total_paths} unique API paths', content) | |
| content = re.sub(r'- \*\*Total Operations\*\*: \d+ HTTP operations', | |
| f'- **Total Operations**: {total_operations} HTTP operations', content) | |
| content = re.sub(r'- `pbs-api\.json` - \*\*[\d.]+\w+\*\* comprehensive JSON', | |
| f'- `pbs-api.json` - **{json_size/1024/1024:.1f}MB** comprehensive JSON', content) | |
| content = re.sub(r'- `pbs-api\.yaml` - \*\*[\d.]+\w+\*\* comprehensive YAML', | |
| f'- `pbs-api.yaml` - **{yaml_size/1024:.0f}KB** comprehensive YAML', content) | |
| with open('README.md', 'w') as f: | |
| f.write(content) | |
| EOF | |
| create-pr: | |
| runs-on: ubuntu-latest | |
| needs: [update-pve-specs, update-pbs-specs] | |
| if: always() && (needs.update-pve-specs.result == 'success' || needs.update-pbs-specs.result == 'success') | |
| outputs: | |
| pr_created: ${{ steps.create-pr.outputs.pr_created }} | |
| pr_number: ${{ steps.create-pr.outputs.pr_number }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: ${{ github.head_ref || github.ref }} | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.14" | |
| - name: Install UV | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| with: | |
| enable-cache: true | |
| - name: Install dependencies | |
| run: uv sync | |
| - name: Download artifacts and regenerate specs | |
| run: | | |
| # Download latest API docs | |
| if [[ "${{ needs.update-pve-specs.result }}" == "success" ]]; then | |
| curl -L "https://pve.proxmox.com/pve-docs/api-viewer/apidoc.js" \ | |
| -o proxmox-virtual-environment/apidoc.js | |
| cd scripts/pve && uv run python generate_openapi.py && uv run python convert_to_yaml.py && cd ../.. | |
| fi | |
| if [[ "${{ needs.update-pbs-specs.result }}" == "success" ]]; then | |
| curl -L "https://pbs.proxmox.com/docs/api-viewer/apidoc.js" \ | |
| -o proxmox-backup-server/apidoc.js | |
| cd scripts/pbs && uv run python generate_openapi.py && uv run python convert_to_yaml.py && cd ../.. | |
| fi | |
| - name: Generate change summary | |
| id: changes | |
| run: | | |
| # Check for changes | |
| if git diff --quiet; then | |
| echo "No changes detected" | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| # Generate detailed diff summary | |
| echo "## 📊 API Specification Changes" > pr_body.md | |
| echo "" >> pr_body.md | |
| echo "This PR contains automated updates to the Proxmox API specifications." >> pr_body.md | |
| echo "" >> pr_body.md | |
| # Check PVE changes | |
| if git diff --name-only | grep -q "proxmox-virtual-environment/"; then | |
| echo "### PVE API Changes" >> pr_body.md | |
| echo "" >> pr_body.md | |
| # Get endpoint counts | |
| OLD_PVE_ENDPOINTS=$(git show HEAD:proxmox-virtual-environment/pve-api.json | jq '.paths | length' || echo 0) | |
| NEW_PVE_ENDPOINTS=$(jq '.paths | length' proxmox-virtual-environment/pve-api.json || echo 0) | |
| DIFF_PVE=$((NEW_PVE_ENDPOINTS - OLD_PVE_ENDPOINTS)) | |
| echo "- Endpoints: $OLD_PVE_ENDPOINTS → $NEW_PVE_ENDPOINTS ($(printf '%+d' $DIFF_PVE))" >> pr_body.md | |
| # File sizes | |
| echo "- File sizes:" >> pr_body.md | |
| echo " - JSON: $(ls -lh proxmox-virtual-environment/pve-api.json | awk '{print $5}')" >> pr_body.md | |
| echo " - YAML: $(ls -lh proxmox-virtual-environment/pve-api.yaml | awk '{print $5}')" >> pr_body.md | |
| echo "" >> pr_body.md | |
| fi | |
| # Check PBS changes | |
| if git diff --name-only | grep -q "proxmox-backup-server/"; then | |
| echo "### PBS API Changes" >> pr_body.md | |
| echo "" >> pr_body.md | |
| # Get endpoint counts | |
| OLD_PBS_ENDPOINTS=$(git show HEAD:proxmox-backup-server/pbs-api.json | jq '.paths | length' || echo 0) | |
| NEW_PBS_ENDPOINTS=$(jq '.paths | length' proxmox-backup-server/pbs-api.json || echo 0) | |
| DIFF_PBS=$((NEW_PBS_ENDPOINTS - OLD_PBS_ENDPOINTS)) | |
| echo "- Endpoints: $OLD_PBS_ENDPOINTS → $NEW_PBS_ENDPOINTS ($(printf '%+d' $DIFF_PBS))" >> pr_body.md | |
| # File sizes | |
| echo "- File sizes:" >> pr_body.md | |
| echo " - JSON: $(ls -lh proxmox-backup-server/pbs-api.json | awk '{print $5}')" >> pr_body.md | |
| echo " - YAML: $(ls -lh proxmox-backup-server/pbs-api.yaml | awk '{print $5}')" >> pr_body.md | |
| echo "" >> pr_body.md | |
| fi | |
| echo "### Validation Status" >> pr_body.md | |
| echo "" >> pr_body.md | |
| echo "✅ All specifications pass OpenAPI 3.0.3 validation" >> pr_body.md | |
| echo "" >> pr_body.md | |
| echo "### Performance Metrics" >> pr_body.md | |
| echo "" >> pr_body.md | |
| if [[ -n "${{ env.PVE_GENERATION_TIME }}" ]]; then | |
| echo "- PVE generation time: ${{ env.PVE_GENERATION_TIME }}s" >> pr_body.md | |
| fi | |
| if [[ -n "${{ env.PBS_GENERATION_TIME }}" ]]; then | |
| echo "- PBS generation time: ${{ env.PBS_GENERATION_TIME }}s" >> pr_body.md | |
| fi | |
| echo "" >> pr_body.md | |
| echo "### Next Steps" >> pr_body.md | |
| echo "" >> pr_body.md | |
| echo "1. Review the changes in the Files tab" >> pr_body.md | |
| echo "2. Check for any breaking changes" >> pr_body.md | |
| echo "3. Merge to update the API specifications" >> pr_body.md | |
| echo "" >> pr_body.md | |
| echo "---" >> pr_body.md | |
| echo "*Generated automatically by GitHub Actions on $(date -u +'%Y-%m-%d %H:%M:%S UTC')*" >> pr_body.md | |
| - name: Create Pull Request | |
| id: create-pr | |
| if: steps.changes.outputs.has_changes == 'true' && (github.event_name == 'schedule' || github.event.inputs.create_pr == 'true') | |
| run: | | |
| # Create a unique branch name | |
| BRANCH_NAME="auto-update/api-specs-$(date +%Y%m%d-%H%M%S)" | |
| # Configure git | |
| git config --local user.email "action@github.com" | |
| git config --local user.name "GitHub Action" | |
| # Create and checkout new branch | |
| git checkout -b $BRANCH_NAME | |
| # Stage all changes | |
| git add . | |
| # Commit changes with PR body | |
| { | |
| echo "chore: update Proxmox API specifications" | |
| echo "" | |
| cat pr_body.md | |
| } > commit_message.txt | |
| git commit -F commit_message.txt | |
| # Push branch | |
| git push origin $BRANCH_NAME | |
| # Create PR using GitHub CLI | |
| PR_URL=$(gh pr create \ | |
| --title "chore: Update Proxmox API Specifications" \ | |
| --body-file pr_body.md \ | |
| --base main \ | |
| --head $BRANCH_NAME \ | |
| --label "⚡ api" \ | |
| --label "automated" \ | |
| --assignee "${{ github.repository_owner }}") | |
| # Extract PR number | |
| PR_NUMBER=$(echo $PR_URL | grep -oE '[0-9]+$') | |
| echo "pr_created=true" >> $GITHUB_OUTPUT | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| notify-completion: | |
| runs-on: ubuntu-latest | |
| needs: [create-pr, update-pve-specs, update-pbs-specs] | |
| if: always() | |
| steps: | |
| - name: Create issue on failure | |
| if: needs.update-pve-specs.result == 'failure' || needs.update-pbs-specs.result == 'failure' | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| with: | |
| script: | | |
| const date = new Date().toISOString().split('T')[0]; | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| let failedAPIs = []; | |
| if ('${{ needs.update-pve-specs.result }}' === 'failure') failedAPIs.push('PVE'); | |
| if ('${{ needs.update-pbs-specs.result }}' === 'failure') failedAPIs.push('PBS'); | |
| const issueBody = [ | |
| '## 🚨 API Update Workflow Failed', | |
| '', | |
| `**Date**: ${date}`, | |
| `**Failed APIs**: ${failedAPIs.join(', ')}`, | |
| `**Workflow Run**: [View Details](${runUrl})`, | |
| '', | |
| '### Summary', | |
| `The automated API specification update workflow failed for the following APIs: ${failedAPIs.join(', ')}.`, | |
| '', | |
| '### Next Steps', | |
| `1. Check the [workflow logs](${runUrl}) for detailed error information`, | |
| '2. Verify the API endpoints are accessible', | |
| '3. Check for any changes in the API documentation format', | |
| '4. Run the workflow manually with debugging enabled', | |
| '', | |
| '### Debug Commands', | |
| '```bash', | |
| '# Test PVE API download', | |
| 'curl -I https://pve.proxmox.com/pve-docs/api-viewer/apidoc.js', | |
| '', | |
| '# Test PBS API download', | |
| 'curl -I https://pbs.proxmox.com/docs/api-viewer/apidoc.js', | |
| '', | |
| '# Run generation locally', | |
| 'cd scripts/pve && uv run python generate_openapi.py', | |
| 'cd scripts/pbs && uv run python generate_openapi.py', | |
| '```', | |
| '', | |
| '---', | |
| '*This issue was automatically created by the API update workflow.*' | |
| ].join('\\n'); | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `API Update Failed - ${date}`, | |
| body: issueBody, | |
| labels: ['bug', 'automated', 'ci/cd'] | |
| }); | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # PVE Status | |
| if [[ "${{ needs.update-pve-specs.result }}" == "success" ]]; then | |
| echo "✅ PVE API update: Success" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "❌ PVE API update: Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # PBS Status | |
| if [[ "${{ needs.update-pbs-specs.result }}" == "success" ]]; then | |
| echo "✅ PBS API update: Success" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "❌ PBS API update: Failed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| # PR Status | |
| if [[ "${{ needs.create-pr.outputs.pr_created }}" == "true" ]]; then | |
| echo "📝 Pull Request: #${{ needs.create-pr.outputs.pr_number }}" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "ℹ️ No changes detected - no PR created" >> $GITHUB_STEP_SUMMARY | |
| fi |