Deploying Review App #38
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: | | |
echo "PR_REF=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName | jq -r '.headRefName')" >> $GITHUB_OUTPUT | |
echo "PR_SHA=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefOid | 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 | |
ref: ${{ steps.getRef.outputs.PR_REF || github.ref }} | |
- 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: | | |
const prNumber = process.env.PR_NUMBER; | |
const getWorkflowUrl = (runId) => | |
`${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
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: 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: ${{ github.event.comment.body && steps.getRef.outputs.PR_SHA || github.sha }} | |
- 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 | |
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; | |
const commitSha = '${{ github.event.comment.body && steps.getRef.outputs.PR_SHA || github.sha }}'; | |
// 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; | |
} | |
try { | |
await github.rest.repos.createDeploymentStatus(deploymentStatus); | |
} catch (error) { | |
console.error('Failed to update deployment status:', error); | |
throw error; | |
} | |
// Update the initial comment | |
const successMessage = [ | |
'🚀 Rails App Deployed to Control Plane for Commit ' + commitSha.substring(0, 7), | |
'', | |
'🌐 Rails App: [' + railsUrl + '](' + railsUrl + ')', | |
'', | |
'⚡ [Control Plane Console for Review App](https://console.cpln.io/console/org/' + process.env.CPLN_ORG + '/gvc/' + process.env.APP_NAME + '/workload)', | |
'', | |
'[View Workflow Status](' + workflowUrl + ')' | |
].join('\n'); | |
const failureMessage = [ | |
'❌ Deployment failed for PR ' + prNumber, | |
'', | |
'Commit: ' + commitSha.substring(0, 7), | |
'', | |
'[View Workflow Status](' + workflowUrl + ')' | |
].join('\n'); | |
try { | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: commentId, | |
body: isSuccess ? successMessage : failureMessage | |
}); | |
} catch (error) { | |
console.error('Failed to update comment:', error); | |
throw error; | |
} |