HubotにOpsWorksのDeploymentを監視させてSlackに流す

# 経緯

- OpsWorks（Chef）の実行時間は長い
- 完了はできるだけ早く知りたい
- 誰かが操作したら教えて欲しい
- つまり、操作の通知の機能が欲しい

## OpsWorksには通知の機能がない
インスタンス間のオーケストレーション的な通知はあるんですが、SNSでイベント通知的な機能がない。

# 作ってみた
各操作の情報は `OpsWorks:DescribeDeployments` で取れる。 Deploymentsと書いてるけどCommandsも取れるので、これを監視して新しいのが来たら通知する感じ。

## 依存ライブラリ
- aws-sdk (2.1.42)
- cron (1.0.9)

※()内は実際に使用しているバージョンでそれでしか動かないわけではないです

## 必要なIAM権限

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "opsworks:Describe*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
```

## Hubot Script
~~`stack_id`や~~ `channel`、cron周期やメッセージ内容は適宜変更してください。
IAM Roleが付与されたEC2で動く前提なので、そうではない場合はcredentialの指定が必要です。
あと、 `:opsworks:` はカスタム絵文字なのでSlack標準では無いです。

```coffeescript:opswork-deployment-notify.coffee
# Description
#   OpsWorks deployment notify
#
# Configuration:
#   None
#
# Commands:
#   None
#
# Author:
#   Masashi Terui

cron = require('cron').CronJob
AWS = require('aws-sdk');
opsworks = new AWS.OpsWorks({region: 'us-east-1'});
channel = '#your-channel'

module.exports = (robot) ->
  new cron '*/10 * * * * *', () =>
    before = Math.floor(new Date().getTime() / 1000) - 10
    opsworks.describeStacks {}, (err, stacks) ->
      if err
        console.log(err, err.stack)
      else
        for stack in stacks.Stacks
          opsworks.describeDeployments {StackId: stack.StackId}, (err, deploys) ->
            if err
              console.log(err, err.stack)
            else
              for deploy in deploys.Deployments
                created_at = Math.floor(new Date(deploy.CreatedAt).getTime() / 1000)
                if deploy.CompletedAt == ""
                  completed_at = 0
                else
                  completed_at = Math.floor(new Date(deploy.CompletedAt).getTime() / 1000)
                if before <= created_at
                  data = deploy
                  opsworks.describeInstances {InstanceIds: data.InstanceIds}, (err, instances) ->
                    if err
                      console.log(err, err.stack)
                    else
                      ins_list = []
                      for instance in instances.Instances
                        ins_list.push "- #{instance.Hostname} (#{instance.PrivateIp})"
                      msg = "
:opsworks: 開始しました \n
```\n
ID: #{data.DeploymentId} \n
Command: #{data.Command.Name} \n
Comment: #{data.Comment} \n
[Instances] \n#{ins_list.join('\n')} \n
```\n
"
                      robot.send {room: channel}, msg
                if before <= completed_at
                  data = deploy
                  opsworks.describeInstances {InstanceIds: data.InstanceIds}, (err, instances) ->
                    if err
                      console.log(err, err.stack)
                    else
                      ins_list = []
                      for instance in instances.Instances
                        ins_list.push "- #{instance.Hostname} (#{instance.PrivateIp})"
                      emoji = ':ok:'
                      emoji = ':ng:' if deploy.Status == "failed"
                      msg = "
:opsworks: 終了しました #{emoji} \n
```\n
ID: #{data.DeploymentId} \n
Status : #{data.Status} \n
Command: #{data.Command.Name} \n
Comment: #{data.Comment} \n
[Instances] \n#{ins_list.join('\n')} \n
```\n
"
                      robot.send {room: channel}, msg
  , null, true, "Asia/Tokyo"
```

ソース汚くてごめんなさい。。。
でも、これで快適なOpsWorks生活に一歩近づきました。