Skip to content

Sanitize Control Plane org names in production promotion#759

Merged
justin808 merged 1 commit into
masterfrom
jg-codex/trim-control-plane-org-vars
Jun 2, 2026
Merged

Sanitize Control Plane org names in production promotion#759
justin808 merged 1 commit into
masterfrom
jg-codex/trim-control-plane-org-vars

Conversation

@justin808

@justin808 justin808 commented Jun 2, 2026

Copy link
Copy Markdown
Member

Summary

  • normalize Control Plane org repository variables after config validation
  • strip accidental CR/LF line endings before building Docker registry hosts
  • route downstream setup, copy, deploy, health, and rollback steps through the normalized org outputs

Why

The latest production promote run failed in Docker auth with an invalid registry host: shakacode-open-source-examples-production\r\n.registry.cpln.io. gh variable list confirms CPLN_ORG_PRODUCTION is currently stored as shakacode-open-source-examples-production\r\n, so the workflow needs to tolerate copied/pasted line endings and fail early for truly invalid org names.

Failed run: https://github.com/shakacode/react-webpack-rails-tutorial/actions/runs/26849977870

Validation

  • git diff --check -- .github/workflows/cpflow-promote-staging-to-production.yml
  • bin/conductor-exec bin/test-cpflow-github-flow

Note

Low Risk
CI-only guardrails on repository variables; no application runtime or auth logic changes.

Overview
Adds a Normalize Control Plane org names step to the staging→production promotion workflow so CPLN_ORG_STAGING and CPLN_ORG_PRODUCTION are trimmed, rejected if they still contain embedded line breaks, and checked against Control Plane’s org naming rules before promotion continues.

All later steps that talk to Control Plane or Docker registries (setup, env parity, image capture/copy, deploy, health check, rollback) now use steps.cpln-orgs.outputs instead of raw repository variables, which avoids invalid hosts like org\r\n.registry.cpln.io when a variable was pasted with trailing CR/LF.

Reviewed by Cursor Bugbot for commit 14533bd. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Improvements
    • Deployment process now normalizes and validates environment identifiers, rejecting malformed values and trimming whitespace to prevent configuration-related failures.
    • Safer, more reliable promotion of images and deployments between staging and production, reducing risk of failed or misrouted releases.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🚀 Quick Review App Commands

Welcome! Here are the commands you can use in this PR:
They require the repository to have cpflow review apps configured, including the CPLN_TOKEN_STAGING secret.

+review-app-deploy

Deploy your PR branch for testing.

+review-app-delete

Remove the review app when done.

+review-app-help

Show detailed instructions, environment setup, and configuration options.

Comment +review-app-help for full setup details.

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4819622-d2c3-440f-96a9-abc15cd25db7

📥 Commits

Reviewing files that changed from the base of the PR and between 7975fee and 14533bd.

📒 Files selected for processing (1)
  • .github/workflows/cpflow-promote-staging-to-production.yml

Walkthrough

The workflow adds a Normalize Control Plane org names step that trims and validates staging/production org variables, rejects embedded CR/LF, and exposes sanitized staging/production outputs which are then used by all downstream steps in the promotion pipeline.

Changes

Control Plane org name normalization

Layer / File(s) Summary
Input normalization and validation
.github/workflows/cpflow-promote-staging-to-production.yml
New Normalize Control Plane org names step sanitizes staging and production org inputs by trimming whitespace, hard-failing on embedded \r/\n, validating against a lowercase/digit/hyphen pattern with no leading/trailing hyphen, and writing staging/production outputs.
Deployment pipeline integration
.github/workflows/cpflow-promote-staging-to-production.yml
Production environment setup, env var verification, image capture (production & staging), image copy, deploy, health checks, rollback, and rollback-readiness steps now consume steps.cpln-orgs.outputs.staging / .production instead of vars.CPLN_ORG_*.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I trimmed the names with careful care,
No stray newlines hiding there,
Lowercase hops and hyphens checked,
Staging and prod now neatly decked.
Off the pipeline goes, tidy and fair.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: sanitizing Control Plane org names in the production promotion workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-codex/trim-control-plane-org-vars

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Jun 2, 2026

Copy link
Copy Markdown

Code Review

Overall: Solid, well-targeted fix for a confirmed production bug. The approach — normalize early, validate, then propagate sanitized values — is the right one. All 9 downstream consumer references have been updated consistently with no missed usages.

What this does

  • Adds a Normalize Control Plane org names step that strips \r/\n from CPLN_ORG_STAGING and CPLN_ORG_PRODUCTION, validates against a DNS-label pattern, and exposes clean values as step outputs.
  • Routes all subsequent steps through steps.cpln-orgs.outputs.* instead of raw vars.CPLN_ORG_*.

The two remaining vars.CPLN_ORG_* references are intentional

