Skip to content

Deploying Review App #35

Deploying Review App

Deploying Review App #35

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-command:
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
(github.event.comment.body == '/deploy-review-app' ||
github.event.comment.body == '/delete-app') &&
github.event.issue.pull_request)
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
pull-requests: write
issues: write
steps:
- name: Determine Action
id: determine_action
run: |
if [[ "${{ github.event.comment.body }}" == "/delete-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
uses: ./.github/actions/delete-control-plane-app
continue-on-error: true
with:
app_name: ${{ env.APP_NAME }}
org: ${{ env.CPLN_ORG }}
- 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
? '✅ App deletion successful'
: '❌ 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 (existing steps)
- name: Get PR HEAD Ref
if: steps.determine_action.outputs.action == 'deploy'
id: getRef
run: |
echo "PR_REF=$(gh pr view $PR_NUMBER --repo ${{ github.repository }} --json headRefName | jq -r '.headRefName')" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ steps.getRef.outputs.PR_REF || github.ref }}
- name: Initialize Deployment
id: init-deployment
uses: actions/github-script@v7
with:
script: |
// Helper functions
const getWorkflowUrl = (runId) =>
`${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
const getJobUrl = (runId, jobId, prNumber) =>
`${getWorkflowUrl(runId)}/job/${jobId}?pr=${prNumber}`;
// Get PR number consistently
const prNumber = process.env.PR_NUMBER;
// Create GitHub deployment
const deployment = await github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'review-app',
auto_merge: false,
required_contexts: []
});
// Get the job ID
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-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: [
'🚀 Starting deployment for PR ' + prNumber,
'',
'[View Deployment Log](' + jobUrl + ')'
].join('\n')
});
// Set deployment status to in_progress
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: deployment.data.id,
state: 'in_progress',
description: 'Deployment is in progress'
});
return {
deploymentId: deployment.data.id,
commentId: comment.data.id,
workflowUrl: getWorkflowUrl(context.runId)
};
- name: Setup cpflow app
run: |
set -e
# Validate required environment variables
: "${APP_NAME:?APP_NAME environment variable is required}"
if ! cpflow exists -a ${{ env.APP_NAME }} ; then
echo "🔧 Setting up new application: ${{ env.APP_NAME }}"
if ! cpflow setup-app -a ${{ env.APP_NAME }}; then
echo "❌ Failed to setup application"
exit 1
fi
echo "✅ Application setup complete"
fi
- name: Deploy to Control Plane
id: deploy
uses: ./.github/actions/deploy-to-control-plane
env:
CPLN_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }}
CPLN_ORG: ${{ secrets.CPLN_ORG_STAGING }}
with:
app_name: ${{ env.APP_NAME }}
org: ${{ env.CPLN_ORG }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Status
if: always()
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;
}
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 = [
'✅ Deployment Successful for PR ' + prNumber,
'',
'🚀 Rails App: [' + railsUrl + '](' + railsUrl + ')',
'',
'[View Workflow Status](' + workflowUrl + ')'
].join('\n');
const failureMessage = [
'❌ Deployment failed for PR ' + prNumber,
'',
'Commit: ' + context.sha.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;
}