Skip to content

Commit

Permalink
Merge pull request #23 from diggyk/master
Browse files Browse the repository at this point in the history
Add email notifications for quest updates and completions
  • Loading branch information
gmjosack committed Jul 8, 2015
2 parents 85854ba + 1a4eb41 commit 216bf6d
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 19 deletions.
7 changes: 7 additions & 0 deletions config/dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ query_server: "http://localhost:5353/api/query"
# slack_webhook: "https://hooks.slack.com/services/"
# slack_proxyhost: "proxyserver:port"

# Email notifications
email_notifications: false
# email_sender_address: "hermes@localhost"

# Always send email notifications to this comma seperated list
# email_always_copy: "admin@company.com"

# This is the expiration (in seconds) of auth_tokens used for API calls
# Type: int
auth_token_expiry: 600
148 changes: 131 additions & 17 deletions hermes/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import unicode_literals
from __future__ import unicode_literals, division

from datetime import datetime
import functools
import logging
import textwrap

from sqlalchemy import create_engine, or_, union_all, desc, and_
from sqlalchemy.event import listen
Expand All @@ -16,7 +17,7 @@
from sqlalchemy.types import Integer, String, Boolean, BigInteger
from sqlalchemy.types import DateTime

from .util import slack_message
from .util import slack_message, email_message
from .settings import settings
import exc

Expand Down Expand Up @@ -1038,6 +1039,27 @@ def check_for_victory(self):
self.description
))

msg = "QUEST {} COMPLETED:\n\n\t\"{}\"\n\n".format(
self.id,
textwrap.fill(
self.description,
width=60, subsequent_indent="\t "
)
)

msg += "Quest was embarked on {} and completed on {}.\n".format(
self.embark_time, self.completion_time
)

msg += "There were {} labors in the Quest.".format(
len(self.labors)
)

email_message(
self.creator, "Quest {} completed".format(self.id),
msg
)

