Skip to content

Commit

Permalink
Flexible templates and fixes (#14)
Browse files Browse the repository at this point in the history
* refactor; fix for timestamp handling; initial updates to the templates.

* templates alignment after feedback

* add .gitignore files

* improve handling of SMTP_HOST env var

* use "Notification configuration" string for the link to notifs config on UI

* minor: improve subscription names in test input files

* sonar: use lang="en" in <html> template

* sonar: exclude analysis of templates

* sonar: remove exclusion config; define in sonarcloud instead

* fix access to templates during tests
  • Loading branch information
konstan authored Dec 5, 2023
1 parent 5d72e82 commit 8f9d734
Show file tree
Hide file tree
Showing 19 changed files with 763 additions and 122 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.pytest_cache
__pycache__
94 changes: 65 additions & 29 deletions src/notify-email.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

log_local = get_logger('email')

EMAIL_TEMPLATE_FILE = 'templates/base.html'
EMAIL_TEMPLATE = Template('')
EMAIL_TEMPLATE_DEFAULT_FILE = 'templates/default.html'
EMAIL_TEMPLATE_APP_PUB_FILE = 'templates/app-pub.html'

EMAIL_TEMPLATES = {
'default': Template('dummy'),
'app-pub': Template('dummy'),
}

NUVLA_API_LOCAL = 'http://api:8200'

Expand All @@ -41,10 +46,10 @@ def get_nuvla_config():
return resp.json()


def set_smpt_params():
def set_smtp_params():
global SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_PORT, SMTP_SSL
try:
if 'SMTP_HOST' in os.environ:
if os.environ.get('SMTP_HOST') and len(os.environ.get('SMTP_HOST')) > 0:
SMTP_HOST = os.environ['SMTP_HOST']
SMTP_USER = os.environ['SMTP_USER']
SMTP_PASSWORD = os.environ['SMTP_PASSWORD']
Expand Down Expand Up @@ -84,35 +89,56 @@ def get_smtp_server(debug_level=0) -> smtplib.SMTP:
return server


def html_content(values: dict):
subs_config_link = f'<a href="{NUVLA_ENDPOINT}/ui/notifications">Notification configuration</a>'
def get_email_template(msg_params: dict) -> Template:
if 'TEMPLATE' not in msg_params:
return EMAIL_TEMPLATES['default']
tmpl_name = msg_params.get('TEMPLATE', 'default')
template = EMAIL_TEMPLATES.get(tmpl_name)
if template is None:
log_local.warning('Failed to find email template: %s', tmpl_name)
template = EMAIL_TEMPLATES['default']
return template


def html_content(msg_params: dict):
r_uri = msg_params.get('RESOURCE_URI')
link_text = msg_params.get('RESOURCE_NAME') or r_uri
component_link = f'<a href="{NUVLA_ENDPOINT}/ui/{r_uri}">{link_text}</a>'

r_uri = values.get('RESOURCE_URI')
r_name = values.get('RESOURCE_NAME')
component_link = f'<a href="{NUVLA_ENDPOINT}/ui/{r_uri}">{r_name or r_uri}</a>'
metric = values.get('METRIC')
condition = values.get('CONDITION')
if values.get('RECOVERY', False):
if msg_params.get('RECOVERY', False):
img_alert = IMG_ALERT_OK
title = f"[OK] {values.get('SUBS_NAME')}"
notif_title = f"[OK] {msg_params.get('SUBS_NAME')}"
else:
img_alert = IMG_ALERT_NOK
title = f"[Alert] {values.get('SUBS_NAME')}"
notif_title = f"[Alert] {msg_params.get('SUBS_NAME')}"

subs_name = 'Notification configuration'
subs_config_link = f'<a href="{NUVLA_ENDPOINT}/ui/notifications">{subs_name}</a>'

params = {
'title': title,
'subs_description': values.get('SUBS_DESCRIPTION'),
'title': notif_title,
'subs_description': msg_params.get('SUBS_DESCRIPTION'),
'component_link': component_link,
'metric': metric,
'condition': condition.upper(),
'timestamp': timestamp_convert(values.get('TIMESTAMP')),
'metric': msg_params.get('METRIC'),
'timestamp': timestamp_convert(msg_params.get('TIMESTAMP')),
'subs_config_link': subs_config_link,
'header_img': f'{NUVLA_ENDPOINT}/{img_alert}',
'current_year': str(datetime.now().year)
}
if values.get('VALUE'):
params['condition'] = f"{values.get('CONDITION')} {values.get('CONDITION_VALUE')}"
params['value'] = values.get('VALUE')
return EMAIL_TEMPLATE.render(**params)

if 'TRIGGER_RESOURCE_PATH' in msg_params:
resource_path = msg_params.get('TRIGGER_RESOURCE_PATH')
resource_name = msg_params.get('TRIGGER_RESOURCE_NAME')
params['trigger_link'] = \
f'<a href="{NUVLA_ENDPOINT}/ui/{resource_path}">{resource_name}</a>'

if msg_params.get('CONDITION'):
params['condition'] = msg_params.get('CONDITION')
if 'VALUE' in msg_params:
params['condition'] = f"{msg_params.get('CONDITION')} {msg_params.get('CONDITION_VALUE')}"
params['value'] = msg_params.get('VALUE')

return get_email_template(msg_params).render(**params)


def send(server: smtplib.SMTP, recipients, subject, html, attempts=SEND_EMAIL_ATTEMPTS):
Expand All @@ -125,9 +151,9 @@ def send(server: smtplib.SMTP, recipients, subject, html, attempts=SEND_EMAIL_AT
if i > 0:
log_local.warning(f'Failed sending email: retry {i}')
time.sleep(.5)
log_local.warning(f'Reconnecting to SMTP server...')
log_local.warning('Reconnecting to SMTP server...')
server = get_smtp_server()
log_local.warning(f'Reconnecting to SMTP server... done.')
log_local.warning('Reconnecting to SMTP server... done.')
try:
resp = server.sendmail(server.user, recipients, msg.as_string())
if resp:
Expand Down Expand Up @@ -160,14 +186,24 @@ def worker(workq: multiprocessing.Queue):
log_local.info(f'sent: {msg} to {recipients}')
except smtplib.SMTPException as ex:
log_local.error(f'Failed sending email due to SMTP error: {ex}')
log_local.warning(f'Reconnecting to SMTP server...')
log_local.warning('Reconnecting to SMTP server...')
smtp_server = get_smtp_server()
log_local.warning(f'Reconnecting to SMTP server... done.')
log_local.warning('Reconnecting to SMTP server... done.')
except Exception as ex:
log_local.error(f'Failed sending email: {ex}')


def email_template(template_file=EMAIL_TEMPLATE_DEFAULT_FILE):
return Template(open(template_file).read())


def init_email_templates(default=EMAIL_TEMPLATE_DEFAULT_FILE,
app_pub=EMAIL_TEMPLATE_APP_PUB_FILE):
EMAIL_TEMPLATES['default'] = email_template(default)
EMAIL_TEMPLATES['app-pub'] = email_template(app_pub)


if __name__ == "__main__":
EMAIL_TEMPLATE = Template(open(EMAIL_TEMPLATE_FILE).read())
set_smpt_params()
init_email_templates()
set_smtp_params()
main(worker, KAFKA_TOPIC, KAFKA_GROUP_ID)
144 changes: 82 additions & 62 deletions src/notify-slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,73 +26,93 @@ def now_timestamp():
return datetime.now().timestamp()


def message_content(values: dict):
subs_name = lt.sub('&lt;', gt.sub('&gt;', values.get('SUBS_NAME', '')))
subs_config_txt = f'<{NUVLA_ENDPOINT}/ui/notifications|{subs_name}>'

metric = values.get('METRIC')
if values.get('VALUE'):
cond_value = values.get('CONDITION_VALUE')
condition = f"{values.get('CONDITION')}"
criteria = f'_{metric}_ {gt.sub("&gt;", lt.sub("&lt;", condition))} *{cond_value}*'
value = f"*{values.get('VALUE')}*"
else:
condition = values.get('CONDITION', '').upper()
criteria = f'_{metric}_'
value = f'*{condition}*'

r_uri = values.get('RESOURCE_URI')
r_name = values.get('RESOURCE_NAME')
link_text = r_name or r_uri
component = f'<{NUVLA_ENDPOINT}/ui/{r_uri}|{link_text}>'

ts = timestamp_convert(values.get('TIMESTAMP'))
def message_content(msg_params: dict):
r_uri = msg_params.get('RESOURCE_URI')
link_text = msg_params.get('RESOURCE_NAME') or r_uri
component_link = f'<{NUVLA_ENDPOINT}/ui/{r_uri}|{link_text}>'

if values.get('RECOVERY', False):
if msg_params.get('RECOVERY', False):
color = COLOR_OK
notif_title = "[OK] Notification"
notif_title = f"[OK] {msg_params.get('SUBS_NAME')}"
else:
color = COLOR_NOK
notif_title = "[Alert] Notification"

return {
"attachments": [
notif_title = f"[Alert] {msg_params.get('SUBS_NAME')}"

subs_config_link = f'<{NUVLA_ENDPOINT}/ui/notifications|Notification configuration>'

# Order of the fields defines the layout of the message.

fields = [
{
'title': notif_title,
'value': subs_config_link,
'short': True
}
]

if 'TRIGGER_RESOURCE_PATH' in msg_params:
resource_path = msg_params.get('TRIGGER_RESOURCE_PATH')
resource_name = msg_params.get('TRIGGER_RESOURCE_NAME')
trigger_link = \
f'<{NUVLA_ENDPOINT}/ui/{resource_path}|{resource_name}>'
fields.append({
'title': 'Application was published',
'value': trigger_link,
'short': True
})

fields.append({
'title': 'Affected resource(s)',
'value': component_link,
'short': True
})

if msg_params.get('CONDITION'):
metric = msg_params.get('METRIC')
if 'VALUE' in msg_params:
cond_value = msg_params.get('CONDITION_VALUE')
condition = f"{msg_params.get('CONDITION')}"
criteria = f'_{metric}_ {gt.sub("&gt;", lt.sub("&lt;", condition))} *{cond_value}*'
value = f"*{msg_params.get('VALUE')}*"
else:
condition = msg_params.get('CONDITION', '').upper()
criteria = f'_{metric}_'
value = f'*{condition}*'

fields.extend([
{
'title': 'Criteria',
'value': criteria,
'short': True
},
{
"color": color,
"author_name": "Nuvla",
"author_link": "https://sixsq.com",
"author_icon": "https://sixsq.com/img/logo/logo_sixsq.png",
"fields": [
{
"title": notif_title,
"value": subs_config_txt,
"short": True
},
{
"title": "Affected resource",
"value": component,
"short": True
},
{
"title": "Criteria",
"value": criteria,
"short": True
},
{
"title": "Value",
"value": value,
"short": True
},
{
"title": "Event Timestamp",
"value": ts,
"short": True
}
],
"ts": now_timestamp()
}
]
}
'title': 'Value',
'value': value,
'short': True
}]
)

fields.append(
{
'title': 'Event Timestamp',
'value': timestamp_convert(msg_params.get('TIMESTAMP')),
'short': True
}
)

attachments = [{
'color': color,
'author_name': 'Nuvla.io',
'author_link': 'https://nuvla.io',
'author_icon': 'https://sixsq.com/assets/img/logo-sixsq.svg',
'fields': fields,
'footer': 'https://sixsq.com',
'footer_icon': 'https://sixsq.com/assets/img/logo-sixsq.svg',
'ts': now_timestamp()
}
]

return {'attachments': attachments}


def send_message(dest, message):
Expand Down
2 changes: 1 addition & 1 deletion src/notify_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def kafka_consumer(topic, bootstrap_servers, group_id, auto_offset_reset='latest

def timestamp_convert(ts):
return datetime.strptime(ts, '%Y-%m-%dT%H:%M:%SZ'). \
strftime('%a %d, %Y %H:%M:%S UTC')
strftime('%Y-%m-%d %H:%M:%S UTC')


def main(worker, kafka_topic, group_id):
Expand Down
Loading

0 comments on commit 8f9d734

Please sign in to comment.