Lines 99–100 (Validate required secrets and variables step) still use raw vars.* — this is correct. That step just checks for variable presence before normalization runs, and needs the raw values to do so.

Minor observations

  1. sanitize_control_plane_name strips only \r/\n, not spaces. If a variable has a leading/trailing space (e.g. from a GUI copy-paste), the sanitize step passes it through unchanged and the regex rejects it — but the error message says "remove whitespace, line endings, or unsupported characters", which doesn't help the user understand whether to look for newlines vs. spaces. Stripping \t and as well would make this more defensive and the error message unambiguous.

  2. Regex allows consecutive hyphens. ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ permits my--org. DNS labels (and most cloud provider naming rules) prohibit consecutive hyphens. Tightening to ^[a-z0-9]([a-z0-9]*(-[a-z0-9]+)*)?$ would close this gap.

  3. Error message doesn't show the invalid value. When validation fails, the message doesn't print the actual (already-sanitized) string. Since org names are stored as vars.* (not secrets), including the value in the error output would speed up debugging significantly.

See inline comments for suggested code changes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7975fee3e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to +124
value="${value//$'\r'/}"
value="${value//$'\n'/}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject embedded line breaks instead of concatenating org names

If a repository variable contains a CR/LF anywhere other than the accidental trailing line ending this change is trying to tolerate, these substitutions concatenate the pieces before validation, so foo\nbar becomes foobar and passes the regex. Because every subsequent deploy, copy, health, and rollback step uses these normalized outputs, a malformed variable can target a different valid Control Plane org/registry instead of failing early; strip only boundary line endings or fail when CR/LF appears inside the name.

Useful? React with 👍 / 👎.

Comment on lines +121 to +126
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
printf '%s' "${value}"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The function strips \r and \n but not spaces or tabs. A value like " my-org " (copy-pasted from a UI with surrounding spaces) passes through sanitized but then fails the regex with a message that says "remove whitespace" — which is technically correct but unhelpful.

Consider also stripping leading/trailing whitespace:

Suggested change
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
printf '%s' "${value}"
}
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
value="${value//$'\t'/}"
# strip leading/trailing spaces
value="${value#"${value%%[! ]*}"}"
value="${value%"${value##*[! ]}"}"
printf '%s' "${value}"
}

local label="$1"
local value="$2"

