Files
ajarbot/config/config_loader.py

166 lines
5.7 KiB
Python
Raw Normal View History

"""
Configuration loader for adapter system.
Loads from YAML with environment variable override support.
"""
import os
from pathlib import Path
from typing import Any, Dict, Optional
import yaml
# Environment variable mappings: env var name -> (adapter, credential key)
_ENV_OVERRIDES = {
"AJARBOT_SLACK_BOT_TOKEN": ("slack", "bot_token"),
"AJARBOT_SLACK_APP_TOKEN": ("slack", "app_token"),
"AJARBOT_TELEGRAM_BOT_TOKEN": ("telegram", "bot_token"),
}
class ConfigLoader:
"""Load adapter configuration from YAML files with env var support."""
def __init__(self, config_dir: Optional[str] = None) -> None:
if config_dir is None:
config_dir = str(Path(__file__).parent)
self.config_dir = Path(config_dir)
self.config: Dict[str, Any] = {}
def load(self, filename: str = "adapters.yaml") -> Dict[str, Any]:
"""
Load configuration from YAML file.
Looks for files in this order:
1. {filename}.local.yaml (gitignored, for secrets)
2. {filename}
Environment variables can override any setting:
AJARBOT_SLACK_BOT_TOKEN -> adapters.slack.credentials.bot_token
AJARBOT_TELEGRAM_BOT_TOKEN -> adapters.telegram.credentials.bot_token
"""
local_file = self.config_dir / f"{Path(filename).stem}.local.yaml"
config_file = self.config_dir / filename
if local_file.exists():
print(f"[Config] Loading from {local_file}")
with open(local_file) as f:
self.config = yaml.safe_load(f) or {}
elif config_file.exists():
print(f"[Config] Loading from {config_file}")
with open(config_file) as f:
self.config = yaml.safe_load(f) or {}
else:
print("[Config] No config file found, using defaults")
self.config = {"adapters": {}}
self._apply_env_overrides()
return self.config
def _apply_env_overrides(self) -> None:
"""Apply environment variable overrides."""
for env_var, (adapter, credential_key) in _ENV_OVERRIDES.items():
value = os.getenv(env_var)
if not value:
continue
adapters = self.config.setdefault("adapters", {})
adapter_config = adapters.setdefault(adapter, {})
credentials = adapter_config.setdefault("credentials", {})
credentials[credential_key] = value
print(f"[Config] Using {env_var} from environment")
def get_adapter_config(
self, platform: str
) -> Optional[Dict[str, Any]]:
"""Get configuration for a specific platform."""
return self.config.get("adapters", {}).get(platform)
def is_adapter_enabled(self, platform: str) -> bool:
"""Check if an adapter is enabled."""
adapter_config = self.get_adapter_config(platform)
if not adapter_config:
return False
return adapter_config.get("enabled", False)
def get_user_mapping(self) -> Dict[str, str]:
"""Get user ID to username mapping."""
return self.config.get("user_mapping", {})
def save_template(
self, filename: str = "adapters.local.yaml"
) -> Path:
"""Save a template configuration file."""
template = {
"adapters": {
"slack": {
"enabled": False,
"credentials": {
"bot_token": "xoxb-YOUR-BOT-TOKEN",
"app_token": "xapp-YOUR-APP-TOKEN",
},
"settings": {},
},
"telegram": {
"enabled": False,
"credentials": {
"bot_token": "YOUR-BOT-TOKEN",
},
"settings": {
"allowed_users": [],
"parse_mode": "Markdown",
},
},
},
"user_mapping": {},
}
output_path = self.config_dir / filename
with open(output_path, "w") as f:
yaml.dump(
template, f, default_flow_style=False, sort_keys=False
)
print(f"[Config] Template saved to {output_path}")
return output_path
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "init":
loader = ConfigLoader()
path = loader.save_template()
print(f"\nConfiguration template created at: {path}")
print(
"\nEdit this file with your credentials, "
"then set enabled: true for each adapter you want to use."
)
else:
loader = ConfigLoader()
config = loader.load()
# Redact credentials before printing
def redact_credentials(data):
"""Redact sensitive credential values."""
if isinstance(data, dict):
redacted = {}
for key, value in data.items():
if key == "credentials" and isinstance(value, dict):
redacted[key] = {
k: f"{str(v)[:4]}****{str(v)[-4:]}" if v else None
for k, v in value.items()
}
elif isinstance(value, (dict, list)):
redacted[key] = redact_credentials(value)
else:
redacted[key] = value
return redacted
elif isinstance(data, list):
return [redact_credentials(item) for item in data]
return data
safe_config = redact_credentials(config)
print("\nLoaded configuration (credentials redacted):")
print(yaml.dump(safe_config, default_flow_style=False))