Skip to content

Support multiple environment variable placeholders in config values#2070

Open
aantn wants to merge 2 commits into
masterfrom
claude/fact-check-secrets-docs-bE9H3
Open

Support multiple environment variable placeholders in config values#2070
aantn wants to merge 2 commits into
masterfrom
claude/fact-check-secrets-docs-bE9H3

Conversation

@aantn
Copy link
Copy Markdown
Collaborator

@aantn aantn commented May 6, 2026

Summary

Enhanced environment variable substitution in Robusta configuration to support multiple placeholders in a single field and improved the regex pattern for more robust matching.

Key Changes

  • Multiple placeholders support: Configuration values can now contain multiple {{ env.VAR }} placeholders (e.g., "{{ env.USER }}:{{ env.PASSWORD }}") instead of just one
  • Static text mixing: Placeholders can be combined with static text (e.g., "Bearer {{ env.TOKEN }}")
  • Improved regex pattern: Replaced the loose regex pattern with a stricter _ENV_PLACEHOLDER_RE that properly handles whitespace and prevents over-matching
  • Refactored substitution logic: Changed from extracting the first match to using re.sub() with a callback function for proper multi-placeholder replacement

Implementation Details

  • The new regex pattern r"{{\s*env\.([^}\s]+)\s*}}" is more precise:
    • Allows flexible whitespace around env.
    • Prevents matching incomplete or malformed placeholders
    • Stops at the first } or whitespace to avoid capturing extra characters
  • The substitution now uses a callback function _sub() that processes each match independently, enabling proper handling of multiple placeholders in one value
  • Early return optimization: checks for placeholder existence before attempting substitution

Documentation

Added comprehensive documentation covering:

  • Multiple placeholder usage examples
  • New runner.additional_env_froms feature for bulk secret injection
  • Clarification on environment variable scope between runner and HolmesGPT pods
  • Configuration guidance for HolmesGPT add-ons (operator, AWS MCP, Azure MCP)

https://claude.ai/code/session_013EFjPwGU1FDY6fs6rcELac

claude added 2 commits May 6, 2026 13:17
…and Holmes add-on env vars

- Warn that {{ env.X }} replaces the entire field value (no prefix/suffix concatenation)
- Document runner.additional_env_froms for bulk-injecting Secret/ConfigMap keys
- Clarify that runner and HolmesGPT pods do not share environment variables, so secrets used by both must be declared in both additional_env_vars blocks
- Mention holmes.operator.additionalEnvVars and holmes.mcpAddons.{aws,azure}.additionalEnvVars for users running those add-ons
…ering the whole value

Previously, get_env_replacement returned the env var's value as the new field
value, so `"Bearer {{ env.TOKEN }}"` was silently replaced with just the token
and any prefix/suffix was dropped. Switch to re.sub so only the placeholder is
substituted, leaving the rest of the string intact. Multiple placeholders in
the same field are also now supported (e.g. `"{{ env.USER }}:{{ env.PASSWORD }}"`).

The "return None when there is no placeholder" contract is preserved so
existing callers in runner_config.py and replace_env_vars_values keep working.

Also updates the docs to advertise the supported concatenation/multi-placeholder
patterns.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

Walkthrough

This PR enhances environment variable placeholder substitution in Robusta configuration by introducing a new regex-based mechanism that replaces multiple {{ env.NAME }} placeholders within a single string, accompanied by comprehensive documentation explaining placeholder usage, the additional_env_froms mechanism, and how secrets are provisioned across Runner and HolmesGPT pods.

Changes

Environment Variable Placeholder Substitution

Layer / File(s) Summary
Core Implementation
src/robusta/core/playbooks/playbook_utils.py
Introduced a compiled regex constant to detect {{ env.NAME }} placeholders. Refactored get_env_replacement() to perform all placeholder substitutions via a nested callback function, enabling multiple replacements within a single string instead of only the first match.
Documentation
docs/setup-robusta/configuration-secrets.rst
Added comprehensive guidance on combining static text with environment variable placeholders, using additional_env_froms to expose multiple secret keys as environment variables, and distinctions between Runner and HolmesGPT pod scoping. Includes examples and notes on substitution scope across sinks, globalConfig, customPlaybooks, and playbookRepos.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 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
Title check ✅ Passed The title 'Support multiple environment variable placeholders in config values' accurately and concisely describes the main change: enabling multiple {{ env.VAR }} placeholders in configuration values instead of just one.
Description check ✅ Passed The description comprehensively covers the changeset, detailing multiple placeholder support, static text mixing, improved regex patterns, refactored substitution logic, and documentation updates that align with the file changes.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fact-check-secrets-docs-bE9H3

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Docker image ready for 554009b (built in 3m 35s)