if ! [[ "${value}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The current pattern allows consecutive hyphens (e.g. my--org), which DNS labels and most cloud providers prohibit. Consider tightening to reject them:

Suggested change
if ! [[ "${value}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then
if ! [[ "${value}" =~ ^[a-z0-9]([a-z0-9]*(-[a-z0-9]+)*)?$ ]]; then

This rewrites the middle section as zero-or-more runs of alphanumerics optionally separated by single hyphens, so my-org passes but my--org doesn't.

local value="$2"

if ! [[ "${value}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then
echo "::error::${label} must be a valid Control Plane org name; remove whitespace, line endings, or unsupported characters."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The error message doesn't show the invalid value. Since org names are vars.* (not secrets), printing the sanitized string here would make debugging much faster — the user can immediately see whether the issue is residual spaces, uppercase letters, etc.:

Suggested change
echo "::error::${label} must be a valid Control Plane org name; remove whitespace, line endings, or unsupported characters."
echo "::error::${label} ('${value}') must be a valid Control Plane org name (lowercase alphanumeric and hyphens, no consecutive hyphens); remove any whitespace, line endings, uppercase letters, or unsupported characters."

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a Docker auth failure caused by CR/LF characters being stored in the CPLN_ORG_PRODUCTION GitHub repository variable, which produced an invalid registry host like shakacode-open-source-examples-production\r\n.registry.cpln.io.

  • Adds a new "Normalize Control Plane org names" step that strips \r/\n from both org variables, validates them against a slug regex (^[a-z0-9]([a-z0-9-]*[a-z0-9])?$), and writes the sanitized values to GITHUB_OUTPUT.
  • Routes all eight downstream steps (setup, verify, capture-current, staging-image, copy-image, deploy, health-check, and both rollback steps) through steps.cpln-orgs.outputs.* instead of the raw vars.CPLN_ORG_* values.

Confidence Score: 4/5

Safe to merge; the normalization step is positioned after the existing non-empty validation and before all usages, so no step can receive a CR/LF-tainted org name.

The fix is well-structured and covers all eight downstream consumers. The only gap is that the sanitizer strips CR/LF but not spaces or tabs, which the validator would catch and reject rather than auto-fix — a slight asymmetry that could confuse operators who pasted a trailing space instead of a newline.

.github/workflows/cpflow-promote-staging-to-production.yml — specifically the sanitize_control_plane_name function and the validate_control_plane_org error message.

Important Files Changed

Filename Overview
.github/workflows/cpflow-promote-staging-to-production.yml Adds a "Normalize Control Plane org names" step that strips CR/LF from the two org variables, validates them against a slug regex, and exposes sanitized values as step outputs; all downstream steps (setup, verify, capture, copy-image, deploy, health-check, rollback × 2) are switched from raw vars.* to the new steps.cpln-orgs.outputs.* references.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[validate-required-config\nraw vars.CPLN_ORG_*] --> B[Normalize Control Plane org names\nstrip CR/LF → validate regex]
    B -->|validation fails| FAIL[❌ exit 1 with error]
    B -->|steps.cpln-orgs.outputs.staging/production| C[Setup production environment]
    B --> D[Verify production env vars]
    B --> E[Capture current production image]
    B --> F[Capture deployed staging image]
    B --> G[Copy image from staging]
    B --> H[Deploy image to production]
    B --> I[Wait for deployment health]
    H --> I
    I -->|failure| J[Roll back on failure]
    J --> K[Wait for rollback readiness]
    B --> J
    B --> K
Loading

Reviews (1): Last reviewed commit: "Sanitize Control Plane org names in prod..." | Re-trigger Greptile

Comment on lines +121 to +126
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
printf '%s' "${value}"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 sanitize_control_plane_name strips only CR/LF, not spaces or tabs

The function removes \r and \n, which handles the documented copy-paste artifact. However, spaces and tabs (also common in pasted values) are passed through unchanged. The downstream validate_control_plane_org regex will then reject them with a message that says "remove whitespace, line endings, or unsupported characters" — which is correct — but the value won't actually be fixed by the sanitizer. Extending the sanitizer to also strip horizontal whitespace would make the fix symmetric and reduce operator confusion when someone copies with a trailing space instead of a newline.

Suggested change
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
printf '%s' "${value}"
}
sanitize_control_plane_name() {
local value="$1"
value="${value//$'\r'/}"
value="${value//$'\n'/}"
value="${value//$' '/}"
value="${value//$'\t'/}"
printf '%s' "${value}"
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +128 to +135
validate_control_plane_org() {
local label="$1"
local value="$2"

if ! [[ "${value}" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then
echo "::error::${label} must be a valid Control Plane org name; remove whitespace, line endings, or unsupported characters."
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Validation error omits the offending value

The validate_control_plane_org error message names only the variable label, not the sanitized value being rejected. If validation fails for a reason other than trailing CR/LF (e.g. an upper-case letter, a leading dash, or a space that wasn't stripped), an operator looking at the Actions log won't be able to see the actual problematic value without inspecting the repository variable store manually. Including the sanitized value in the error message would halve the investigation round-trip.

@justin808 justin808 force-pushed the jg-codex/trim-control-plane-org-vars branch from 7975fee to 14533bd Compare June 2, 2026 22:34
@claude

claude Bot commented Jun 2, 2026

Copy link
Copy Markdown

Code Review - Solid fix for a real production failure. The normalization step correctly handles the documented root cause (trailing CR/LF on a copy-pasted repository variable) and routes all downstream steps through sanitized outputs. Low risk, CI-only change with no effect on application runtime. See inline comments for specific notes.

value="${value%"${value##*[![:space:]]}"}"

if [[ "${value}" == *$'\r'* || "${value}" == *$'\n'* ]]; then
echo "::error::${label} contains embedded line endings; remove them from the repository variable instead of relying on normalization." >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor inconsistency: this error message is sent to stderr (>&2) while validate_control_plane_org's error (line 143) goes to stdout. GitHub Actions processes ::error:: workflow commands from both streams, so both create annotations — but stdout is the conventional channel. Suggest aligning by dropping >&2 here:

Suggested change
echo "::error::${label} contains embedded line endings; remove them from the repository variable instead of relying on normalization." >&2
echo "::error::${label} contains embedded line endings; remove them from the repository variable instead of relying on normalization."

Comment on lines +125 to +126
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These lines implement a pure-bash leading/trailing whitespace trim via nested parameter expansion — a correct but fairly cryptic idiom. The logic is:

  • Line 125: ${value%%[![:space:]]*} strips everything from the first non-space char to the end, leaving just the leading whitespace prefix; then ${value#...} removes that prefix.
  • Line 126: mirror operation for the trailing side.

This is intentionally fork-free, but a future maintainer is likely to stumble on it. A short comment explaining the intent (pure-bash trim, avoids subprocesses) would help. Alternatively, value="$(printf '%s' "${value}" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')" is more readable at the cost of one sed subprocess — acceptable here since this step already runs bash.

Suggested change
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Pure-bash leading/trailing whitespace strip (avoids a subprocess).
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"

@justin808 justin808 merged commit 08cdb0f into master Jun 2, 2026
11 checks passed
@justin808 justin808 deleted the jg-codex/trim-control-plane-org-vars branch June 2, 2026 22:39
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

✅ Review App Deleted

Review app for PR #759 is deleted

🎮 Control Plane Console
📋 View Workflow Logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant