# Invoked by: SNS Subscription
# Returns: Error or status message
#
# Triggered by messages sent to various SNS topics resulting from CloudWatch
# Alarms being triggered or changing states.

import boto3
import os
import json
from dateutil.parser import parse
import datetime

sns = boto3.client('sns')
cloudwatch = boto3.client('cloudwatch')

SLACK_ICON = ':ops-cloudwatch-alarm:'
SLACK_USERNAME = 'Amazon CloudWatch Alarms'


def channel_for_topic(sns_topic_arn):
    if 'OpsFatal' in sns_topic_arn:
        return '#ops-fatal'
    elif 'OpsError' in sns_topic_arn:
        return '#ops-error'
    elif 'OpsWarn' in sns_topic_arn:
        return '#ops-warn'
    elif 'OpsInfo' in sns_topic_arn:
        return '#ops-info'
    else:
        return '#ops-debug'


def color_for_alarm(alarm):
    if alarm['NewStateValue'] == 'ALARM':
        return '#cc0000'
    elif alarm['NewStateValue'] == 'OK':
        return '#019933'
    else:
        return '#e07701'

    return {
        ''
    }[alarm['NewStateValue']]


def alarm_slack_attachment(alarm):
    trigger = alarm['Trigger']

    alarm_history = cloudwatch.describe_alarm_history(
        AlarmName=alarm['AlarmName'],
        HistoryItemType='StateUpdate',
        StartDate=datetime.datetime.now() - datetime.timedelta(hours=24),
        EndDate=datetime.datetime.now(),
    )

    items = alarm_history['AlarmHistoryItems']
    alarms = filter(lambda x: ('to ALARM' in x['HistorySummary']), items)

    if 'ExtendedStatistic' in trigger:
        stat = trigger['ExtendedStatistic']
    else:
        stat = trigger['Statistic']

    eper = trigger['EvaluationPeriods']
    per = trigger['Period']

    return {
        'color': color_for_alarm(alarm),
        'fallback': f"{alarm['NewStateValue']} – {alarm['AlarmName']}",
        'title': f"{alarm['NewStateValue']} – {alarm['AlarmName']}",
        'text': f"{trigger['MetricName']}: {alarm['NewStateReason']}",
        'footer': f"{trigger['Namespace']} – {alarm['Region']}",
        'ts': round(parse(alarm['StateChangeTime']).timestamp()),
        'fields': [
            {
                'title': 'Evaluation',
                'value': f"{stat} – {eper} × {per}",
                'short': True,
            }, {
                'title': 'Threshold',
                'value': trigger['Threshold'],
                'short': True,
            }, {
                'title': 'Last 24 Hours',
                'value': f"{len(list(alarms))} alarms",
                'short': True,
            }
        ]
    }


def ok_slack_attachment(alarm):
    return {
        'color': color_for_alarm(alarm),
        'fallback': f"{alarm['NewStateValue']} – {alarm['AlarmName']}",
        'title': f"{alarm['NewStateValue']} – {alarm['AlarmName']}",
        'footer': f"{alarm['Trigger']['Namespace']} – {alarm['Region']}",
        'ts': round(parse(alarm['StateChangeTime']).timestamp())
    }


def slack_message(sns_payload):
    alarm = json.loads(sns_payload['Message'])

    if alarm['NewStateValue'] == 'OK':
        attachment = ok_slack_attachment(alarm)
    else:
        attachment = alarm_slack_attachment(alarm)

    return {
        'channel': channel_for_topic(sns_payload['TopicArn']),
        'username': SLACK_USERNAME,
        'icon_emoji': SLACK_ICON,
        'attachments': [attachment]
    }


def lambda_handler(event, context):
    sns_payload = event['Records'][0]['Sns']

    # TODO This isn't necessary once all stacks stop using the log-level SNS
    # topics for deployment notifications
    if sns_payload['Subject'] == 'AWS CloudFormation Notification':
        return

    sns.publish(
        TopicArn=os.environ['SLACK_MESSAGE_RELAY_TOPIC_ARN'],
        Message=json.dumps(slack_message(sns_payload))
    )

@@ -2545,24 +2545,736 @@
 ent(alarm):%0A
+    duration = 'Unavailable'%0A%0A    now = datetime.datetime.now(datetime.timezone.utc)%0A%0A    alarm_history = cloudwatch.describe_alarm_history(%0A        AlarmName=alarm%5B'AlarmName'%5D,%0A        HistoryItemType='StateUpdate',%0A        StartDate=now - datetime.timedelta(hours=24),%0A        EndDate=now,%0A    )%0A%0A    items = alarm_history%5B'AlarmHistoryItems'%5D%0A%0A    if len(items) %3E 0:%0A        ok_item = items%5B0%5D%0A        history_data = json.loads(ok_item%5B'HistoryData'%5D)%0A%0A        ok_time = ok_item%5B'Timestamp'%5D%0A%0A        alarm_time = history_data%5B'oldState'%5D%5B'stateReasonData'%5D%5B'startDate'%5D%0A        alarm_time = parse(alarm_time)%0A%0A        dif = ok_time - alarm_time%0A        duration = f%22%7Bround(dif.total_seconds() / 60)%7D min.%22%0A%0A
     return %7B
@@ -3448,32 +3448,79 @@
 'AlarmName'%5D%7D%22,%0A
+        'text': f%22Alarm duration: %7Bduration%7D%22,%0A
         'footer'

Add duration to CloudWatch alarm OK notifications