feat: notifier - use NS8 Redis SMTP relay as primary source via smtp_config.resolve_smtp_config()
This commit is contained in:
@@ -6,6 +6,11 @@ Formats a human-readable HTML + text email based on:
|
|||||||
- correlation outcome (SUCCESS / PARTIAL / REPO_FAILURE)
|
- correlation outcome (SUCCESS / PARTIAL / REPO_FAILURE)
|
||||||
- per-module statuses
|
- per-module statuses
|
||||||
- repository check results (if run)
|
- repository check results (if run)
|
||||||
|
|
||||||
|
SMTP configuration is resolved via smtp_config.resolve_smtp_config():
|
||||||
|
1. NS8 cluster Redis (cluster/mail_settings) - same relay used by NS8 itself
|
||||||
|
2. config.yml [smtp] section
|
||||||
|
3. localhost:25 fallback
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -15,9 +20,11 @@ from email.mime.multipart import MIMEMultipart
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from .smtp_config import resolve_smtp_config
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
OUTCOME_EMOJI = {
|
OUTCOME_LABEL = {
|
||||||
"SUCCESS": "OK",
|
"SUCCESS": "OK",
|
||||||
"PARTIAL": "WARNING",
|
"PARTIAL": "WARNING",
|
||||||
"REPO_FAILURE": "CRITICAL",
|
"REPO_FAILURE": "CRITICAL",
|
||||||
@@ -33,7 +40,7 @@ OUTCOME_COLOR = {
|
|||||||
def _build_text(correlation: dict, repo_status: Optional[dict]) -> str:
|
def _build_text(correlation: dict, repo_status: Optional[dict]) -> str:
|
||||||
outcome = correlation["outcome"]
|
outcome = correlation["outcome"]
|
||||||
lines = [
|
lines = [
|
||||||
f"NS8 Backup Monitor - {OUTCOME_EMOJI[outcome]}: {outcome}",
|
f"NS8 Backup Monitor - {OUTCOME_LABEL[outcome]}: {outcome}",
|
||||||
f"Time: {datetime.now(timezone.utc).isoformat()}",
|
f"Time: {datetime.now(timezone.utc).isoformat()}",
|
||||||
f"Plans checked: {', '.join(correlation.get('backup_ids', []))}",
|
f"Plans checked: {', '.join(correlation.get('backup_ids', []))}",
|
||||||
f"Modules: {correlation['succeeded']} OK / {correlation['failed']} FAILED / {correlation['total']} total",
|
f"Modules: {correlation['succeeded']} OK / {correlation['failed']} FAILED / {correlation['total']} total",
|
||||||
@@ -60,10 +67,10 @@ def _build_text(correlation: dict, repo_status: Optional[dict]) -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def _build_html(correlation: dict, repo_status: Optional[dict]) -> str:
|
def _build_html(correlation: dict, repo_status: Optional[dict], smtp_source: str) -> str:
|
||||||
outcome = correlation["outcome"]
|
outcome = correlation["outcome"]
|
||||||
color = OUTCOME_COLOR[outcome]
|
color = OUTCOME_COLOR[outcome]
|
||||||
label = OUTCOME_EMOJI[outcome]
|
label = OUTCOME_LABEL[outcome]
|
||||||
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
|
||||||
plan_ids = ", ".join(correlation.get("backup_ids", [])) or "N/A"
|
plan_ids = ", ".join(correlation.get("backup_ids", [])) or "N/A"
|
||||||
|
|
||||||
@@ -131,6 +138,7 @@ def _build_html(correlation: dict, repo_status: Optional[dict]) -> str:
|
|||||||
<tbody>{rows}</tbody>
|
<tbody>{rows}</tbody>
|
||||||
</table>
|
</table>
|
||||||
{repo_section}
|
{repo_section}
|
||||||
|
<p style="color:#aaa;font-size:11px;margin-top:24px">Sent via {smtp_source}</p>
|
||||||
</div>
|
</div>
|
||||||
</body></html>
|
</body></html>
|
||||||
"""
|
"""
|
||||||
@@ -145,15 +153,20 @@ def send_notification(
|
|||||||
):
|
):
|
||||||
outcome = correlation["outcome"]
|
outcome = correlation["outcome"]
|
||||||
mail_cfg = config.get("mail", {})
|
mail_cfg = config.get("mail", {})
|
||||||
smtp_cfg = config.get("smtp", {})
|
|
||||||
|
|
||||||
mail_from = mail_cfg.get("from", "ns8-backup-monitor@localhost")
|
|
||||||
mail_to = mail_cfg.get("to", [])
|
|
||||||
subject_prefix = mail_cfg.get("subject_prefix", "[NS8 Backup]")
|
subject_prefix = mail_cfg.get("subject_prefix", "[NS8 Backup]")
|
||||||
subject = f"{subject_prefix} {OUTCOME_EMOJI[outcome]}: {outcome} - {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
|
subject = f"{subject_prefix} {OUTCOME_LABEL[outcome]}: {outcome} - {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
|
||||||
|
|
||||||
|
mail_to = mail_cfg.get("to", [])
|
||||||
|
if not mail_to:
|
||||||
|
log.error("No mail.to recipients configured in config.yml - cannot send notification")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Resolve SMTP: NS8 Redis relay -> config.yml -> localhost:25
|
||||||
|
smtp_cfg, mail_from = resolve_smtp_config(config)
|
||||||
|
smtp_source = f"{smtp_cfg['host']}:{smtp_cfg['port']}"
|
||||||
|
|
||||||
text_body = _build_text(correlation, repo_status)
|
text_body = _build_text(correlation, repo_status)
|
||||||
html_body = _build_html(correlation, repo_status)
|
html_body = _build_html(correlation, repo_status, smtp_source)
|
||||||
|
|
||||||
msg = MIMEMultipart("alternative")
|
msg = MIMEMultipart("alternative")
|
||||||
msg["Subject"] = subject
|
msg["Subject"] = subject
|
||||||
@@ -180,6 +193,6 @@ def send_notification(
|
|||||||
smtp.login(username, password)
|
smtp.login(username, password)
|
||||||
smtp.sendmail(mail_from, mail_to, msg.as_string())
|
smtp.sendmail(mail_from, mail_to, msg.as_string())
|
||||||
smtp.quit()
|
smtp.quit()
|
||||||
log.info(f"Notification sent: {subject} -> {mail_to}")
|
log.info(f"Notification sent: {subject} -> {mail_to} (via {smtp_source})")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Failed to send notification: {e}")
|
log.error(f"Failed to send notification via {smtp_source}: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user