Skip to content

Config: Add support for environment variable expansion.#111

Open
ggreer wants to merge 1 commit intomainfrom
ggreer/config-env-vars
Open

Config: Add support for environment variable expansion.#111
ggreer wants to merge 1 commit intomainfrom
ggreer/config-env-vars

Conversation

@ggreer
Copy link
Copy Markdown
Contributor

@ggreer ggreer commented Mar 6, 2026

Description

  • Bug fix
  • New feature

This is a more general solution to #101.

I think it needs the env var expansion removed from the connection string before merging.

Summary by CodeRabbit

Release Notes

  • New Features
    • Configuration files now support environment variable expansion using ${VAR} syntax, which are expanded before processing.
    • Configuration loading now respects context for cancellation and deadline handling.
    • Configuration loading returns an error if any referenced environment variables are missing.

@ggreer ggreer requested a review from a team March 6, 2026 23:51
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

The changes add environment variable expansion support to the configuration loading system. A new LookupFunc type is introduced, LoadConfigFromFile is updated to accept a context parameter, and environment variables in YAML configuration are expanded before unmarshalling using a provided lookup function.

Changes

Cohort / File(s) Summary
Configuration Enhancement
pkg/bsql/config.go
Added LookupFunc type for environment variable lookup. Updated LoadConfigFromFile to accept context.Context parameter and expand ${VAR} patterns in YAML data before unmarshalling via new expandEnvironmentVariables helper function. Added imports for regexp, slices, and strings.
Connector Update
pkg/connector/connector.go
Updated call to LoadConfigFromFile to pass context as first argument, aligning with new function signature.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Through YAML fields, I hop with glee,
${VAR} symbols expand so free,
With context and lookup, I find the way,
Environment secrets no longer fray,
A rabbit's joy in configs today! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main feature addition in the changeset: environment variable expansion support in configuration.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ggreer/config-env-vars

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

Claude finished @ggreer's task in 1m 22s —— View job


PR Review: Config environment variable expansion

  • Read changed files and gather context
  • Review code changes
  • Post review feedback

Summary

This PR adds ${VAR_NAME} environment variable expansion to YAML config files before parsing. The expansion happens as a text-level pre-processing step in LoadConfigFromFile, and expandEnvironmentVariables is extracted with a LookupFunc for testability. The connector call site is updated to pass ctx.

Issues

1. YAML injection via env var valuespkg/bsql/config.go:512-524

