-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d856f25
commit 4f5dd27
Showing
19 changed files
with
307 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from django.db.models import Q | ||
|
||
from aw.base import USERS | ||
from aw.model.base import JOB_EXEC_STATUS_SUCCESS | ||
from aw.model.job import Job, JobExecution, JobExecutionResultHost | ||
from aw.utils.permission import has_job_permission, CHOICE_PERMISSION_READ | ||
from aw.model.alert import BaseAlert, AlertUser, AlertGroup, AlertGlobal, \ | ||
ALERT_CONDITION_FAIL, ALERT_CONDITION_SUCCESS, ALERT_CONDITION_ALWAYS, \ | ||
ALERT_TYPE_PLUGIN | ||
from aw.execute.alert_plugin.plugin_email import alert_plugin_email | ||
from aw.execute.alert_plugin.plugin_wrapper import alert_plugin_wrapper | ||
|
||
|
||
class Alert: | ||
def __init__(self, job: Job, execution: JobExecution): | ||
self.job = job | ||
self.execution = execution | ||
self.failed = execution.status != JOB_EXEC_STATUS_SUCCESS | ||
self.privileged_users = [] | ||
for user in USERS.objects.all(): | ||
if has_job_permission(user=user, job=job, permission_needed=CHOICE_PERMISSION_READ): | ||
self.privileged_users.append(user) | ||
|
||
self.stats = {} | ||
if execution.result is None: | ||
for stats in JobExecutionResultHost.objects.filter(result=execution.result): | ||
self.stats[stats['hostname']] = { | ||
attr: getattr(stats, attr) for attr in JobExecutionResultHost.STATS | ||
} | ||
|
||
def _job_filter(self, model: type): | ||
return model.objects.filter(Q(jobs=self.job) | Q(jobs_all=True)) | ||
|
||
def _condition_filter(self, alerts: list[BaseAlert]): | ||
matching = [] | ||
for alert in alerts: | ||
if alert.condition == ALERT_CONDITION_ALWAYS or \ | ||
(self.failed and alert.condition == ALERT_CONDITION_FAIL) or \ | ||
(not self.failed and alert.condition == ALERT_CONDITION_SUCCESS): | ||
matching.append(alert) | ||
|
||
return matching | ||
|
||
def _route(self, alert: BaseAlert, user: USERS): | ||
if alert.alert_type == ALERT_TYPE_PLUGIN: | ||
alert_plugin_wrapper(alert=alert, user=user, stats=self.stats, execution=self.execution) | ||
|
||
else: | ||
alert_plugin_email(user=user, stats=self.stats, execution=self.execution) | ||
|
||
def _global(self): | ||
for alert in self._condition_filter(self._job_filter(AlertGlobal)): | ||
for user in self.privileged_users: | ||
self._route(alert=alert, user=user) | ||
|
||
def _group(self): | ||
for alert in self._condition_filter(self._job_filter(AlertGroup)): | ||
for user in self.privileged_users: | ||
if user.groups.filter(name=alert.group).exists(): | ||
self._route(alert=alert, user=user) | ||
|
||
def _user(self): | ||
for user in self.privileged_users: | ||
for alert in self._condition_filter(self._job_filter(AlertUser).filter(user=user)): | ||
self._route(alert=alert, user=user) | ||
|
||
def go(self): | ||
self._global() | ||
self._group() | ||
self._user() |
104 changes: 104 additions & 0 deletions
104
src/ansibleguy-webui/aw/execute/alert_plugin/plugin_email.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import ssl | ||
from pathlib import Path | ||
from smtplib import SMTP, SMTP_SSL, SMTPResponseException | ||
from email.mime.text import MIMEText | ||
from email.mime.multipart import MIMEMultipart | ||
from json import dumps as json_dumps | ||
|
||
from aw.base import USERS | ||
from aw.utils.util import valid_email | ||
from aw.utils.debug import log | ||
from aw.config.main import config | ||
from aw.model.job import JobExecution | ||
from aw.settings import get_main_web_address | ||
from aw.model.system import MAIL_TRANSPORT_TYPE_SSL, MAIL_TRANSPORT_TYPE_STARTTLS | ||
|
||
|
||
def _email_send(server: SMTP, user: USERS, stats: list[dict], execution: JobExecution): | ||
server.login(user=config['mail_user'], password=config['mail_pass']) | ||
msg = MIMEMultipart('alternative') | ||
msg['Subject'] = f"Ansible WebUI Alert - Job '{execution.job.name}' - {execution.status_name}" | ||
msg['From'] = config['mail_sender'] | ||
msg['To'] = user.email | ||
|
||
text = f""" | ||
Job: {execution.job.name} | ||
Status: {execution.status_name} | ||
Executed by: {execution.user_name} | ||
Start time: {execution.time_created_str} | ||
""" | ||
|
||
if execution.result is not None: | ||
text += f""" | ||
Finish time: {execution.result.time_fin_str} | ||
Duration: {execution.result.time_duration_str} | ||
""" | ||
|
||
if execution.result.error is not None: | ||
text += f""" | ||
Short error message: '{execution.result.error.short}' | ||
Long error message: '{execution.result.error.med}' | ||
""" | ||
|
||
for log_attr in JobExecution.log_file_fields: | ||
file = getattr(execution, log_attr) | ||
if Path(file).is_file(): | ||
text += f""" | ||
{log_attr.replace('_', ' ').capitalize()}: {get_main_web_address()}{getattr(execution, log_attr + '_url')} | ||
""" | ||
|
||
if len(stats) > 0: | ||
text += f""" | ||
Raw stats: | ||
{json_dumps(stats)} | ||
""" | ||
|
||
msg.attach(MIMEText(text, 'plain')) | ||
# msg.attach(MIMEText(html, 'html')) | ||
|
||
server.sendmail( | ||
from_addr=config['mail_sender'], | ||
to_addrs=user.email, | ||
msg=msg.as_string() | ||
) | ||
|
||
|
||
def alert_plugin_email(user: USERS, stats: list[dict], execution: JobExecution): | ||
if user.email.endswith('@localhost') or not valid_email(user.email): | ||
log(msg=f"User has an invalid email address configured: {user.username} ({user.email})", level=3) | ||
return | ||
|
||
try: | ||
server, port = config['mail_server'].split(':', 1) | ||
|
||
except ValueError: | ||
server = config['mail_server'] | ||
port = 25 | ||
|
||
try: | ||
print(f"Alert user {user.username} via email ({server}:{port} => {user.email})") | ||
ssl_context = ssl.create_default_context() | ||
if config['mail_ssl_verify']: | ||
ssl_context.check_hostname = True | ||
ssl_context.verify_mode = ssl.CERT_REQUIRED | ||
|
||
else: | ||
ssl_context.check_hostname = False | ||
ssl_context.verify_mode = ssl.CERT_NONE | ||
|
||
if config['mail_transport'] == MAIL_TRANSPORT_TYPE_SSL: | ||
with SMTP_SSL(server, port, context=ssl_context) as server: | ||
server.login(config['mail_user'], config['mail_pass']) | ||
_email_send(server=server, user=user, stats=stats, execution=execution) | ||
|
||
else: | ||
with SMTP(server, port) as server: | ||
if config['mail_transport'] == MAIL_TRANSPORT_TYPE_STARTTLS: | ||
server.starttls(context=ssl_context) | ||
|
||
_email_send(server=server, user=user, stats=stats, execution=execution) | ||
|
||
except (SMTPResponseException, OSError) as e: | ||
log(msg=f"Got error sending alert mail: {e}", level=2) |
8 changes: 8 additions & 0 deletions
8
src/ansibleguy-webui/aw/execute/alert_plugin/plugin_wrapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from aw.base import USERS | ||
from aw.model.job import JobExecution, JobExecutionResultHost | ||
from aw.model.alert import BaseAlert, AlertPlugin | ||
|
||
|
||
def alert_plugin_wrapper(alert: BaseAlert, user: USERS, stats: [JobExecutionResultHost], execution: JobExecution): | ||
# implement plugin interface | ||
print(f"Alert user {user.username} via plugin {alert.plugin.executable}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.