Skip to content

Commit 976b320

Browse files
committed
Refresh cpflow workflow generator output
1 parent 9a390c1 commit 976b320

14 files changed

Lines changed: 385 additions & 205 deletions

File tree

.controlplane/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ repository variables, secrets, and docs aligned with `.controlplane/controlplane
419419
For this app, validate a regenerated flow with:
420420

421421
```bash
422-
bundle exec ruby /path/to/control-plane-flow/bin/cpflow generate-github-actions
422+
bundle exec ruby /path/to/control-plane-flow/bin/cpflow generate-github-actions --staging-branch master
423423
bundle exec ruby /path/to/control-plane-flow/bin/cpflow github-flow-readiness
424424
actionlint .github/workflows/cpflow-*.yml
425425
bundle exec rubocop

.controlplane/shakacode-team.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ Optional repository settings:
4848
- `DOCKER_BUILD_SSH_KEY`: secret for private SSH dependencies during Docker builds.
4949
- `DOCKER_BUILD_EXTRA_ARGS`: newline-delimited Docker build tokens, such as `--build-arg=FOO=bar`.
5050
- `DOCKER_BUILD_SSH_KNOWN_HOSTS`: custom `known_hosts` entries when SSH build hosts are not GitHub.com.
51+
- `CPLN_CLI_VERSION`: pin a specific `@controlplane/cli` version; defaults to the generated action pin.
52+
- `CPFLOW_VERSION`: pin a specific cpflow gem version; defaults to the generated action pin.
53+
- `HEALTH_CHECK_ACCEPTED_STATUSES`: production promotion health statuses; defaults to `200 301 302`.
5154

5255
If staging moves off `master`, update both `STAGING_APP_BRANCH` and the branch
5356
filter in `.github/workflows/cpflow-deploy-staging.yml`.
@@ -56,8 +59,8 @@ filter in `.github/workflows/cpflow-deploy-staging.yml`.
5659

5760
When the upstream `control-plane-flow` repo changes the generated GitHub Actions
5861
flow, regenerate the `cpflow-*` actions/workflows in this repo from the target
59-
`cpflow` version or branch, review the diff, and keep the repository variables
60-
above aligned with `.controlplane/controlplane.yml`. Validate with
62+
`cpflow` version or branch using `--staging-branch master`, review the diff, and
63+
keep the repository variables above aligned with `.controlplane/controlplane.yml`. Validate with
6164
`cpflow github-flow-readiness`, `actionlint .github/workflows/cpflow-*.yml`, and
6265
the normal CI checks before merging.
6366

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Detect release phase support
2+
description: >-
3+
Inspects the cpflow config for an app and emits `flag=--run-release-phase`
4+
when a `release_script:` is configured. Outputs an empty `flag` otherwise.
5+
6+
inputs:
7+
app_name:
8+
description: cpflow app name to inspect
9+
required: true
10+
11+
outputs:
12+
flag:
13+
description: Either `--run-release-phase` or empty
14+
value: ${{ steps.detect.outputs.flag }}
15+
16+
runs:
17+
using: composite
18+
steps:
19+
- name: Detect release phase support
20+
id: detect
21+
shell: bash
22+
env:
23+
APP_NAME: ${{ inputs.app_name }}
24+
run: |
25+
set -euo pipefail
26+
27+
# Anchor to start-of-line so commented-out release_script: entries don't enable --run-release-phase.
28+
if cpflow config -a "${APP_NAME}" | grep -qE '^[[:space:]]*release_script:'; then
29+
echo "flag=--run-release-phase" >> "$GITHUB_OUTPUT"
30+
else
31+
echo "flag=" >> "$GITHUB_OUTPUT"
32+
fi

.github/actions/cpflow-setup-environment/action.yml

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ inputs:
1515
required: false
1616
default: ""
1717
cpln_cli_version:
18-
# Bump this default when a new @controlplane/cli release lands that you want to roll out.
19-
description: "@controlplane/cli version"
18+
description: >-
19+
@controlplane/cli version. Empty string falls back to the action's pinned default
20+
so callers can pass `${{ vars.CPLN_CLI_VERSION }}` unconditionally.
2021
required: false
21-
default: "3.3.1"
22+
default: ""
2223
cpflow_version:
23-
description: cpflow gem version
24+
description: >-
25+
cpflow gem version. Empty string falls back to the action's pinned default
26+
so callers can pass `${{ vars.CPFLOW_VERSION }}` unconditionally.
2427
required: false
25-
default: "4.2.0"
28+
default: ""
2629

2730
runs:
2831
using: composite
@@ -34,13 +37,25 @@ runs:
3437

