Skip to content

fix: use StringArrayVar for --key flag to preserve special characters#206

Open
Raymondhou0917 wants to merge 1 commit intozeabur:mainfrom
Raymondhou0917:fix/variable-create-special-chars
Open

fix: use StringArrayVar for --key flag to preserve special characters#206
Raymondhou0917 wants to merge 1 commit intozeabur:mainfrom
Raymondhou0917:fix/variable-create-special-chars

Conversation

@Raymondhou0917
Copy link

@Raymondhou0917 Raymondhou0917 commented Mar 27, 2026

Summary

  • Switch variable create and variable update from StringToStringVarP to StringArrayVarP
  • Manually split each --key entry on the first = only via strings.SplitN(raw, "=", 2)
  • This preserves values containing ${}, commas, and other special characters

Problem

--key 'DATABASE_URL=${POSTGRESQL.POSTGRES_CONNECTION_STRING}' silently truncates or empties the value because Cobra's StringToStringVar uses an internal CSV parser that cannot handle these characters.

Changes

  • internal/cmd/variable/create/create.go — added rawKeys []string field, parseRawKeys() helper
  • internal/cmd/variable/update/update.go — same changes

Backward Compatibility

Users can still use --key KEY=VALUE format exactly as before. Values containing = signs (e.g., connection strings) are preserved because only the first = is used as the delimiter.

Fixes #201

Summary by CodeRabbit

  • Bug Fixes
    • Variable create and update commands now validate --key/-k input more strictly, rejecting empty keys and invalid KEY=VALUE formats with clear error messages.

…zeabur#201)

Cobra's StringToStringVarP uses an internal CSV parser that mangles
values containing ${}, commas, and other special characters. This
causes `--key 'DATABASE_URL=${POSTGRESQL.POSTGRES_CONNECTION_STRING}'`
to silently truncate or empty the value.

Switch to StringArrayVarP and manually split each entry on the first
`=` only, preserving arbitrary characters in values.

Applied to both `variable create` and `variable update` commands.

Fixes zeabur#201

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 27, 2026

Walkthrough

The --key flag in variable create and update commands is switched from Cobra's StringToStringVarP to StringArrayVarP with manual KEY=VALUE parsing. This preserves special characters (including ${}) in values that were previously truncated. A parseRawKeys() helper function splits each entry on the first = only and validates key non-emptiness.

Changes

Cohort / File(s) Summary
Variable key flag refactoring
internal/cmd/variable/create/create.go, internal/cmd/variable/update/update.go
Replaced StringToStringVarP with StringArrayVarP for --key/-k flag; added parseRawKeys() helper to parse raw KEY=VALUE strings with validation; updated non-interactive flow to parse raw keys before variable operations when keys map is empty.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing StringToStringVarP with StringArrayVarP for the --key flag to preserve special characters in variable values.
Linked Issues check ✅ Passed The PR implements the suggested fix from issue #201: switching --key flag from StringToStringVarP to StringArrayVarP, manually parsing entries on the first =, and preserving special characters like ${} in values.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing issue #201. Both variable create and update commands are updated consistently with the parseRawKeys helper and rawKeys field additions.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/cmd/variable/update/update.go (1)

109-119: Consider extracting parseRawKeys to a shared variable helper.

This parser is duplicated in internal/cmd/variable/create/create.go and internal/cmd/variable/update/update.go. A shared helper would reduce drift risk.

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

