Deploying Review App #43
Workflow file for this run
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 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) }} | |
on: | |
pull_request: | |
types: [opened, synchronize, reopened] | |
issue_comment: | |
types: [created] | |
# Use concurrency to cancel in-progress runs | |
concurrency: | |
group: deploy-${{ 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_ORG: ${{ secrets.CPLN_ORG_STAGING }} | |
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_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.comment.body == '/deploy-review-app' || | |
github.event.comment.body == '/delete-review-app') && | |
github.event.issue.pull_request) | |
runs-on: ubuntu-latest | |
permissions: | |
contents: read | |
deployments: write | |
pull-requests: write | |
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 }} | |
- name: Determine Action | |
id: determine_action | |
run: | | |
if [[ "${{ github.event.comment.body }}" == "/delete-review-app" ]]; then | |
echo "action=delete" >> $GITHUB_OUTPUT | |
else | |
echo "action=deploy" >> $GITHUB_OUTPUT | |
fi | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
# For PR events: use the head SHA | |
# For PR comments: use the PR head SHA | |
# For other events: use the push SHA | |
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || steps.getRef.outputs.PR_SHA || github.sha }} | |
- name: Setup Environment | |
uses: ./.github/actions/setup-environment | |
# Delete App Steps | |
- name: Create Initial Delete Comment | |
if: steps.determine_action.outputs.action == 'delete' | |
uses: actions/github-script@v7 | |
id: init-delete | |
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 App | |
if: steps.determine_action.outputs.action == 'delete' | |
id: delete | |
run: | | |
echo "🗑️ Deleting review app: $APP_NAME" | |
${{ github.workspace }}/.github/actions/deploy-to-control-plane/scripts/delete-app.sh | |
- name: Update Delete Status | |
if: steps.determine_action.outputs.action == 'delete' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const success = '${{ steps.delete.outcome }}' === 'success'; | |
const message = success | |
? [ | |
'✅ Review app successfully deleted', | |
'', | |
'⚡ [Control Plane Console](https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/workload)' | |
].join('\n') | |
: '❌ Review app deletion failed'; | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: ${{ fromJSON(steps.init-delete.outputs.result).commentId }}, | |
body: message | |
}); | |
# Deploy Steps | |
- name: Initialize Deployment | |
if: steps.determine_action.outputs.action == 'deploy' | |
id: init-deployment | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
function getWorkflowUrl(runId) { | |
return `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${context.job.id}`; | |
} | |
const prNumber = process.env.PR_NUMBER; | |
const getJobUrl = (runId, jobId, prNumber) => | |
`${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/job/${jobId}?pr=${prNumber}`; | |
// Create deployment | |
const deployment = await github.rest.repos.createDeployment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
ref: context.sha, | |
environment: 'review-' + prNumber, | |
auto_merge: false, | |
required_contexts: [] | |
}); | |
// Get job URL | |
const jobs = await github.rest.actions.listJobsForWorkflowRun({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
run_id: context.runId | |
}); | |
const jobId = jobs.data.jobs.find(job => job.name === 'Process-Deployment-Command')?.id; | |
const jobUrl = getJobUrl(context.runId, jobId, prNumber); | |
// Create initial comment | |
const comment = await github.rest.issues.createComment({ | |
issue_number: prNumber, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
body: '🚀 Deploying to Control Plane...\n\n[View Deployment Progress](' + jobUrl + ')' | |
}); | |
return { | |
deploymentId: deployment.data.id, | |
commentId: comment.data.id, | |
workflowUrl: getWorkflowUrl(context.runId) | |
}; | |
- name: Set commit hash | |
if: steps.determine_action.outputs.action == 'deploy' | |
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: steps.determine_action.outputs.action == 'deploy' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const result = ${{ steps.init-deployment.outputs.result }}; | |
const commentId = result.commentId; | |
const buildingMessage = [ | |
'🏗️ Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', | |
'', | |
'[View Build Logs](' + result.workflowUrl + ')' | |
].join('\n'); | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: commentId, | |
body: buildingMessage | |
}); | |
- name: Build Docker Image | |
if: steps.determine_action.outputs.action == 'deploy' | |
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 | |
if: steps.determine_action.outputs.action == 'deploy' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const result = ${{ steps.init-deployment.outputs.result }}; | |
const commentId = result.commentId; | |
const deployingMessage = [ | |
'🚀 Deploying to Control Plane for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', | |
'', | |
'⏳ Waiting for deployment to be ready...', | |
'', | |
'[View Deploy Logs](' + result.workflowUrl + ')' | |
].join('\n'); | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: commentId, | |
body: deployingMessage | |
}); | |
- name: Deploy to Control Plane | |
if: steps.determine_action.outputs.action == 'deploy' | |
id: deploy | |
uses: ./.github/actions/deploy-to-control-plane | |
with: | |
app_name: ${{ env.APP_NAME }} | |
org: ${{ env.CPLN_ORG }} | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
- name: Update Status - Deployment Complete | |
if: steps.determine_action.outputs.action == 'deploy' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const isSuccess = '${{ job.status }}' === 'success'; | |
const result = ${{ steps.init-deployment.outputs.result }}; | |
const deploymentId = result.deploymentId; | |
const commentId = result.commentId; | |
const workflowUrl = result.workflowUrl; | |
const railsUrl = '${{ steps.deploy.outputs.rails_url }}'; | |
const prNumber = process.env.PR_NUMBER; | |
// Update deployment status | |
const deploymentStatus = { | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
deployment_id: deploymentId, | |
state: isSuccess ? 'success' : 'failure', | |
description: isSuccess ? '✅ Deployment successful' : '❌ Deployment failed' | |
}; | |
if (isSuccess) { | |
deploymentStatus.environment_url = railsUrl; | |
} | |
await github.rest.repos.createDeploymentStatus(deploymentStatus); | |
// Update the initial comment | |
const successMessage = [ | |
'✅ Deployment successful for PR #' + prNumber + ', commit ' + '${{ env.COMMIT_HASH }}', | |
'', | |
'🚀 Rails App: [' + railsUrl + '](' + railsUrl + ')', | |
'', | |
'⚡ [Control Plane Console](https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/workload)', | |
'', | |
'[View Completed Action Build and Deploy Logs](' + workflowUrl + ')' | |
].join('\n'); | |
const failureMessage = [ | |
'❌ Deployment failed for PR #' + prNumber, | |
'', | |
'[View Workflow Status](' + workflowUrl + ')' | |
].join('\n'); | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: commentId, | |
body: isSuccess ? successMessage : failureMessage | |
}); |