3538
- name: Install Control Plane CLI and cpflow gem
3639
shell: bash
40+
env:
41+
CPLN_CLI_VERSION: ${{ inputs.cpln_cli_version }}
42+
CPFLOW_VERSION: ${{ inputs.cpflow_version }}
3743
run: |
3844
set -euo pipefail
3945
40-
sudo npm install -g @controlplane/cli@${{ inputs.cpln_cli_version }}
46+
# Bump these defaults when a new release lands that you want to roll out by default.
47+
# Override per-repo by setting `CPLN_CLI_VERSION` / `CPFLOW_VERSION` repo variables;
48+
# an empty input falls back to the action's pinned default below.
49+
default_cpln_cli_version="3.3.1"
50+
default_cpflow_version="4.2.0"
51+
52+
CPLN_CLI_VERSION="${CPLN_CLI_VERSION:-${default_cpln_cli_version}}"
53+
CPFLOW_VERSION="${CPFLOW_VERSION:-${default_cpflow_version}}"
54+
55+
sudo npm install -g "@controlplane/cli@${CPLN_CLI_VERSION}"
4156
cpln --version
4257
43-
gem install cpflow -v ${{ inputs.cpflow_version }} --no-document
58+
gem install cpflow -v "${CPFLOW_VERSION}" --no-document
4459
cpflow --version
4560
4661
- name: Setup Control Plane profile and registry login
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Validate cpflow GitHub configuration
2+
description: >-
3+
Validates that required secrets and repository variables are set before a workflow
4+
proceeds. Pass each value via `env:` with the same NAME as the secret or variable,
5+
then list the required entries in `required` as `type:NAME` pairs (type is `secret`
6+
or `variable`). When `pull_request_friendly: true` and the current event is a
7+
`pull_request`, missing config writes a step summary and exits 0 with `ready=false`
8+
instead of failing the job.
9+
10+
inputs:
11+
required:
12+
description: |
13+
Newline-separated `type:NAME` pairs. Type is `secret` or `variable`. The
14+
caller MUST export the matching values via `env:` using the same NAME.
15+
required: true
16+
pull_request_friendly:
17+
description: When "true" and event is pull_request, write summary and exit 0 with ready=false.
18+
required: false
19+
default: "false"
20+
21+
outputs:
22+
ready:
23+
description: '"true" when all values are set, "false" when missing in PR-friendly mode.'
24+
value: ${{ steps.check.outputs.ready }}
25+
26+
runs:
27+
using: composite
28+
steps:
29+
- name: Check required secrets and variables
30+
id: check
31+
shell: bash
32+
env:
33+
CPFLOW_REQUIRED: ${{ inputs.required }}
34+
CPFLOW_PR_FRIENDLY: ${{ inputs.pull_request_friendly }}
35+
CPFLOW_EVENT_NAME: ${{ github.event_name }}
36+
run: |
37+
set -euo pipefail
38+
39+
missing=()
40+
while IFS= read -r entry; do
41+
entry="${entry%$'\r'}"
42+
entry="${entry## }"
43+
entry="${entry%% }"
44+
[[ -z "${entry}" ]] && continue
45+
46+
type="${entry%%:*}"
47+
name="${entry#*:}"
48+
49+
# Indirect bash lookup: reads the env var named by ${name} (e.g. CPLN_TOKEN_STAGING)
50+
# so the value never has to round-trip through workflow logs.
51+
if [[ -z "${!name:-}" ]]; then
52+
missing+=("${type}:${name}")
53+
fi
54+
done <<< "${CPFLOW_REQUIRED}"
55+
56+
if [[ ${#missing[@]} -eq 0 ]]; then
57+
echo "ready=true" >> "$GITHUB_OUTPUT"
58+
exit 0
59+
fi
60+
61+
if [[ "${CPFLOW_PR_FRIENDLY}" == "true" && "${CPFLOW_EVENT_NAME}" == "pull_request" ]]; then
62+
echo "ready=false" >> "$GITHUB_OUTPUT"
63+
{
64+
echo "Control Plane review app automation is not configured yet."
65+
echo
66+
echo "Missing required GitHub configuration:"
67+
printf -- '- `%s`\n' "${missing[@]}"
68+
echo
69+
echo "Pushes to this pull request will skip review app deploys until the repository is configured."
70+
} >> "$GITHUB_STEP_SUMMARY"
71+
exit 0
72+
fi
73+
74+
printf 'Missing required GitHub configuration:\n- %s\n' "${missing[@]}" >&2
75+
exit 1
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Wait for Control Plane workload health
2+
description: >-
3+
Polls the workload's status endpoint with curl and exits success when the
4+
HTTP response status is in the accepted list. Fails non-zero (and reports
5+
`healthy=false`) once retries are exhausted.
6+
7+
inputs:
8+
workload_name:
9+
description: Workload to query (e.g. `rails`).
10+
required: true
11+
app_name:
12+
description: GVC / Control Plane app name the workload belongs to.
13+
required: true
14+
org:
15+
description: Control Plane organization.
16+
required: true
17+
max_retries:
18+
description: Number of attempts before giving up.
19+
required: false
20+
default: "24"
21+
interval_seconds:
22+
description: Seconds to sleep between attempts.
23+
required: false
24+
default: "15"
25+
accepted_statuses:
26+
description: >-
27+
Space-separated list of HTTP status codes considered healthy. The default
28+
`200 301 302` accepts redirects because curl is invoked without `-L`, so a
29+
root path that auth-redirects looks like a redirect, not a failure.
30+
required: false
31+
default: "200 301 302"
32+
curl_max_time:
33+
description: Per-request curl timeout, seconds.
34+
required: false
35+
default: "10"
36+
37+
outputs:
38+
healthy:
39+
description: '"true" once a healthy response was observed; "false" otherwise.'
40+
value: ${{ steps.poll.outputs.healthy }}
41+
42+
runs:
43+
using: composite
44+
steps:
45+
- name: Poll workload endpoint
46+
id: poll
47+
shell: bash
48+
env:
49+
CPFLOW_WORKLOAD_NAME: ${{ inputs.workload_name }}
50+
CPFLOW_APP_NAME: ${{ inputs.app_name }}
51+
CPFLOW_ORG: ${{ inputs.org }}
52+
CPFLOW_MAX_RETRIES: ${{ inputs.max_retries }}
53+
CPFLOW_INTERVAL_SECONDS: ${{ inputs.interval_seconds }}
54+
CPFLOW_ACCEPTED_STATUSES: ${{ inputs.accepted_statuses }}
55+
CPFLOW_CURL_MAX_TIME: ${{ inputs.curl_max_time }}
56+
run: |
57+
set -euo pipefail
58+
59+
read -r -a accepted_statuses <<< "${CPFLOW_ACCEPTED_STATUSES}"
60+
61+
for attempt in $(seq 1 "${CPFLOW_MAX_RETRIES}"); do
62+
echo "Health check attempt ${attempt}/${CPFLOW_MAX_RETRIES}"
63+
64+
endpoint="$(cpln workload get "${CPFLOW_WORKLOAD_NAME}" --gvc "${CPFLOW_APP_NAME}" --org "${CPFLOW_ORG}" -o json | jq -r '.status.endpoint // empty')"
65+
if [[ -n "${endpoint}" ]]; then
66+
http_status="$(curl -s -o /dev/null -w '%{http_code}' --max-time "${CPFLOW_CURL_MAX_TIME}" "${endpoint}" 2>/dev/null || echo 000)"
67+
echo "Endpoint: ${endpoint}, HTTP status: ${http_status}"
68+
69+
for accepted in "${accepted_statuses[@]}"; do
70+
if [[ "${http_status}" == "${accepted}" ]]; then
71+
echo "healthy=true" >> "$GITHUB_OUTPUT"
72+
exit 0
73+
fi
74+
done
75+
fi
76+
77+
if [[ "${attempt}" -lt "${CPFLOW_MAX_RETRIES}" ]]; then
78+
sleep "${CPFLOW_INTERVAL_SECONDS}"
79+
fi
80+
done
81+
82+
echo "healthy=false" >> "$GITHUB_OUTPUT"
83+
exit 1

.github/cpflow-help.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Control Plane GitHub Flow
2+
3+
## PR commands
4+
5+
`/deploy-review-app`
6+
- Creates the review app if it does not exist
7+
- Builds the PR commit image
8+
- Deploys the image and comments with the review URL
9+
10+
`/delete-review-app`
11+
- Deletes the review app when the PR is done
12+
- This also runs automatically when the PR closes
13+
14+
## Repository secrets
15+
16+
| Name | Required | Notes |
17+
| --- | --- | --- |
18+
| `CPLN_TOKEN_STAGING` | yes | Service-account token scoped to the staging org. |
19+
| `CPLN_TOKEN_PRODUCTION` | yes (for promote) | Service-account token scoped to the production org. |
20+
| `DOCKER_BUILD_SSH_KEY` | optional | Private SSH key used when Docker builds fetch private deps via `RUN --mount=type=ssh`. |
21+
22+
## Repository variables
23+
24+
| Name | Required | Notes |
25+
| --- | --- | --- |
26+
| `CPLN_ORG_STAGING` | yes | Control Plane org for staging and review apps. |
27+
| `CPLN_ORG_PRODUCTION` | yes (for promote) | Control Plane org for production. |
28+
| `STAGING_APP_NAME` | yes | App name in `controlplane.yml` used as the staging deploy target. |
29+
| `PRODUCTION_APP_NAME` | yes (for promote) | App name in `controlplane.yml` used as the production deploy target. |
30+
| `REVIEW_APP_PREFIX` | yes | Prefix for per-PR review app names (e.g. `review-app`). |
31+
| `STAGING_APP_BRANCH` | optional | Custom staging branch. Custom branches must also appear in `cpflow-deploy-staging.yml`'s push filter. |
32+
| `PRIMARY_WORKLOAD` | optional | Workload polled for health and rollback (defaults to `rails`). |
33+
| `DOCKER_BUILD_EXTRA_ARGS` | optional | Newline-delimited extra docker build tokens (e.g. `--build-arg=FOO=bar`). |
34+
| `DOCKER_BUILD_SSH_KNOWN_HOSTS` | optional | SSH known_hosts entries when SSH build hosts are not GitHub.com. |
35+
| `HEALTH_CHECK_ACCEPTED_STATUSES` | optional | Space-separated HTTP statuses considered healthy on promote (default `200 301 302`). |
36+
| `CPLN_CLI_VERSION` | optional | Pin a specific `@controlplane/cli` version; falls back to the action default when unset. |
37+
| `CPFLOW_VERSION` | optional | Pin a specific cpflow gem version; falls back to the generated default when unset. |
38+
39+
## Workflow behavior
40+
41+
- Review apps are opt-in and created with `/deploy-review-app`
42+
- New commits redeploy existing review apps automatically
43+
- Pushes to the staging branch deploy staging automatically
44+
- Promotion to production is manual via the Actions tab
45+
- A nightly workflow removes stale review apps

.github/workflows/cpflow-cleanup-stale-review-apps.yml

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,40 @@ permissions:
99
contents: read
1010

1111
concurrency:
12+
# Single global group: only one cleanup sweep at a time. Independent of review-app
13+
# deploy/delete groups (different keys), so cleanup will not block per-PR work.
1214
group: cpflow-cleanup-stale-review-apps
15+
# A cancelled `cpflow cleanup-stale-apps` can leave half-deleted review apps; let
16+
# the in-flight run finish before the next scheduled tick begins.
1317
cancel-in-progress: false
1418

1519
jobs:
1620
cleanup:
1721
runs-on: ubuntu-latest
22+
timeout-minutes: 30
1823
steps:
1924
- name: Checkout repository
2025
uses: actions/checkout@v4
2126

2227
- name: Validate required secrets and variables
23-
shell: bash
24-
run: |
25-
set -euo pipefail
26-
27-
missing=()
28-
[[ -n "${{ secrets.CPLN_TOKEN_STAGING }}" ]] || missing+=("secret:CPLN_TOKEN_STAGING")
29-
[[ -n "${{ vars.CPLN_ORG_STAGING }}" ]] || missing+=("variable:CPLN_ORG_STAGING")
30-
[[ -n "${{ vars.REVIEW_APP_PREFIX }}" ]] || missing+=("variable:REVIEW_APP_PREFIX")
31-
32-
if [[ ${#missing[@]} -gt 0 ]]; then
33-
printf 'Missing required GitHub configuration:\n- %s\n' "${missing[@]}" >&2
34-
exit 1
35-
fi
28+
uses: ./.github/actions/cpflow-validate-config
29+
env:
30+
CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }}
31+
CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }}
32+
REVIEW_APP_PREFIX: ${{ vars.REVIEW_APP_PREFIX }}
33+
with:
34+
required: |
35+
secret:CPLN_TOKEN_STAGING
36+
variable:CPLN_ORG_STAGING
37+
variable:REVIEW_APP_PREFIX
3638
3739
- name: Setup environment
3840
uses: ./.github/actions/cpflow-setup-environment
3941
with:
4042
token: ${{ secrets.CPLN_TOKEN_STAGING }}
4143
org: ${{ vars.CPLN_ORG_STAGING }}
44+
cpln_cli_version: ${{ vars.CPLN_CLI_VERSION }}
45+
cpflow_version: ${{ vars.CPFLOW_VERSION }}
4246

4347
- name: Remove stale review apps
4448
shell: bash

0 commit comments

Comments
 (0)