The expansion happens on raw YAML text before parsing. If an env var value contains YAML-significant characters (:, #, [, {, etc.), it can break parsing or alter the config structure. The multi-line quoting logic (lines 515-521) handles \n/\r but doesn't address single-line values with special YAML characters.

For example, if DB_PASSWORD=p@ss: word#123, substituting that directly into a YAML value like password: ${DB_PASSWORD} produces password: p@ss: word#123, which YAML would misparse.

Consider either:

  • Quoting all substituted values (not just multi-line ones), or
  • Doing expansion after YAML parsing on the resulting struct fields, or
  • Using a YAML-aware approach (walk the parsed AST nodes)

Fix this →

2. Regex is recompiled on every callpkg/bsql/config.go:510

regexp.MustCompile is called inside the function body each time. This should be a package-level var for efficiency.

3. Missing tests

There are no tests for expandEnvironmentVariables. Given the YAML injection concerns above, tests would be particularly valuable. The LookupFunc abstraction makes this easy to test — good design choice there.

4. Strict failure on missing env varspkg/bsql/config.go:531-533

The function returns an error if any ${VAR} reference is not found in the environment. This is a reasonable default, but it means any YAML value that happens to contain a literal ${...} pattern (e.g., in SQL queries or documentation strings) will fail. Consider:

  • Supporting an escape syntax like $${VAR} to produce a literal ${VAR}
  • Or documenting this limitation clearly

5. Author's own TODO: remove env var expansion from connection string

The PR description notes: "I think it needs the env var expansion removed from the connection string before merging." This hasn't been done yet. There may be existing env var expansion in the DSN/connection string handling that's now redundant with this general approach.

Positive aspects

  • Clean separation with LookupFunc for testability
  • The context.Context addition to LoadConfigFromFile is forward-looking
  • Pre-parse expansion is simpler than post-parse field walking for most use cases
  • Error message for missing vars is clear and lists all missing vars at once

Recommendation

This needs the YAML injection issue (item 1) addressed before merging — it's a correctness bug that will surface in real configs with passwords or DSNs containing special characters. Adding tests and addressing the author's noted TODO would also be good before merge.


Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/bsql/config.go`:
- Around line 531-533: Update the error messages in pkg/bsql/config.go that
validate/expand environment variables to include the connector prefix and proper
wrapping: change the "missing environment variables" return to prefix with
"baton-sql: " (e.g., "baton-sql: missing environment variables: ...") and modify
the error returned when expansion fails to include the "baton-sql: " prefix and
use %w to wrap the original error (e.g., "baton-sql: failed to expand
environment variables: %w"). Locate the two error returns used after env
validation/expansion (the missingVars join and the expansion failure) and update
their fmt.Errorf calls accordingly.
- Around line 512-529: The current envVarRegex.ReplaceAllStringFunc run on
dataStr mutates the raw file text (including comments, keys, and already-quoted
scalars); instead parse the YAML into a yaml.Node and perform the ${...}
expansion only on node.Value for string scalar nodes. Replace the
ReplaceAllStringFunc usage by: unmarshal into a root *yaml.Node, recursively
walk nodes and for nodes where Kind==yaml.ScalarNode (and/or Tag=="!!str") run
the envVarRegex replacement using lookup and append to missingVars as before;
when a replacement contains newlines or needs escaping, set the node.Style to
yaml.DoubleQuotedStyle and escape backslashes, quotes and newline characters in
node.Value so the resulting node remains valid YAML; finally marshal the node
back to bytes (replacing dataStr) and preserve the existing missingVars
collection and lookup semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9dbcc10b-a173-40b5-8be9-a04cc7af7fb7

📥 Commits

Reviewing files that changed from the base of the PR and between d7776f7 and b6cd506.

📒 Files selected for processing (2)
  • pkg/bsql/config.go
  • pkg/connector/connector.go

Comment on lines +512 to +529
dataStr := envVarRegex.ReplaceAllStringFunc(string(data), func(match string) string {
envVar := match[2 : len(match)-1]
if envVal, exists := lookup(envVar); exists {
if strings.ContainsAny(envVal, "\n\r") {
// Quote multi-line values so they remain a single YAML scalar.
escaped := strings.ReplaceAll(envVal, `\`, `\\`)
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
escaped = strings.ReplaceAll(escaped, "\n", `\n`)
escaped = strings.ReplaceAll(escaped, "\r", `\r`)
return `"` + escaped + `"`
}
return envVal
}
if !slices.Contains(missingVars, envVar) {
missingVars = append(missingVars, envVar)
}
return match
})
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

Expand parsed YAML scalars, not raw file text.

This replacement runs before YAML parsing, so it also rewrites ${...} inside comments, keys, and already-quoted values. That breaks valid configs in common cases—for example, a commented example like # ${DB_PASSWORD} becomes a hard startup error, and a quoted placeholder with a multiline secret produces invalid YAML. Please expand only yaml.Node string scalar values after parsing.

Possible direction
- dataStr := envVarRegex.ReplaceAllStringFunc(string(data), func(match string) string {
-   ...
- })
- return []byte(dataStr), nil
+ var root yaml.Node
+ if err := yaml.Unmarshal(data, &root); err != nil {
+   return nil, err
+ }
+
+ // Walk the parsed tree and expand only scalar string nodes.
+ // Then marshal the modified node tree back to bytes.
+ walkStringScalars(&root, func(value string) (string, error) {
+   return expandString(value, lookup)
+ })
+
+ out, err := yaml.Marshal(&root)
+ if err != nil {
+   return nil, err
+ }
+ return out, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/bsql/config.go` around lines 512 - 529, The current
envVarRegex.ReplaceAllStringFunc run on dataStr mutates the raw file text
(including comments, keys, and already-quoted scalars); instead parse the YAML
into a yaml.Node and perform the ${...} expansion only on node.Value for string
scalar nodes. Replace the ReplaceAllStringFunc usage by: unmarshal into a root
*yaml.Node, recursively walk nodes and for nodes where Kind==yaml.ScalarNode
(and/or Tag=="!!str") run the envVarRegex replacement using lookup and append to
missingVars as before; when a replacement contains newlines or needs escaping,
set the node.Style to yaml.DoubleQuotedStyle and escape backslashes, quotes and
newline characters in node.Value so the resulting node remains valid YAML;
finally marshal the node back to bytes (replacing dataStr) and preserve the
existing missingVars collection and lookup semantics.

Comment on lines +531 to +533
if len(missingVars) > 0 {
return nil, fmt.Errorf("missing environment variables: %s", strings.Join(missingVars, ", "))
}
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

Prefix the new errors with baton-sql.

Both new error messages omit the required connector prefix. Please make these baton-sql: missing environment variables: ... and baton-sql: failed to expand environment variables: %w.

Suggested diff
- return nil, fmt.Errorf("missing environment variables: %s", strings.Join(missingVars, ", "))
+ return nil, fmt.Errorf("baton-sql: missing environment variables: %s", strings.Join(missingVars, ", "))

- return nil, fmt.Errorf("failed to expand environment variables: %w", err)
+ return nil, fmt.Errorf("baton-sql: failed to expand environment variables: %w", err)

As per coding guidelines, **/*.go: Always wrap errors with %w format verb and include connector prefix (baton-*) in error messages.

Also applies to: 547-548

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

In `@pkg/bsql/config.go` around lines 531 - 533, Update the error messages in
pkg/bsql/config.go that validate/expand environment variables to include the
connector prefix and proper wrapping: change the "missing environment variables"
return to prefix with "baton-sql: " (e.g., "baton-sql: missing environment
variables: ...") and modify the error returned when expansion fails to include
the "baton-sql: " prefix and use %w to wrap the original error (e.g.,
"baton-sql: failed to expand environment variables: %w"). Locate the two error
returns used after env validation/expansion (the missingVars join and the
expansion failure) and update their fmt.Errorf calls accordingly.

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