From 151c5272379039dae0a13c1bfc8cce467465dc4e Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sun, 26 Jan 2025 01:35:46 -1000 Subject: [PATCH] Enhance GitHub Actions workflows with new features and improvements (#618) Summary: Added validation for required secrets in deployment workflows. Introduced multiple status update steps during deployment for better feedback. Enhanced help command to reflect updated deployment command names. Refined deployment triggers to focus on issue comments and push events. Enhanced error handling and logging in deployment scripts. Streamlined deployment process with checks for existing review apps. Simplified workflow configuration for better readability and management. --- .../deploy-to-control-plane/action.yml | 267 ++++++++++++---- .../deploy-to-control-plane/scripts/deploy.sh | 51 +-- .../workflows/add-comment-on-pr-creation.yml | 36 +-- .github/workflows/delete-review-app.yml | 52 ++- .github/workflows/deploy-to-control-plane.yml | 302 ++++++++---------- .github/workflows/help-command.yml | 36 ++- .github/workflows/review-app-help.yml | 52 +++ 7 files changed, 501 insertions(+), 295 deletions(-) create mode 100644 .github/workflows/review-app-help.yml 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}`); + }