diff --git a/.github/actions/deploy-to-control-plane/action.yml b/.github/actions/deploy-to-control-plane/action.yml index 1e5c9fa7..b8f9b733 100644 --- a/.github/actions/deploy-to-control-plane/action.yml +++ b/.github/actions/deploy-to-control-plane/action.yml @@ -26,66 +26,223 @@ outputs: runs: using: "composite" steps: + - name: Validate Required Secrets + shell: bash + run: | + missing_secrets=() + for secret in "CPLN_TOKEN" "CPLN_ORG"; do + if [ -z "${!secret}" ]; then + missing_secrets+=("$secret") + fi + done + + if [ ${#missing_secrets[@]} -ne 0 ]; then + echo "Required secrets are not set: ${missing_secrets[*]}" + exit 1 + fi + - name: Setup Environment uses: ./.github/actions/setup-environment - - name: Get Commit SHA - id: get_sha + - name: Set shared functions + id: shared-functions + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('GET_CONSOLE_LINK', ` + function getConsoleLink(prNumber) { + return ' [Control Plane Console for Review App with PR #' + prNumber + '](' + + 'https://console.cpln.io/org/' + process.env.CPLN_ORG + '/workloads/' + process.env.APP_NAME + ')'; + } + `); + + - name: Initialize Deployment + id: init-deployment + uses: actions/github-script@v7 + with: + script: | + eval(process.env.GET_CONSOLE_LINK); + + async function getWorkflowUrl(runId) { + // Get the current job ID + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId + }); + + const currentJob = jobs.data.jobs.find(job => job.status === 'in_progress'); + const jobId = currentJob?.id; + + if (!jobId) { + console.log('Warning: Could not find current job ID'); + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; + } + + return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}`; + } + + // Create initial deployment comment + const comment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.PR_NUMBER, + body: ' Initializing deployment...' + }); + + // Create GitHub deployment + const deployment = await github.rest.repos.createDeployment({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: context.sha, + environment: 'review', + auto_merge: false, + required_contexts: [] + }); + + const workflowUrl = await getWorkflowUrl(context.runId); + + core.exportVariable('WORKFLOW_URL', workflowUrl); + core.exportVariable('COMMENT_ID', comment.data.id); + core.exportVariable('DEPLOYMENT_ID', deployment.data.id); + + - name: Set commit hash shell: bash - run: ${{ github.action_path }}/scripts/get-commit-sha.sh - env: - GITHUB_TOKEN: ${{ inputs.github_token }} - PR_NUMBER: ${{ env.PR_NUMBER }} + run: | + FULL_COMMIT=$(git rev-parse HEAD) + echo "COMMIT_HASH=${FULL_COMMIT:0:7}" >> $GITHUB_ENV - - name: Deploy to Control Plane - id: deploy + - name: Update Status - Setting Up + uses: actions/github-script@v7 + with: + script: | + eval(process.env.GET_CONSOLE_LINK); + + const setupMessage = [ + '🔧 Setting up Control Plane app...', + '', + ' [View Setup Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: setupMessage + }); + + - name: Setup Control Plane App shell: bash run: | - echo "🚀 Deploying app for PR #${PR_NUMBER}..." - - # Create temp file for output - TEMP_OUTPUT=$(mktemp) - trap 'rm -f "${TEMP_OUTPUT}"' EXIT - - # Deploy the application and show output in real-time while capturing it - if ! cpflow deploy-image -a "${{ inputs.app_name }}" --run-release-phase --org "${{ inputs.org }}" 2>&1 | tee "${TEMP_OUTPUT}"; then - echo "❌ Deployment failed for PR #${PR_NUMBER}" - echo "Error output:" - cat "${TEMP_OUTPUT}" - exit 1 - fi - - # Extract app URL from captured output - REVIEW_APP_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "${TEMP_OUTPUT}" | head -n1) - if [ -z "${REVIEW_APP_URL}" ]; then - echo "❌ Failed to get app URL from deployment output" - echo "Deployment output:" - cat "${TEMP_OUTPUT}" - exit 1 - fi - - # Wait for all workloads to be ready - WAIT_TIMEOUT=${WAIT_TIMEOUT:-${{ inputs.wait_timeout }}} - if ! [[ "${WAIT_TIMEOUT}" =~ ^[0-9]+$ ]]; then - echo "❌ Invalid timeout value: ${WAIT_TIMEOUT}" - exit 1 + echo "🔧 Checking if app exists..." + if ! cpflow exists -a ${{ inputs.app_name }} ; then + echo "📦 Setting up new Control Plane app..." + cpflow setup-app -a ${{ inputs.app_name }} fi - echo "⏳ Waiting for all workloads to be ready (timeout: ${WAIT_TIMEOUT}s)" - - # Use timeout command with ps:wait and show output in real-time - if ! timeout "${WAIT_TIMEOUT}" bash -c "cpflow ps:wait -a \"${{ inputs.app_name }}\"" 2>&1 | tee -a "${TEMP_OUTPUT}"; then - TIMEOUT_EXIT=$? - if [ ${TIMEOUT_EXIT} -eq 124 ]; then - echo "❌ Timed out waiting for workloads after ${WAIT_TIMEOUT} seconds" - else - echo "❌ Workloads did not become ready for PR #${PR_NUMBER} (exit code: ${TIMEOUT_EXIT})" - fi - echo "Full output:" - cat "${TEMP_OUTPUT}" - exit 1 - fi - - echo "✅ Deployment successful for PR #${PR_NUMBER}" - echo "🌐 App URL: ${REVIEW_APP_URL}" - echo "review_app_url=${REVIEW_APP_URL}" >> $GITHUB_OUTPUT - echo "REVIEW_APP_URL=${REVIEW_APP_URL}" >> $GITHUB_ENV + + - name: Update Status - Building + uses: actions/github-script@v7 + with: + script: | + eval(process.env.GET_CONSOLE_LINK); + + const buildingMessage = [ + '🏗️ Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + process.env.COMMIT_HASH, + '', + ' [View Build Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: buildingMessage + }); + + - name: Update Status - Deploying + uses: actions/github-script@v7 + with: + script: | + eval(process.env.GET_CONSOLE_LINK); + + const deployingMessage = [ + '🚀 Deploying to Control Plane...', + '', + '⏳ Waiting for deployment to be ready...', + '', + ' [View Deploy Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: deployingMessage + }); + + - name: Deploy to Control Plane + id: deploy + shell: bash + run: ${{ github.action_path }}/scripts/deploy.sh + env: + APP_NAME: ${{ inputs.app_name }} + CPLN_ORG: ${{ inputs.org }} + WAIT_TIMEOUT: ${{ inputs.wait_timeout }} + + - name: Update Status - Deployment Complete + if: always() + uses: actions/github-script@v7 + with: + script: | + eval(process.env.GET_CONSOLE_LINK); + + const prNumber = process.env.PR_NUMBER; + const appUrl = process.env.REVIEW_APP_URL; + const workflowUrl = process.env.WORKFLOW_URL; + const isSuccess = '${{ job.status }}' === 'success'; + + // Create GitHub deployment status + const deploymentStatus = { + owner: context.repo.owner, + repo: context.repo.repo, + deployment_id: process.env.DEPLOYMENT_ID, + state: isSuccess ? 'success' : 'failure', + environment_url: isSuccess ? appUrl : undefined, + log_url: workflowUrl, + environment: 'review' + }; + + await github.rest.repos.createDeploymentStatus(deploymentStatus); + + // Define messages based on deployment status + const successMessage = [ + '✅ Deployment complete for PR #' + prNumber + ', commit ' + process.env.COMMIT_HASH, + '', + '🌐 [Review App for PR #' + prNumber + '](' + appUrl + ')', + '', + ' [View Completed Action Build and Deploy Logs](' + workflowUrl + ')', + '', + getConsoleLink(prNumber) + ].join('\n'); + + const failureMessage = [ + '❌ Deployment failed for PR #' + prNumber + ', commit ' + process.env.COMMIT_HASH, + '', + ' [View Deployment Logs with Errors](' + workflowUrl + ')', + '', + getConsoleLink(prNumber) + ].join('\n'); + + // Update the existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: process.env.COMMENT_ID, + body: isSuccess ? successMessage : failureMessage + }); diff --git a/.github/actions/deploy-to-control-plane/scripts/deploy.sh b/.github/actions/deploy-to-control-plane/scripts/deploy.sh index 9d070b64..73bb8968 100755 --- a/.github/actions/deploy-to-control-plane/scripts/deploy.sh +++ b/.github/actions/deploy-to-control-plane/scripts/deploy.sh @@ -1,7 +1,7 @@ #!/bin/bash # This script handles the deployment to Control Plane and extracts the Rails URL -# +# # Required environment variables: # - APP_NAME: Name of the application to deploy # - CPLN_ORG: Control Plane organization @@ -31,21 +31,36 @@ trap 'rm -f "$TEMP_OUTPUT"' EXIT # Deploy the application echo "🚀 Deploying to Control Plane (timeout: ${WAIT_TIMEOUT}s)" -if timeout "$WAIT_TIMEOUT" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose | tee "$TEMP_OUTPUT"; then - # Extract Rails URL from deployment output - RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1) - if [ -n "$RAILS_URL" ]; then - echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT" - echo "✅ Deployment successful" - echo "🚀 Rails URL: $RAILS_URL" - else - echo "❌ Failed to extract Rails URL from deployment output" - exit 1 - fi -elif [ $? -eq 124 ]; then - echo "❌ Deployment timed out after $WAIT_TIMEOUT seconds" - exit 1 -else - echo "❌ Deployment to Control Plane failed" - exit 1 +if ! timeout "${WAIT_TIMEOUT}" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose 2>&1 | tee "$TEMP_OUTPUT"; then + echo "❌ Deployment failed" + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 +fi + +# Extract app URL from deployment output +RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1) +if [ -z "$RAILS_URL" ]; then + echo "❌ Failed to get app URL from deployment output" + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 fi + +# Wait for all workloads to be ready +echo "⏳ Waiting for all workloads to be ready (timeout: ${WAIT_TIMEOUT}s)" +if ! timeout "${WAIT_TIMEOUT}" bash -c "cpflow ps:wait -a \"$APP_NAME\"" 2>&1 | tee -a "$TEMP_OUTPUT"; then + TIMEOUT_EXIT=$? + if [ ${TIMEOUT_EXIT} -eq 124 ]; then + echo "❌ Timed out waiting for workloads after ${WAIT_TIMEOUT} seconds" + else + echo "❌ Workloads did not become ready" + fi + echo "Full output:" + cat "$TEMP_OUTPUT" + exit 1 +fi + +echo "✅ Deployment successful" +echo "🌐 Rails URL: $RAILS_URL" +echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/add-comment-on-pr-creation.yml b/.github/workflows/add-comment-on-pr-creation.yml index eef586a6..fa686305 100644 --- a/.github/workflows/add-comment-on-pr-creation.yml +++ b/.github/workflows/add-comment-on-pr-creation.yml @@ -10,21 +10,21 @@ jobs: permissions: pull-requests: write steps: - name: Add GitHub Comment for review app instructions - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: [ - "Hi 👋 Here are the commands available for this PR:", - "", - "- `/deploy-review-app`: Deploy your changes to a review environment", - "- `/delete-review-app`: Clean up the review environment when you're done", - "- `/help`: Show detailed information about all commands", - "", - "Use `/help` to see full documentation, including configuration options." - ].join("\n") - }); + - uses: actions/github-script@v7 + name: Add GitHub Comment for review app instructions + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: [ + "Hi 👋 Here are the commands available for this PR:", + "", + "- `/deploy-review-app`: Deploy your changes to a review environment", + "- `/delete-review-app`: Clean up the review environment when you're done", + "- `/help`: Show detailed information about all commands", + "", + "Use `/help` to see full documentation, including configuration options." + ].join("\n") + }); diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index caf90b18..fa24902c 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -55,18 +55,16 @@ jobs: script: | core.exportVariable('GET_CONSOLE_LINK', ` function getConsoleLink(prNumber) { - return ' [Control Plane Console for Review App with PR #' + prNumber + '](' + - 'https://console.cpln.io/org/' + process.env.CPLN_ORG + '/workloads/' + process.env.APP_NAME + ')'; + return '🎮 [Control Plane Console](' + + 'https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; } `); - - name: Initialize Delete - id: init-delete + - name: Setup Workflow URL + id: setup-workflow-url uses: actions/github-script@v7 with: script: | - eval(process.env.GET_CONSOLE_LINK); - async function getWorkflowUrl(runId) { // Get the current job ID const jobs = await github.rest.actions.listJobsForWorkflowRun({ @@ -87,34 +85,16 @@ jobs: } const workflowUrl = await getWorkflowUrl(context.runId); - - const comment = await github.rest.issues.createComment({ - issue_number: process.env.PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - body: [ - ' Starting app deletion...', - '', - ' [View Delete Logs](' + workflowUrl + ')', - '', - getConsoleLink(process.env.PR_NUMBER) - ].join('\n') - }); - - return { - commentId: comment.data.id, - workflowUrl - }; - - - name: Set workflow URL - run: | - echo "WORKFLOW_URL=${{ fromJSON(steps.init-delete.outputs.result).workflowUrl }}" >> $GITHUB_ENV + core.exportVariable('WORKFLOW_URL', workflowUrl); + return { workflowUrl }; - name: Create Initial Delete Comment - id: init-delete + id: create-delete-comment uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + let message = '🗑️ Starting app deletion'; if ('${{ github.event_name }}' === 'pull_request') { const merged = '${{ github.event.pull_request.merged }}' === 'true'; @@ -125,7 +105,13 @@ jobs: issue_number: process.env.PR_NUMBER, owner: context.repo.owner, repo: context.repo.repo, - body: message + body: [ + message, + '', + ' [View Delete Logs](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n') }); return { commentId: comment.data.id }; @@ -151,7 +137,9 @@ jobs: const successMessage = [ '✅ Review app for PR #' + prNumber + ' was successfully deleted', '', - ' [View Completed Delete Logs](' + process.env.WORKFLOW_URL + ')' + ' [View Completed Delete Logs](' + process.env.WORKFLOW_URL + ')', + '', + ' [Control Plane Organization](https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/-info)' ].join('\n'); const failureMessage = [ @@ -165,6 +153,6 @@ jobs: await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, - comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, + comment_id: ${{ fromJSON(steps.create-delete-comment.outputs.result).commentId }}, body: success ? successMessage : failureMessage }); diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 2adad3d7..9cdc4599 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -1,31 +1,33 @@ name: Deploy Review App to Control Plane -run-name: ${{ (github.event_name == 'pull_request' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)) && 'Deploying Review App' || format('Deploying {0} to Staging App', github.ref_name) }} +run-name: ${{ github.event_name == 'issue_comment' && 'Deploying Review App' || format('Updating Review App for {0}', github.ref_name) }} on: - pull_request: - types: [opened, synchronize, reopened] issue_comment: types: [created] + push: + branches-ignore: + - main # Don't run on main branch pushes + - master # Don't run on master branch pushes # Use concurrency to cancel in-progress runs concurrency: - group: deploy-${{ github.event.pull_request.number || github.event.issue.number }} + group: deploy-pr-${{ github.event.issue.number }} cancel-in-progress: true env: - APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} CPLN_ORG: ${{ secrets.CPLN_ORG }} CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: Process-Deployment-Command: + # For issue comments, only run on /deploy-review-app command + # For push events, only run if PR exists and has a review app if: | - (github.event_name == 'pull_request') || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - github.event.comment.body == '/deploy-review-app') + github.event.comment.body == '/deploy-review-app') || + github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: read @@ -34,60 +36,100 @@ jobs: issues: write steps: - - name: Get PR HEAD Ref - if: github.event_name == 'issue_comment' - id: getRef - run: | - # For PR comments, get the actual PR head commit - PR_DATA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName,headRefOid) - echo "PR_REF=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT - echo "PR_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')" >> $GITHUB_OUTPUT - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || steps.getRef.outputs.PR_REF || github.ref }} - - name: Validate Required Secrets + - name: Setup Environment + uses: ./.github/actions/setup-environment + + - name: Get PR Number for Push Event + if: github.event_name == 'push' + id: get-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - missing_secrets=() - for secret in "CPLN_TOKEN" "CPLN_ORG"; do - if [ -z "${!secret}" ]; then - missing_secrets+=("$secret") - fi - done - - if [ ${#missing_secrets[@]} -ne 0 ]; then - echo "Required secrets are not set: ${missing_secrets[*]}" - exit 1 + # Get PR number from branch + PR_NUMBER=$(gh pr list --head ${{ github.ref_name }} --json number --jq '.[0].number') + if [ -n "$PR_NUMBER" ]; then + echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV + echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-$PR_NUMBER" >> $GITHUB_ENV + echo "has_pr=true" >> $GITHUB_OUTPUT + else + echo "No PR found for this branch" + exit 0 fi - - name: Setup Environment - uses: ./.github/actions/setup-environment + - name: Set PR Number for Comment Event + if: github.event_name == 'issue_comment' + run: | + echo "PR_NUMBER=${{ github.event.issue.number }}" >> $GITHUB_ENV + echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-${{ github.event.issue.number }}" >> $GITHUB_ENV + + - name: Check if Review App Exists + id: check-app + if: github.event_name == 'push' && steps.get-pr.outputs.has_pr == 'true' + env: + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + run: | + if ! cpflow exists -a ${{ env.APP_NAME }}; then + echo "No review app exists for this PR" + exit 0 + fi + echo "app_exists=true" >> $GITHUB_OUTPUT - - name: Set shared functions - id: shared-functions + - name: Get PR HEAD Ref + id: getRef + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') + run: | + PR_DATA=$(gh pr view ${{ env.PR_NUMBER }} --repo ${{ github.repository }} --json headRefName,headRefOid) + echo "PR_REF=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT + echo "PR_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Initial Comment + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') + id: create-comment uses: actions/github-script@v7 with: script: | - core.exportVariable('GET_CONSOLE_LINK', ` - function getConsoleLink(prNumber) { - return ' [Control Plane Console for Review App with PR #' + prNumber + '](' + - 'https://console.cpln.io/org/' + process.env.CPLN_ORG + '/workloads/' + process.env.APP_NAME + ')'; - } - `); + const result = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: process.env.PR_NUMBER, + body: '🚀 Starting deployment process...' + }); + console.log('Created comment:', result.data.id); + return { commentId: result.data.id }; + + - name: Set Comment ID + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') + run: echo "COMMENT_ID=${{ fromJSON(steps.create-comment.outputs.result).commentId }}" >> $GITHUB_ENV - - name: Initialize Deployment - id: init-deployment + - name: Set Workflow URL + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') + id: workflow-url uses: actions/github-script@v7 with: script: | - eval(process.env.GET_CONSOLE_LINK); - async function getWorkflowUrl(runId) { - // Get the current job ID const jobs = await github.rest.actions.listJobsForWorkflowRun({ owner: context.repo.owner, repo: context.repo.repo, @@ -98,59 +140,36 @@ jobs: const jobId = currentJob?.id; if (!jobId) { - console.log('Warning: Could not find current job ID'); return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; } return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}`; } - // Create initial deployment comment - const comment = await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: process.env.PR_NUMBER, - body: ' Initializing deployment...' - }); - - // Create GitHub deployment - const deployment = await github.rest.repos.createDeployment({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: context.sha, - environment: 'review', - auto_merge: false, - required_contexts: [] - }); - const workflowUrl = await getWorkflowUrl(context.runId); - - return { - deploymentId: deployment.data.id, - commentId: comment.data.id, - workflowUrl - }; - - - name: Set comment ID and workflow URL - run: | - echo "COMMENT_ID=${{ fromJSON(steps.init-deployment.outputs.result).commentId }}" >> $GITHUB_ENV - echo "WORKFLOW_URL=${{ fromJSON(steps.init-deployment.outputs.result).workflowUrl }}" >> $GITHUB_ENV - - - name: Set commit hash - run: | - FULL_COMMIT="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || steps.getRef.outputs.PR_SHA || github.sha }}" - echo "COMMIT_HASH=${FULL_COMMIT:0:7}" >> $GITHUB_ENV + core.exportVariable('WORKFLOW_URL', workflowUrl); + core.exportVariable('GET_CONSOLE_LINK', ` + function getConsoleLink(prNumber) { + return '🎮 [Control Plane Console](' + + 'https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; + } + `); - name: Update Status - Building + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') uses: actions/github-script@v7 with: script: | eval(process.env.GET_CONSOLE_LINK); const buildingMessage = [ - ' Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', + '🏗️ Building Docker image...', '', - ' [View Build Logs](' + process.env.WORKFLOW_URL + ')', + '📝 [View Build Logs](' + process.env.WORKFLOW_URL + ')', '', getConsoleLink(process.env.PR_NUMBER) ].join('\n'); @@ -162,26 +181,44 @@ jobs: body: buildingMessage }); + - name: Checkout PR Branch + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') + run: git checkout ${{ steps.getRef.outputs.PR_REF }} + - name: Build Docker Image + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') uses: ./.github/actions/build-docker-image with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} - commit: ${{ env.COMMIT_HASH }} + commit: ${{ steps.getRef.outputs.PR_SHA }} PR_NUMBER: ${{ env.PR_NUMBER }} - name: Update Status - Deploying + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') uses: actions/github-script@v7 with: script: | eval(process.env.GET_CONSOLE_LINK); const deployingMessage = [ - ' Deploying to Control Plane...', + '🚀 Deploying to Control Plane...', '', - ' Waiting for deployment to be ready...', + '⏳ Waiting for deployment to be ready...', '', - ' [View Deploy Logs](' + process.env.WORKFLOW_URL + ')', + '📝 [View Deploy Logs](' + process.env.WORKFLOW_URL + ')', '', getConsoleLink(process.env.PR_NUMBER) ].join('\n'); @@ -194,102 +231,41 @@ jobs: }); - name: Deploy to Control Plane + if: | + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/deploy-review-app') || + (steps.get-pr.outputs.has_pr == 'true' && steps.check-app.outputs.app_exists == 'true') uses: ./.github/actions/deploy-to-control-plane with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} github_token: ${{ secrets.GITHUB_TOKEN }} wait_timeout: ${{ vars.WAIT_TIMEOUT || 900 }} + env: + CPLN_TOKEN: ${{ env.CPLN_TOKEN }} + PR_NUMBER: ${{ env.PR_NUMBER }} - - name: Update Status - Deployment Complete + - name: Update Status - Success + if: success() uses: actions/github-script@v7 with: script: | eval(process.env.GET_CONSOLE_LINK); - const prNumber = process.env.PR_NUMBER; - const appUrl = process.env.REVIEW_APP_URL; - const workflowUrl = process.env.WORKFLOW_URL; - const isSuccess = '${{ job.status }}' === 'success'; - - // Create GitHub deployment status - const deploymentStatus = { - owner: context.repo.owner, - repo: context.repo.repo, - deployment_id: ${{ fromJSON(steps.init-deployment.outputs.result).deploymentId }}, - state: isSuccess ? 'success' : 'failure', - environment_url: isSuccess ? appUrl : undefined, - log_url: workflowUrl, - environment: 'review' - }; - - await github.rest.repos.createDeploymentStatus(deploymentStatus); - - // Define messages based on deployment status const successMessage = [ - ' Deployment complete for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', - '', - ' [Review App for PR #' + prNumber + '](' + appUrl + ')', - '', - ' [View Completed Action Build and Deploy Logs](' + workflowUrl + ')', + '✅ Deployment successful!', '', - getConsoleLink(prNumber) - ].join('\n'); - - const failureMessage = [ - ' Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', + '🌐 Review app is ready at: ${{ env.REVIEW_APP_URL }}', '', - ' [View Deployment Logs with Errors](' + workflowUrl + ')', + '📝 [View App Logs](' + process.env.WORKFLOW_URL + ')', '', - getConsoleLink(prNumber) + getConsoleLink(process.env.PR_NUMBER) ].join('\n'); - // Update the existing comment await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: process.env.COMMENT_ID, - body: isSuccess ? successMessage : failureMessage + body: successMessage }); - - show-help: - if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/help' - runs-on: ubuntu-latest - - steps: - - name: Show Available Commands - uses: actions/github-script@v7 - with: - script: | - const helpMessage = [ - '## Available Commands', - '', - '### `/deploy-review-app`', - 'Deploys your PR branch to a review environment on Control Plane.', - '- Creates a new review app if one doesn\'t exist', - '- Updates the existing review app if it already exists', - '- Provides a unique URL to preview your changes', - '- Shows build and deployment progress in real-time', - '', - '### `/delete-review-app`', - 'Deletes the review app associated with this PR.', - '- Removes all resources from Control Plane', - '- Helpful for cleaning up when you\'re done testing', - '- Can be re-deployed later using `/deploy-review-app`', - '', - '### `/help`', - 'Shows this help message explaining available commands.', - '', - '---', - '_Note: These commands only work in pull request comments._' - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: helpMessage - }); \ No newline at end of file diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index ff59b65f..8894d034 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -1,4 +1,4 @@ -name: Show Help for Commands +name: Show Detailed Help on: issue_comment: @@ -27,11 +27,15 @@ jobs: with: script: | try { - console.log('Creating help message...'); + console.log('Creating detailed help message...'); const helpMessage = [ - '## 📚 Available Commands', + '# 📚 Detailed Review App Commands Guide', '', - '### `/deploy`', + 'This is a detailed guide to using review app commands. For a quick reference, see the message posted when your PR was created.', + '', + '## Available Commands', + '', + '### `/deploy-review-app`', 'Deploys your PR branch to a review environment on Control Plane.', '- Creates a new review app if one doesn\'t exist', '- Updates the existing review app if it already exists', @@ -52,23 +56,37 @@ jobs: 'Deletes the review app associated with this PR.', '- Removes all resources from Control Plane', '- Helpful for cleaning up when you\'re done testing', - '- Can be re-deployed later using `/deploy`', + '- Can be re-deployed later using `/deploy-review-app`', '', '**Required Environment Variables:**', '- `CPLN_TOKEN`: Control Plane authentication token', '- `CPLN_ORG`: Control Plane organization name', '', '### `/help`', - 'Shows this help message explaining available commands and configuration.', + 'Shows this detailed help message.', '', '---', - '**Note:** These commands only work in pull request comments.', + '## Environment Setup', '', - '**Environment Setup:**', '1. Set required secrets in your repository settings:', ' - `CPLN_TOKEN`', ' - `CPLN_ORG`', - '2. Optional: Configure `WAIT_TIMEOUT` in GitHub Actions variables to customize deployment timeout' + '', + '2. Optional: Configure `WAIT_TIMEOUT` in GitHub Actions variables to customize deployment timeout', + '', + '## Control Plane Integration', + '', + 'Review apps are deployed to Control Plane with the following configuration:', + '- App Name Format: `qa-react-webpack-rails-tutorial-pr-{PR_NUMBER}`', + '- Console URL: `https://console.cpln.io/console/org/{CPLN_ORG}/gvc/{APP_NAME}/-info`', + '', + '## Automatic Cleanup', + '', + 'Review apps are automatically deleted when:', + '- The PR is closed (merged or not merged)', + '- The PR is stale (via nightly cleanup job)', + '', + 'For more information, see the [React on Rails Tutorial documentation](https://github.com/shakacode/react-on-rails/tree/master/react-webpack-rails-tutorial)' ].join('\n'); console.log('Issue number:', github.context.payload.issue.number); diff --git a/.github/workflows/review-app-help.yml b/.github/workflows/review-app-help.yml new file mode 100644 index 00000000..313101e4 --- /dev/null +++ b/.github/workflows/review-app-help.yml @@ -0,0 +1,52 @@ +name: Show Quick Help on PR Creation + +on: + pull_request: + types: [opened] + +permissions: + issues: write + pull-requests: write + +jobs: + show-quick-help: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + steps: + - name: Show Quick Reference + uses: actions/github-script@v7 + with: + script: | + try { + console.log('Creating quick reference message...'); + const helpMessage = [ + '# 🚀 Quick Review App Commands', + '', + 'Welcome! Here are the commands you can use in this PR:', + '', + '### `/deploy-review-app`', + 'Deploy your PR branch for testing', + '', + '### `/delete-review-app`', + 'Remove the review app when done', + '', + '### `/help`', + 'Show detailed documentation', + '', + '---', + '**Note:** Type `/help` for detailed instructions, environment setup, and configuration options.' + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: helpMessage + }); + + console.log('Quick reference posted successfully'); + } catch (error) { + console.error('Error posting quick reference:', error); + core.setFailed(`Failed to post quick reference: ${error.message}`); + }