Skip to content

Commit eb3d861

Browse files
committed
Modernize Control Plane GitHub flow
1 parent 5cf245f commit eb3d861

24 files changed

+1284
-1183
lines changed

.controlplane/readme.md

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ _If you need a free demo account for Control Plane (no CC required), you can con
66

77
---
88

9-
Check [how the `cpflow` gem (this project) is used in the Github actions](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.github/actions/deploy-to-control-plane/action.yml).
9+
See the reusable `cpflow-*` GitHub Actions files in this repo's [`.github`](https://github.com/shakacode/react-webpack-rails-tutorial/tree/master/.github) directory for review apps, staging deploys, and production promotion.
1010
Here is a brief [video overview](https://www.youtube.com/watch?v=llaQoAV_6Iw).
1111

1212
---
@@ -69,55 +69,99 @@ You should be able to see this information in the Control Plane UI.
6969
and not `cpln` which is the Control Plane CLI.
7070

7171
```sh
72-
# Use environment variable to prevent repetition
73-
export APP_NAME=react-webpack-rails-tutorial
72+
# Use the staging app defined in .controlplane/controlplane.yml
73+
export APP_NAME=react-webpack-rails-tutorial-staging
7474

7575
# Provision all infrastructure on Control Plane.
76-
# app react-webpack-rails-tutorial will be created per definition in .controlplane/controlplane.yml
77-
cpflow setup-app -a $APP_NAME
76+
cpflow setup-app -a "$APP_NAME"
7877

79-
# Build and push docker image to Control Plane repository
80-
# Note, may take many minutes. Be patient.
81-
# Check for error messages, such as forgetting to run `cpln image docker-login --org <your-org>`
82-
cpflow build-image -a $APP_NAME
78+
# Build and push the Docker image to the Control Plane registry.
79+
cpflow build-image -a "$APP_NAME"
8380

84-
# Promote image to app after running `cpflow build-image command`
85-
# Note, the UX of images may not show the image for up to 5 minutes.
86-
# However, it's ready.
87-
cpflow deploy-image -a $APP_NAME
81+
# Run the configured release phase before cutting staging over to the new image.
82+
cpflow deploy-image -a "$APP_NAME" --run-release-phase
8883

89-
# See how app is starting up
90-
cpflow logs -a $APP_NAME
84+
# See how the app is starting up
85+
cpflow logs -a "$APP_NAME"
9186

9287
# Open app in browser (once it has started up)
93-
cpflow open -a $APP_NAME
88+
cpflow open -a "$APP_NAME"
9489
```
9590

9691
### Promoting code updates
9792

98-
After committing code, you will update your deployment of `react-webpack-rails-tutorial` with the following commands:
93+
After committing code, you will update your staging deployment with the following commands:
9994

10095
```sh
101-
# Assuming you have already set APP_NAME env variable to react-webpack-rails-tutorial
102-
# Build and push new image with sequential image tagging, e.g. 'react-webpack-rails-tutorial:1', then 'react-webpack-rails-tutorial:2', etc.
103-
cpflow build-image -a $APP_NAME
96+
# Assuming APP_NAME is still react-webpack-rails-tutorial-staging
97+
cpflow build-image -a "$APP_NAME"
10498

10599
# Run database migrations (or other release tasks) with latest image,
106100
# while app is still running on previous image.
107101
# This is analogous to the release phase.
108-
cpflow run -a $APP_NAME --image latest -- rails db:migrate
102+
cpflow run -a "$APP_NAME" --image latest -- rails db:migrate
109103

110104
# Pomote latest image to app after migrations run
111-
cpflow deploy-image -a $APP_NAME
105+
cpflow deploy-image -a "$APP_NAME" --run-release-phase
112106
```
113107

114108
If you needed to push a new image with a specific commit SHA, you can run the following command:
115109

116110
```sh
117-
# Build and push with sequential image tagging and commit SHA, e.g. 'react-webpack-rails-tutorial:123_ABCD'
118-
cpflow build-image -a $APP_NAME --commit ABCD
111+
# Build and push with sequential image tagging and commit SHA
112+
cpflow build-image -a "$APP_NAME" --commit ABCD
119113
```
120114

115+
## GitHub Actions Flow
116+
117+
This repo now uses the shared `cpflow-*` GitHub Actions scaffolding:
118+
119+
- `.github/workflows/cpflow-review-app-help.yml`
120+
- `.github/workflows/cpflow-help-command.yml`
121+
- `.github/workflows/cpflow-deploy-review-app.yml`
122+
- `.github/workflows/cpflow-delete-review-app.yml`
123+
- `.github/workflows/cpflow-deploy-staging.yml`
124+
- `.github/workflows/cpflow-promote-staging-to-production.yml`
125+
- `.github/workflows/cpflow-cleanup-stale-review-apps.yml`
126+
127+
Behavior:
128+
129+
- comment `/deploy-review-app` on a PR to create or update a review app
130+
- later pushes to that PR auto-redeploy the existing review app
131+
- pushes to `master` auto-deploy staging unless `STAGING_APP_BRANCH` overrides it
132+
- production promotion happens manually from the Actions tab
133+
- stale review apps are cleaned up nightly
134+
135+
This repo keeps its historical `qa-react-webpack-rails-tutorial` prefix for review apps, so:
136+
137+
- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial`
138+
- PR 123 deploys to `qa-react-webpack-rails-tutorial-123`
139+
140+
Required GitHub repository secrets:
141+
142+
- `CPLN_TOKEN_STAGING`
143+
- `CPLN_TOKEN_PRODUCTION`
144+
145+
Required GitHub repository variables:
146+
147+
- `CPLN_ORG_STAGING`
148+
- `CPLN_ORG_PRODUCTION`
149+
- `STAGING_APP_NAME=react-webpack-rails-tutorial-staging`
150+
- `PRODUCTION_APP_NAME=react-webpack-rails-tutorial-production`
151+
- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial`
152+
153+
Optional variables:
154+
155+
- `STAGING_APP_BRANCH=master`
156+
- `PRIMARY_WORKLOAD=rails`
157+
- `DOCKER_BUILD_EXTRA_ARGS`
158+
159+
Operational notes:
160+
161+
- `/deploy-review-app` and `/delete-review-app` only run for trusted commenters (`OWNER`, `MEMBER`, `COLLABORATOR`)
162+
- fork PRs still receive help comments, but review app deploys are skipped because the workflow builds Docker images with repository secrets
163+
- PR pushes do not auto-create review apps; the first deploy remains opt-in
164+
121165
## HTTP/2 and Thruster Configuration
122166

123167
This application uses [Thruster](https://github.com/basecamp/thruster), a zero-config HTTP/2 proxy from Basecamp, for optimized performance on Control Plane.
@@ -362,29 +406,12 @@ openssl rand -hex 64
362406

363407
## CI Automation, Review Apps and Staging
364408

365-
_Note, some of the URL references are internal for the ShakaCode team._
366-
367-
Review Apps (deployment of apps based on a PR) are done via Github Actions.
368-
369-
The review apps work by creating isolated deployments for each branch through this automated process. When a branch is pushed, the action:
370-
371-
1. Sets up the necessary environment and tools
372-
2. Creates a unique deployment for that branch if it doesn't exist
373-
3. Builds a Docker image tagged with the branch's commit SHA
374-
4. Deploys this image to Control Plane with its own isolated environment
375-
376-
This allows teams to:
377-
- Preview changes in a production-like environment
378-
- Test features independently
379-
- Share working versions with stakeholders
380-
- Validate changes before merging to main branches
381-
382-
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.
383-
409+
Review apps, staging deploys, and production promotion are all driven by the
410+
`cpflow-*` workflows in `.github/workflows/`.
384411

385-
### Workflow for Developing Github Actions for Review Apps
412+
### Workflow for Developing GitHub Actions for Review Apps
386413

387-
1. Create a PR with changes to the Github Actions workflow
388-
2. Make edits to file such as `.github/actions/deploy-to-control-plane/action.yml`
389-
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)
390-
4. Check the Github Actions tab in the PR to see the status of the workflow
414+
1. Create a PR with changes to the GitHub Actions workflow.
415+
2. Make edits to files such as `.github/workflows/cpflow-deploy-review-app.yml` or `.github/actions/cpflow-build-docker-image/action.yml`.
416+
3. Commit and push the `.github` changes.
417+
4. Check the GitHub Actions tab in the PR to see the status of the workflow.

.github/actions/build-docker-image/action.yml

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Build Docker Image
2+
description: Builds and pushes the app image for a Control Plane workload
3+
4+
inputs:
5+
app_name:
6+
description: Name of the application
7+
required: true
8+
org:
9+
description: Control Plane organization name
10+
required: true
11+
commit:
12+
description: Commit SHA to tag the image with
13+
required: true
14+
pr_number:
15+
description: Pull request number for status messaging
16+
required: false
17+
docker_build_extra_args:
18+
description: Optional newline-delimited extra arguments passed through to docker build
19+
required: false
20+
docker_build_ssh_key:
21+
description: Optional private SSH key used for Docker builds that fetch private dependencies with RUN --mount=type=ssh
22+
required: false
23+
24+
outputs:
25+
image_tag:
26+
description: Fully qualified image tag
27+
value: ${{ steps.build.outputs.image_tag }}
28+
29+
runs:
30+
using: composite
31+
steps:
32+
- name: Build Docker image
33+
id: build
34+
shell: bash
35+
run: |
36+
set -euo pipefail
37+
38+
PR_INFO=""
39+
docker_build_args=()
40+
41+
if [[ -n "${{ inputs.pr_number }}" ]]; then
42+
PR_INFO=" for PR #${{ inputs.pr_number }}"
43+
fi
44+
45+
if [[ -n "${{ inputs.docker_build_extra_args }}" ]]; then
46+
while IFS= read -r arg; do
47+
arg="${arg%$'\r'}"
48+
[[ -n "${arg}" ]] || continue
49+
docker_build_args+=("${arg}")
50+
done <<< "${{ inputs.docker_build_extra_args }}"
51+
fi
52+
53+
if [[ -n "${{ inputs.docker_build_ssh_key }}" ]]; then
54+
mkdir -p ~/.ssh
55+
chmod 700 ~/.ssh
56+
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
57+
chmod 600 ~/.ssh/known_hosts
58+
59+
eval "$(ssh-agent -s)"
60+
trap 'ssh-agent -k >/dev/null' EXIT
61+
ssh-add - <<< "${{ inputs.docker_build_ssh_key }}"
62+
docker_build_args+=("--ssh default")
63+
fi
64+
65+
echo "🏗️ Building Docker image${PR_INFO} (commit ${{ inputs.commit }})..."
66+
cpflow build-image -a "${{ inputs.app_name }}" --commit="${{ inputs.commit }}" --org="${{ inputs.org }}" "${docker_build_args[@]}"
67+
68+
image_tag="${{ inputs.org }}/${{ inputs.app_name }}:${{ inputs.commit }}"
69+
echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
70+
echo "✅ Docker image build successful${PR_INFO} (commit ${{ inputs.commit }})"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Delete Control Plane App
2+
description: Deletes a Control Plane app and all associated resources
3+
4+
inputs:
5+
app_name:
6+
description: Name of the application to delete
7+
required: true
8+
cpln_org:
9+
description: Control Plane organization name
10+
required: true
11+
review_app_prefix:
12+
description: Prefix used for review app names
13+
required: true
14+
15+
runs:
16+
using: composite
17+
steps:
18+
- name: Delete application
19+
shell: bash
20+
run: ${{ github.action_path }}/delete-app.sh
21+
env:
22+
APP_NAME: ${{ inputs.app_name }}
23+
CPLN_ORG: ${{ inputs.cpln_org }}
24+
REVIEW_APP_PREFIX: ${{ inputs.review_app_prefix }}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
: "${APP_NAME:?APP_NAME environment variable is required}"
6+
: "${CPLN_ORG:?CPLN_ORG environment variable is required}"
7+
: "${REVIEW_APP_PREFIX:?REVIEW_APP_PREFIX environment variable is required}"
8+
9+
expected_prefix="${REVIEW_APP_PREFIX}-"
10+
if [[ "$APP_NAME" != "${expected_prefix}"* ]]; then
11+
echo "❌ ERROR: refusing to delete an app outside the review app prefix" >&2
12+
echo "App name: $APP_NAME" >&2
13+
echo "Expected prefix: ${expected_prefix}" >&2
14+
exit 1
15+
fi
16+
17+
echo "🔍 Checking if application exists: $APP_NAME"
18+
exists_output=""
19+
if ! exists_output="$(cpflow exists -a "$APP_NAME" --org "$CPLN_ORG" 2>&1)"; then
20+
if [[ -z "$exists_output" ]]; then
21+
echo "⚠️ Application does not exist: $APP_NAME"
22+
exit 0
23+
fi
24+
25+
echo "❌ ERROR: failed to determine whether application exists: $APP_NAME" >&2
26+
printf '%s\n' "$exists_output" >&2
27+
exit 1
28+
fi
29+
30+
if [[ -n "$exists_output" ]]; then
31+
printf '%s\n' "$exists_output"
32+
fi
33+
34+
echo "🗑️ Deleting application: $APP_NAME"
35+
cpflow delete -a "$APP_NAME" --org "$CPLN_ORG" --yes
36+
37+
echo "✅ Successfully deleted application: $APP_NAME"

0 commit comments

Comments
 (0)