From 63fa0e8765ed2d21db3f02b79ee4e20369870a57 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 22:40:30 -1000 Subject: [PATCH 1/8] delete-on-pr-close --- .github/workflows/delete-review-app.yml | 49 ++++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index 074b7c7e..205fb748 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -1,6 +1,8 @@ name: Delete Review App on: + pull_request: + types: [closed] issue_comment: types: [created] @@ -13,8 +15,8 @@ permissions: env: CPLN_ORG: ${{ secrets.CPLN_ORG }} CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} - APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.issue.number }} - PR_NUMBER: ${{ github.event.issue.number }} + APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number }} + PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: debug-trigger: @@ -26,20 +28,26 @@ jobs: EVENT_NAME: ${{ github.event_name }} IS_PR: ${{ toJSON(github.event.issue.pull_request) }} COMMENT: ${{ github.event.comment.body }} + PR_ACTION: ${{ github.event.action }} + PR_MERGED: ${{ github.event.pull_request.merged }} run: | echo "Debug information for delete-review-app command:" echo "Event name: $EVENT_NAME" echo "Is PR (raw): $IS_PR" echo "Comment body: $COMMENT" + echo "PR action: $PR_ACTION" + echo "PR merged: $PR_MERGED" echo "Raw event payload:" echo '${{ toJSON(github.event) }}' Process-Delete-Command: needs: debug-trigger if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/delete-review-app' + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.body == '/delete-review-app') || + (github.event_name == 'pull_request' && + github.event.action == 'closed') runs-on: ubuntu-latest steps: @@ -55,7 +63,7 @@ jobs: done if [ ${#missing_secrets[@]} -ne 0 ]; then - echo " Required secrets are not set: ${missing_secrets[*]}" + echo "Required secrets are not set: ${missing_secrets[*]}" exit 1 fi @@ -67,11 +75,17 @@ jobs: uses: actions/github-script@v7 with: script: | + let message = '🗑️ Starting app deletion'; + if ('${{ github.event_name }}' === 'pull_request') { + const merged = '${{ github.event.pull_request.merged }}' === 'true'; + message += merged ? ' (PR merged)' : ' (PR closed)'; + } + 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...' + body: message }); return { commentId: comment.data.id }; @@ -93,13 +107,20 @@ jobs: const prNumber = process.env.PR_NUMBER; const cpConsoleUrl = `https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME}`; - const message = success - ? ' Review app for PR #' + prNumber + ' was successfully deleted' - : [ - ' Review app for PR #' + prNumber + ' failed to be deleted', - '', - '[Control Plane Console for Review App with PR #' + prNumber + '](' + cpConsoleUrl + ')' - ].join('\n'); + let message; + if (success) { + message = '✅ Review app for PR #' + prNumber + ' was successfully deleted'; + if ('${{ github.event_name }}' === 'pull_request') { + const merged = '${{ github.event.pull_request.merged }}' === 'true'; + message += merged ? ' after merge' : ' after PR was closed'; + } + } else { + message = [ + '❌ Review app for PR #' + prNumber + ' failed to be deleted', + '', + '🎮 [Control Plane Console for Review App with PR #' + prNumber + '](' + cpConsoleUrl + ')' + ].join('\n'); + } await github.rest.issues.updateComment({ owner: context.repo.owner, From 7e409cb686aa34aa9ea2e623cbfd42b8ad5e1aeb Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 22:48:19 -1000 Subject: [PATCH 2/8] delete-on-pr-close --- .../workflows/add-comment-on-pr-creation.yml | 24 +++++++++++++------ .github/workflows/deploy-to-control-plane.yml | 18 ++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/add-comment-on-pr-creation.yml b/.github/workflows/add-comment-on-pr-creation.yml index bd4b69de..cd6f3662 100644 --- a/.github/workflows/add-comment-on-pr-creation.yml +++ b/.github/workflows/add-comment-on-pr-creation.yml @@ -1,20 +1,30 @@ -name: Add helper Comment on PR creation +name: Add Comment on PR Creation on: pull_request: types: [opened] jobs: - comment-on-pr: + add-comment: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - - name: Add GitHub Comment for review app instructions + - name: Add comment uses: actions/github-script@v7 with: script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, + await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - body: "Hi 👋 To deploy a review app, please comment `/deploy-review-app`" - }) + issue_number: context.payload.pull_request.number, + body: [ + "Hi 👋 Here are the commands available for this PR:", + "", + "- `/deploy`: 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/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index ad1dbb49..20079626 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -20,7 +20,25 @@ env: PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: + debug-trigger: + if: always() + runs-on: ubuntu-latest + steps: + - name: Debug Trigger Conditions + env: + EVENT_NAME: ${{ github.event_name }} + IS_PR: ${{ toJSON(github.event.issue.pull_request) }} + COMMENT: ${{ github.event.comment.body }} + run: | + echo "Debug information for deploy command:" + echo "Event name: $EVENT_NAME" + echo "Is PR (raw): $IS_PR" + echo "Comment body: $COMMENT" + echo "Raw event payload:" + echo '${{ toJSON(github.event) }}' + Process-Deployment-Command: + needs: debug-trigger if: | (github.event_name == 'pull_request') || (github.event_name == 'issue_comment' && From 7685e2b102c66a6fb0d7c28ecfc91df3236f4a81 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 22:52:59 -1000 Subject: [PATCH 3/8] delete-on-pr-close --- .github/workflows/help-command.yml | 112 ++++++++++++++++------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 20a0e828..4d647de4 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -18,11 +18,13 @@ jobs: EVENT_NAME: ${{ github.event_name }} IS_PR: ${{ toJSON(github.event.issue.pull_request) }} COMMENT: ${{ github.event.comment.body }} + ISSUE_NUMBER: ${{ github.event.issue.number }} run: | echo "Debug information for help command:" echo "Event name: $EVENT_NAME" echo "Is PR (raw): $IS_PR" echo "Comment body: $COMMENT" + echo "Issue number: $ISSUE_NUMBER" echo "Raw event payload:" echo '${{ toJSON(github.event) }}' @@ -39,52 +41,64 @@ jobs: uses: actions/github-script@v7 with: script: | - const helpMessage = [ - '## 📚 Available Commands', - '', - '### `/deploy`', - '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', - '', - '**Required Environment Variables:**', - '- `CPLN_TOKEN`: Control Plane authentication token', - '- `CPLN_ORG`: Control Plane organization name', - '', - '**Optional Configuration:**', - '- `WAIT_TIMEOUT`: Deployment timeout in seconds (default: 900)', - ' - Must be a positive integer', - ' - Can be set in GitHub Actions variables', - ' - Applies to both deployment and workload readiness checks', - '', - '### `/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`', - '', - '**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.', - '', - '---', - '**Note:** These commands only work in pull request comments.', - '', - '**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' - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.issue.number, - body: helpMessage - }); + try { + console.log('Creating help message...'); + const helpMessage = [ + '## 📚 Available Commands', + '', + '### `/deploy`', + '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', + '', + '**Required Environment Variables:**', + '- `CPLN_TOKEN`: Control Plane authentication token', + '- `CPLN_ORG`: Control Plane organization name', + '', + '**Optional Configuration:**', + '- `WAIT_TIMEOUT`: Deployment timeout in seconds (default: 900)', + ' - Must be a positive integer', + ' - Can be set in GitHub Actions variables', + ' - Applies to both deployment and workload readiness checks', + '', + '### `/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`', + '', + '**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.', + '', + '---', + '**Note:** These commands only work in pull request comments.', + '', + '**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' + ].join('\n'); + + console.log('Issue number:', github.context.payload.issue.number); + console.log('Owner:', context.repo.owner); + console.log('Repo:', context.repo.repo); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: github.context.payload.issue.number, + body: helpMessage + }); + + console.log('Help message posted successfully'); + } catch (error) { + console.error('Error posting help message:', error); + core.setFailed(`Failed to post help message: ${error.message}`); + } From f489d7859855a3d32c77f586ce777f63fe8d12b7 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 22:56:35 -1000 Subject: [PATCH 4/8] delete-on-pr-close --- .github/workflows/deploy-to-control-plane.yml | 60 ------------------- .github/workflows/help-command.yml | 5 ++ 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 20079626..9d994b94 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -270,66 +270,6 @@ jobs: body: isSuccess ? successMessage : failureMessage }); - debug-help: - if: always() - runs-on: ubuntu-latest - steps: - - name: Debug Trigger Conditions - env: - EVENT_NAME: ${{ github.event_name }} - IS_PR: ${{ toJSON(github.event.issue.pull_request) }} - COMMENT: ${{ github.event.comment.body }} - run: | - echo "Debug information for help command:" - echo "Event name: $EVENT_NAME" - echo "Is PR (raw): $IS_PR" - echo "Comment body: $COMMENT" - echo "Raw event payload:" - echo '${{ toJSON(github.event) }}' - - show-help: - needs: debug-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`', - '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`', - '', - '### `/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 - }); - debug-delete: if: always() runs-on: ubuntu-latest diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 4d647de4..8c4de379 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -4,6 +4,11 @@ on: issue_comment: types: [created] +# Use concurrency to prevent duplicate runs +concurrency: + group: help-${{ github.event.issue.number }} + cancel-in-progress: true + permissions: issues: write pull-requests: write From e83b55c3bb32942613f191bb13b7a510e240f6c6 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 23:12:21 -1000 Subject: [PATCH 5/8] delete-on-pr-close --- .github/workflows/delete-review-app.yml | 22 -- .github/workflows/deploy-to-control-plane.yml | 290 ++---------------- 2 files changed, 32 insertions(+), 280 deletions(-) diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index 205fb748..8a0b07e6 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -19,29 +19,7 @@ env: PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: - debug-trigger: - if: always() - runs-on: ubuntu-latest - steps: - - name: Debug Trigger Conditions - env: - EVENT_NAME: ${{ github.event_name }} - IS_PR: ${{ toJSON(github.event.issue.pull_request) }} - COMMENT: ${{ github.event.comment.body }} - PR_ACTION: ${{ github.event.action }} - PR_MERGED: ${{ github.event.pull_request.merged }} - run: | - echo "Debug information for delete-review-app command:" - echo "Event name: $EVENT_NAME" - echo "Is PR (raw): $IS_PR" - echo "Comment body: $COMMENT" - echo "PR action: $PR_ACTION" - echo "PR merged: $PR_MERGED" - echo "Raw event payload:" - echo '${{ toJSON(github.event) }}' - Process-Delete-Command: - needs: debug-trigger if: | (github.event_name == 'issue_comment' && github.event.issue.pull_request && diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 9d994b94..15f10d47 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -20,25 +20,7 @@ env: PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} jobs: - debug-trigger: - if: always() - runs-on: ubuntu-latest - steps: - - name: Debug Trigger Conditions - env: - EVENT_NAME: ${{ github.event_name }} - IS_PR: ${{ toJSON(github.event.issue.pull_request) }} - COMMENT: ${{ github.event.comment.body }} - run: | - echo "Debug information for deploy command:" - echo "Event name: $EVENT_NAME" - echo "Is PR (raw): $IS_PR" - echo "Comment body: $COMMENT" - echo "Raw event payload:" - echo '${{ toJSON(github.event) }}' - Process-Deployment-Command: - needs: debug-trigger if: | (github.event_name == 'pull_request') || (github.event_name == 'issue_comment' && @@ -52,21 +34,17 @@ jobs: issues: write steps: + - uses: actions/checkout@v4 + - 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 }} + PR_URL=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.v3+json" \ + "${{ github.event.issue.pull_request.url }}") + HEAD_REF=$(echo "$PR_URL" | jq -r .head.ref) + echo "ref=$HEAD_REF" >> "$GITHUB_OUTPUT" - name: Validate Required Secrets run: | @@ -85,278 +63,74 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup-environment - - 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 + - name: Create Initial Status Comment + id: init-status 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: [] + body: '🚀 Starting deployment...' }); - - 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 + return { commentId: comment.data.id }; - 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 ' + '${{ 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: Build Docker Image - uses: ./.github/actions/build-docker-image - with: - app_name: ${{ env.APP_NAME }} - org: ${{ env.CPLN_ORG }} - commit: ${{ env.COMMIT_HASH }} - PR_NUMBER: ${{ env.PR_NUMBER }} - - - 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 + comment_id: ${{ fromJSON(steps.init-status.outputs.result).commentId }}, + body: [ + '🏗️ Building review app...', + '', + getConsoleLink(process.env.PR_NUMBER) + ].join('\n') }); - - name: Deploy to Control Plane + - name: Deploy Review App + id: deploy uses: ./.github/actions/deploy-to-control-plane with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} + wait_timeout: 900 github_token: ${{ secrets.GITHUB_TOKEN }} - wait_timeout: ${{ vars.WAIT_TIMEOUT || 900 }} + env: + CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} - - name: Update Status - Deployment Complete + - name: Update Status - 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: ${{ 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); + const isSuccess = '${{ job.status }}' === 'success'; + const railsUrl = '${{ steps.deploy.outputs.rails_url }}'; - // Define messages based on deployment status const successMessage = [ - ' Deployment complete for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', - '', - ' [Review App for PR #' + prNumber + '](' + appUrl + ')', + '✅ Review app deployed successfully!', '', - ' [View Completed Action Build and Deploy Logs](' + workflowUrl + ')', + '🌐 [Rails App](' + railsUrl + ')', '', - getConsoleLink(prNumber) + getConsoleLink(process.env.PR_NUMBER) ].join('\n'); const failureMessage = [ - ' Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', - '', - ' [View Deployment Logs with Errors](' + workflowUrl + ')', + '❌ Review app deployment failed', '', - 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, + comment_id: ${{ fromJSON(steps.init-status.outputs.result).commentId }}, body: isSuccess ? successMessage : failureMessage - }); - - debug-delete: - if: always() - runs-on: ubuntu-latest - steps: - - name: Debug Trigger Conditions - env: - EVENT_NAME: ${{ github.event_name }} - IS_PR: ${{ toJSON(github.event.issue.pull_request) }} - COMMENT: ${{ github.event.comment.body }} - run: | - echo "Debug information for delete-review-app command:" - echo "Event name: $EVENT_NAME" - echo "Is PR (raw): $IS_PR" - echo "Comment body: $COMMENT" - echo "Raw event payload:" - echo '${{ toJSON(github.event) }}' - - Process-Delete-Command: - needs: debug-delete - if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request && - github.event.comment.body == '/delete-review-app' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Validate Required Secrets - 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: Create Initial Delete Comment - id: init-delete - uses: actions/github-script@v7 - with: - script: | - 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...' - }); - return { commentId: comment.data.id }; - - - name: Delete Review App - uses: ./.github/actions/delete-control-plane-app - with: - app_name: ${{ env.APP_NAME }} - org: ${{ env.CPLN_ORG }} - github_token: ${{ secrets.GITHUB_TOKEN }} - env: - CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} - - - name: Update Delete Status - if: always() - uses: actions/github-script@v7 - with: - script: | - const success = '${{ job.status }}' === 'success'; - const prNumber = process.env.PR_NUMBER; - const cpConsoleUrl = `https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME}`; - - const message = success - ? ' Review app for PR #' + prNumber + ' was successfully deleted' - : [ - ' Review app for PR #' + prNumber + ' failed to be deleted', - '', - ' [Control Plane Console for Review App with PR #' + prNumber + '](' + cpConsoleUrl + ')' - ].join('\n'); - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, - body: message }); \ No newline at end of file From 927533e6acdcb97e1962b739ebf340978d0d1242 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 23:16:34 -1000 Subject: [PATCH 6/8] delete-on-pr-close --- .github/workflows/deploy-to-control-plane.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 15f10d47..4f8f9ae0 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -25,7 +25,7 @@ jobs: (github.event_name == 'pull_request') || (github.event_name == 'issue_comment' && github.event.issue.pull_request && - github.event.comment.body == '/deploy') + github.event.comment.body == '/deploy-review-app') runs-on: ubuntu-latest permissions: contents: read @@ -80,7 +80,9 @@ jobs: uses: actions/github-script@v7 with: script: | - eval(process.env.GET_CONSOLE_LINK); + function getConsoleLink(prNumber) { + return `[Control Plane Console](https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME})`; + } await github.rest.issues.updateComment({ owner: context.repo.owner, @@ -109,7 +111,9 @@ jobs: uses: actions/github-script@v7 with: script: | - eval(process.env.GET_CONSOLE_LINK); + function getConsoleLink(prNumber) { + return `[Control Plane Console](https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME})`; + } const isSuccess = '${{ job.status }}' === 'success'; const railsUrl = '${{ steps.deploy.outputs.rails_url }}'; From 393ac6c5cb9426446abf73ec12b226f5e1df5299 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 23:26:43 -1000 Subject: [PATCH 7/8] delete-on-pr-close --- .github/workflows/deploy-to-control-plane.yml | 231 +++++++++++++++--- 1 file changed, 193 insertions(+), 38 deletions(-) diff --git a/.github/workflows/deploy-to-control-plane.yml b/.github/workflows/deploy-to-control-plane.yml index 4f8f9ae0..2adad3d7 100644 --- a/.github/workflows/deploy-to-control-plane.yml +++ b/.github/workflows/deploy-to-control-plane.yml @@ -34,17 +34,21 @@ jobs: issues: write steps: - - uses: actions/checkout@v4 - - name: Get PR HEAD Ref if: github.event_name == 'issue_comment' id: getRef run: | - PR_URL=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "${{ github.event.issue.pull_request.url }}") - HEAD_REF=$(echo "$PR_URL" | jq -r .head.ref) - echo "ref=$HEAD_REF" >> "$GITHUB_OUTPUT" + # 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 run: | @@ -63,78 +67,229 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup-environment - - name: Create Initial Status Comment - id: init-status + - 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, - body: '🚀 Starting deployment...' + ref: context.sha, + environment: 'review', + auto_merge: false, + required_contexts: [] }); - return { commentId: comment.data.id }; + + 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 - name: Update Status - Building uses: actions/github-script@v7 with: script: | - function getConsoleLink(prNumber) { - return `[Control Plane Console](https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME})`; - } + eval(process.env.GET_CONSOLE_LINK); + + const buildingMessage = [ + ' Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ 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: ${{ fromJSON(steps.init-status.outputs.result).commentId }}, - body: [ - '🏗️ Building review app...', - '', - getConsoleLink(process.env.PR_NUMBER) - ].join('\n') + comment_id: process.env.COMMENT_ID, + body: buildingMessage }); - - name: Deploy Review App - id: deploy + - name: Build Docker Image + uses: ./.github/actions/build-docker-image + with: + app_name: ${{ env.APP_NAME }} + org: ${{ env.CPLN_ORG }} + commit: ${{ env.COMMIT_HASH }} + PR_NUMBER: ${{ env.PR_NUMBER }} + + - 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 uses: ./.github/actions/deploy-to-control-plane with: app_name: ${{ env.APP_NAME }} org: ${{ env.CPLN_ORG }} - wait_timeout: 900 github_token: ${{ secrets.GITHUB_TOKEN }} - env: - CPLN_TOKEN: ${{ secrets.CPLN_TOKEN }} + wait_timeout: ${{ vars.WAIT_TIMEOUT || 900 }} - - name: Update Status - Complete - if: always() + - name: Update Status - Deployment Complete uses: actions/github-script@v7 with: script: | - function getConsoleLink(prNumber) { - return `[Control Plane Console](https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME})`; - } - + 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'; - const railsUrl = '${{ steps.deploy.outputs.rails_url }}'; + // 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 = [ - '✅ Review app deployed successfully!', + ' Deployment complete for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', '', - '🌐 [Rails App](' + railsUrl + ')', + ' [Review App for PR #' + prNumber + '](' + appUrl + ')', '', - getConsoleLink(process.env.PR_NUMBER) + ' [View Completed Action Build and Deploy Logs](' + workflowUrl + ')', + '', + getConsoleLink(prNumber) ].join('\n'); const failureMessage = [ - '❌ Review app deployment failed', + ' Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', '', - getConsoleLink(process.env.PR_NUMBER) + ' [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: ${{ fromJSON(steps.init-status.outputs.result).commentId }}, + comment_id: process.env.COMMENT_ID, body: isSuccess ? successMessage : failureMessage + }); + + 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 From 68d9ecd440cbcdd8e61c74d25651b38a974cb844 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Sat, 25 Jan 2025 23:33:32 -1000 Subject: [PATCH 8/8] delete-on-pr-close --- .../workflows/add-comment-on-pr-creation.yml | 4 +- .github/workflows/delete-review-app.yml | 94 +++++++++++++++---- .github/workflows/help-command.yml | 20 ---- 3 files changed, 80 insertions(+), 38 deletions(-) diff --git a/.github/workflows/add-comment-on-pr-creation.yml b/.github/workflows/add-comment-on-pr-creation.yml index cd6f3662..eef586a6 100644 --- a/.github/workflows/add-comment-on-pr-creation.yml +++ b/.github/workflows/add-comment-on-pr-creation.yml @@ -10,7 +10,7 @@ jobs: permissions: pull-requests: write steps: - - name: Add comment + name: Add GitHub Comment for review app instructions uses: actions/github-script@v7 with: script: | @@ -21,7 +21,7 @@ jobs: body: [ "Hi 👋 Here are the commands available for this PR:", "", - "- `/deploy`: Deploy your changes to a review environment", + "- `/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", "", diff --git a/.github/workflows/delete-review-app.yml b/.github/workflows/delete-review-app.yml index 8a0b07e6..caf90b18 100644 --- a/.github/workflows/delete-review-app.yml +++ b/.github/workflows/delete-review-app.yml @@ -48,6 +48,68 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup-environment + - 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 Delete + id: init-delete + 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}`; + } + + 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 + - name: Create Initial Delete Comment id: init-delete uses: actions/github-script@v7 @@ -81,28 +143,28 @@ jobs: uses: actions/github-script@v7 with: script: | + eval(process.env.GET_CONSOLE_LINK); + const success = '${{ job.status }}' === 'success'; const prNumber = process.env.PR_NUMBER; - const cpConsoleUrl = `https://console.cpln.io/org/${process.env.CPLN_ORG}/workloads/${process.env.APP_NAME}`; - let message; - if (success) { - message = '✅ Review app for PR #' + prNumber + ' was successfully deleted'; - if ('${{ github.event_name }}' === 'pull_request') { - const merged = '${{ github.event.pull_request.merged }}' === 'true'; - message += merged ? ' after merge' : ' after PR was closed'; - } - } else { - message = [ - '❌ Review app for PR #' + prNumber + ' failed to be deleted', - '', - '🎮 [Control Plane Console for Review App with PR #' + prNumber + '](' + cpConsoleUrl + ')' - ].join('\n'); - } + const successMessage = [ + '✅ Review app for PR #' + prNumber + ' was successfully deleted', + '', + ' [View Completed Delete Logs](' + process.env.WORKFLOW_URL + ')' + ].join('\n'); + + const failureMessage = [ + '❌ Review app for PR #' + prNumber + ' failed to be deleted', + '', + ' [View Delete Logs with Errors](' + process.env.WORKFLOW_URL + ')', + '', + getConsoleLink(prNumber) + ].join('\n'); await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, - body: message + body: success ? successMessage : failureMessage }); diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 8c4de379..ff59b65f 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -14,27 +14,7 @@ permissions: pull-requests: write jobs: - debug-trigger: - if: always() - runs-on: ubuntu-latest - steps: - - name: Debug Trigger Conditions - env: - EVENT_NAME: ${{ github.event_name }} - IS_PR: ${{ toJSON(github.event.issue.pull_request) }} - COMMENT: ${{ github.event.comment.body }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - run: | - echo "Debug information for help command:" - echo "Event name: $EVENT_NAME" - echo "Is PR (raw): $IS_PR" - echo "Comment body: $COMMENT" - echo "Issue number: $ISSUE_NUMBER" - echo "Raw event payload:" - echo '${{ toJSON(github.event) }}' - show-help: - needs: debug-trigger if: | github.event_name == 'issue_comment' && github.event.issue.pull_request &&