メールの受け口にAmazon SESを使うようリファクタリングしました [2020/04/26] → 記事はこちら
はじめに
メールしか使えない環境からGitHubに新規issueを追加できるようになると便利、というニッチな個人的ニーズがあり、やってみました。
方針
まず、メールで何らかのプログラムをキックする方法を考えます。自由に使えるドメインとメールサーバがあれば何でもできそうですが、そうでない場合、メール受信を起点とするWEBサービスを作る選択肢ってそうたくさんはなさそうです。
というわけで、今回は、IFTTTに用意されている「Email」サービスを使ってみます。IFTTTのトリガーとして使うことができて、IFTTTアカウントに紐付けられたメールアドレスからIFTTTの所定のメールアドレス宛にメールを送ることで、IFTTTのアクションをキックできます。
IFTTTの「GitHub」サービスを使ったissue追加の課題
IFTTTには「GitHub」サービスも用意されているので、トリガーを「Email」、アクションを「GitHub」にすれば確かに目的のレシピは作れます。この場合、IFTTTに送りつけたメールのタイトルと本文で新規issueを追加できます。
が、、、これを実際に使ってみると、追加された新規issueにはメール本文の改行がなぜか反映されません。
GitHubのissueはMarkdownが使えるところが便利ポイントなので、改行が消えてしまうこのレシピでは使い物になりません。
というわけで「Webhooks」サービス
IFTTTのサービスの中にニーズを満たすものがないときは「Webhooks」サービスの出番です。新規issueの追加はGitHub REST API v3を使えばできるので、トリガーを「Email」、アクションを「Webhooks」にして、自作のWEBサービスでGitHubへissueを追加することにします。
IFTTTのレシピにするとこんな感じ。
実装
小さなWEBサービスなので、Amazon API Gateway+AWS Lambdaと、この環境向けのPythonフレームワークであるchaliceを使って簡単に作ってしまいます。
説明をすっ飛ばして、コードは以下だけ。
中で使っているいくつかの環境変数(os.environ['hoge']
)をchaliceの設定ファイル(.chalice/config.json
)で設定する必要はありますが、chalice deploy
のコマンド一発でWEBサービスが動き出します。API GatewayやLambdaの管理コンソールに触る必要は全くありません。
from chalice import Chalice
import logging, os, json
import urllib.request
# setup logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# setup chalice
app = Chalice(app_name='hook2issue')
@app.route('/', methods=['POST'])
def index():
json_body = app.current_request.json_body
logger.info("request body: {}".format(json_body))
key = json_body['key']
subject = json_body['subject']
body = json_body['body']
if key != os.environ['IFTTT_WEBHOOKS_KEY']:
logger.warning("wrong key: {}".format(key))
else:
url = 'https://api.github.com/repos/{}/{}/issues?access_token={}'.format(
os.environ['GITHUB_OWNER'],
os.environ['GITHUB_REPOSITORY'],
os.environ['GITHUB_ACCESS_TOKEN']
)
headers = {
'Content-Type': 'application/json'
}
body = {
'title': subject,
'body': body
}
req = urllib.request.Request(
url,
data=json.dumps(body).encode('utf-8'),
method='POST',
headers=headers
)
try:
with urllib.request.urlopen(req) as res:
logger.info(res.read().decode("utf-8"))
except Exception as e:
logger.exception("Error in request: %s", e)
# 200OK
return {}
ちなみに、上記の自作WEBサービスに合わせて、IFTTTの「Webhooks」は以下のような感じで設定します。
これで所望の動きをしてくれるのですが、ハマリポイントが一点だけ。
「Body (optional)」の注意書きに、Surround any text with "<<>>" to escape the content
とあるのですが、きちんと文字列をエスケープするには"<<<>>>"
で囲ってあげる必要があります。運良くこのことが書かれたWEB記事を見つけられたので良かったですが、もう少しで諦めるところでした。。。
おわりに
メールはもはやレガシーな手段になってしまった感がありますが、それでもなお、メールしかまともに使えない環境もあるにはあります。メールを起点にしたWEBサービス連携の一つのパターンとして、他にも使えるものが何か作れそうです。