Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bioengine/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
Must stay in lock-step with ``pyproject.toml``'s ``version`` field.
The ``version-check.yml`` CI workflow enforces the match.
"""
__version__ = "0.11.24"
__version__ = "0.11.25"
32 changes: 31 additions & 1 deletion bioengine/worker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@
import argparse
import asyncio
import json
import os
import shlex
import signal
import sys
from typing import Dict
from typing import Any, Dict

from bioengine.worker import BioEngineWorker

Expand Down Expand Up @@ -487,6 +488,30 @@ def get_args_by_group(parser: argparse.ArgumentParser) -> Dict[str, Dict[str, an
return group_configs


def _expand_env_in_config(value: Any) -> Any:
"""Recursively expand ``${VAR}`` / ``$VAR`` from the worker's own
environment in every string within a startup-application config.

This runs ONLY on operator-supplied ``--startup-applications`` config
(never on a tenant's ``deploy_app`` call), so it is safe to source
values from the worker's environment — including secrets mounted via
``valueFrom.secretKeyRef`` (the same way ``HYPHA_TOKEN`` reaches the
worker). It lets a startup app pull a k8s secret into an app's env
without writing the secret value into git-tracked helm values, e.g.::

"application_env_vars": {"*": {"_HF_READ_TOKEN": "${HF_READ_TOKEN}"}}

An unset ``${VAR}`` is left verbatim (``os.path.expandvars`` semantics).
"""
if isinstance(value, str):
return os.path.expandvars(value)
if isinstance(value, dict):
return {k: _expand_env_in_config(v) for k, v in value.items()}
if isinstance(value, list):
return [_expand_env_in_config(v) for v in value]
return value


def read_startup_applications(
group_configs: Dict[str, Dict[str, any]],
) -> Dict[str, Dict[str, any]]:
Expand Down Expand Up @@ -527,6 +552,11 @@ def read_startup_applications(
if not isinstance(application_config, dict):
raise ValueError("Application configuration must be a JSON object")

# Resolve ${VAR} references (e.g. secrets injected into the
# worker env via valueFrom.secretKeyRef) so a startup app can
# carry a secret in its env without the value living in git.
application_config = _expand_env_in_config(application_config)

# Validate required fields
if "artifact_id" not in application_config:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "bioengine"
version = "0.11.24"
version = "0.11.25"
description = "BioEngine — CLI and SDK for deploying and calling AI model services on BioEngine workers"
requires-python = ">=3.11"
authors = [
Expand Down
59 changes: 59 additions & 0 deletions tests/worker/test_startup_app_env_expansion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Env-var expansion in worker startup-application config.

Startup apps may reference the worker's own environment (e.g. a secret
mounted via ``valueFrom.secretKeyRef``) as ``${VAR}``; the worker expands
these before deploying, so the secret value never has to live in
git-tracked helm values.
"""

from bioengine.worker.__main__ import (
_expand_env_in_config,
read_startup_applications,
)


def test_expand_env_recurses_and_preserves_types(monkeypatch):
monkeypatch.setenv("SECRET_TOKEN", "s3kret")
config = {
"artifact_id": "bioimage-io/model-runner",
"application_env_vars": {"*": {"_HF_READ_TOKEN": "${SECRET_TOKEN}"}},
"nested": [{"k": "$SECRET_TOKEN"}],
"disable_gpu": False,
"num_gpus": 1,
}

expanded = _expand_env_in_config(config)

assert expanded["application_env_vars"]["*"]["_HF_READ_TOKEN"] == "s3kret"
assert expanded["nested"][0]["k"] == "s3kret"
# Non-string values pass through untouched.
assert expanded["disable_gpu"] is False
assert expanded["num_gpus"] == 1


def test_expand_env_leaves_unset_vars_verbatim(monkeypatch):
monkeypatch.delenv("DEFINITELY_UNSET_VAR", raising=False)
assert _expand_env_in_config("${DEFINITELY_UNSET_VAR}") == "${DEFINITELY_UNSET_VAR}"


def test_read_startup_applications_expands_env(monkeypatch):
monkeypatch.setenv("HF_READ_TOKEN", "hf_fromsecret")
group_configs = {
"Core Options": {
"startup_applications": [
'{"artifact_id": "bioimage-io/model-runner", '
'"application_env_vars": {"*": {"_HF_READ_TOKEN": "${HF_READ_TOKEN}"}}}'
]
}
}

result = read_startup_applications(group_configs)

apps = result["Core Options"]["startup_applications"]
assert isinstance(apps, list) and len(apps) == 1
assert apps[0]["application_env_vars"]["*"]["_HF_READ_TOKEN"] == "hf_fromsecret"


def test_read_startup_applications_noop_without_apps():
group_configs = {"Core Options": {}}
assert read_startup_applications(group_configs) == group_configs
Loading