Netdata.cloud bot for Zulip
1"""Configuration management for the Netdata Zulip bot."""
2
3import os
4from pathlib import Path
5from typing import Optional
6
7import structlog
8from dotenv import load_dotenv
9
10from .models import ZulipConfig, ServerConfig
11
12logger = structlog.get_logger()
13
14
15def load_config() -> tuple[ZulipConfig, ServerConfig]:
16 """Load configuration from environment variables and .env files."""
17
18 # Load .env file if present
19 env_file = Path(".env")
20 if env_file.exists():
21 load_dotenv(env_file)
22 logger.info("Loaded configuration from .env file")
23
24 # Load Zulip configuration (optional from env, main source should be zuliprc)
25 zulip_config = ZulipConfig(
26 site=os.getenv("ZULIP_SITE"),
27 email=os.getenv("ZULIP_EMAIL"),
28 api_key=os.getenv("ZULIP_API_KEY"),
29 stream=os.getenv("ZULIP_STREAM", "netdata-alerts"),
30 )
31
32 # Load server configuration
33 server_config = ServerConfig(
34 host=os.getenv("SERVER_HOST", "0.0.0.0"),
35 port=int(os.getenv("SERVER_PORT", "8080")),
36 challenge_secret=os.getenv("SERVER_CHALLENGE_SECRET"),
37 )
38
39 logger.info(
40 "Configuration loaded",
41 zulip_site=zulip_config.site or "(from zuliprc)",
42 zulip_email=zulip_config.email or "(from zuliprc)",
43 zulip_stream=zulip_config.stream,
44 server_host=server_config.host,
45 server_port=server_config.port,
46 )
47
48 return zulip_config, server_config
49
50
51def load_zuliprc_config(zuliprc_path: Optional[str] = None) -> ZulipConfig:
52 """Load Zulip configuration from a zuliprc file.
53
54 Args:
55 zuliprc_path: Path to zuliprc file. If None, looks for ~/.zuliprc
56
57 Returns:
58 ZulipConfig instance
59 """
60 if zuliprc_path is None:
61 zuliprc_path = Path.home() / ".zuliprc"
62 else:
63 zuliprc_path = Path(zuliprc_path)
64
65 if not zuliprc_path.exists():
66 raise FileNotFoundError(f"Zuliprc file not found: {zuliprc_path}")
67
68 config = {}
69 with open(zuliprc_path, 'r') as f:
70 for line in f:
71 line = line.strip()
72 if line and not line.startswith('#') and '=' in line:
73 key, value = line.split('=', 1)
74 config[key.strip()] = value.strip()
75
76 # Map zuliprc keys to our config
77 zulip_config = ZulipConfig(
78 site=config.get('site', ''),
79 email=config.get('email', ''),
80 api_key=config.get('key', ''),
81 stream=config.get('stream', 'netdata-alerts'),
82 )
83
84 # Validate required fields from zuliprc
85 if not all([zulip_config.site, zulip_config.email, zulip_config.api_key]):
86 missing = []
87 if not zulip_config.site: missing.append('site')
88 if not zulip_config.email: missing.append('email')
89 if not zulip_config.api_key: missing.append('key')
90 raise ValueError(
91 f"Missing required Zulip configuration in {zuliprc_path}: {', '.join(missing)}"
92 )
93
94 logger.info(
95 "Loaded Zulip configuration from zuliprc",
96 path=str(zuliprc_path),
97 site=zulip_config.site,
98 email=zulip_config.email,
99 stream=zulip_config.stream
100 )
101
102 return zulip_config