Source code for loom.core.config.ssm
"""AWS SSM Parameter Store resolver for loom configuration.
Resolves ``${ssm:/path/to/parameter}`` placeholders in OmegaConf configs
by fetching values from AWS Systems Manager Parameter Store at parse time.
Example::
from loom.core.config import load_config
from loom.core.config.ssm import SsmResolver
cfg = load_config("config/prod.yaml", resolvers=[SsmResolver("eu-west-1")])
"""
from __future__ import annotations
import logging
from typing import Any
try:
import boto3 as _boto3_module # type: ignore[import-untyped]
except ImportError:
_boto3_module = None
from loom.core.config._resolver_utils import _expand_env_vars, _navigate_json, _split_resolver_key
from loom.core.config.errors import ConfigError
logger = logging.getLogger(__name__)
def _fetch_parameter(client: Any, name: str, with_decryption: bool) -> str:
"""Fetch a single parameter value from AWS SSM.
Args:
client: Boto3 SSM client.
name: Fully-qualified SSM parameter name.
with_decryption: Whether to decrypt SecureString parameters.
Returns:
The parameter value as a string.
Raises:
ConfigError: On any SSM API error.
"""
try:
result = client.get_parameter(Name=name, WithDecryption=with_decryption)
except Exception as exc:
raise ConfigError(f"Failed to fetch SSM parameter {name!r}: {exc}") from exc
return str(result["Parameter"]["Value"])
[docs]
class SsmResolver:
"""Resolves SSM Parameter Store paths for use with :func:`~loom.core.config.load_config`.
Fetches parameter values from AWS Systems Manager Parameter Store.
The boto3 client is created lazily on first use and reused across calls.
Env-var tokens in the form ``%VAR_NAME%`` (uppercase letters, digits,
and underscores only) are expanded from ``os.environ`` before the
SSM request is made.
Args:
region: AWS region name. Passed directly to ``boto3.client``.
Defaults to ``None``, which lets boto3 use its own resolution
chain (env vars, instance metadata, etc.).
with_decryption: Whether to decrypt SecureString parameters.
Defaults to ``True``.
Example::
resolver = SsmResolver("eu-west-1")
value = resolver.resolve("/myapp/%ENV%/db_password")
"""
def __init__(
self,
region: str | None = None,
*,
with_decryption: bool = True,
) -> None:
self._region = region
self._with_decryption = with_decryption
self._client: Any = None
@property
def name(self) -> str:
"""Resolver name used as the OmegaConf placeholder prefix.
Returns:
The string ``"ssm"``.
"""
return "ssm"
def _get_client(self) -> Any:
"""Return the boto3 SSM client, creating it on first call.
Returns:
A boto3 SSM client instance.
Raises:
ConfigError: When boto3 is not installed.
"""
if self._client is None:
if _boto3_module is None:
raise ConfigError(
"boto3 is required for SsmResolver."
" Install it with: pip install loom[config-ssm]"
)
self._client = _boto3_module.client("ssm", region_name=self._region)
return self._client
[docs]
def resolve(self, key: str) -> object:
"""Resolve an SSM parameter path to its stored value.
Expands ``%VAR_NAME%`` tokens in *key* from the environment, then
fetches the parameter from AWS SSM Parameter Store.
Args:
key: SSM parameter path, optionally containing ``%VAR_NAME%``
placeholders that are replaced with environment variable values.
Supports dot-notation for JSON key navigation: ``/path/param.key``
fetches ``/path/param`` and returns ``param["key"]``.
Returns:
Resolved value. A plain string for parameters without dot-notation;
a structured value (string, int, dict, etc.) when dot-notation
navigates into a JSON parameter.
Raises:
ConfigError: When *key* is empty, an env-var placeholder is missing,
boto3 is not installed, or the SSM API call fails.
"""
if not key:
raise ConfigError("SSM key must not be empty")
expanded = _expand_env_vars(key)
ssm_path, json_keys = _split_resolver_key(expanded)
logger.info("ssm_resolver: fetching %s", ssm_path)
client = self._get_client()
raw = _fetch_parameter(client, ssm_path, self._with_decryption)
if not json_keys:
return raw
return _navigate_json(raw, json_keys, ssm_path)