In `@internal/cmd/variable/update/update.go` around lines 109 - 119, The
parseRawKeys function is duplicated; extract it into a single shared helper
(e.g., export as ParseRawKeys or keep parseRawKeys in a new helper file) and
replace both copies with calls to that helper. Specifically, move the logic from
parseRawKeys into a new helper file in the same package, keep the same signature
(map[string]string, error) and name (or export as ParseRawKeys), update callers
in update.go and create.go to call the shared helper, and adjust
imports/visibility as needed so both command implementations use the single
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/variable/create/create.go`:
- Line 40: The interactive prompt path currently ignores the supplied --key/-k
flags (opts.rawKeys) causing prompts even when flags exist; change the logic in
the create command so that before invoking interactive prompts (check
f.Interactive), you parse opts.rawKeys into the expected map/list (the same
parsing done in the non-interactive branch around the current parsing block that
uses opts.rawKeys -> opts.Pairs or similar) and, if any keys were provided,
bypass the prompt and use the parsed values; ensure you reference and reuse the
existing parsing routine or replicate its behavior so opts.rawKeys is honored
and prompts are skipped when non-empty.

In `@internal/cmd/variable/update/update.go`:
- Line 40: runUpdateVariable currently ignores opts.rawKeys in the interactive
path; update runUpdateVariable to check opts.rawKeys (the flag bound via
cmd.Flags().StringArrayVarP) and, when non-empty, skip the interactive prompt
and call the existing parseKeyPairs (or equivalent) to populate the variable
values instead of prompting. Ensure you reference opts.rawKeys,
runUpdateVariable, and parseKeyPairs (or the function used at 122-129 in the
non-interactive path) so the non-interactive parsing logic is reused for both
modes.

---

Nitpick comments:
In `@internal/cmd/variable/update/update.go`:
- Around line 109-119: The parseRawKeys function is duplicated; extract it into
a single shared helper (e.g., export as ParseRawKeys or keep parseRawKeys in a
new helper file) and replace both copies with calls to that helper.
Specifically, move the logic from parseRawKeys into a new helper file in the
same package, keep the same signature (map[string]string, error) and name (or
export as ParseRawKeys), update callers in update.go and create.go to call the
shared helper, and adjust imports/visibility as needed so both command
implementations use the single implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 681dd2e6-e038-4aca-adc8-93b8d656a485

📥 Commits

Reviewing files that changed from the base of the PR and between 76b8768 and 3ef3425.

📒 Files selected for processing (2)
  • internal/cmd/variable/create/create.go
  • internal/cmd/variable/update/update.go

util.AddEnvOfServiceParam(cmd, &opts.environmentID)
cmd.Flags().BoolVarP(&opts.skipConfirm, "yes", "y", false, "Skip confirmation")
cmd.Flags().StringToStringVarP(&opts.keys, "key", "k", nil, "Key value pair of the variable")
cmd.Flags().StringArrayVarP(&opts.rawKeys, "key", "k", nil, "Key value pair of the variable (KEY=VALUE)")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

--key should bypass prompts in interactive sessions, but currently doesn’t.

Line 40 captures raw --key values, yet Line 102-109 parses them only in the non-interactive path. With f.Interactive=true, users are still forced into prompts even when flags are supplied.

Suggested fix
func runCreateVariable(f *cmdutil.Factory, opts *Options) error {
+	// If --key is provided, execute flag-driven flow directly.
+	if len(opts.rawKeys) > 0 {
+		return runCreateVariableNonInteractive(f, opts)
+	}
 	if f.Interactive {
 		return runCreateVariableInteractive(f, opts)
 	} else {
 		return runCreateVariableNonInteractive(f, opts)
 	}
}

As per coding guidelines: "Commands should support both interactive and non-interactive modes; if a flag is provided, skip the interactive prompt".

Also applies to: 102-109

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

In `@internal/cmd/variable/create/create.go` at line 40, The interactive prompt
path currently ignores the supplied --key/-k flags (opts.rawKeys) causing
prompts even when flags exist; change the logic in the create command so that
before invoking interactive prompts (check f.Interactive), you parse
opts.rawKeys into the expected map/list (the same parsing done in the
non-interactive branch around the current parsing block that uses opts.rawKeys
-> opts.Pairs or similar) and, if any keys were provided, bypass the prompt and
use the parsed values; ensure you reference and reuse the existing parsing
routine or replicate its behavior so opts.rawKeys is honored and prompts are
skipped when non-empty.

util.AddEnvOfServiceParam(cmd, &opts.environmentID)
cmd.Flags().BoolVarP(&opts.skipConfirm, "yes", "y", false, "Skip confirmation")
cmd.Flags().StringToStringVarP(&opts.keys, "key", "k", nil, "Key value pair of the variable")
cmd.Flags().StringArrayVarP(&opts.rawKeys, "key", "k", nil, "Key value pair of the variable (KEY=VALUE)")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

--key input is ignored when running interactively.

Line 40 accepts --key, but Line 122-129 only parses it in the non-interactive path. In an interactive terminal, runUpdateVariable still prompts instead of honoring provided flags, which breaks expected flag-driven execution.

Suggested fix
func runUpdateVariable(f *cmdutil.Factory, opts *Options) error {
+	// If --key is provided, honor flags and skip interactive prompts.
+	if len(opts.rawKeys) > 0 {
+		return runUpdateVariableNonInteractive(f, opts)
+	}
 	if f.Interactive {
 		opts.keys = make(map[string]string)
 		return runUpdateVariableInteractive(f, opts)
 	}
 	return runUpdateVariableNonInteractive(f, opts)
}

As per coding guidelines: "Commands should support both interactive and non-interactive modes; if a flag is provided, skip the interactive prompt".

Also applies to: 122-129

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

In `@internal/cmd/variable/update/update.go` at line 40, runUpdateVariable
currently ignores opts.rawKeys in the interactive path; update runUpdateVariable
to check opts.rawKeys (the flag bound via cmd.Flags().StringArrayVarP) and, when
non-empty, skip the interactive prompt and call the existing parseKeyPairs (or
equivalent) to populate the variable values instead of prompting. Ensure you
reference opts.rawKeys, runUpdateVariable, and parseKeyPairs (or the function
used at 122-129 in the non-interactive path) so the non-interactive parsing
logic is reused for both modes.

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.

variable create --key flag cannot handle values containing ${} (Cobra StringToStringVar limitation)

1 participant