Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .controlplane/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ RUN SECRET_KEY_BASE=precompile_placeholder bin/rails react_on_rails:locale
# and /app/client/app are the client assets that are bundled, so not needed once built
# Helps to have smaller images b/c of smaller Docker Layer Caches and smaller final images
# SECRET_KEY_BASE is required for asset precompilation but is not persisted in the image
RUN SECRET_KEY_BASE=precompile_placeholder yarn res:build && \
RUN SECRET_KEY_BASE=precompile_placeholder bundle exec rake react_on_rails:generate_packs && \
SECRET_KEY_BASE=precompile_placeholder yarn res:build && \
SECRET_KEY_BASE=precompile_placeholder bin/rails assets:precompile && \
rm -rf /app/lib/bs /app/client/app

Expand Down
2 changes: 1 addition & 1 deletion .controlplane/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ echo " -- Waiting for services"
wait_for_service $(echo $DATABASE_URL | sed -e 's|^.*@||' -e 's|/.*$||')
wait_for_service $(echo $REDIS_URL | sed -e 's|redis://||' -e 's|/.*$||')

echo " -- Finishing entrypoint.sh, executing '$@'"
echo " -- Finishing entrypoint.sh, executing '$*'@'"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: typo corrupts the echo output.

The change from '$@' to '$*'@' introduces a stray @' literal at the end of the string. Inside the outer double-quotes, the shell sees '$*'@' which expands to the arguments followed by the literal characters @'.

Suggested change
echo " -- Finishing entrypoint.sh, executing '$*'@'"
echo " -- Finishing entrypoint.sh, executing '$*'"

While you're here: $* (space-joined) is fine for an echo message, but the actual exec "$@" below must stay with "$@" (array-safe) — that part is correct.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: garbled echo output

The change from '$@' to '$*'@' introduces a typo — the closing ' ends the $* expansion prematurely and the literal @' is appended to the output.

Current output at runtime: -- Finishing entrypoint.sh, executing 'arg1 arg2'@'

Suggested change
echo " -- Finishing entrypoint.sh, executing '$*'@'"
echo " -- Finishing entrypoint.sh, executing '$@'"

($@ and $* produce the same result in an echo context, so restoring $@ keeps the intent clear.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify current log line in the repo
rg -n 'Finishing entrypoint\.sh' .controlplane/entrypoint.sh

# Demonstrate why $* is ambiguous vs argv-safe rendering
bash -lc '
set -- "bundle" "exec rails s" "--port=3000"
echo "with \$*: <$*>"
printf "with %%q:"
printf " %q" "$@"
printf "\n"
'

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 339


Fix misleading command logging output—$* flattens arguments and @' appears unintentional.

The $* expansion in the log loses argument boundaries (e.g., "exec rails s" becomes indistinguishable from separate words), making startup logs harder to debug. The trailing @' looks like a stray literal. Use printf with %q formatting to properly quote and preserve argument structure in logs.

Suggested patch
-echo " -- Finishing entrypoint.sh, executing '$*'@'"
+printf ' -- Finishing entrypoint.sh, executing'
+printf ' %q' "$@"
+printf '\n'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo " -- Finishing entrypoint.sh, executing '$*'@'"
printf ' -- Finishing entrypoint.sh, executing'
printf ' %q' "$@"
printf '\n'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.controlplane/entrypoint.sh at line 24, The log line in entrypoint.sh uses
echo " -- Finishing entrypoint.sh, executing '$*'@'" which flattens arguments
and leaves a stray "@'"; replace this with a printf-based quoted expansion to
preserve argument boundaries and remove the stray characters: use printf with %q
to quote each positional argument (e.g., build a quoted-args string via printf
'%q ' "$@" ) and include that in the log message instead of '$*' and the
trailing "@'".

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stray characters in entrypoint log message

Low Severity

The echo statement has a stray @' inside the double-quoted string. The old line was executing '$@'" and the intent was to change $@ to $*, but the replacement left behind the @ character and an extra single quote, producing output like executing 'rails server'@' instead of executing 'rails server'.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0e5f9e9. Configure here.


# Run the main command
exec "$@"
123 changes: 75 additions & 48 deletions .controlplane/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ _If you need a free demo account for Control Plane (no CC required), you can con

---

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).
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.
Here is a brief [video overview](https://www.youtube.com/watch?v=llaQoAV_6Iw).

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

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

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

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

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

# See how app is starting up
cpflow logs -a $APP_NAME
# See how the app is starting up
cpflow logs -a "$APP_NAME"

# Open app in browser (once it has started up)
cpflow open -a $APP_NAME
cpflow open -a "$APP_NAME"
```

### Promoting code updates

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

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

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

# Pomote latest image to app after migrations run
cpflow deploy-image -a $APP_NAME
cpflow deploy-image -a "$APP_NAME" --run-release-phase
```

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

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

## GitHub Actions Flow

This repo now uses the shared `cpflow-*` GitHub Actions scaffolding:

- `.github/workflows/cpflow-review-app-help.yml`
- `.github/workflows/cpflow-help-command.yml`
- `.github/workflows/cpflow-deploy-review-app.yml`
- `.github/workflows/cpflow-delete-review-app.yml`
- `.github/workflows/cpflow-deploy-staging.yml`
- `.github/workflows/cpflow-promote-staging-to-production.yml`
- `.github/workflows/cpflow-cleanup-stale-review-apps.yml`

Behavior:

- comment `/deploy-review-app` on a PR to create or update a review app
- later pushes to that PR auto-redeploy the existing review app
- pushes to `master` auto-deploy staging unless `STAGING_APP_BRANCH` overrides it
- production promotion happens manually from the Actions tab
- stale review apps are cleaned up nightly

This repo keeps its historical `qa-react-webpack-rails-tutorial` prefix for review apps, so:

- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial`
- PR 123 deploys to `qa-react-webpack-rails-tutorial-123`

Required GitHub repository secrets:

- `CPLN_TOKEN_STAGING`
- `CPLN_TOKEN_PRODUCTION`

Required GitHub repository variables:

- `CPLN_ORG_STAGING`
- `CPLN_ORG_PRODUCTION`
- `STAGING_APP_NAME=react-webpack-rails-tutorial-staging`
- `PRODUCTION_APP_NAME=react-webpack-rails-tutorial-production`
- `REVIEW_APP_PREFIX=qa-react-webpack-rails-tutorial`

Optional variables:

- `STAGING_APP_BRANCH=master`
- `PRIMARY_WORKLOAD=rails`
- `DOCKER_BUILD_EXTRA_ARGS`

Operational notes:

- `/deploy-review-app` and `/delete-review-app` only run for trusted commenters (`OWNER`, `MEMBER`, `COLLABORATOR`)
- fork PRs still receive help comments, but review app deploys are skipped because the workflow builds Docker images with repository secrets
- PR pushes do not auto-create review apps; the first deploy remains opt-in

## HTTP/2 and Thruster Configuration

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

## CI Automation, Review Apps and Staging

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

Review Apps (deployment of apps based on a PR) are done via Github Actions.

The review apps work by creating isolated deployments for each branch through this automated process. When a branch is pushed, the action:

1. Sets up the necessary environment and tools
2. Creates a unique deployment for that branch if it doesn't exist
3. Builds a Docker image tagged with the branch's commit SHA
4. Deploys this image to Control Plane with its own isolated environment

This allows teams to:
- Preview changes in a production-like environment
- Test features independently
- Share working versions with stakeholders
- Validate changes before merging to main branches

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.

Review apps, staging deploys, and production promotion are all driven by the
`cpflow-*` workflows in `.github/workflows/`.

### Workflow for Developing Github Actions for Review Apps
### Workflow for Developing GitHub Actions for Review Apps

1. Create a PR with changes to the Github Actions workflow
2. Make edits to file such as `.github/actions/deploy-to-control-plane/action.yml`
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)
4. Check the Github Actions tab in the PR to see the status of the workflow
1. Create a PR with changes to the GitHub Actions workflow.
2. Make edits to files such as `.github/workflows/cpflow-deploy-review-app.yml` or `.github/actions/cpflow-build-docker-image/action.yml`.
3. Commit and push the `.github` changes.
4. Check the GitHub Actions tab in the PR to see the status of the workflow.
39 changes: 0 additions & 39 deletions .github/actions/build-docker-image/action.yml

This file was deleted.

85 changes: 85 additions & 0 deletions .github/actions/cpflow-build-docker-image/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Build Docker Image
description: Builds and pushes the app image for a Control Plane workload

inputs:
app_name:
description: Name of the application
required: true
org:
description: Control Plane organization name
required: true
commit:
description: Commit SHA to tag the image with
required: true
pr_number:
description: Pull request number for status messaging
required: false
docker_build_extra_args:
description: Optional newline-delimited extra arguments passed through to docker build
required: false
docker_build_ssh_key:
description: Optional private SSH key used for Docker builds that fetch private dependencies with RUN --mount=type=ssh
required: false
docker_build_ssh_known_hosts:
description: Optional SSH known_hosts entries used with docker_build_ssh_key. Defaults to pinned GitHub.com host keys.
required: false

outputs:
image_tag:
description: Fully qualified image tag
value: ${{ steps.build.outputs.image_tag }}

runs:
using: composite
steps:
- name: Build Docker image
id: build
shell: bash
run: |
set -euo pipefail

PR_INFO=""
docker_build_args=()

if [[ -n "${{ inputs.pr_number }}" ]]; then
PR_INFO=" for PR #${{ inputs.pr_number }}"
fi

if [[ -n "${{ inputs.docker_build_extra_args }}" ]]; then
while IFS= read -r arg; do
arg="${arg%$'\r'}"
[[ -n "${arg}" ]] || continue
docker_build_args+=("${arg}")
done <<< "${{ inputs.docker_build_extra_args }}"
fi

if [[ -n "${{ inputs.docker_build_ssh_key }}" ]]; then
mkdir -p ~/.ssh
chmod 700 ~/.ssh

if [[ -n "${{ inputs.docker_build_ssh_known_hosts }}" ]]; then
cat <<'EOF' > ~/.ssh/known_hosts
${{ inputs.docker_build_ssh_known_hosts }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Indent heredoc body in Docker build action

This line is flush-left instead of being indented within the run: | block, which makes .github/actions/cpflow-build-docker-image/action.yml invalid YAML (parsers raise could not find expected ':' at this section). Since both cpflow-deploy-review-app.yml and cpflow-deploy-staging.yml call this composite action, those workflows will fail to load/execute before the Docker build step runs.

Useful? React with 👍 / 👎.

EOF
Comment on lines +68 to +70
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

🏁 Script executed:

#!/bin/bash
python -m pip -q install pyyaml
python - <<'PY'
from pathlib import Path
import yaml
p = Path(".github/actions/cpflow-build-docker-image/action.yml")
yaml.safe_load(p.read_text())
print("YAML parses successfully.")
PY

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 1818


🏁 Script executed:

#!/bin/bash
# Try to access the file and check YAML syntax
cd /tmp && git clone https://github.com/shakacode/react-webpack-rails-tutorial.git repo 2>&1 | tail -20 && cd repo && cat -n .github/actions/cpflow-build-docker-image/action.yml | sed -n '65,80p'

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 1939


Fix YAML block parsing around heredoc content (currently breaks action loading).

Lines 68-70 and Lines 72-76 require attention for proper YAML run: | block structure, which appears to be causing parsing issues based on linter feedback.

Proposed fix
-          if [[ -n "${{ inputs.docker_build_ssh_known_hosts }}" ]]; then
-            cat <<'EOF' > ~/.ssh/known_hosts
-${{ inputs.docker_build_ssh_known_hosts }}
-EOF
-          else
-            cat <<'EOF' > ~/.ssh/known_hosts
-github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
-github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
-github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
-EOF
-          fi
+          if [[ -n "${{ inputs.docker_build_ssh_known_hosts }}" ]]; then
+            printf '%s\n' "${{ inputs.docker_build_ssh_known_hosts }}" > ~/.ssh/known_hosts
+          else
+            {
+              printf '%s\n' 'github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl'
+              printf '%s\n' 'github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg='
+              printf '%s\n' 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk='
+            } > ~/.ssh/known_hosts
+          fi
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 70-70: syntax error: could not find expected ':'

(syntax)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/cpflow-build-docker-image/action.yml around lines 68 - 70,
The heredoc lines for writing known_hosts are not indented to live inside the
YAML run: | block, causing the action to fail parsing; fix by placing the entire
heredoc (the line starting with cat <<'EOF' > ~/.ssh/known_hosts, the
interpolated ${ { inputs.docker_build_ssh_known_hosts } } content, and the
closing EOF) as an indented block under the existing run: | scalar so the
opening command and the terminating EOF align with the run block indentation;
ensure the EOF marker is at the same indentation as the heredoc start and that
the ${ { inputs.docker_build_ssh_known_hosts } } token remains inside the
heredoc content.

else
cat <<'EOF' > ~/.ssh/known_hosts
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
EOF
fi

chmod 600 ~/.ssh/known_hosts

eval "$(ssh-agent -s)"
trap 'ssh-agent -k >/dev/null' EXIT
ssh-add - <<< "${{ inputs.docker_build_ssh_key }}"
docker_build_args+=("--ssh" "default")
fi

echo "🏗️ Building Docker image${PR_INFO} (commit ${{ inputs.commit }})..."
cpflow build-image -a "${{ inputs.app_name }}" --commit="${{ inputs.commit }}" --org="${{ inputs.org }}" "${docker_build_args[@]}"

image_tag="${{ inputs.org }}/${{ inputs.app_name }}:${{ inputs.commit }}"
echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
echo "✅ Docker image build successful${PR_INFO} (commit ${{ inputs.commit }})"
24 changes: 24 additions & 0 deletions .github/actions/cpflow-delete-control-plane-app/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Delete Control Plane App
description: Deletes a Control Plane app and all associated resources

inputs:
app_name:
description: Name of the application to delete
required: true
cpln_org:
description: Control Plane organization name
required: true
review_app_prefix:
description: Prefix used for review app names
required: true

runs:
using: composite
steps:
- name: Delete application
shell: bash
run: ${{ github.action_path }}/delete-app.sh
env:
APP_NAME: ${{ inputs.app_name }}
CPLN_ORG: ${{ inputs.cpln_org }}
REVIEW_APP_PREFIX: ${{ inputs.review_app_prefix }}
43 changes: 43 additions & 0 deletions .github/actions/cpflow-delete-control-plane-app/delete-app.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

set -euo pipefail

: "${APP_NAME:?APP_NAME environment variable is required}"
: "${CPLN_ORG:?CPLN_ORG environment variable is required}"
: "${REVIEW_APP_PREFIX:?REVIEW_APP_PREFIX environment variable is required}"

expected_prefix="${REVIEW_APP_PREFIX}-"
if [[ "$APP_NAME" != "${expected_prefix}"* ]]; then
echo "❌ ERROR: refusing to delete an app outside the review app prefix" >&2
echo "App name: $APP_NAME" >&2
echo "Expected prefix: ${expected_prefix}" >&2
exit 1
fi

echo "🔍 Checking if application exists: $APP_NAME"
exists_output=""
if ! exists_output="$(cpflow exists -a "$APP_NAME" --org "$CPLN_ORG" 2>&1)"; then
case "$exists_output" in
*"Double check your org"*|*"Unknown API token format"*|*"ERROR"*|*"Error:"*|*"Traceback"*|*"Net::"*)
echo "❌ ERROR: failed to determine whether application exists: $APP_NAME" >&2
printf '%s\n' "$exists_output" >&2
exit 1
;;
esac

if [[ -n "$exists_output" ]]; then
printf '%s\n' "$exists_output"
fi

echo "⚠️ Application does not exist: $APP_NAME"
exit 0
Comment on lines +19 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For the cpflowCLI commandcpflow exists`, what are the documented exit codes and output patterns for:

  1. app exists,
  2. app does not exist,
  3. auth/token/org errors,
  4. network/API failures?
    Please include authoritative docs or source references.`

💡 Result:

The cpflow CLI is part of the Control Plane Flow project (https://github.com/shakacode/control-plane-flow), a Ruby gem providing Heroku-like deployment to Control Plane Kubernetes. For cpflow exists (checks if an application/GVC exists, used in scripts like if cpflow exists -a $APP_NAME; then ...): No official documentation specifies exact exit codes or output patterns beyond general CLI conventions. Authoritative sources (project docs at shakacode.com/control-plane-flow/docs/commands/ and GitHub READMEs) describe it as a shell-check command but do not detail outputs. Inferred from documentation and standard CLI practices (e.g., similar to heroku apps:exists or Unix tools): 1) App exists: Exit code 0 (success), minimal/no stdout (silent success for scripting). 2) App does not exist: Exit code non-zero (likely 1), error message on stderr (e.g., "App does not exist"). 3) Auth/token/org errors: Non-zero exit (e.g., 1), error on stderr (e.g., "Authentication failed" or "Invalid org"). 4) Network/API failures: Non-zero exit (e.g., 1 or network-specific), error on stderr (e.g., "Network error" or "API unavailable"). Many cpflow commands exit non-zero on hook failures or errors, propagating exit codes. Check $? after running. Primary sources: GitHub repo (result 5,12,21,33), Shakacode docs (7,21), RubyGems (32). Source code inspection recommended for precise details (lib/cpflow/commands likely contains Exists command implementation).

Citations:


🏁 Script executed:

#!/bin/bash

# First, let's check if we can find the cpflow source code online
# cpflow is at https://github.com/shakacode/control-plane-flow

# Let's look for documentation and source code information
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/README.md" | head -100

# Also try to find the exists command implementation
curl -s "https://api.github.com/repos/shakacode/control-plane-flow/contents/lib/cpflow/commands" | jq -r '.[].name' 2>/dev/null || echo "API call details will be checked via web"

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 6076


🌐 Web query:

cpflow exists command implementation shakacode/control-plane-flow github exit code

💡 Result:

The cpflow exists command in the shakacode/control-plane-flow GitHub repository is a shell-check utility that verifies if a specified application (GVC, or Global Virtual Cluster) exists on Control Plane. It is designed for use in scripts. Usage example from official docs: if [ cpflow exists -a $APP_NAME ]; then # app exists else # app does not exist fi It requires the -a or --app flag with the app reference name. Exit code: Follows standard shell conventions for script-friendly use (0 if the app exists, non-zero if it does not), consistent with cpflow's general behavior where failures exit non-zero (e.g., code 64 for command failures per CHANGELOG). The incomplete [ cpflow exists -a $APP_NAME ] syntax in docs implies the command returns a shell-true/false value based on exit code for conditional checks. Implementation: Part of the Ruby-based cpflow CLI gem (primary language Ruby, 93.6%). Exact source code not surfaced in search results, but defined as a command in docs/commands.md. Likely uses cpln CLI internally to query GVC existence (e.g., cpln gvc get), exiting 0/1 accordingly. Repository last updated 2026-04-06.

Citations:


🏁 Script executed:

#!/bin/bash

# Search for the cpflow exists command implementation
# First, check if we can access the GitHub API for file listing
curl -s "https://api.github.com/repos/shakacode/control-plane-flow/git/trees/main?recursive=1" | \
  jq -r '.tree[] | select(.path | contains("command") and contains("exist")) | .path' 2>/dev/null | head -20

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 131


🏁 Script executed:

#!/bin/bash

# Try to find documentation or tests for exists command
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/docs/commands.md" | \
  grep -A 30 "exists" | head -50

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 1979


🏁 Script executed:

#!/bin/bash

# Search the GitHub repo for exists.rb file
curl -s "https://api.github.com/search/code?q=repo:shakacode/control-plane-flow+exists+filename:exists.rb" | \
  jq '.items[0].download_url' 2>/dev/null || echo "Not found via API"

# Alternative: try direct URL to common locations
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/commands/exists.rb" 2>/dev/null | head -100

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 101


🏁 Script executed:

#!/bin/bash

# Try alternative path for exists command
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/command/exists.rb" | head -100

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 637


🏁 Script executed:

#!/bin/bash

# Also try the spec file to understand expected behavior
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/spec/command/exists_spec.rb" | head -150

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 643


🏁 Script executed:

#!/bin/bash

# Search for "does not exist" and error patterns in cpflow repo
curl -s "https://api.github.com/search/code?q=repo:shakacode/control-plane-flow+%22does+not+exist%22" 2>/dev/null | jq '.items[] | {path: .path, name: .name}' | head -30

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 139


🏁 Script executed:

#!/bin/bash

# Search for fetch_gvc implementation
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/cp.rb" | grep -A 20 "def fetch_gvc" | head -30

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 64


🏁 Script executed:

#!/bin/bash

# Search for error messages in the codebase to understand what cpflow outputs
curl -s "https://api.github.com/search/code?q=repo:shakacode/control-plane-flow+%22ERROR%22+%22Error:%22" 2>/dev/null | jq '.items[0:3] | .[].path' 2>/dev/null

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 64


🏁 Script executed:

#!/bin/bash

# Look at the base command class to understand error handling
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/command/base.rb" | head -150

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 4308


🏁 Script executed:

#!/bin/bash

# Search for error handling, rescue blocks, or exception patterns in cpflow
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/cp.rb" | head -200

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 96


🏁 Script executed:

#!/bin/bash

# Search for what happens when cpln (the underlying CLI) fails
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/runner.rb" | grep -A 10 -B 5 "raise\|rescue\|Error" | head -80

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 64


🏁 Script executed:

#!/antml:bin/bash

# Look for CHANGELOG to understand what errors cpflow has
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/CHANGELOG.md" | grep -i "error\|exit\|fail" | head -40

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 4048


🏁 Script executed:

#!/bin/bash

# Search for all error/exception messages in cpflow to see what strings might be output
curl -s "https://api.github.com/search/code?q=repo:shakacode/control-plane-flow+raise+Error+language:ruby" 2>/dev/null | jq '.items[0:5] | .[].path' 2>/dev/null

# Also search for what strings cpflow actually outputs
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/runner.rb" | head -100

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 96


🏁 Script executed:

#!/bin/bash

# Look for all puts/warn/print statements in cpflow that might output errors
curl -s "https://api.github.com/search/code?q=repo:shakacode/control-plane-flow+%22puts%22+%22Error%22+language:ruby" 2>/dev/null | jq '.items[] | select(.path | contains("core") or contains("runner")) | .path' | head -10

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 139


🏁 Script executed:

#!/bin/bash

# Search for error handling in the cp.rb file that wraps cpln
curl -s "https://raw.githubusercontent.com/shakacode/control-plane-flow/main/lib/cpflow/core/cp.rb" 2>/dev/null | head -200

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 96


Fail closed on unknown cpflow exists errors.

The script assumes that any non-zero exit from cpflow exists that doesn't match hardcoded error patterns means the app doesn't exist and exits successfully. However, cpflow exists can return non-zero for actual errors—auth failures, API errors, network issues—with error messages that might not match your patterns. This masks real failures and could leave resources undeleted. Consider instead explicitly checking for the "app not found" condition or fail on any unrecognized error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/cpflow-delete-control-plane-app/delete-app.sh around lines
19 - 33, The current exists check for cpflow (the cpflow exists invocation
assigning to exists_output) treats any non-zero exit that doesn't match a small
set of error patterns as "app does not exist" and returns success; instead,
change the logic so that any non-zero exit from cpflow exists fails by default
unless the output explicitly indicates the app is missing (match the exact "not
found"/"does not exist"/"Application not found" message your cpflow CLI returns)
— i.e., after running cpflow exists (the exists_output variable for APP_NAME and
CPLN_ORG), check for the explicit "app not found" text and exit 0 only for that
case, otherwise log exists_output and exit 1 to surface auth/API/network errors.

fi

if [[ -n "$exists_output" ]]; then
printf '%s\n' "$exists_output"
fi

echo "🗑️ Deleting application: $APP_NAME"
cpflow delete -a "$APP_NAME" --org "$CPLN_ORG" --yes

echo "✅ Successfully deleted application: $APP_NAME"
Loading
Loading