なんかAlexa skillを作ってみたかったので、手っ取り早くpythonでやってみた。
ただ残念なことに現時点で招待リクエストを出しているが、未だに反応がないので現物で試せていない。
仕方ないので、現状developerコンソールでの確認で止まっている。
また今回はMackerel skillなので公開しない、開発用のプライベートスキルとする。
##1. 今回使うもの
Serverlessのフレームワーク
Lambda + API Gateway を勝手によしなにしてくれる
flask-ask
Document
Alexaスキルを作るのに、いろいろwrapしてくれる便利なもの
pyenv
python環境構築用
その他クライアント環境
OS : Mac OS Sierra 10.12.6
aws cli : aws-cli/1.11.182 Python/2.7.10 Darwin/16.7.0 botocore/1.7.40
python : 2.7.10
事前にAWSアカウントを取得して、access key / secret key を取得しているとする
*developerアカウントも含む
##2. 準備
pyenvインストール/設定/必要パッケージインストール
$mkdir mackerel_skill
$cd mackerel_skill
$sudo pip install virtualenv
$virtualenv venv
$source venv/bin/activate
(venv) jwheeler:mackerel_skill$ pip install flask-ask zappa requests awscli
##3. Lambda functionの作成
"""
This mackerel skill with buildt mackerel api.
The Intent Schema is "MackerelStatus", Slots is "Hostname".
The Intent Schema is "MackerelChangeStatus", Slots is "Hostname", "Condition".
The Intent Schema is "AlertStatus", Slots is no exist.
"""
import json
import logging
import os
import requests
from operator import itemgetter
from flask import Flask
from flask_ask import Ask, statement
# --------------- Define vars ----------------------------------------
apikey = os.environ['MACKEREL_APIKEY']
mackerel_target_dict = {}
# --------------- Functions that control the skill ------------------
class MackerelApi():
def __init__(self, apikey):
self.mc_id_info = self.get_mc_id(apikey)
self.hostname_info = self.get_mc_hostname(self.mc_id_info, apikey)
self.register_host_info(self.mc_id_info, self.hostname_info)
def get_mc_id(self, apikey):
"""
return mackerel host id list
"""
headers = {'X-Api-Key': apikey}
param = {'status' : ['working', 'standby', 'maintenance'] }
r = requests.get('https://mackerel.io/api/v0/hosts', headers=headers, params=param)
j_values = r.json()
host_list = j_values["hosts"]
host_id_list = []
for x in list(host_list):
host_id_list.append(str(x["id"]))
return host_id_list
def get_mc_hostname(self, lt_mc_id, apikey):
"""
first arg specific mackerel id
return mackerel host name list
"""
hos_name_list = []
for x in list(lt_mc_id):
headers = {'X-Api-Key': apikey}
r = requests.get(('https://mackerel.io/api/v0/hosts/%s' % x ), headers=headers)
j_values = r.json()
host_info = j_values["host"]
hos_name_list.append(str(host_info["name"]).rstrip(".ppsys.jp"))
return hos_name_list
def register_host_info(self, lt_mc_id, lt_host_name):
"""
register mackerel { key hostname : value mackerel_id }
first arg mackerel_id list, second arg hostname list
"""
merge_list = zip(lt_host_name, lt_mc_id)
for x, y in merge_list:
mackerel_target_dict[str(x)] = str(y)
mackerel_target_dict[str(y)] = str(x)
### all mackerel all id and host name set ###
mackerel_target_dict["mc_id_all"] = tuple(lt_mc_id)
mackerel_target_dict["mc_name_all"] = tuple(lt_host_name)
def get_mc_status(self, mc_id, apikey):
"""
first arg specific mackerel id
return mackerel host status
"""
### all status get param ###
if str(mc_id) == "all":
host_status = {}
for x in self.get_mc_id(apikey):
headers = {'X-Api-Key': apikey}
r = requests.get(('https://mackerel.io/api/v0/hosts/%s' % x ), headers=headers)
j_values = r.json()
host_info = j_values["host"]
lc_value = host_info["status"]
lc_key = self.get_value(str(x))
host_status[lc_key] = lc_value
return host_status
else:
headers = {'X-Api-Key': apikey}
r = requests.get(('https://mackerel.io/api/v0/hosts/%s' % mc_id ), headers=headers)
j_values = r.json()
host_info = j_values["host"]
return host_info["status"]
def get_mc_alert(self, apikey):
"""
get alert and set alert
"""
headers = {'X-Api-Key': apikey}
r = requests.get('https://mackerel.io/api/v0/alerts', headers=headers)
j_values = r.json()
alert_list = j_values["alerts"]
alert_dic = {}
for x in alert_list:
if 'hostId' in x:
alert_dic[str(x['hostId'])] = str(x['status'])
else:
alert_dic[str(x['id'])] = str(x['status'])
return alert_dic
def change_mc_status(self, mc_id, mc_status, apikey):
"""
mackerel status change
first arg specific mackerel id
second arg status standby, working, maintenance, poweroff
"""
### all change param ###
if str(mc_id) == "all":
mackerel_id = self.get_mc_id(apikey)
for x in mackerel_id:
payload = {'status': mc_status}
headers = {'X-Api-Key': apikey, 'content-type': 'application/json'}
url = ('https://mackerel.io/api/v0/hosts/%s/status' % x )
r = requests.post(url, data=json.dumps(payload), headers=headers)
else:
payload = {'status': mc_status}
headers = {'X-Api-Key': apikey, 'content-type': 'application/json'}
url = ('https://mackerel.io/api/v0/hosts/%s/status' % mc_id )
r = requests.post(url, data=json.dumps(payload), headers=headers)
# --------------- Main handler ------------------
app = Flask(__name__)
ask = Ask(app, '/')
logger = logging.getLogger()
@ask.launch
def launch():
return mackerel()
@ask.intent("MackerelStatus")
def mackerel_status(Hostname):
mc = MackerelApi(apikey)
name = str(Hostname).replace("zero", "0").replace("one", "1").replace("two", "2").strip()
name = name.decode('utf-8')
name = name.replace(" ","")
if str(name) in mackerel_target_dict["mc_name_all"]:
host_id = mackerel_target_dict[name]
status_list = mc.get_mc_status(str(host_id), apikey)
speech = "{host} is {status}" \
.format(host=name, status=status_list)
else:
speech = "{host} is not exist".format(host=name)
logger.info('speech = {}'.format(speech))
return statement(speech).simple_card('Hi', speech)
@ask.intent("AlertStatus")
def mackerel_alert():
mc = MackerelApi(apikey)
alert_list = mc.get_mc_alert(apikey)
if alert_list:
for k, v in alert_list.items():
if k in mackerel_target_dict:
speech = 'Now {host} is {condition}'.format(host=k, condition=v)
else:
speech = 'Now {condition} is exist'.format(condition=v)
else:
speech = "Now all ok"
logger.info('speech = {}'.format(speech))
return statement(speech).simple_card('Hi', speech)
@ask.intent("MackerelChangeStatus")
def mackerel_change_status(Hostname, Condition):
mc = MackerelApi(apikey)
name = str(Hostname).replace("zero", "0").replace("one", "1").replace("two", "2").strip()
name = name.decode('utf-8')
name = name.replace(" ","")
if str(name) in mackerel_target_dict["mc_name_all"] or str(name) == "all":
host_id = (lambda target : mackerel_target_dict[name] if str(name) in mackerel_target_dict["mc_name_all"] else "all" )
target_Condition = str(Condition).decode('utf-8').replace(" ","")
if target_Condition == "working":
mc.change_mc_status(host_id(name), "working", apikey)
speech = "{host} is {status}" \
.format(host=name, status=target_Condition)
elif target_Condition == "maintenance":
mc.change_mc_status(host_id(name), "maintenance", apikey)
speech = "{host} is {status}" \
.format(host=name, status=target_Condition)
elif target_Condition == "standby":
mc.change_mc_status(host_id(name), "standby", apikey)
speech = "{host} is {status}" \
.format(host=name, status=target_Condition)
elif target_Condition == "poweroff":
mc.change_mc_status(host_id(name), "poweroff", apikey)
speech = "{host} is {status}" \
.format(host=name, status=target_Condition)
else:
speech = "{target_Condition} is invalid status".format(target_Condition=target_Condition)
else:
speech = "Not found {name}".format(name=name)
logger.info('speech = {}'.format(speech))
return statement(speech).simple_card('Hi change status', speech)
環境変数をコンソールから設定
"MACKEREL_APIKEY"にmackerel側のaccess keyを設定
KMSを使用する場合は、事前に準備したKMS keyを使用
##3. IAMユーザーの作成/credential設定
ユーザー名 : zappa-mackerel
ユーザー作成後、access key / secret key を書き留める
credential/region設定
$aws configure
##4. deploy(zappa使用)
##initialize
$zappa init
##インタラクティブに設定を聞かれるが、ここではdefaultの設定を使うので"enter"を押せばOK
$zappa deploy dev
##自動的に下記のようなURL(API Gateway)が払い出されるので、書き留める
https://XXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/dev
その他、役に立つコマンド
##lambdaを呼び出してエラーになっている箇所を調べるのに利用
$zappa tail dev
##4. alexa skill 作成(developer console)
alexaのリストから”カスタム対話モデル”を選び、実際のスキルを作成。
今回は英語で作成。
スキル名 : mackerel skill
呼び出し名 : mackerel skill
今回できる内容は以下の3つ
インテント MackerelStatus - 現在のステータス確認
インテント MackerelChangeStatus - ホストステータスの変更
インテント AlertStatus - アラート確認
対象サーバ名
ardb001 / ardb002
サンプル発音の定義によるが、上記形式に合えば”rrap001”とか"rrdb002"とかでも対応される。
ここら辺は、自分の環境に合わせたホスト名でいろいろ設定してみれば対応も広がる。
対話モデルで作成
インテントスキーマ
{
"intents": [
{
"slots": [
{
"name": "Hostname",
"type": "LITERAL"
}
],
"intent": "MackerelStatus"
},
{
"slots": [
{
"name": "Hostname",
"type": "LITERAL"
},
{
"name": "Condition",
"type": "LITERAL"
}
],
"intent": "MackerelChangeStatus"
},
{
"intent": "AlertStatus"
}
]
}
サンプル発話
MackerelStatus Host status {a r d b zero zero one|Hostname}
MackerelStatus Host status {a r d b zero zero two|Hostname}
MackerelChangeStatus change status {a r d b zero zero one|Hostname} {working|Condition}
MackerelChangeStatus change status {a r d b zero zero one|Hostname} {power off|Condition}
MackerelChangeStatus change status {a r d b zero zero one|Hostname} {maintenance|Condition}
MackerelChangeStatus change status {a r d b zero zero one|Hostname} {standby|Condition}
MackerelChangeStatus change status {all|Hostname} {working|Condition}
MackerelChangeStatus change status {all|Hostname} {power off|Condition}
MackerelChangeStatus change status {all|Hostname} {maintenance|Condition}
MackerelChangeStatus change status {all|Hostname} {standby|Condition}
AlertStatus mackerel alert
エンドポイント設定
endpointは"HTTPS"を選択し、先ほど払い出されたAPIGatewayのURLを設定
SSL証明書設定
テスト
アラート確認
レスポンス
##5. やり残し
やはり、現物のechoが手元にないので試してみないとだめ。
サーバのホスト名とか日本語じゃないので、今回は英語でスキル作成したが日本語も試してみたい。
あとは、mackerelのhost_idとホスト名の取得のために毎度Mackerel側のAPIを叩いているので、サーバ台数が増えたらどこかに保存した方がいいかも(ElastiCacheなど)。