Skip to content

Commit b75a6ac

Browse files
authored
[codex] Apply generated cpflow GitHub Actions flow (#732)
* Apply generated cpflow GitHub Actions flow * Document generated cpflow workflow settings * Harden cpflow review app workflow * Improve cpflow workflow review feedback * Fix cpflow token propagation * Clarify cpflow workflow safety tradeoffs * Document cpflow workflow updates * Refresh cpflow workflow generator output * Fix cpflow setup action metadata * Handle fork review app comments cleanly * Address cpflow workflow review comments * Address generated workflow review follow-ups * Ensure Docker SSH key cleanup * Address cpflow review app follow-ups * Harden cpflow review follow-ups * Address final cpflow review notes * Refresh cpflow flow from latest PR 278 * Fix cpflow setup action metadata * Persist Control Plane token for cpflow steps * Address latest cpflow review polish * Address latest cpflow flow review updates * Address cpflow review workflow feedback * Tighten cpflow workflow permissions and triggers * Update cpflow workflows to 5.0.0.rc.0 * Bootstrap cpflow actions for PR deploy checks * Prepare Ruby for cpflow action bootstrap * Patch bootstrapped cpflow setup metadata * Escape cpflow bootstrap metadata patch * Pass Control Plane token to cpflow steps * Address cpflow workflow review fixes
1 parent 2f7d333 commit b75a6ac

28 files changed

Lines changed: 2167 additions & 1147 deletions

.controlplane/readme.md

Lines changed: 66 additions & 10 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+
Check [how the `cpflow` gem is used in the generated GitHub Actions flow](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/.github/actions/cpflow-build-docker-image/action.yml).
1010
Here is a brief [video overview](https://www.youtube.com/watch?v=llaQoAV_6Iw).
1111

1212
---
@@ -364,27 +364,83 @@ openssl rand -hex 64
364364

365365
_Note, some of the URL references are internal for the ShakaCode team._
366366

367-
Review Apps (deployment of apps based on a PR) are done via Github Actions.
367+
Review Apps (deployment of apps based on a PR) are done via the generated
368+
`cpflow-*` GitHub Actions flow.
368369

369-
The review apps work by creating isolated deployments for each branch through this automated process. When a branch is pushed, the action:
370+
The review apps work by creating isolated deployments for pull requests through
371+
this automated process. When an approved collaborator comments exactly
372+
`/deploy-review-app` on a PR, the action:
370373

371374
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
375+
2. Creates a unique review app if it doesn't exist
376+
3. Builds a Docker image tagged with the PR commit SHA
374377
4. Deploys this image to Control Plane with its own isolated environment
375378

379+
After the review app exists, new pushes to the PR redeploy it automatically.
380+
Use `/delete-review-app` to delete it manually; closing the PR deletes it
381+
automatically. Pushes to the staging branch deploy staging, and production
382+
promotion is manual from the `cpflow-promote-staging-to-production` workflow.
383+
If staging moves off `master`, update both the `STAGING_APP_BRANCH` repository
384+
variable and the `branches:` filter in `.github/workflows/cpflow-deploy-staging.yml`;
385+
GitHub does not allow repository variables in trigger branch filters.
386+
The production promotion workflow checks that production has all environment
387+
variable names present in staging; it does not compare secret values, workload
388+
environment variables, or Control Plane secret references.
389+
390+
The repository variables and secrets must match the app names in
391+
`.controlplane/controlplane.yml`. In particular, `REVIEW_APP_PREFIX` should
392+
include the `-pr` suffix for this app, such as
393+
`qa-react-webpack-rails-tutorial-pr`, so generated review apps are named
394+
`qa-react-webpack-rails-tutorial-pr-1234`.
395+
376396
This allows teams to:
377397
- Preview changes in a production-like environment
378398
- Test features independently
379399
- Share working versions with stakeholders
380400
- Validate changes before merging to main branches
381401

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.
402+
The system uses Control Plane's infrastructure to manage these deployments, with
403+
each review app getting its own resources as defined in the controlplane.yml
404+
configuration.
383405

384406

385-
### Workflow for Developing Github Actions for Review Apps
407+
### Workflow for Developing GitHub Actions for Review Apps
386408

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`
409+
1. Create a PR with changes to the GitHub Actions workflow
410+
2. Make edits to files such as `.github/actions/cpflow-build-docker-image/action.yml` or `.github/workflows/cpflow-deploy-review-app.yml`
389411
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
412+
4. Check the GitHub Actions tab in the PR to see the status of the workflow
413+
414+
### Keeping Generated cpflow Workflows Updated
415+
416+
Treat `.github/actions/cpflow-*` and `.github/workflows/cpflow-*` as generated
417+
workflow files with project-specific settings layered on top. When `cpflow`
418+
releases generator fixes or the upstream `control-plane-flow` repo changes the
419+
GitHub Actions flow, update a project by regenerating the flow from the desired
420+
`cpflow` version or branch, reviewing the diff, and keeping any local app names,
421+
repository variables, secrets, and docs aligned with `.controlplane/controlplane.yml`.
422+
423+
For this app, validate a regenerated flow with:
424+
425+
```bash
426+
bundle exec ruby /path/to/control-plane-flow/bin/cpflow generate-github-actions --staging-branch master
427+
bundle exec ruby /path/to/control-plane-flow/bin/cpflow github-flow-readiness
428+
actionlint .github/workflows/cpflow-*.yml
429+
bundle exec rubocop
430+
```
431+
432+
Then open a normal PR and let GitHub Actions prove the generated review-app,
433+
staging, lint, JS, and RSpec workflows before merging. For review-app workflow
434+
changes, test both the local workflow syntax and a real deployment. GitHub runs
435+
`issue_comment` workflows from the default branch, so a `/deploy-review-app`
436+
comment on the PR does not fully exercise slash-command changes that are only on
437+
the PR branch. Before merge, run the PR branch workflow explicitly:
438+
439+
```bash
440+
gh workflow run cpflow-deploy-review-app.yml --ref <branch> -f pr_number=<pr-number>
441+
```
442+
443+
After the workflow reports a review-app URL, verify the URL returns HTTP 200.
444+
If a project needs to track generator changes automatically, use a scheduled
445+
maintenance PR or Renovate-style workflow that bumps the `cpflow` version,
446+
regenerates these files, and runs the same validation commands.

.controlplane/shakacode-team.md

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ Deployments are handled by Control Plane configuration in this repo and GitHub A
66

77
### Review Apps
88
- Add a comment `/deploy-review-app` to any PR to deploy a review app
9+
- The generated app name is `${REVIEW_APP_PREFIX}-${PR_NUMBER}`. Keep
10+
`REVIEW_APP_PREFIX` set to `qa-react-webpack-rails-tutorial-pr` so review
11+
apps use names like `qa-react-webpack-rails-tutorial-pr-1234`, matching the
12+
prefix-backed config in `.controlplane/controlplane.yml`.
13+
- New pushes to a PR redeploy only after the review app already exists.
14+
- Add `/delete-review-app` to delete a review app manually; closing the PR also
15+
deletes it automatically.
916

1017
### Staging Environment
1118
- **Automatic**: Any merge to the `master` branch automatically deploys to staging
@@ -14,14 +21,56 @@ Deployments are handled by Control Plane configuration in this repo and GitHub A
1421
- [Staging App](https://staging.reactrails.com/)
1522

1623
### Production Environment
17-
- **Manual**: Run the [promote-staging-to-production workflow](https://github.com/shakacode/react-webpack-rails-tutorial/actions/workflows/promote-staging-to-production.yml) on GitHub
24+
- **Manual**: Run the [cpflow-promote-staging-to-production workflow](https://github.com/shakacode/react-webpack-rails-tutorial/actions/workflows/cpflow-promote-staging-to-production.yml) on GitHub
25+
- Rollback restores workload images only; database migrations and other
26+
`--run-release-phase` side effects are not reversed automatically.
1827
- **URLs**:
1928
- [Control Plane Console - Production](https://console.cpln.io/console/org/shakacode-open-source-examples-production/gvc/react-webpack-rails-tutorial-production/workload/rails/-info)
2029
- [Production App](https://reactrails.com/)
2130

22-
See [./README.md](./README.md) for more details.
31+
### GitHub Repository Settings
32+
33+
Required repository secrets:
34+
35+
- `CPLN_TOKEN_STAGING`
36+
- `CPLN_TOKEN_PRODUCTION`
37+
38+
Required repository variables:
39+
40+
- `CPLN_ORG_STAGING=shakacode-open-source-examples-staging`
41+
- `CPLN_ORG_PRODUCTION=shakacode-open-source-examples-production`
42+
- `STAGING_APP_NAME=react-webpack-rails-tutorial-staging`
43+
- `PRODUCTION_APP_NAME=react-webpack-rails-tutorial-production`
44+
- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial-pr`
45+
- `STAGING_APP_BRANCH=master`
46+
- `PRIMARY_WORKLOAD=rails`
47+
48+
Optional repository settings:
49+
50+
- `DOCKER_BUILD_SSH_KEY`: secret for private SSH dependencies during Docker builds.
51+
- `DOCKER_BUILD_EXTRA_ARGS`: newline-delimited Docker build tokens, such as `--build-arg=FOO=bar`.
52+
- `DOCKER_BUILD_SSH_KNOWN_HOSTS`: custom `known_hosts` entries when SSH build hosts are not GitHub.com.
53+
- `CPLN_CLI_VERSION`: pin a specific `@controlplane/cli` version; defaults to the generated action pin.
54+
- `CPFLOW_VERSION`: pin a specific cpflow gem version; defaults to the generated action pin.
55+
- `HEALTH_CHECK_ACCEPTED_STATUSES`: production promotion health statuses; defaults to `200 301 302`.
56+
- `HEALTH_CHECK_RETRIES` / `HEALTH_CHECK_INTERVAL`: production health polling controls; defaults to `24` retries and `15` seconds.
57+
- `ROLLBACK_READINESS_RETRIES` / `ROLLBACK_READINESS_INTERVAL`: post-rollback health polling controls; defaults to `24` retries and `15` seconds.
58+
59+
If staging moves off `master`, update both `STAGING_APP_BRANCH` and the branch
60+
filter in `.github/workflows/cpflow-deploy-staging.yml`.
61+
62+
### Keeping cpflow Automation Current
63+
64+
When the upstream `control-plane-flow` repo changes the generated GitHub Actions
65+
flow, regenerate the `cpflow-*` actions/workflows in this repo from the target
66+
`cpflow` version or branch using `--staging-branch master`, review the diff, and
67+
keep the repository variables above aligned with `.controlplane/controlplane.yml`. Validate with
68+
`cpflow github-flow-readiness`, `actionlint .github/workflows/cpflow-*.yml`, and
69+
the normal CI checks before merging.
70+
71+
See [readme.md](readme.md) for more details.
2372

2473
## Links
2574

2675
- [Control Plane Org for Staging and Review Apps](https://console.cpln.io/console/org/shakacode-open-source-examples-staging/-info)
27-
- [Control Plane Org for Deployed App](https://console.cpln.io/console/org/shakacode-open-source-examples/-info)
76+
- [Control Plane Org for Production App](https://console.cpln.io/console/org/shakacode-open-source-examples-production/-info)

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

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 docker build tokens. Use key=value forms like --build-arg=FOO=bar.
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+
docker_build_ssh_known_hosts:
24+
description: Optional SSH known_hosts entries used with docker_build_ssh_key. Defaults to pinned GitHub.com host keys.
25+
required: false
26+
working_directory:
27+
description: Directory containing the app .controlplane config and Docker build context
28+
required: false
29+
default: "."
30+
31+
runs:
32+
using: composite
33+
steps:
34+
# Keep SSH key handling in a dedicated step so DOCKER_BUILD_SSH_KEY is never present
35+
# in the main build step's environment. ACTIONS_STEP_DEBUG=true dumps env before any
36+
# command runs, so keeping the key out of env there avoids even admin-triggered exposure.
37+
- name: Prepare SSH agent for Docker build
38+
if: ${{ inputs.docker_build_ssh_key != '' }}
39+
shell: bash
40+
env:
41+
# Pass the key via env so the file write is a single printf call rather than a
42+
# heredoc with a fixed terminator (a heredoc would silently truncate the key if
43+
# any line of the key value happened to match the terminator). Scope is still
44+
# this step only — the build step below does not receive DOCKER_BUILD_SSH_KEY.
45+
DOCKER_BUILD_SSH_KEY: ${{ inputs.docker_build_ssh_key }}
46+
DOCKER_BUILD_SSH_KNOWN_HOSTS: ${{ inputs.docker_build_ssh_known_hosts }}
47+
run: |
48+
set -euo pipefail
49+
50+
umask 077
51+
mkdir -p ~/.ssh
52+
chmod 700 ~/.ssh
53+
54+
if [[ -n "${DOCKER_BUILD_SSH_KNOWN_HOSTS}" ]]; then
55+
printf '%s\n' "${DOCKER_BUILD_SSH_KNOWN_HOSTS}" | tr -d '\r' > ~/.ssh/known_hosts
56+
else
57+
printf '%s\n' \
58+
'github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl' \
59+
'github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=' \
60+
'github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=' \
61+
> ~/.ssh/known_hosts
62+
fi
63+
chmod 600 ~/.ssh/known_hosts
64+
65+
printf '%s\n' "${DOCKER_BUILD_SSH_KEY}" | tr -d '\r' > ~/.ssh/cpflow_build_key
66+
chmod 600 ~/.ssh/cpflow_build_key
67+
68+
- name: Build Docker image
69+
shell: bash
70+
env:
71+
APP_NAME: ${{ inputs.app_name }}
72+
COMMIT_SHA: ${{ inputs.commit }}
73+
CONTROL_PLANE_ORG: ${{ inputs.org }}
74+
DOCKER_BUILD_EXTRA_ARGS: ${{ inputs.docker_build_extra_args }}
75+
PR_NUMBER: ${{ inputs.pr_number }}
76+
WORKING_DIRECTORY: ${{ inputs.working_directory }}
77+
run: |
78+
set -euo pipefail
79+
80+
PR_INFO=""
81+
docker_build_args=()
82+
ssh_agent_started=false
83+
build_ssh_prepped=false
84+
85+
cleanup_build_ssh() {
86+
if [[ "${ssh_agent_started}" == "true" ]]; then
87+
ssh-agent -k >/dev/null || true
88+
fi
89+
rm -f "${HOME}/.ssh/cpflow_build_key"
90+
# Only remove known_hosts if this action's prep step wrote it. On self-hosted
91+
# or reused runners we must not touch a user-managed file we did not create,
92+
# so the flag is set inside the same prep-detection branch below.
93+
if [[ "${build_ssh_prepped}" == "true" ]]; then
94+
rm -f "${HOME}/.ssh/known_hosts"
95+
fi
96+
}
97+
trap cleanup_build_ssh EXIT
98+
cd "${WORKING_DIRECTORY}"
99+
100+
if [[ -n "${PR_NUMBER}" ]]; then
101+
PR_INFO=" for PR #${PR_NUMBER}"
102+
fi
103+
104+
if [[ -n "${DOCKER_BUILD_EXTRA_ARGS}" ]]; then
105+
while IFS= read -r arg; do
106+
arg="${arg%$'\r'}"
107+
[[ -n "${arg}" ]] || continue
108+
109+
if [[ "${arg}" =~ [[:space:]] ]]; then
110+
echo "docker_build_extra_args entries must be single docker-build tokens. " \
111+
"Use key=value forms like --build-arg=FOO=bar." >&2
112+
exit 1
113+
fi
114+
115+
docker_build_args+=("${arg}")
116+
done <<< "${DOCKER_BUILD_EXTRA_ARGS}"
117+
fi
118+
119+
if [[ -f "${HOME}/.ssh/cpflow_build_key" ]]; then
120+
# Mark prep-step ownership so cleanup_build_ssh only removes known_hosts
121+
# when this action wrote it (see trap above).
122+
build_ssh_prepped=true
123+
eval "$(ssh-agent -s)"
124+
ssh_agent_started=true
125+
ssh-add "${HOME}/.ssh/cpflow_build_key"
126+
docker_build_args+=("--ssh=default")
127+
fi
128+
129+
echo "🏗️ Building Docker image${PR_INFO} (commit ${COMMIT_SHA})..."
130+
cpflow build-image -a "${APP_NAME}" --commit="${COMMIT_SHA}" --org="${CONTROL_PLANE_ORG}" "${docker_build_args[@]}"
131+
echo "✅ Docker image build successful${PR_INFO} (commit ${COMMIT_SHA})"
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 }}

0 commit comments

Comments
 (0)