AWS Service health dashboardのRSSを読んで、updateがあればAWS Lambda経由でSlackに通知

# 追記
パーソナルヘルスダッシュボードが出来ておりますので、そちらをベースに考えたほうが良くなっております。
https://aws.amazon.com/jp/blogs/news/aws-personal-health-dashboard/


# 目的
AWSのサービスヘルスダッシュボードをわざわざ見なくても、通知が受けられるれると便利かな−と
AWS LambdaのスケジュールイベントでRSSを読みに行って、Lambdaスケジュール期間中にupdate情報があればその情報をslackに投げるボット的なやつを作ってみました。
slackにrssをintegrationをする機能がありますが、1-rss/1-accountになるので数に制限のある無料ユーザなら１-アカウントで複数を読むほうがアカウント効率が良いかと思いました。

# 使うもの
Slack：投稿用
AWS Lambda：RSSチェック
Amazon S3：購読するRSSを定義する

# 事前準備
当然Slackを持っていることを前提とします。Slackに[slack Incoming WebHooks](https://api.slack.com/incoming-webhooks)を作ります。

上位で得られたwebhook用のURLをメモしてください
https://hooks.slack.com/services/{hogehoge}
のような形だと思います。

#AWS側の設定
## S3へ設定ファイル配置する
とりあえず、人間的に読みやすいようなjsonにしてみました。
サービス名とか、サービス毎でAWSとかの接頭詞があったりなかったり、だったので最早RSSフィードをコピペしてもらうほうが良いかと思い、以下の形にしています。
Keyにサービス名を適当に設定してください。表示にしか使わないので、適当でいいです。。
rssフィードは[サービスヘルスダッシュボード](https://status.aws.amazon.com/)を参照の上、rssフィードのURLを設定してください。

```json:setup.json
{
    "Tokyo":{
        "EC2":"https://status.aws.amazon.com/rss/ec2-ap-northeast-1.rss",
        "S3":"https://status.aws.amazon.com/rss/s3-ap-northeast-1.rss",
        "Lambda":"https://status.aws.amazon.com/rss/lambda-ap-northeast-1.rss",
        "IoT":"https://status.aws.amazon.com/rss/awsiot-ap-northeast-1.rss"
    },
    "Other":{
        "Console":"https://status.aws.amazon.com/rss/management-console.rss",
        "Route53":"https://status.aws.amazon.com/rss/route53.rss"
    }

}
```

とりあえず、S3のバケットに設定します。今回は秘匿情報が一切ないので設定例は参照フルオープンにしてます。
バケット名とフォルダを含めたパス名をメモです。
バケット名はちょっと消しておきました。 パスは最初の"/"が不要です。今回の例では SHD/SHD_setup.jsonです。
![スクリーンショット 2016-11-06 23.38.28.png](https://qiita-image-store.s3.amazonaws.com/0/100893/bd6013be-5459-4a53-b413-9f6dff119d2a.png)

## AWS Lambda
今回Pythonで作っています。slack投稿とrssフィード解析には便利なパッケージがありますが、AWS Lambdaから実行することを考慮して標準パッケージのみで書いています。以下の変数はご自分の環境に合わせて設定ください。

SLACK_URL：上で取得した slack webhook用のURL
BUCKET ： 設定ファイルをおいたS3バケット名
S3_FILE： バケット以下の設定ファイルのパス名
DURATION：　監視間隔=Lambdaのスケジュールをどの程度の感覚で動かすかを秒で（本例では5分間隔を想定)

Configuration trigerでのイベント設定は以下のような感じです。
rate で 5分間隔を指定してください

![スクリーンショット 2016-11-06 23.58.07.png](https://qiita-image-store.s3.amazonaws.com/0/100893/b98e7863-061d-fe04-06c6-94a966397c9d.png)


以下をrun timeをpython2.7として、Lambdaのeditorにコピペしてください。
roleに必要な権限は 標準的なLambda実行=lamda_basic_executionだけで構いません。
rssのreport timeのPST/PDTを考慮に入れました。

```py:SHD_check.py
################################
# Check AWS Service Health Dashboard
#  Someting item post within 1-hour
###############################
import xml.etree.ElementTree as ET
import urllib2
import sys
import json
import time
import datetime
import boto3

#Slack_Webhook
SLACK_URL='YOUR_SLACK_URL'

#For setup.json
BUCKET='YOUR_BUCKET'
S3_FILE='S3_FILE'
LOCAL_SETUP_FILE='/tmp/SHD_setup.json'

# Check duration interval
DURATION = 500

# Initial global setup
s3 = boto3.client('s3')
now = datetime.datetime.now()

################################
# Slack post
################################
def post2Slack(msg):
    try:
        payload = {
            "text":msg
        }
        payload_json = json.dumps(payload)
        req = urllib2.Request(SLACK_URL, data=payload_json)
        res = urllib2.urlopen(req)

        return

    except Exception as e:
        print 'Slack Post Error'
        print e.message
        
###############################
# Serup file Download from S3
###############################
def read_Setup():
    try:
        s3.download_file(BUCKET, S3_FILE, LOCAL_SETUP_FILE)
        f = open(LOCAL_SETUP_FILE, "r")
        setfile = json.load(f)
        f.close()

        return setfile

    except Exception as e:
        print "Setup file init Error"
        print e.message
        exit()

###############################
# consider timezone
#   rss report timezone is PDT/PST
#   return changetime
###############################
def changeToUTC(pubtime, tz):
    if tz == "PDT":
        diff = 7
    elif tz == "PST":
        diff = 8
    else:
        diff = 0

    pubtime += datetime.timedelta(hours = diff)

    return pubtime


###############################
# rss_update check
#   No update:NULL
#   Update : rss description filed
#-----------------------------------
# Item filed: 0:tile, 1:link, 2:pubDate, 3:guid, 4:description
###############################
def rss_update(xml_path):
    try:
        feed = ET.fromstring(urllib2.urlopen(xml_path).read())

        for item in feed.iter('item'):
            description = item[4].text
            pubdate = item[2].text

            if 'None' != pubdate:
                date = pubdate.split(',')
                element_date = date[1].split(' ')
                while element_date.count("") > 0:
                    element_date.remove("")
                pub = element_date[2]+'-'+element_date[1]+'-'+element_date[0]+' '+element_date[3]
                stime = datetime.datetime.strptime(pub, '%Y-%b-%d %H:%M:%S' )
                timezone = element_date[4]
                UTC_pubtime = changeToUTC(stime, timezone)
                
                delta = now - UTC_pubtime
                delta_sec = delta.total_seconds()

                check_time = delta_sec - DURATION
                if check_time < 0:
                    return description

        return

    except Exception as e:
        print "Get RSS error"
        print e.message
        exit()

#################################
#main
#################################
def lambda_handler(event, context):
    J_setfile = read_Setup()
    
    regions = J_setfile.keys()
    try:
        updates={}
        for region in regions:
            services = J_setfile[region].keys()
            for service in services:
                rss = J_setfile[region][service]
                print '%s:%s' % (region, service)

                res = rss_update(rss)
                if res :
                    post_msg = region+':'+service+':'+res
                    post2Slack(post_msg)
                #else:
                    #post_msg = region+':'+service+' No update in this duration'
                    #post2Slack(post_msg)
                

    except Exception as e:
        print "Error in main"
        print e.message
        exit()

```

問題なしルートはコメントアウトしていますが、問題なしを表示したければ、elseルートのコメントアウトを外してください。


#　免責
本投稿は、個人の意見で、所属する企業や団体は関係ありません。
また掲載しているsampleプログラムの動作に関しても保障いたしませんので、参考程度にしてください。