North Detail / ノースディテール

BLOG ブログ

ブログ
CATEGORY
TECH

Ansible でタスクの結果を通知 / Slack と Hangouts Chat

こんにちは。tacckです。

前回 に引き続き、今回も Ansible を使った記事です。
今回は少し視点を変えて、 Playbook で実行したタスクの「通知」をやってみたいと思います。

例えば SlackGoogle Hangouts Chat といったチャットシステムへの通知ができると、「ビルドに失敗した」や「デプロイが終わった」といったステータスを簡単にチームで共有できるようになります。

今回は、 Ansible のモジュールが存在している Slack と、モジュールを自作した Google Hangouts Chat を例に見ていきます。

準備

基本的な準備事項は、こちらの記事を確認お願いします。

また、 SlackGoogle Hangouts Chat 自体を利用できる状態である前提とします。

Slack の場合

Slack は Ansible の公式モジュールがあるので、簡単に利用できます。

https://docs.ansible.com/ansible/latest/modules/slack_module.html

設定項目は色々ありますが、 Webhook で利用するトークン (token) と、表示したいメッセージ (msg) さえ設定すれば利用できます。

タスクとしては、下記のような感じです。

- name: Send message to Slack
  slack:
    token: thetoken/generatedby/slack
    msg: Message from Ansible

Slackアプリ作成ページからアプリを作成し、 "Incoming Webhooks" を有効にすると Webhook URL が発行されます。
このURLの https://hooks.slack.com/services/ 以降の部分が token になります。

Slack を利用されている方は、上記のタスクを追加して試してみてください。下記のような通知を受け取れるはずです。

Google Hangouts Chat の場合

さて、次は Google Hangouts Chat です。こちらは、残念ながら公式モジュールが存在していないので、自分でローカルモジュールを作って対応してみたいと思います。

ローカルモジュール作成

公式モジュールの Typetalk のコードをベースに作ってみます。

https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/notification/typetalk.py

ファイルは、 library/hangouts_chat.py という名前で作成しましょう。
( hangouts_chat がそのままモジュール名となります。)

#!/usr/bin/python
# -*- coding: utf-8 -*-
#

from __future__ import absolute_import, division, print_function
from ansible.module_utils.urls import fetch_url, ConnectionError
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.basic import AnsibleModule
import json
__metaclass__ = type


ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}


DOCUMENTATION = '''
---
module: hangouts_chat
version_added: "2.9"
short_description: Send a message to Hangouts Chat
description:
  - Send a message to Hangouts Chat using Hangouts Chat API
options:
  space_id:
    description:
      - Path parameter parent space id
    required: true
  key:
    description:
      - Query parameter key
    required: true
  token:
    description:
      - Query parameter token
    required: true
  thread_key:
    description:
      - Query parameter threadKey
    required: false
  msg:
    description:
      - message
    required: true
requirements: [ json ]
author: "Kihara, Takuya (@tacck)"
'''

EXAMPLES = '''
- hangouts_chat:
    space_id: SPACE_ID
    key: WEBHOOK_KEY
    token: WEBHOOK_TOKEN
    thread_key: THREAD_KEY
    msg: install completed
'''

HANGOUTS_CHAT_ENDPOINT = 'https://chat.googleapis.com/v1/spaces'


def do_request(module, url, text, headers=None):
    data = json.dumps(text)
    if headers is None:
        headers = dict()
    headers = dict(headers, **{
        'User-Agent': 'Ansible/hangouts_chat module',
    })
    r, info = fetch_url(module, url, data=data, headers=headers)
    if info['status'] != 200:
        exc = ConnectionError(info['msg'])
        exc.code = info['status']
        raise exc
    return r


def send_message(module, space_id, key, token, thread_key, msg):
    """
    send message to hangouts_chat
    """
    try:
        url = f'{HANGOUTS_CHAT_ENDPOINT}/{space_id}/messages?key={key}&token={token}&threadKey={thread_key}'
        headers = {
            'Content-Type': 'application/json; charset=UTF-8'
        }
        do_request(module, url, {'text': msg}, headers)
        return True, ''
    except ConnectionError as e:
        return False, e


def main():
    module = AnsibleModule(
        argument_spec=dict(
            space_id=dict(required=True),
            key=dict(required=True),
            token=dict(required=True),
            thread_key=dict(),
            msg=dict(required=True),
        ),
        supports_check_mode=False
    )

    if not json:
        module.fail_json(msg="json module is required")

    space_id = module.params["space_id"]
    key = module.params["key"]
    token = module.params["token"]
    thread_key = module.params["thread_key"]
    msg = module.params["msg"]

    res, error = send_message(module, space_id, key, token, thread_key, msg)
    if not res:
        module.fail_json(
            msg='fail to send message with response code %s' % error.code)

    module.exit_json(changed=True, thread_key=thread_key, msg=msg)


if __name__ == '__main__':
    main()

色々とありますが、必要なのはパラメータに何が使えるかなので、そちらを紹介していきます。

パラメータ名必須意味
space_idHangouts Chat の「チャットルーム」を表すID
keyチャットルームで作成した 着信 Webhook の固有キー
tokenチャットルームで作成した 着信 Webhook の固有トークン
thread_keyチャットルーム内のスレッドを表す名前 (ユーザーが自由に定義可能)
msg送信したいテキストメッセージ

Google Hangouts Chat のチャットルームで "Webhook を設定" から Webhook を自由に追加することができます。
追加すると、下記のような URL が発行されるので、こちらを上記のパラメータに当てはめていきます。

https://chat.googleapis.com/v1/spaces/[space_id]/messages?key=[key]&token=[token]

Google Hangouts Chat のチャットルームにはスレッドという概念があるのですが、 thread_key に名前を設定しておくと、通知をすべて同じスレッドにまとめてくれるようになります。
(逆に、設定しない場合には常に新しくスレッドが作られてしまうので、とても読みにくい状態になります。)

Hangouts Chat API の仕様については、 Google のドキュメントを参照してください。

https://developers.google.com/hangouts/chat/quickstart/incoming-bot-python

https://developers.google.com/hangouts/chat/reference/rest/v1/spaces.messages/create

通知の実行

では、こちらのモジュールを使って通知を送ってみましょう。
下記のようにタスクを追加します。

- name: Send message to Slack
  hangouts_chat:
    space_id: [space_id]
    key: [key]
    token: [token]
    thread_key: SAMPLE
    msg: Message from Ansible

こちらを実行すると、下記のような通知を受け取れるはずです。

このような感じで、チャットシステムへの通知モジュールは比較的簡単に作ることができます。


API連携のできるものなら同じ仕組みでデータを送れるので、色々と活用できるかもしれません。
ぜひみなさんも試してみてください。

tacck
WRITER:tacck
元 技術推進Group Group Leader

現在は株式会社ノースディテールを離れて、
エバンジェリストとして技術啓蒙や勉強会の開催、各種プロジェクトに参画しています。
機会があれば、ノースディテールでのプロジェクトに参加できればと思っています。

言語は問わずに対応しますが、心はPHPer。
フロントエンド・バックエンド・インフラ・スマホアプリなどを、
「垣根を超えて」どう作るか、を考えるのが好きです。

好きなフィギュアスケートの技はスプレッド・イーグル。
主な記事 一覧へ

一覧へ

IS 501383 / ISO 27001