Deploying Review App #141
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Deploy Review App to Control Plane | |
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] | |
# Use concurrency to cancel in-progress runs | |
concurrency: | |
group: deploy-pr-${{ github.event.pull_request.number || 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_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} | |
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} | |
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }} | |
jobs: | |
Process-Deployment-Command: | |
if: | | |
(github.event_name == 'pull_request') || | |
(github.event_name == 'issue_comment' && | |
github.event.issue.pull_request && | |
github.event.comment.body == '/deploy-review-app') | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
deployments: write | |
pull-requests: write | |
issues: write | |
steps: | |
- 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: Setup Environment | |
uses: ./.github/actions/setup-environment@justin808-working-for-deploys | |
with: | |
token: ${{ env.CPLN_TOKEN }} | |
org: ${{ env.CPLN_ORG }} | |
- name: Get PR HEAD Ref | |
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 | |
# 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 }} | |
- name: Check if Review App Exists | |
id: check-app | |
if: github.event_name == 'push' | |
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 Workflow URL | |
id: workflow-url | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
async function getWorkflowUrl(runId) { | |
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) { | |
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); | |
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: Create Initial Comment | |
if: | | |
(github.event_name == 'issue_comment' && | |
github.event.issue.pull_request && | |
github.event.comment.body == '/deploy-review-app') || | |
( steps.check-app.outputs.app_exists == 'true') | |
id: create-comment | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
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.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 | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
async function getWorkflowUrl(runId) { | |
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); | |
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 | |
if: | | |
(github.event_name == 'issue_comment' && | |
github.event.issue.pull_request && | |
github.event.comment.body == '/deploy-review-app') || | |
(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 + ')', | |
'', | |
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: Checkout PR Branch | |
if: | | |
(github.event_name == 'issue_comment' && | |
github.event.issue.pull_request && | |
github.event.comment.body == '/deploy-review-app') || | |
(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.check-app.outputs.app_exists == 'true') | |
uses: ./.github/actions/build-docker-image@justin808-working-for-deploys | |
with: | |
app_name: ${{ env.APP_NAME }} | |
org: ${{ env.CPLN_ORG }} | |
commit: ${{ env.COMMIT_HASH }} | |
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.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...', | |
'', | |
'⏳ 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 | |
if: | | |
(github.event_name == 'issue_comment' && | |
github.event.issue.pull_request && | |
github.event.comment.body == '/deploy-review-app') || | |
(steps.check-app.outputs.app_exists == 'true') | |
uses: ./.github/actions/deploy-to-control-plane@justin808-working-for-deploys | |
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 | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
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 consoleLink = '🎮 [Control Plane Console](https://console.cpln.io/console/org/' + | |
process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/-info)'; | |
// 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 + ')', | |
consoleLink, | |
'', | |
'📋 [View Completed Action Build and Deploy Logs](' + workflowUrl + ')' | |
].join('\n'); | |
const failureMessage = [ | |
'❌ Deployment failed for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', | |
'', | |
consoleLink, | |
'', | |
'📋 [View Deployment Logs with Errors](' + workflowUrl + ')' | |
].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 | |
}); |