chaliceでBacklogの通知をSlackに投稿する

# はじめに
Backlog、Slackをプロジェクトで使用しています。
Slackは使い始めたばかりなのですが、うまくBacklogと連携させたくて、
chaliceを使って、まずは通知から初めてみました。

# やりたいこと
Backlogが更新された時に、Slackに通知させたい、がやりたいことです。
Backlogには、WebHookが設定できるので、WebHook URLに作成したAPIを設定します。
Slackには、incoming WeHookがあるので、そこを作成したAPIで叩く感じです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/63125/310adae8-3c2e-0a4a-5672-72e0cb3fa041.png)

Backlogからはとりあえず、

- 課題の追加
- 課題の更新
- 課題にコメント
- Subversionコミット

の通知ができればいいかと思い、それらを対象としました。

# 実行環境

- AWS
  - Lambda(Python2.7)
  - API gateway

# chaliceとは
[chalice](https://github.com/aws/chalice)とはAmazon公式の Python ライブラリです。
AWS CLIが実行可能な環境であれば、以下のように、瞬殺でHello worldできます。

```:command
$ chalice new-project helloworld
$ cd helloworld/
$ ll
total 8
drwxr-xr-x 6 centos centos 204 Oct 17 23:41 .
drwxr-xr-x 4 centos centos 136 Oct 17 23:41 ..
drwxr-xr-x 3 centos centos 102 Oct 17 23:41 .chalice
-rw-r--r-- 1 centos centos  37 Oct 17 23:41 .gitignore
-rw-r--r-- 1 centos centos 735 Oct 17 23:41 app.py
-rw-r--r-- 1 centos centos   0 Oct 17 23:41 requirements.txt
$ chalice deploy
Creating role: helloworld-dev
Creating deployment package.
Creating lambda function: helloworld-dev
Initiating first time deployment.
Deploying to API Gateway stage: api
https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/
$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/
{"hello": "world"}
```
詳しい使い方は[こちら](http://chalice.readthedocs.io/en/latest/)

# Slackへの通知
pythonのライブラリである「slackweb」を使用します。
[attachment](https://api.slack.com/docs/message-attachments)は自力で作成する必要がありますが、非常に簡単に、slack通知が可能です。

```py:slack_notify
slack_url = "https://hooks.slack.com/services/xxxxxxxx/xxxxxxxx/xxxxxxxxxxxxx"
slack = slackweb.Slack(url=slack_url)
attachments[]
attachment = {
    "author_name": user,
    "title": project,
    "title_link": project_url,
    "pretext": attachment_pretext,
    "fields": field_list}
attachments.append(attachment)
slack.notify(attachments=attachments, channel=CHANNEL_NAME, username=USER_NAME)
```



# 困ったこと
1. BacklogのOutbound WebHook
公式のドキュメントを探したのですが、見つからず、
どんなフォーマットなのかは、実際に通知させてみるしかありませんでした。
WebHookの内容をみるために、実行テストが利用できるので、それを利用しています。。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/63125/06c68184-b64d-78d0-d379-e530856cda71.png)

例えば、課題の通知は、こんなフォーマットでした。
typeの数値で、通知の種類が変わるらしい。タスク追加が1、タスクの更新が2、みたいな。

```json:add_task
{
    "content": {
        "category": [
            {
                "name": "Category1",
                "displayOrder": null,
                "id": null
            },
            {
                "name": "Category2",
                "displayOrder": null,
                "id": null
            }
        ],
        "startDate": "",
        "estimatedHours": null,
        "description": "test description",
        "versions": [
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "Version0.1"
            },
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "Version0.2"
            },
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "Version1.0"
            }
        ],
        "id": 100,
        "actualHours": null,
        "summary": "test issue",
        "priority": {
            "name": "",
            "id": null
        },
        "assignee": null,
        "status": {
            "name": "In Progress",
            "id": 2
        },
        "milestone": [
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "prototype release"
            },
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "alpha release"
            },
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "beta release"
            },
            {
                "startDate": "null",
                "archived": "false",
                "description": "",
                "projectId": null,
                "releaseDueDate": "null",
                "id": null,
                "displayOrder": null,
                "name": "product release"
            }
        ],
        "issueType": {
            "color": "null",
            "projectId": null,
            "name": "Bug",
            "displayOrder": null,
            "id": 400
        },
        "key_id": 100,
        "customFields": [],
        "parentIssueId": null,
        "resolution": {
            "name": "",
            "id": null
        },
        "dueDate": ""
    },
    "created": "2017-10-19T14:29:53Z",
    "project": {
        "archived": false,
        "name": "TestProject",
        "chartEnabled": false,
        "subtaskingEnabled": false,
        "id": 100,
        "projectKey": "TEST"
    },
    "notifications": [],
    "createdUser": {
        "roleType": 1,
        "name": "y-kawahara",
        "userId": null,
        "nulabAccount": null,
        "mailAddress": null,
        "id": 13079
    },
    "type": 1,
    "id": 10
}
```

2. chaliceで外部ライブラリを使用する方法
chaliceは、[ドキュメント](http://chalice.readthedocs.io/en/latest/topics/packaging.html)によると以下のようなフォルダ構成が推奨されています。

```:recommended_folder_structure
.
├── app.py
├── chalicelib　　　　　　　←独自実装のライブラリ
│   ├── __init__.py
│   └── utils.py
├── requirements.txt
└── vendor　　　　　　　　　←サードパーティのライブラリ
    └── internalpackage
        └── __init__.py
```

つまり、slackwebなんかはvendorフォルダ配下にダウンロードしておく必要があります。
もちろんrequirementも必要です。
最初、`pip download slackweb`でダウンロードしてきた`slackweb-1.0.5.tar.gz`を格納していたのですが、
wheelファイルにビルドする必要があるみたいです。

```:create_whl_file
pip install wheel

cd vendor
pip download slackweb
pip wheel slackweb-1.0.5.tar.gz
```

最終的な構成は以下になりました。

```
.
├── README.md
├── app.py
├── requirements.txt
└── vendor
    └── slackweb-1.0.5-py2-none-any.whl
```

# デバッグ
デバッグは、`chalice local`コマンドとngrokを使用しました。
ngrokはサブドメインを払い出してくれて、ローカルのPCの特定ポートに引き込んでくれるサービスです。
以下のコマンドで、`https://7e9a9046.ngrok.io`へのリクエストを、
localhostのポート8000で受信できます。払い出されるサブドメインは毎回変わります。

```
$ ngrok http 8000
grok by @inconshreveable                                                                                                                                                           (Ctrl+C to quit)
                                                                                                                                                                                                    
Session Status                online                                                                                                                                                                
Version                       2.2.8                                                                                                                                                                 
Region                        United States (us)                                                                                                                                                    
Web Interface                 http://127.0.0.1:4040                                                                                                                                                 
Forwarding                    http://7e9a9046.ngrok.io -> localhost:8000                                                                                                                            
Forwarding                    https://7e9a9046.ngrok.io -> localhost:8000                                                                                                                           
                                                                                                                                                                                                    
Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                                           
                              0       0       0.00    0.00    0.00    0.00                  
```

chaliceのほうは`chalice local`コマンドでローカルにポート8000で受け付けてくれます。

```
$ chalice local
Serving on localhost:8000
```

これで、backlogのWebHookに`https://7e9a9046.ngrok.io`を設定すればローカルPCの8000ポートにリクエストが飛んできて、chaliceでリクエストを受けることができる
ということになります。
毎回`chalice deploy`で動作確認するのも大変なので、最初はこちらのほうが楽でした。

[Github](https://github.com/yutako0217/backlog2slack)にアップしてみました！