Deploy Review App - master #199
This file contains 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: Deploy Review App - ${{ github.ref_name }} | |
on: | |
pull_request: | |
types: [opened, synchronize, reopened] | |
push: | |
branches: | |
- '**' # Any branch | |
- '!main' # Except main | |
- '!master' # Except master | |
issue_comment: | |
types: [created] | |
workflow_dispatch: | |
inputs: | |
pr_number: | |
description: 'Pull Request number to deploy' | |
required: true | |
type: number | |
concurrency: | |
group: deploy-pr-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} | |
cancel-in-progress: true | |
env: | |
APP_NAME: qa-react-webpack-rails-tutorial-pr-${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} | |
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} | |
CPLN_ORG: ${{ vars.CPLN_ORG_STAGING }} | |
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }} | |
jobs: | |
Process-Deployment-Command: | |
if: | | |
(github.event_name == 'pull_request') || | |
(github.event_name == 'push') || | |
(github.event_name == 'workflow_dispatch') || | |
(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: | |
# Initial checkout only for pull_request and push events | |
- name: Checkout code | |
if: github.event_name == 'pull_request' || github.event_name == 'push' | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} | |
# Basic checkout for other events (workflow_dispatch, issue_comment) | |
# We'll do proper checkout after getting PR info | |
- name: Initial checkout | |
if: github.event_name == 'workflow_dispatch' || github.event_name == 'issue_comment' | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Get PR HEAD Ref | |
id: getRef | |
env: | |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
PR_NUMBER="${{ github.event.inputs.pr }}" | |
elif [[ "${{ github.event_name }}" == "issue_comment" ]]; then | |
PR_NUMBER="${{ github.event.issue.number }}" | |
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
PR_NUMBER="${{ github.event.pull_request.number }}" | |
elif [[ "${{ github.event_name }}" == "push" ]]; then | |
# For push events, find associated PR | |
PR_DATA=$(gh pr list --head "${{ github.ref_name }}" --json number --jq '.[0].number') | |
if [[ -n "$PR_DATA" ]]; then | |
PR_NUMBER="$PR_DATA" | |
else | |
echo "Error: No PR found for branch ${{ github.ref_name }}" | |
exit 1 | |
fi | |
fi | |
if [[ -z "$PR_NUMBER" ]]; then | |
echo "Error: Could not determine PR number" | |
exit 1 | |
fi | |
# Set environment variables | |
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | |
echo "APP_NAME=qa-react-webpack-rails-tutorial-pr-$PR_NUMBER" >> $GITHUB_ENV | |
# Get PR data using GitHub CLI | |
PR_DATA=$(gh pr view $PR_NUMBER --repo shakacode/react-webpack-rails-tutorial --json headRefName,headRefOid) | |
if [[ $? -eq 0 ]]; then | |
echo "PR_REF=$(echo $PR_DATA | jq -r .headRefName)" >> $GITHUB_OUTPUT | |
echo "PR_SHA=$(echo $PR_DATA | jq -r .headRefOid)" >> $GITHUB_ENV | |
else | |
echo "Error: Could not fetch PR data for PR #$PR_NUMBER" | |
exit 1 | |
fi | |
- name: Checkout PR code | |
if: github.event_name == 'workflow_dispatch' || github.event_name == 'issue_comment' | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
ref: ${{ steps.getRef.outputs.PR_SHA }} | |
- name: Setup Environment | |
uses: ./.github/actions/setup-environment | |
with: | |
token: ${{ secrets.CPLN_TOKEN_STAGING }} | |
org: ${{ vars.CPLN_ORG_STAGING }} | |
- name: Check if Review App Exists | |
id: check-app | |
if: github.event_name == 'pull_request' | |
env: | |
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} | |
run: | | |
# First check if cpflow exists | |
if ! command -v cpflow &> /dev/null; then | |
echo "Error: cpflow command not found" | |
exit 1 | |
fi | |
# Then check if app exists | |
if ! cpflow exists -a ${{ env.APP_NAME }}; then | |
echo "No review app exists for this PR" | |
echo "DO_DEPLOY=false" >> $GITHUB_ENV | |
else | |
echo "DO_DEPLOY=true" >> $GITHUB_ENV | |
fi | |
- name: Validate Deployment Request | |
id: validate | |
if: env.DO_DEPLOY != 'false' | |
run: | | |
if ! [[ "${{ github.event_name }}" == "workflow_dispatch" || \ | |
("${{ github.event_name }}" == "issue_comment" && "${{ github.event.comment.body }}" == "/deploy-review-app") || \ | |
"${{ github.event_name }}" == "pull_request" ]]; then | |
echo "Skipping deployment - not a valid trigger (event: ${{ github.event_name }})" | |
exit 1 | |
fi | |
- name: Create Initial Comment | |
if: env.DO_DEPLOY != 'false' | |
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...\n\n' + process.env.CONSOLE_LINK | |
}); | |
core.setOutput('comment-id', result.data.id); | |
- name: Set Deployment URLs | |
id: set-urls | |
if: env.DO_DEPLOY != 'false' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
// Set workflow URL for logs | |
const getWorkflowUrl = async (runId) => { | |
const { data: run } = await github.rest.actions.getWorkflowRun({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
run_id: runId | |
}); | |
return run.html_url; | |
}; | |
const workflowUrl = await getWorkflowUrl(context.runId); | |
core.exportVariable('WORKFLOW_URL', workflowUrl); | |
core.exportVariable('CONSOLE_LINK', | |
'🎮 [Control Plane Console](' + | |
'https://console.cpln.io/console/org/' + process.env.CPLN_ORG_STAGING + '/gvc/' + process.env.APP_NAME + '/-info)' | |
); | |
- name: Update Status - Building | |
if: env.DO_DEPLOY != 'false' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const buildingMessage = [ | |
'🏗️ Building Docker image for PR #' + process.env.PR_NUMBER + ', commit ' + '${{ env.COMMIT_HASH }}', | |
'', | |
'📝 [View Build Logs](' + process.env.WORKFLOW_URL + ')', | |
'', | |
process.env.CONSOLE_LINK | |
].join('\n'); | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: ${{ steps.create-comment.outputs.comment-id }}, | |
body: buildingMessage | |
}); | |
- name: Checkout PR Branch | |
if: env.DO_DEPLOY != 'false' | |
run: git checkout ${{ steps.getRef.outputs.PR_REF }} | |
- name: Build Docker Image | |
if: env.DO_DEPLOY != 'false' | |
uses: ./.github/actions/build-docker-image | |
with: | |
app_name: ${{ env.APP_NAME }} | |
org: ${{ env.CPLN_ORG_STAGING }} | |
commit: ${{ env.COMMIT_HASH }} | |
PR_NUMBER: ${{ env.PR_NUMBER }} | |
- name: Update Status - Deploying | |
if: env.DO_DEPLOY != 'false' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const deployingMessage = [ | |
'🚀 Deploying to Control Plane...', | |
'', | |
'⏳ Waiting for deployment to be ready...', | |
'', | |
'📝 [View Deploy Logs](' + process.env.WORKFLOW_URL + ')', | |
'', | |
process.env.CONSOLE_LINK | |
].join('\n'); | |
await github.rest.issues.updateComment({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
comment_id: ${{ steps.create-comment.outputs.comment-id }}, | |
body: deployingMessage | |
}); | |
- name: Deploy to Control Plane | |
if: env.DO_DEPLOY != 'false' | |
uses: ./.github/actions/deploy-to-control-plane | |
with: | |
app_name: ${{ env.APP_NAME }} | |
org: ${{ env.CPLN_ORG_STAGING }} | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
wait_timeout: ${{ vars.WAIT_TIMEOUT || 900 }} | |
cpln_token: ${{ secrets.CPLN_TOKEN_STAGING }} | |
pr_number: ${{ env.PR_NUMBER }} | |
- name: Update Status - Deployment Complete | |
if: env.DO_DEPLOY != 'false' | |
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 = process.env.CONSOLE_LINK; | |
// 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: ${{ steps.create-comment.outputs.comment-id }}, | |
body: isSuccess ? successMessage : failureMessage | |
}); |