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