⚠️ Warning: does not support ARM (ARM images are built on release only - not on every PR)

Use this tag to pull the image for testing.

📋 Copy commands

⚠️ Temporary images are deleted after 30 days. Copy to a permanent registry before using them:

gcloud auth configure-docker us-central1-docker.pkg.dev
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:554009b
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:554009b me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:554009b
docker push me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:554009b

Patch Helm values in one line:

helm upgrade --install robusta robusta/robusta \
  --reuse-values \
  --set runner.image=me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:554009b

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/robusta/core/playbooks/playbook_utils.py`:
- Around line 19-24: The code currently treats empty environment variables as
missing by checking falsiness in the env lookup; update the existence checks to
use None semantics so empty strings are allowed: in the function that reads
env_var_value (the block using os.environ.get(name, None) and the subsequent if
not env_var_value check), change the condition to check "is None" before
logging/raising; likewise update the truthy checks in replace_env_vars_values
(the checks at the locations referenced in the comment) to use "is not None" so
that empty-string substitutions are preserved rather than treated as absent.
🪄 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: e365fa0a-b831-48ab-8769-013525033bae

📥 Commits

Reviewing files that changed from the base of the PR and between cd99816 and eb254f1.

📒 Files selected for processing (2)
  • docs/setup-robusta/configuration-secrets.rst
  • src/robusta/core/playbooks/playbook_utils.py

Comment on lines +19 to 24
env_var_value = os.environ.get(name, None)
if not env_var_value:
msg = f"ENV var replacement {env_values[0]} does not exist for param: {value}"
msg = f"ENV var replacement {name} does not exist for param: {value}"
logging.error(msg)
raise Exception(msg)
return env_var_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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Allow empty env vars; only fail when the variable is truly missing.

At Line 20, if not env_var_value treats VAR="" as nonexistent and raises, which blocks valid empty-value substitutions. Use is None for existence checks. Also switch truthy checks in replace_env_vars_values (Lines 33/37/41) to is not None so empty replacements are preserved.

Suggested patch
 def get_env_replacement(value: str) -> Optional[str]:
     if not _ENV_PLACEHOLDER_RE.search(value):
         return None

     def _sub(match: "re.Match[str]") -> str:
         name = match.group(1).strip()
-        env_var_value = os.environ.get(name, None)
-        if not env_var_value:
+        env_var_value = os.environ.get(name)
+        if env_var_value is None:
             msg = f"ENV var replacement {name} does not exist for param: {value}"
             logging.error(msg)
             raise Exception(msg)
         return env_var_value

     return _ENV_PLACEHOLDER_RE.sub(_sub, value)

 def replace_env_vars_values(values: Dict) -> Dict:
     for key, value in values.items():
         if isinstance(value, str):
             env_var_value = get_env_replacement(value)
-            if env_var_value:
+            if env_var_value is not None:
                 values[key] = env_var_value
         elif isinstance(value, SecretStr):
             env_var_value = get_env_replacement(value.get_secret_value())
-            if env_var_value:
+            if env_var_value is not None:
                 values[key] = SecretStr(env_var_value)
         elif isinstance(value, dict):
             env_var_value = replace_env_vars_values(value)
-            if env_var_value:
+            if env_var_value is not None:
                 values[key] = env_var_value
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/robusta/core/playbooks/playbook_utils.py` around lines 19 - 24, The code
currently treats empty environment variables as missing by checking falsiness in
the env lookup; update the existence checks to use None semantics so empty
strings are allowed: in the function that reads env_var_value (the block using
os.environ.get(name, None) and the subsequent if not env_var_value check),
change the condition to check "is None" before logging/raising; likewise update
the truthy checks in replace_env_vars_values (the checks at the locations
referenced in the comment) to use "is not None" so that empty-string
substitutions are preserved rather than treated as absent.

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.

2 participants