Skip to content

Commit 5a01718

Browse files
authored
Improvements to deployments (#615)
Improve deployments and CI/CD workflows Summary: Introduced new GitHub Actions workflows for Docker image building and deployments. Enhanced Control Plane CLI and environment setup configurations. Streamlined concurrency management and deployment status tracking. Added new scripts and environment variables for commit tracking and deployment actions. Added documentation for CI automation and Review Apps.
1 parent 005eab7 commit 5a01718

20 files changed

+1010
-138
lines changed

.controlplane/Dockerfile

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
ARG RUBY_VERSION=3.3.4
33
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
44

5+
# Current commit hash environment variable
6+
ARG GIT_COMMIT
7+
ENV GIT_COMMIT_SHA=${GIT_COMMIT}
8+
59
# Install packages needed to build gems and node modules
610
RUN apt-get update -qq && \
711
apt-get install --no-install-recommends -y build-essential curl git libpq-dev libvips node-gyp pkg-config python-is-python3
@@ -76,7 +80,3 @@ ENTRYPOINT ["./.controlplane/entrypoint.sh"]
7680
# Default args to pass to the entry point that can be overridden
7781
# For Kubernetes and ControlPlane, these are the "workload args"
7882
CMD ["./bin/rails", "server"]
79-
80-
# Current commit hash environment variable
81-
ARG GIT_COMMIT_SHA
82-
ENV GIT_COMMIT_SHA=${GIT_COMMIT_SHA}

.controlplane/readme.md

+29
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,32 @@ cpflow build-image -a $APP_NAME --commit ABCD
123123
### `entrypoint.sh`
124124
- waits for Postgres and Redis to be available
125125
- runs `rails db:prepare` to create/seed or migrate the database
126+
127+
## CI Automation, Review Apps and Staging
128+
129+
_Note, some of the URL references are internal for the ShakaCode team._
130+
131+
Review Apps (deployment of apps based on a PR) are done via Github Actions.
132+
133+
The review apps work by creating isolated deployments for each branch through this automated process. When a branch is pushed, the action:
134+
135+
1. Sets up the necessary environment and tools
136+
2. Creates a unique deployment for that branch if it doesn't exist
137+
3. Builds a Docker image tagged with the branch's commit SHA
138+
4. Deploys this image to Control Plane with its own isolated environment
139+
140+
This allows teams to:
141+
- Preview changes in a production-like environment
142+
- Test features independently
143+
- Share working versions with stakeholders
144+
- Validate changes before merging to main branches
145+
146+
The system uses Control Plane's infrastructure to manage these deployments, with each branch getting its own resources as defined in the controlplane.yml configuration.
147+
148+
149+
### Workflow for Developing Github Actions for Review Apps
150+
151+
1. Create a PR with changes to the Github Actions workflow
152+
2. Make edits to file such as `.github/actions/deploy-to-control-plane/action.yml`
153+
3. Run a script like `ga .github && gc -m fixes && gp` to commit and push changes (ga = git add, gc = git commit, gp = git push)
154+
4. Check the Github Actions tab in the PR to see the status of the workflow

.controlplane/shakacode-team.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Internal Notes to the Shakacode Team
2+
3+
## Links
4+
5+
- [Control Plane Org for Staging and Review Apps](https://console.cpln.io/console/org/shakacode-open-source-examples-staging/-info)
6+
- [Control Plane Org for Deployed App](https://console.cpln.io/console/org/shakacode-open-source-examples/-info)

.dockerignore

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ dump.rdb
1919
.DS_Store
2020

2121
# Ignore bundle dependencies
22-
vendor/ruby
22+
vendor/bundle
23+
24+
# Ignore GitHub Actions and workflows
25+
.github/
2326

2427
# RVM gemset
2528
.ruby-gemset
@@ -45,6 +48,5 @@ yarn-debug.log*
4548
###################################################
4649
# Specific to .dockerignore
4750
.git/
48-
.github/
4951
spec/
5052
scripts/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Build Docker Image
2+
description: 'Builds a Docker image for the application'
3+
4+
inputs:
5+
app_name:
6+
description: 'Name of the application'
7+
required: true
8+
org:
9+
description: 'Organization name'
10+
required: true
11+
commit:
12+
description: 'Commit SHA to tag the image with'
13+
required: true
14+
PR_NUMBER:
15+
description: 'PR number'
16+
required: true
17+
18+
runs:
19+
using: "composite"
20+
steps:
21+
- name: Build Docker Image
22+
id: build
23+
shell: bash
24+
run: |
25+
echo "🏗️ Building Docker image for PR #${PR_NUMBER} (commit ${{ inputs.commit }})..."
26+
27+
if cpflow build-image -a "${{ inputs.app_name }}" --commit="${{ inputs.commit }}" --org="${{ inputs.org }}"; then
28+
echo "✅ Docker image build successful for PR #${PR_NUMBER} (commit ${{ inputs.commit }})"
29+
else
30+
echo "❌ Docker image build failed for PR #${PR_NUMBER} (commit ${{ inputs.commit }})"
31+
exit 1
32+
fi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Delete Control Plane App
2+
description: 'Deletes a Control Plane application and all its resources'
3+
4+
inputs:
5+
app_name:
6+
description: 'Name of the application to delete'
7+
required: true
8+
org:
9+
description: 'Organization name'
10+
required: true
11+
12+
runs:
13+
using: "composite"
14+
steps:
15+
- name: Delete Application
16+
shell: bash
17+
run: ${{ github.action_path }}/../deploy-to-control-plane/scripts/delete-app.sh
18+
env:
19+
APP_NAME: ${{ inputs.app_name }}
20+
CPLN_ORG: ${{ inputs.org }}
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,91 @@
11
# Control Plane GitHub Action
22

3-
name: Deploy-To-Control-Plane
4-
description: 'Deploys both to staging and to review apps'
3+
name: Deploy to Control Plane
4+
description: 'Deploys an application to Control Plane'
55

66
inputs:
77
app_name:
8-
description: 'The name of the app to deploy'
8+
description: 'Name of the application'
99
required: true
10-
default:
1110
org:
12-
description: 'The org of the app to deploy'
11+
description: 'Organization name'
1312
required: true
14-
default:
13+
github_token:
14+
description: 'GitHub token'
15+
required: true
16+
wait_timeout:
17+
description: 'Timeout in seconds for waiting for workloads to be ready'
18+
required: false
19+
default: '900'
20+
21+
outputs:
22+
review_app_url:
23+
description: 'URL of the deployed application'
24+
value: ${{ steps.deploy.outputs.review_app_url }}
1525

1626
runs:
17-
using: 'composite'
27+
using: "composite"
1828
steps:
1929
- name: Setup Environment
2030
uses: ./.github/actions/setup-environment
2131

22-
- name: Set Short SHA
23-
id: vars
32+
- name: Get Commit SHA
33+
id: get_sha
2434
shell: bash
25-
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
26-
27-
# Caching step
28-
- uses: actions/cache@v2
29-
with:
30-
path: /tmp/.docker-cache
31-
key: ${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile', '**/package.json', '**/yarn.lock') }}-${{ github.sha }}
32-
restore-keys: |
33-
${{ runner.os }}-docker-${{ hashFiles('**/Dockerfile', '**/package.json', '**/yarn.lock') }}
34-
${{ runner.os }}-docker-
35+
run: ${{ github.action_path }}/scripts/get-commit-sha.sh
36+
env:
37+
GITHUB_TOKEN: ${{ inputs.github_token }}
38+
PR_NUMBER: ${{ env.PR_NUMBER }}
3539

36-
- name: cpflow setup-app
37-
shell: bash
38-
run: |
39-
if ! cpflow exists -a ${{ inputs.app_name }} ; then
40-
cpflow setup-app -a ${{ inputs.app_name }}
41-
fi
42-
# Provision all infrastructure on Control Plane.
43-
# app react-webpack-rails-tutorial will be created per definition in .controlplane/controlplane.yml
44-
- name: cpflow build-image
45-
shell: bash
46-
run: |
47-
cpln image docker-login
48-
# Use BUILDKIT_PROGRESS=plain to get more verbose logging of the build
49-
# BUILDKIT_PROGRESS=plain cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}}
50-
cpflow build-image -a ${{ inputs.app_name }} --commit ${{steps.vars.outputs.sha_short}} --org ${{inputs.org}}
51-
# --cache /tmp/.docker-cache
5240
- name: Deploy to Control Plane
41+
id: deploy
5342
shell: bash
5443
run: |
55-
echo "Deploying to Control Plane"
56-
cpflow deploy-image -a ${{ inputs.app_name }} --run-release-phase --org ${{inputs.org}} --verbose
44+
echo "🚀 Deploying app for PR #${PR_NUMBER}..."
45+
46+
# Create temp file for output
47+
TEMP_OUTPUT=$(mktemp)
48+
trap 'rm -f "${TEMP_OUTPUT}"' EXIT
49+
50+
# Deploy the application and show output in real-time while capturing it
51+
if ! cpflow deploy-image -a "${{ inputs.app_name }}" --run-release-phase --org "${{ inputs.org }}" 2>&1 | tee "${TEMP_OUTPUT}"; then
52+
echo "❌ Deployment failed for PR #${PR_NUMBER}"
53+
echo "Error output:"
54+
cat "${TEMP_OUTPUT}"
55+
exit 1
56+
fi
57+
58+
# Extract app URL from captured output
59+
REVIEW_APP_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "${TEMP_OUTPUT}" | head -n1)
60+
if [ -z "${REVIEW_APP_URL}" ]; then
61+
echo "❌ Failed to get app URL from deployment output"
62+
echo "Deployment output:"
63+
cat "${TEMP_OUTPUT}"
64+
exit 1
65+
fi
66+
67+
# Wait for all workloads to be ready
68+
WAIT_TIMEOUT=${WAIT_TIMEOUT:-${{ inputs.wait_timeout }}}
69+
if ! [[ "${WAIT_TIMEOUT}" =~ ^[0-9]+$ ]]; then
70+
echo "❌ Invalid timeout value: ${WAIT_TIMEOUT}"
71+
exit 1
72+
fi
73+
echo "⏳ Waiting for all workloads to be ready (timeout: ${WAIT_TIMEOUT}s)"
74+
75+
# Use timeout command with ps:wait and show output in real-time
76+
if ! timeout "${WAIT_TIMEOUT}" bash -c "cpflow ps:wait -a \"${{ inputs.app_name }}\"" 2>&1 | tee -a "${TEMP_OUTPUT}"; then
77+
TIMEOUT_EXIT=$?
78+
if [ ${TIMEOUT_EXIT} -eq 124 ]; then
79+
echo "❌ Timed out waiting for workloads after ${WAIT_TIMEOUT} seconds"
80+
else
81+
echo "❌ Workloads did not become ready for PR #${PR_NUMBER} (exit code: ${TIMEOUT_EXIT})"
82+
fi
83+
echo "Full output:"
84+
cat "${TEMP_OUTPUT}"
85+
exit 1
86+
fi
87+
88+
echo "✅ Deployment successful for PR #${PR_NUMBER}"
89+
echo "🌐 App URL: ${REVIEW_APP_URL}"
90+
echo "review_app_url=${REVIEW_APP_URL}" >> $GITHUB_OUTPUT
91+
echo "REVIEW_APP_URL=${REVIEW_APP_URL}" >> $GITHUB_ENV
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
3+
# Script to delete a Control Plane application
4+
# Required environment variables:
5+
# - APP_NAME: Name of the application to delete
6+
# - CPLN_ORG: Organization name
7+
8+
set -e
9+
10+
# Validate required environment variables
11+
: "${APP_NAME:?APP_NAME environment variable is required}"
12+
: "${CPLN_ORG:?CPLN_ORG environment variable is required}"
13+
14+
# Safety check: prevent deletion of production or staging apps
15+
if echo "$APP_NAME" | grep -iqE '(production|staging)'; then
16+
echo "❌ ERROR: Cannot delete apps containing 'production' or 'staging' in their name" >&2
17+
echo "🛑 This is a safety measure to prevent accidental deletion of production or staging environments" >&2
18+
echo " App name: $APP_NAME" >&2
19+
exit 1
20+
fi
21+
22+
# Check if app exists before attempting to delete
23+
echo "🔍 Checking if application exists: $APP_NAME"
24+
if ! cpflow exists -a "$APP_NAME"; then
25+
echo "⚠️ Application does not exist: $APP_NAME"
26+
exit 0
27+
fi
28+
29+
# Delete the application
30+
echo "🗑️ Deleting application: $APP_NAME"
31+
if ! cpflow delete -a "$APP_NAME" --force; then
32+
echo "❌ Failed to delete application: $APP_NAME" >&2
33+
exit 1
34+
fi
35+
36+
echo "✅ Successfully deleted application: $APP_NAME"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
3+
# This script handles the deployment to Control Plane and extracts the Rails URL
4+
#
5+
# Required environment variables:
6+
# - APP_NAME: Name of the application to deploy
7+
# - CPLN_ORG: Control Plane organization
8+
#
9+
# Optional environment variables:
10+
# - WAIT_TIMEOUT: Timeout in seconds for deployment (default: 900)
11+
# Must be a positive integer
12+
#
13+
# Outputs:
14+
# - rails_url: URL of the deployed Rails application
15+
16+
set -e
17+
18+
# Validate required environment variables
19+
: "${APP_NAME:?APP_NAME environment variable is required}"
20+
: "${CPLN_ORG:?CPLN_ORG environment variable is required}"
21+
22+
# Set and validate deployment timeout
23+
WAIT_TIMEOUT=${WAIT_TIMEOUT:-900}
24+
if ! [[ "${WAIT_TIMEOUT}" =~ ^[0-9]+$ ]]; then
25+
echo "❌ Invalid timeout value: ${WAIT_TIMEOUT}"
26+
exit 1
27+
fi
28+
29+
TEMP_OUTPUT=$(mktemp)
30+
trap 'rm -f "$TEMP_OUTPUT"' EXIT
31+
32+
# Deploy the application
33+
echo "🚀 Deploying to Control Plane (timeout: ${WAIT_TIMEOUT}s)"
34+
if timeout "$WAIT_TIMEOUT" cpflow deploy-image -a "$APP_NAME" --run-release-phase --org "$CPLN_ORG" --verbose | tee "$TEMP_OUTPUT"; then
35+
# Extract Rails URL from deployment output
36+
RAILS_URL=$(grep -oP 'https://rails-[^[:space:]]*\.cpln\.app(?=\s|$)' "$TEMP_OUTPUT" | head -n1)
37+
if [ -n "$RAILS_URL" ]; then
38+
echo "rails_url=$RAILS_URL" >> "$GITHUB_OUTPUT"
39+
echo "✅ Deployment successful"
40+
echo "🚀 Rails URL: $RAILS_URL"
41+
else
42+
echo "❌ Failed to extract Rails URL from deployment output"
43+
exit 1
44+
fi
45+
elif [ $? -eq 124 ]; then
46+
echo "❌ Deployment timed out after $WAIT_TIMEOUT seconds"
47+
exit 1
48+
else
49+
echo "❌ Deployment to Control Plane failed"
50+
exit 1
51+
fi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
3+
# This script retrieves the commit SHA for deployment
4+
# It handles both PR and direct branch deployments
5+
#
6+
# Required environment variables:
7+
# - PR_NUMBER: Pull request number (optional)
8+
# - GITHUB_TOKEN: GitHub token for API access
9+
#
10+
# Outputs:
11+
# - sha: Full commit SHA
12+
# - sha_short: Short (7 char) commit SHA
13+
14+
set -e
15+
16+
if [ -n "${PR_NUMBER}" ]; then
17+
# If PR_NUMBER is set, get the PR's head SHA
18+
if ! PR_SHA=$(gh pr view "${PR_NUMBER}" --json headRefOid --jq '.headRefOid'); then
19+
echo "Failed to get PR head SHA" >&2
20+
exit 1
21+
fi
22+
echo "sha=${PR_SHA}" >> "$GITHUB_OUTPUT"
23+
echo "sha_short=${PR_SHA:0:7}" >> "$GITHUB_OUTPUT"
24+
echo "Using PR head commit SHA: ${PR_SHA:0:7}"
25+
else
26+
# For direct branch deployments, use the current commit SHA
27+
if ! CURRENT_SHA=$(git rev-parse HEAD); then
28+
echo "Failed to get current SHA" >&2
29+
exit 1
30+
fi
31+
echo "sha=${CURRENT_SHA}" >> "$GITHUB_OUTPUT"
32+
echo "sha_short=${CURRENT_SHA:0:7}" >> "$GITHUB_OUTPUT"
33+
echo "Using branch commit SHA: ${CURRENT_SHA:0:7}"
34+
fi

0 commit comments

Comments
 (0)