@classmethod
def get_open_quests(cls, session):
"""Get a list of open Quests
Expand Down Expand Up @@ -1069,6 +1091,69 @@ def get_open_labors(self):
)
)

@classmethod
def email_quest_updates(cls, quests_updated):
for quest in quests_updated.itervalues():
msg = "QUEST {} UPDATED:\n\n\t\"{}\"\n\n".format(
quest["quest"].id,
textwrap.fill(
quest["quest"].description,
width=60, subsequent_indent="\t "
)
)

labors_completed = 0;
remaining_types = {}
for labor in quest["quest"].labors:
if labor.completion_time is None:
type_key = "{} {}".format(
labor.creation_event.event_type.category,
labor.creation_event.event_type.state
)
if type_key in remaining_types:
remaining_types[type_key] += 1
else:
remaining_types[type_key] = 1
else:
labors_completed += 1

total_labors = len(quest["quest"].labors)
labors_remaining = total_labors - labors_completed
if labors_remaining:
msg += "\nOPEN LABORS BY TYPE:\n"
for type in remaining_types.iterkeys():
msg += "\t{}: {}".format(
type,
remaining_types[type]
)
msg += (
"\n\nOVERALL STATUS:\n\t{:.2%} complete. "
"{} total labors. {} remain open.\n\n".format(
labors_completed / total_labors,
total_labors,
labors_remaining
)
)

msg += "LABORS UPDATED:\n"
for labor in quest["labors"]:
msg += (
"\tLabor {} completed.\n\t\t{}: {} {} => {} {}\n\n".format(
labor.id, labor.host.hostname,
labor.creation_event.event_type.category,
labor.creation_event.event_type.state,
labor.completion_event.event_type.category,
labor.completion_event.event_type.state,
)
)

email_message(
quest["quest"].creator,
"Quest {} updated".format(quest["quest"].id),
msg
)


def href(self, base_uri):
"""Create an HREF value for this object
Expand Down Expand Up @@ -1192,8 +1277,18 @@ def achieve_many(cls, session, labor_dicts):
labor_dicts: the list of Labors dicts to achieve
(with keys labor and event)
"""
quests = []
# here we will track the quests that need to get checked for victory
quests_to_check = []

# here we will organize the labors into quests so we can send an email
# of the updated quests
quests_updated = {}

# here we will create a giant string of the message to send to Slack
all_messages = ""

# Let us examine and update the completion of each labor with the given
# event, as we were passed in the dict
for labor_dict in labor_dicts:
labor = labor_dict["labor"]
event = labor_dict["event"]
Expand All @@ -1202,28 +1297,47 @@ def achieve_many(cls, session, labor_dicts):
flush=False, commit=False
)

all_messages += ("*Labor {}* completed.\n\t{}: {} {} => {} {}{}\n".format(
labor.id, labor.host.hostname,
labor.creation_event.event_type.category,
labor.creation_event.event_type.state,
labor.completion_event.event_type.category,
labor.completion_event.event_type.state,
"\n\tPart of Quest [{}] \"{}\"".format(
labor.quest_id, labor.quest.description
) if labor.quest_id else ""
))
if labor.quest and labor.quest not in quests:
quests.append(labor.quest)
# add to the message we will post to Slack
all_messages += (
"*Labor {}* completed.\n\t{}: {} {} => {} {}{}\n\n".format(
labor.id, labor.host.hostname,
labor.creation_event.event_type.category,
labor.creation_event.event_type.state,
labor.completion_event.event_type.category,
labor.completion_event.event_type.state,
"\n\tPart of Quest [{}] \"{}\"".format(
labor.quest_id, labor.quest.description
) if labor.quest_id else ""
)
)


if labor.quest:
# If this labor was part of a quest, let's sort it into the
# quests_updated dict so we can more easily generate emails
if labor.quest_id in quests_updated:
quests_updated[labor.quest_id]["labors"].append(labor)
else:
quests_updated[labor.quest_id] = {
"quest": labor.quest,
"labors": [labor]
}
if labor.quest not in quests_to_check:
quests_to_check.append(labor.quest)

session.flush()
session.commit()

if len(labor_dicts) < 10:
slack_message(all_messages)
else:
slack_message(
"*Labor:* completed {} Labors".format(len(labor_dicts))
"*Labors:* completed {} Labors".format(len(labor_dicts))
)
for quest in quests:

Quest.email_quest_updates(quests_updated)

for quest in quests_to_check:
quest.check_for_victory()

@classmethod
Expand Down
3 changes: 3 additions & 0 deletions hermes/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def __getattr__(self, name):
"domain": "localhost",
"port": 8990,
"user_auth_header": "X-Hermes-Email",
"email_notifications": False,
"email_sender_address": "hermes@localhost",
"email_always_copy": "",
"restrict_networks": [],
"bind_address": None,
"api_xsrf_enabled": True,
Expand Down
37 changes: 36 additions & 1 deletion hermes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

import logging
import requests
import smtplib

from email.mime.text import MIMEText

from .settings import settings

Expand Down Expand Up @@ -39,4 +42,36 @@ def slack_message(message):
settings.slack_webhook, json=json, proxies=proxies
)
except Exception as exc:
log.warn("Error writing to Slack: {}".format(exc.message))
log.warn("Error writing to Slack: {}".format(exc.message))

def email_message(recipients, subject, message):
"""Email a message to a user.
Args:
subject: the subject of the email we wish to send
message: the content of the email we wish to send
recipients: the email address to whom we wish to send the email
"""
if not settings.email_notifications:
return

if isinstance(recipients, basestring):
recipients = recipients.split(",")
if isinstance(settings.email_always_copy, basestring):
extra_recipients = settings.email_always_copy.split(",")

msg = MIMEText(message)
msg["Subject"] = subject
msg["From"] = "hermes@localhost"
msg["To"] = ", ".join(recipients)
if extra_recipients:
msg["Cc"] = ", ".join(extra_recipients)

try:
smtp = smtplib.SMTP("localhost")
smtp.sendmail(
settings.email_sender_address, recipients, msg.as_string()
)
smtp.quit()
except Exception as exc:
log.warn("Error sending email: {}".format(exc.message))
2 changes: 1 addition & 1 deletion hermes/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.31"
__version__ = "0.2.0"

0 comments on commit 216bf6d

Please sign in to comment.