Add GitOps read-only dashboard config mode #7753
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: Manual ReadTheDocs Build on PR | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request: | |
| types: [closed] | |
| # No global env variables needed - using secrets | |
| jobs: | |
| trigger-rtd-build: | |
| # Only run if: | |
| # 1. The comment is on a pull request (not an issue) | |
| # 2. The comment contains /build-docs | |
| if: | | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '/build-docs') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check authorization | |
| id: check-auth | |
| env: | |
| AUTHORIZED_USERS: ${{ vars.AUTHORIZED_RTD_BUILDERS }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| // Get authorized users from repository variable (comma-separated list) | |
| const authorizedUsersStr = process.env.AUTHORIZED_USERS || ''; | |
| const authorizedUsers = authorizedUsersStr | |
| .split(',') | |
| .map(u => u.trim()) | |
| .filter(u => u.length > 0); | |
| if (authorizedUsers.length === 0) { | |
| core.setFailed('AUTHORIZED_RTD_BUILDERS variable is not set or empty. Please configure it in repository settings.'); | |
| return false; | |
| } | |
| const commenter = context.payload.comment.user.login; | |
| const isAuthorized = authorizedUsers.includes(commenter); | |
| if (!isAuthorized) { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `❌ @${commenter} is not authorized to trigger ReadTheDocs builds. Please contact a maintainer.` | |
| }); | |
| core.setFailed(`User ${commenter} is not authorized`); | |
| return false; | |
| } | |
| console.log(`✅ User ${commenter} is authorized to trigger builds`); | |
| return true; | |
| result-encoding: string | |
| - name: Get PR branch | |
| id: get-branch | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const pr = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| return pr.data.head.ref; | |
| result-encoding: string | |
| - name: Trigger ReadTheDocs build | |
| env: | |
| READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }} | |
| READTHEDOCS_PROJECT: ${{ secrets.READTHEDOCS_PROJECT }} | |
| BRANCH_NAME: ${{ steps.get-branch.outputs.result }} | |
| run: | | |
| # Trigger a build for the PR branch on ReadTheDocs | |
| # The branch needs to exist as a version on ReadTheDocs | |
| # ReadTheDocs automatically replaces forward slashes with dashes and | |
| # lowercases letters when generating version slugs. | |
| # See: https://docs.readthedocs.com/platform/latest/versions.html | |
| # So we need to match that behavior when calling the API | |
| # Then URL-encode the sanitized branch name for the API call | |
| SANITIZED_BRANCH=$(echo -n "${BRANCH_NAME}" | sed 's/\//-/g' | tr '[:upper:]' '[:lower:]') | |
| VERSION_SLUG=$(echo -n "${SANITIZED_BRANCH}" | jq -sRr @uri) | |
| echo "Triggering ReadTheDocs build for project: ${READTHEDOCS_PROJECT}, branch: ${BRANCH_NAME}" | |
| echo "Using version slug: ${SANITIZED_BRANCH} (sanitized from branch name)" | |
| # Activate and configure the version (works for both existing and inactive versions) | |
| echo "Activating and configuring version for branch: ${BRANCH_NAME}" | |
| CONFIG_RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \ | |
| "https://readthedocs.com/api/v3/projects/${READTHEDOCS_PROJECT}/versions/${VERSION_SLUG}/" \ | |
| -H "Authorization: Token ${READTHEDOCS_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "active": true, | |
| "hidden": true, | |
| "privacy_level": "public" | |
| }') | |
| CONFIG_HTTP_CODE=$(echo "$CONFIG_RESPONSE" | tail -n1) | |
| CONFIG_BODY=$(echo "$CONFIG_RESPONSE" | head -n-1) | |
| if [ "$CONFIG_HTTP_CODE" -eq 404 ]; then | |
| echo "⚠️ Branch '${BRANCH_NAME}' not found in ReadTheDocs." | |
| echo "Triggering version sync from GitHub..." | |
| # Trigger ReadTheDocs to sync versions from GitHub | |
| SYNC_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ | |
| "https://readthedocs.com/api/v3/projects/${READTHEDOCS_PROJECT}/sync-versions/" \ | |
| -H "Authorization: Token ${READTHEDOCS_TOKEN}" \ | |
| -H "Content-Type: application/json") | |
| SYNC_HTTP_CODE=$(echo "$SYNC_RESPONSE" | tail -n1) | |
| SYNC_BODY=$(echo "$SYNC_RESPONSE" | head -n-1) | |
| if [ "$SYNC_HTTP_CODE" -eq 202 ] || [ "$SYNC_HTTP_CODE" -eq 200 ]; then | |
| echo "✅ Version sync triggered successfully" | |
| echo "Waiting 10 seconds for sync to complete..." | |
| sleep 10 | |
| # Retry activating the version | |
| echo "Retrying activation..." | |
| CONFIG_RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \ | |
| "https://readthedocs.com/api/v3/projects/${READTHEDOCS_PROJECT}/versions/${VERSION_SLUG}/" \ | |
| -H "Authorization: Token ${READTHEDOCS_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "active": true, | |
| "hidden": true, | |
| "privacy_level": "public" | |
| }') | |
| CONFIG_HTTP_CODE=$(echo "$CONFIG_RESPONSE" | tail -n1) | |
| CONFIG_BODY=$(echo "$CONFIG_RESPONSE" | head -n-1) | |
| if [ "$CONFIG_HTTP_CODE" -eq 404 ]; then | |
| echo "❌ Branch still not found after sync." | |
| echo "The branch might not exist in GitHub, or the sync is taking longer than expected." | |
| echo "Please verify the branch exists and try /build-docs again in a minute." | |
| echo "" | |
| echo "Response: $CONFIG_BODY" | |
| exit 1 | |
| elif [ "$CONFIG_HTTP_CODE" -ne 200 ] && [ "$CONFIG_HTTP_CODE" -ne 204 ]; then | |
| echo "❌ Failed to activate version after sync. HTTP status: $CONFIG_HTTP_CODE" | |
| echo "$CONFIG_BODY" | |
| exit 1 | |
| fi | |
| echo "✅ Version activated and configured successfully after sync" | |
| else | |
| echo "❌ Failed to trigger version sync. HTTP status: $SYNC_HTTP_CODE" | |
| echo "$SYNC_BODY" | |
| exit 1 | |
| fi | |
| elif [ "$CONFIG_HTTP_CODE" -ne 200 ] && [ "$CONFIG_HTTP_CODE" -ne 204 ]; then | |
| echo "❌ Failed to activate version. HTTP status: $CONFIG_HTTP_CODE" | |
| echo "$CONFIG_BODY" | |
| exit 1 | |
| else | |
| echo "✅ Version activated and configured successfully" | |
| fi | |
| # Trigger the build | |
| echo "Triggering build..." | |
| BUILD_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ | |
| "https://readthedocs.com/api/v3/projects/${READTHEDOCS_PROJECT}/versions/${VERSION_SLUG}/builds/" \ | |
| -H "Authorization: Token ${READTHEDOCS_TOKEN}" \ | |
| -H "Content-Type: application/json") | |
| BUILD_HTTP_CODE=$(echo "$BUILD_RESPONSE" | tail -n1) | |
| BUILD_BODY=$(echo "$BUILD_RESPONSE" | head -n-1) | |
| if [ "$BUILD_HTTP_CODE" -eq 202 ] || [ "$BUILD_HTTP_CODE" -eq 201 ]; then | |
| echo "✅ ReadTheDocs build triggered successfully!" | |
| echo "$BUILD_BODY" | |
| else | |
| echo "❌ Failed to trigger ReadTheDocs build. HTTP status: $BUILD_HTTP_CODE" | |
| echo "$BUILD_BODY" | |
| exit 1 | |
| fi | |
| - name: Add label and comment on PR | |
| if: success() | |
| env: | |
| READTHEDOCS_PROJECT: ${{ secrets.READTHEDOCS_PROJECT }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const branch = '${{ steps.get-branch.outputs.result }}'; | |
| const projectSlug = process.env.READTHEDOCS_PROJECT; | |
| // ReadTheDocs automatically replaces forward slashes with dashes | |
| // and lowercases letters when generating version slugs. | |
| // See: https://docs.readthedocs.com/platform/latest/versions.html | |
| // So we need to match that behavior for the documentation URL | |
| // Then URL-encode the sanitized branch name for the documentation URL | |
| const sanitizedBranch = branch.replace(/\//g, '-').toLowerCase(); | |
| const encodedBranch = encodeURIComponent(sanitizedBranch); | |
| // Add label to track that a preview was built | |
| try { | |
| await github.rest.issues.addLabels({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: ['rtd-preview'] | |
| }); | |
| } catch (error) { | |
| console.log('Label may not exist or could not be added:', error.message); | |
| } | |
| // Comment on PR | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `✅ ReadTheDocs build triggered for branch \`${branch}\`\n\nThe documentation will be available at: https://docs.skypilot.co/en/${encodedBranch}/` | |
| }) | |
| - name: Comment on failure | |
| if: failure() | |
| env: | |
| BRANCH_NAME: ${{ steps.get-branch.outputs.result || 'unknown' }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const branch = process.env.BRANCH_NAME; | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `❌ Failed to trigger ReadTheDocs build for branch \`${branch}\`. Please check the [workflow logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for details.` | |
| }) | |
| cleanup-rtd-preview: | |
| # Only run when a pull request is closed AND has the rtd-preview label | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'closed' && | |
| contains(github.event.pull_request.labels.*.name, 'rtd-preview') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Deactivate ReadTheDocs preview | |
| env: | |
| READTHEDOCS_TOKEN: ${{ secrets.READTHEDOCS_TOKEN }} | |
| READTHEDOCS_PROJECT: ${{ secrets.READTHEDOCS_PROJECT }} | |
| BRANCH_NAME: ${{ github.event.pull_request.head.ref }} | |
| run: | | |
| # ReadTheDocs automatically replaces forward slashes with dashes and | |
| # lowercases letters when generating version slugs. | |
| # See: https://docs.readthedocs.com/platform/latest/versions.html | |
| # So we need to match that behavior when calling the API | |
| # Then URL-encode the sanitized branch name for the API call | |
| SANITIZED_BRANCH=$(echo -n "${BRANCH_NAME}" | sed 's/\//-/g' | tr '[:upper:]' '[:lower:]') | |
| VERSION_SLUG=$(echo -n "${SANITIZED_BRANCH}" | jq -sRr @uri) | |
| echo "Deactivating ReadTheDocs preview for branch: ${BRANCH_NAME}" | |
| echo "Using version slug: ${SANITIZED_BRANCH} (sanitized from branch name)" | |
| # Deactivate the version (set active to false) | |
| RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \ | |
| "https://readthedocs.com/api/v3/projects/${READTHEDOCS_PROJECT}/versions/${VERSION_SLUG}/" \ | |
| -H "Authorization: Token ${READTHEDOCS_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "active": false | |
| }') | |
| HTTP_CODE=$(echo "$RESPONSE" | tail -n1) | |
| BODY=$(echo "$RESPONSE" | head -n-1) | |
| if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 204 ]; then | |
| echo "✅ ReadTheDocs preview deactivated successfully for branch: ${BRANCH_NAME}" | |
| echo "$BODY" | |
| elif [ "$HTTP_CODE" -eq 404 ]; then | |
| echo "ℹ️ No ReadTheDocs preview found for branch: ${BRANCH_NAME} (this is normal if no preview was created)" | |
| else | |
| echo "⚠️ Failed to deactivate ReadTheDocs preview. HTTP status: $HTTP_CODE" | |
| echo "$BODY" | |
| # Don't fail the workflow - this is just cleanup | |
| fi |