Skip to content

Commit 031f4b9

Browse files
committed
fix: skip resources with empty IDs from conditional env vars in config processing
Added handling in replace_env_vars() to skip registered resources when their ID fields (model_id, shield_id, dataset_id, scoring_fn_id, benchmark_id, toolgroup_id) resolve to empty/None from conditional env var syntax like ${env.VAR:+value}. Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
1 parent ad7aa08 commit 031f4b9

File tree

2 files changed

+107
-4
lines changed

2 files changed

+107
-4
lines changed

src/llama_stack/core/stack.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ class LlamaStack(
110110
REGISTRY_REFRESH_TASK = None
111111
TEST_RECORDING_CONTEXT = None
112112

113+
# ID fields for registered resources that should trigger skipping
114+
# when they resolve to empty/None (from conditional env vars like :+)
115+
RESOURCE_ID_FIELDS = ["model_id", "shield_id", "dataset_id", "scoring_fn_id", "benchmark_id", "toolgroup_id"]
116+
113117

114118
def is_request_model(t: Any) -> bool:
115119
"""Check if a type is a request model (Pydantic BaseModel).
@@ -346,15 +350,31 @@ def replace_env_vars(config: Any, path: str = "") -> Any:
346350
logger.debug(
347351
f"Skipping config env variable expansion for disabled provider: {v.get('provider_id', '')}"
348352
)
349-
# Create a copy with resolved provider_id but original config
350-
disabled_provider = v.copy()
351-
disabled_provider["provider_id"] = resolved_provider_id
352353
continue
353354
except EnvVarError:
354355
# If we can't resolve the provider_id, continue with normal processing
355356
pass
356357

357-
# Normal processing for non-disabled providers
358+
# Special handling for registered resources: check if ID field resolves to empty/None
359+
# from conditional env vars (e.g., ${env.VAR:+value}) and skip the entry if so
360+
if isinstance(v, dict):
361+
should_skip = False
362+
for id_field in RESOURCE_ID_FIELDS:
363+
if id_field in v:
364+
try:
365+
resolved_id = replace_env_vars(v[id_field], f"{path}[{i}].{id_field}")
366+
if resolved_id is None or resolved_id == "":
367+
logger.debug(
368+
f"Skipping resource with empty {id_field} (conditional env var not set)"
369+
)
370+
should_skip = True
371+
break
372+
except EnvVarError:
373+
pass
374+
if should_skip:
375+
continue
376+
377+
# Normal processing
358378
result.append(replace_env_vars(v, f"{path}[{i}]"))
359379
except EnvVarError as e:
360380
raise EnvVarError(e.var_name, e.path) from None

tests/unit/server/test_replace_env_vars.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,86 @@ def test_explicit_strings_preserved(setup_env_vars):
9595
data = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"}
9696
expected = {"port": "8080", "enabled": "true", "count": "123", "ratio": "3.14"}
9797
assert replace_env_vars(data) == expected
98+
99+
100+
def test_resource_with_empty_benchmark_id_skipped(setup_env_vars):
101+
"""Test that resources with empty benchmark_id from conditional env vars are skipped."""
102+
data = {
103+
"benchmarks": [
104+
{"benchmark_id": "${env.BENCHMARK_ID:+my-benchmark}", "dataset_id": "test-dataset"},
105+
{"benchmark_id": "always-present", "dataset_id": "another-dataset"},
106+
]
107+
}
108+
# BENCHMARK_ID is not set, so first benchmark should be skipped
109+
result = replace_env_vars(data)
110+
assert len(result["benchmarks"]) == 1
111+
assert result["benchmarks"][0]["benchmark_id"] == "always-present"
112+
113+
114+
def test_resource_with_set_benchmark_id_not_skipped(setup_env_vars):
115+
"""Test that resources with set benchmark_id are not skipped."""
116+
os.environ["BENCHMARK_ID"] = "enabled"
117+
try:
118+
data = {
119+
"benchmarks": [
120+
{"benchmark_id": "${env.BENCHMARK_ID:+my-benchmark}", "dataset_id": "test-dataset"},
121+
{"benchmark_id": "always-present", "dataset_id": "another-dataset"},
122+
]
123+
}
124+
result = replace_env_vars(data)
125+
assert len(result["benchmarks"]) == 2
126+
assert result["benchmarks"][0]["benchmark_id"] == "my-benchmark"
127+
assert result["benchmarks"][1]["benchmark_id"] == "always-present"
128+
finally:
129+
del os.environ["BENCHMARK_ID"]
130+
131+
132+
def test_resource_with_empty_model_id_skipped(setup_env_vars):
133+
"""Test that resources with empty model_id from conditional env vars are skipped."""
134+
data = {
135+
"models": [
136+
{"model_id": "${env.MODEL_ID:+my-model}", "provider_id": "test-provider"},
137+
{"model_id": "always-present", "provider_id": "another-provider"},
138+
]
139+
}
140+
# MODEL_ID is not set, so first model should be skipped
141+
result = replace_env_vars(data)
142+
assert len(result["models"]) == 1
143+
assert result["models"][0]["model_id"] == "always-present"
144+
145+
146+
def test_resource_with_empty_shield_id_skipped(setup_env_vars):
147+
"""Test that resources with empty shield_id from conditional env vars are skipped."""
148+
data = {
149+
"shields": [
150+
{"shield_id": "${env.SHIELD_ID:+my-shield}", "provider_id": "test-provider"},
151+
{"shield_id": "always-present", "provider_id": "another-provider"},
152+
]
153+
}
154+
# SHIELD_ID is not set, so first shield should be skipped
155+
result = replace_env_vars(data)
156+
assert len(result["shields"]) == 1
157+
assert result["shields"][0]["shield_id"] == "always-present"
158+
159+
160+
def test_multiple_resources_with_conditional_ids(setup_env_vars):
161+
"""Test that multiple resource types with conditional IDs are handled correctly."""
162+
os.environ["INCLUDE_BENCHMARK"] = "yes"
163+
try:
164+
data = {
165+
"benchmarks": [
166+
{"benchmark_id": "${env.INCLUDE_BENCHMARK:+included-benchmark}", "dataset_id": "ds1"},
167+
{"benchmark_id": "${env.EXCLUDE_BENCHMARK:+excluded-benchmark}", "dataset_id": "ds2"},
168+
],
169+
"models": [
170+
{"model_id": "${env.EXCLUDE_MODEL:+excluded-model}", "provider_id": "p1"},
171+
],
172+
}
173+
result = replace_env_vars(data)
174+
# Only the benchmark with INCLUDE_BENCHMARK set should remain
175+
assert len(result["benchmarks"]) == 1
176+
assert result["benchmarks"][0]["benchmark_id"] == "included-benchmark"
177+
# Model with unset env var should be skipped
178+
assert len(result["models"]) == 0
179+
finally:
180+
del os.environ["INCLUDE_BENCHMARK"]

0 commit comments

Comments
 (0)