てくなべ (tekunabe)

ansible / network automation / 学習メモ

StackStorm で Slack 投稿時に特定のアクションをするルールを作ってみた

■ はじめに

以前のエントリで、 StackStorm の Slack Pack の Action を利用して、Slack への投稿を試しました。

tekunabe.hatenablog.jp

今回は、Action ではなく、Trigger や Sensor を使って Slack 上のイベントを拾って何かする、という機能を試してみます。


■ ためすこと

Slack の投稿を検出して、以下のようにテキストファイルに出力する、という簡単な処理をためすことにします。

名前: 投稿内容
名前: 投稿内容
名前: 投稿内容
名前: 投稿内容


■ 準備

トークンの取得

連携のためには Slack のトークンが必要です。 Slack Pack の GitHub リポジトリの README に説明があります。 今回は、とにかく簡単に試したかったため、「For testing purposes..」の下りで説明されている方法でトークンを取得しました。

Slack Pack の設定

先程取得したトークンを、st2 pack config slack コマンドで設定します。

今回は以下の値にして、他はデフォルトのままにしました。

設定項目 備考
post_message_action.username akira6592 任意の名前
post_message_action.webhook_url (着信WebフックのURL)
sensor.token (先程控えたトークン)
admin.organization admin 任意の名前

途中、以下のように聞かれますのでエンターを押します。

Do you want to preview the config in an editor before saving? [y]:

設定内容がテキストとしてエディタで開かれるので、確認後エディタを終了します。

最後に

Do you want me to save it? [y]: 

と聞かれるのでエンターを押します。


■ Slack Trigger の仕様調査

後で必要になる情報を調べておきます。

Slack Pack に含まれる Trigger を調べます。

[ec2-user@ip-172-31-4-147 ~]$ st2 trigger list -p slack
+---------------+-------+------------------------------------------------------+
| ref           | pack  | description                                          |
+---------------+-------+------------------------------------------------------+
| slack.message | slack | Trigger which indicates a new message has been       |
|               |       | posted to a channel                                  |
+---------------+-------+------------------------------------------------------+

slack.message という Trigger があり、新着メッセージを拾えるようです。

次に、この Trigger の詳細を調べます。

[ec2-user@ip-172-31-4-147 ~]$ st2 trigger get slack.message
+-------------------+--------------------------------------------------------------+
| Property          | Value                                                        |
+-------------------+--------------------------------------------------------------+
| id                | 5b0b5b426fb12304dc0e014f                                     |
| ref               | slack.message                                                |
| pack              | slack                                                        |
| name              | message                                                      |
| description       | Trigger which indicates a new message has been posted to a   |
|                   | channel                                                      |
| parameters_schema |                                                              |
| payload_schema    | {                                                            |
|                   |     "type": "object",                                        |
|                   |     "properties": {                                          |
|                   |         "text": {                                            |
|                   |             "type": "string"                                 |
|                   |         },                                                   |
|                   |         "timestamp_raw": {                                   |
|                   |             "type": "string"                                 |
|                   |         },                                                   |
|                   |         "user": {                                            |
|                   |             "type": "object"                                 |
|                   |         },                                                   |
|                   |         "channel": {                                         |
|                   |             "type": "object"                                 |
|                   |         },                                                   |
|                   |         "timestamp": {                                       |
|                   |             "type": "integer"                                |
|                   |         }                                                    |
|                   |     }                                                        |
|                   | }                                                            |
| tags              |                                                              |
| uid               | trigger_type:slack:message                                   |
+-------------------+--------------------------------------------------------------+

例えば、投稿内容そのものは text で取得できそうです。

Slack Pack の GitHub リポジトリの README の slack.message trigger に、ペイロードのサンプルが載っていました。

(引用)

{
    "user": {
        "first_name": "Tomaz",
        "last_name": "Muraus",
        "is_owner": false,
        "name": "kami",
        "real_name": "Tomaz Muraus",
        "is_admin": false,
        "id": "U0CCCCC"
    },
    "channel": {
        "topic": "",
        "id": "C0CCCCCC",
        "name": "test"
    },
    "timestamp": 1419164091,
    "timestamp_raw": "1419164091.00005",
    "text": "This is a test message."
}

例えば、投稿者の名前は user.name で取得できそうです。


■ ルールの作成

何が起きたときに何をする、というルールを作成します。

ルールの書き方は、公式ドキュメントの Quick Start の Define a Ruleを参考にしました。後でわかりましたが、ここでの ~/slack.log/home/stanley/slack.log になります。

r_slack.yml

---
name: "r_slack"
pack: "test"
description: "test rule for slack"
enabled: true

trigger:
  type: "slack.message"
  parameters: {}
action:
  ref: "core.local"
  parameters:
    cmd: "echo \"{{trigger.user.name}}: {{trigger.text}}\" >>  ; sync"

続いて、ルールを登録します。

[ec2-user@ip-172-31-4-147 ~]$ st2 rule create r_slack.yml 
+-------------+--------------------------------------------------------------+
| Property    | Value                                                        |
+-------------+--------------------------------------------------------------+
| id          | 5b128cfd6fb12304f47d825c                                     |
| name        | r_slack                                                      |
| pack        | test                                                         |
| description | test rule for slack                                          |
| action      | {                                                            |
|             |     "ref": "core.local",                                     |
|             |     "parameters": {                                          |
|             |         "cmd": "echo "{{trigger.user.name}}:                 |
|             | {{trigger.text}}" >> ~/slack.log ; sync"                     |
|             |     }                                                        |
|             | }                                                            |
| criteria    |                                                              |
| enabled     | True                                                         |
| ref         | test.r_slack                                                 |
| tags        |                                                              |
| trigger     | {                                                            |
|             |     "type": "slack.message",                                 |
|             |     "ref": "slack.message",                                  |
|             |     "parameters": {}                                         |
|             | }                                                            |
| type        | {                                                            |
|             |     "ref": "standard",                                       |
|             |     "parameters": {}                                         |
|             | }                                                            |
| uid         | rule:test:r_slack                                            |
+-------------+--------------------------------------------------------------+

念の為、ルールのリストを確認します。

[ec2-user@ip-172-31-4-147 ~]$ st2 rule list -p test
+--------------+------+---------------------+---------+
| ref          | pack | description         | enabled |
+--------------+------+---------------------+---------+
| test.r_slack | test | test rule for slack | True    |
+--------------+------+---------------------+---------+

無事に登録されたようです。


■ おためし

それではいよいよ試してみます。

Slack の general チャンネルに Hello, Slack! と投稿すると、/home/stanley/slack.log

yokochi: Hello, Slack!

と書き込まれました。

デモ動画 f:id:akira6592:20180603133910g:plain

試した限り、今回利用したトークンでは、いずれかのユーザーが general に投稿したイベントを取得することができるようです。他のチャンネルではイベントを取得することができませんでした。


■ まとめ

簡単な例ですが、Slack Pack の Trigger を利用して、Slack に投稿があったら何かする、というルールの動作の確認ができました。 chatops Pack でも色々できそうなので調べてみたいと思います。

はじめて StackStorm の Action を作ってみた

■ はじめに

2018/05/29 に開催された、Tech Night @ Shiodome # 8の中で「明日からできる、st2のActionの作り方」という発表がありました。 まだActionは作ったことがなかったのですが、これを聞いて簡単なものを作ってみようと思ってやってみました。

speakerdeck.com


■ つくるもの

何でもよかったのですが、GoogleDNS-over-HTTPSを利用して、パラメータ name で与えられた名前のAレコードをリストで返すActionを作ることにしました。


■ コードの準備

まずは、Actionの肝になるコードの準備です。 Packの作成方法がまだ理解不足だったため、default配下にしました。また、環境構築を単純化するため、 requests など別途インストールが必要なものは使わないようにしておきました。名前解決失敗時は空のリストを返すようにしました。

パス:/opt/stackstorm/packs/default/actions/dnsquery.py

from st2common.runners.base_action import Action
import json
import urllib2

class DnsQueryAction(Action):
    def run(self, name):

        url = "https://dns.google.com/resolve?name=" + name

        req = urllib2.Request(url)
        res = urllib2.urlopen(req)
        res_json  = json.loads(res.read())

        if "Answer" in res_json:
            # successed
            records = [v["data"] for v in res_json["Answer"]]
            return (True, records)
        else:
            # failed
            return (False, [])


■ metadataファイルの準備

続いて、Actionがどういうパラメータを持つかなどの情報を定義する、metadataファイルの準備です。YAML形式です。

パス:/opt/stackstorm/packs/default/actions/dnsquery.yml

---
name: "dnsquery"
runner_type: "python-script"
entry_point: "dnsquery.py"
description: "resolve dns query"
enabled: true
parameters:
    name:
        type: "string"
        description: "dns query name for type A"
        required: true


■ Actionの作成

各ファイルを作っただけでは StackStorm はまだ認識しません。st2 action create コマンドで metadata ファイルを元に Action を作成します。

[ec2-user@ip-172-31-4-147 actions]$ st2 action create /opt/stackstorm/packs/default/actions/dnsquery.yml
+-------------+----------------------------------------------------+
| Property    | Value                                              |
+-------------+----------------------------------------------------+
| id          | 5b10a7a96fb12304de15a1d2                           |
| name        | dnsquery                                           |
| pack        | default                                            |
| description | resolve dns query                                  |
| enabled     | True                                               |
| entry_point | dnsquery.py                                        |
| notify      |                                                    |
| parameters  | {                                                  |
|             |     "name": {                                      |
|             |         "required": true,                          |
|             |         "type": "string",                          |
|             |         "description": "dns query name for type A" |
|             |     }                                              |
|             | }                                                  |
| ref         | default.dnsquery                                   |
| runner_type | python-script                                      |
| tags        |                                                    |
| uid         | action:default:dnsquery                            |
+-------------+----------------------------------------------------+

作成できたようなので、 st2 action list のリストで確認します。

[ec2-user@ip-172-31-4-147 actions]$ st2 action list --pack=default
+---------------------+---------+-----------------------------------+
| ref                 | pack    | description                       |
+---------------------+---------+-----------------------------------+
| default.dnsquery    | default | resolve dns query                 |
+---------------------+---------+-----------------------------------+

認識されているようです。


■ 実行

はじめての実行(失敗・・)

それでは、st2 run による単発の実行で試してみます。

[ec2-user@ip-172-31-4-147 actions]$ st2 run default.dnsquery name=www.stackstorm.com
.
id: 5b10a8036fb12304de15a1d4
status: failed
parameters:
  name: www.stackstorm.com
result:
  error: '
    The virtual environment (/opt/stackstorm/virtualenvs/default) for pack "default" does not exist. Normally this is
    created when you install a pack using "st2 pack install". If you installed your pack by some other
    means, you can create a new virtual environment using the command:
    "st2 run packs.setup_virtualenv packs=default"
    '
  traceback: "  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2actions/container/base.py", line 119, in _do_run
    (status, result, context) = runner.run(action_params)
  File "/opt/stackstorm/runners/python_runner/python_runner/python_runner.py", line 143, in run
    raise Exception(msg)
"

おっと・・・、default pack向けの virtualenv が無いためエラーになってしまいました。

エラーメッセージに親切に書かれているコマンドで virtualenv を用意します。

[ec2-user@ip-172-31-4-147 actions]$ st2 run packs.setup_virtualenv packs=default
..
id: 5b10a8cf6fb12304de15a1d7
status: succeeded
parameters:
  packs:
  - default
result:
  exit_code: 0
  result: 'Successfuly set up virtualenv for the following packs: default'
  stderr: 'st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Setting up virtualenv for pack "default" (/opt/stackstorm/packs/default)
    st2.actions.python.SetupVirtualEnvironmentAction: INFO     Virtualenv path "/opt/stackstorm/virtualenvs/default" doesn''t exist
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Creating virtualenv for pack "default" in "/opt/stackstorm/virtualenvs/default"
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Creating virtualenv in "/opt/stackstorm/virtualenvs/default" using Python binary "/opt/stackstorm/st2/bin/python"
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Running command "/opt/stackstorm/st2/bin/virtualenv -p /opt/stackstorm/st2/bin/python --no-download /opt/stackstorm/virtualenvs/default" to create virtualenv.
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Installing base requirements
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Installing requirement six>=1.9.0,<2.0 with command /opt/stackstorm/virtualenvs/default/bin/pip install six>=1.9.0,<2.0.
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    No pack specific requirements found
    st2.actions.python.SetupVirtualEnvironmentAction: DEBUG    Virtualenv for pack "default" successfully created in "/opt/stackstorm/virtualenvs/default"
    '
  stdout: ''

無事に /opt/stackstorm/virtualenvs/default として virtualenvが作成されたようです。

もう一度実行(名前解決成功)

気を取り直してもう一度 Action を実行します。

[ec2-user@ip-172-31-4-147 actions]$ st2 run default.dnsquery name=www.stackstorm.com
.
id: 5b10b23e6fb12304de15a1f0
status: succeeded
parameters:
  name: www.stackstorm.com
result:
  exit_code: 0
  result:
  - 104.28.16.123
  - 104.28.17.123
  stderr: ''
  stdout: ''

無事、status: succeeded となり、2つのIPアドレスが返ってきました。

もう一度実行(名前解決失敗)

今度は、名前解決ができないパターンで試します。

[ec2-user@ip-172-31-4-147 actions]$ st2 run default.dnsquery name=hogehogehoge.stackstorm.com
.
id: 5b10b3016fb12304de15a1f3
status: failed
parameters:
  name: hogehogehoge.stackstorm.com
result:
  exit_code: 0
  result: []
  stderr: ''
  stdout: ''

無事、status: failed となり、空のリストが返ってきました。


■ まとめ

非常に簡単な例ではありますが、初めてPythonによる Action を作成して動作を確認してみました。今のところは、そこまで覚えることも多くない印象です。 基本的には既存の Action を中心に使っていきますが、それでは実現できないものについては、カスタムのActionを作って見たいと思います。

参考資料

https://docs.stackstorm.com/actions.html#writing-custom-python-actions

StackStorm 経由で Slack に投稿する(準備 ~ st2 run編)

■ はじめに

StackStorm には Slack と連携するための Pack が用意されています。 この記事では、環境の準備と、簡単な動作確認としてSlackへの手動投稿を試します。


■ 環境の準備

StackStorm 本体のインストール

今回は 以下の方法でインストールします。 tekunabe.hatenablog.jp 2.7.2 というバージョンがインストールされました。

Slack Pack のインストール

まず st2 login コマンドでログインします。

[ec2-user@ip-172-31-4-147 ~]$ st2 login testu
Password:
Logged in as testu

Note: You didn't use --write-password option so the password hasn't been stored in the client config and you will need to login again in 24 hours when the auth token expires.
As an alternative, you can run st2 login command with the "--write-password" flag, but keep it mind this will cause it to store the password in plain-text in the client config file (~/.st2/config).

続いて st2 pack install slack コマンドで Slack Pack をインストールします。

[ec2-user@ip-172-31-4-147 ~]$ st2 pack install slack

For the "slack" pack, the following content will be registered:

rules     |  0
sensors   |  1
triggers  |  0
actions   |  113
aliases   |  0

Installation may take a while for packs with many items.

        [ succeeded ] download pack
        [ succeeded ] make a prerun
        [ succeeded ] install pack dependencies
        [ succeeded ] register pack

+-------------+------------------------------------------------+
| Property    | Value                                          |
+-------------+------------------------------------------------+
| name        | slack                                          |
| description | st2 content pack containing slack integrations |
| version     | 0.10.0                                         |
| author      | StackStorm, Inc.                               |
+-------------+------------------------------------------------+

Slack Pack の内容確認

Slack Pack がインストールされたことを確認します。

[ec2-user@ip-172-31-4-147 ~]$ st2 pack show slack
+-------------+---------------------------------------------------------+
| Property    | Value                                                   |
+-------------+---------------------------------------------------------+
| name        | slack                                                   |
| description | st2 content pack containing slack integrations          |
| author      | StackStorm, Inc.                                        |
| content     | {                                                       |
|             |     "sensors": {                                        |
|             |         "count": 1,                                     |
|             |         "resources": [                                  |
|             |             "SlackSensor"                               |
|             |         ]                                               |
|             |     },                                                  |
|             |     "actions": {                                        |
|             |         "count": 113,                                   |
|             |         "resources": [                                  |
|             |             "dnd.endSnooze",                            |
(略)
|             |             "post_message",                             |
|             |             "dnd.info",                                 |
|             |             "team.billableInfo",                        |
|             |             "channels.replies",                         |
|             |             "groups.setPurpose",                        |
|             |             "channels.list",                            |
|             |             "groups.rename",                            |
|             |             "im.replies",                               |
|             |             "im.list",                                  |
(略)
|             |             "stars.add"                                 |
|             |         ]                                               |
|             |     }                                                   |
|             | }                                                       |
| email       | info@stackstorm.com                                     |
| keywords    | [                                                       |
|             |     "slack",                                            |
|             |     "chat",                                             |
|             |     "messaging",                                        |
|             |     "instant messaging"                                 |
|             | ]                                                       |
| ref         | slack                                                   |
| repo_url    | https://github.com/StackStorm-Exchange/stackstorm-slack |
| version     | 0.10.0                                                  |
+-------------+---------------------------------------------------------+

多くのActionがあるようです。

メッセージを投稿するには、 slack.post_message という Action を利用すればよさそうなので、slack.post_message のヘルプを参照します。

[ec2-user@ip-172-31-4-147 ~]$ st2 action get slack.post_message
+-------------+--------------------------------------------------------------+
| Property    | Value                                                        |
+-------------+--------------------------------------------------------------+
| id          | 5b0b5b446fb12304dc0e0199                                     |
| uid         | action:slack:post_message                                    |
| ref         | slack.post_message                                           |
| pack        | slack                                                        |
| name        | post_message                                                 |
| description | Post a message to the Slack channel.                         |
| enabled     | True                                                         |
| entry_point | post_message.py                                              |
| runner_type | python-script                                                |
| parameters  | {                                                            |
|             |     "username": {                                            |
|             |         "required": false,                                   |
|             |         "type": "string",                                    |
|             |         "description": "Bot username."                       |
|             |     },                                                       |
|             |     "disable_formatting": {                                  |
|             |         "default": false,                                    |
|             |         "required": false,                                   |
|             |         "type": "boolean",                                   |
|             |         "description": "Disable formatting, don't parse the  |
|             | message and treat it as raw text"                            |
|             |     },                                                       |
|             |     "icon_emoji": {                                          |
|             |         "required": false,                                   |
|             |         "type": "string",                                    |
|             |         "description": "Bot icon"                            |
|             |     },                                                       |
|             |     "webhook_url": {                                         |
|             |         "required": false,                                   |
|             |         "type": "string",                                    |
|             |         "description": "Optional Webhook url"                |
|             |     },                                                       |
|             |     "message": {                                             |
|             |         "required": true,                                    |
|             |         "type": "string",                                    |
|             |         "description": "Message to send."                    |
|             |     },                                                       |
|             |     "channel": {                                             |
|             |         "required": false,                                   |
|             |         "type": "string",                                    |
|             |         "description": "Optional channel to post to. Note    |
|             | channel must contain leading #"                              |
|             |     }                                                        |
|             | }                                                            |
| notify      |                                                              |
| tags        |                                                              |
+-------------+--------------------------------------------------------------+

Slack Pack の 設定

予め 投稿したい先の Slack の着信WebフックのURLを控えておきます。 f:id:akira6592:20180528183246p:plain

続いて、 st2 pack config slack コマンドで Slack Pack の設定を行います。 いくつか対話形式で設定項目が聞かれます。

今回は以下の値にして、他はデフォルトのままにしました。

設定項目 備考
post_message_action.username akira6592 任意の名前
post_message_action.webhook_url (先ほど控えた着信WebフックのURL)
post_message_action.icon_emoji :smiley: 投稿時のアイコン。省略時は :panda:
admin.organization admin 任意の名前

途中、以下のように聞かれますのでエンターを押します。

Do you want to preview the config in an editor before saving? [y]:

設定内容がテキストとしてエディタで開かれるので、確認後エディタを終了します。

最後に

Do you want me to save it? [y]: 

と聞かれるのでエンターを押します。

設定が完了すると以下のように、設定内容が出力されます。

+----------+--------------------------------------------------------------+
| Property | Value                                                        |
+----------+--------------------------------------------------------------+
| id       | 5b0b5b466fb12304dc0e01c3                                     |
| pack     | slack                                                        |
| values   | {                                                            |
|          |     "action_token": "********",                              |
|          |     "admin": {                                               |
|          |         "organization": "admin",                             |
|          |         "attempts": 1,                                       |
|          |         "admin_token": "",                                   |
|          |         "auto_join_channels": null,                          |
|          |         "set_active": true                                   |
|          |     },                                                       |
|          |     "sensor": {                                              |
|          |         "strip_formatting": false,                           |
|          |         "token": ""                                          |
|          |     },                                                       |
|          |     "post_message_action": {                                 |
|          |         "username": "akira6592",                             |
|          |         "webhook_url": "https://hooks.slack.com/services/xxx |
|          | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",                  |
|          |         "icon_emoji": ":smiley:",                            |
|          |         "channel": null                                      |
|          |     }                                                        |
|          | }                                                            |
+----------+--------------------------------------------------------------+

設定内容は /opt/stackstorm/configs/slack.yaml に保存されています。

[ec2-user@ip-172-31-4-147 ~]$ cat /opt/stackstorm/configs/slack.yaml
action_token: null
admin:
  admin_token: ''
  attempts: 1
  auto_join_channels: null
  organization: admin
  set_active: true
post_message_action:
  channel: null
  icon_emoji: ':smiley:'
  username: akira6592
  webhook_url: https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sensor:
  strip_formatting: false
  token: ''


■ StackStorm から Slack への投稿

それでは実際に StackStorm から Slack へ投稿してみます。 手動での投稿は st2 run slack.post_message コマンドを利用し、 message オプションで投稿したいテキストを指定します。

[ec2-user@ip-172-31-4-147 ~]$ st2 run slack.post_message message="Hello, St2! :tada:"
.
id: 5b0bca1b6fb12304dc0e01d4
status: succeeded
parameters:
  message: 'Hello, St2! :tada:'
result:
  exit_code: 0
  result: true
  stderr: 'st2.actions.python.PostMessageAction: INFO     Message successfully posted
    '
  stdout: ''

無事、以下のようにSlackに投稿されました。 f:id:akira6592:20180528183318p:plain


■ まとめ

StackStorm と Slack を連携するための準備と、手動でSlackに投稿するところまで確認できました。 Notification先として便利そうです。 また、 slack.SlackSensor というセンサーも含まれているようなので、今度こちらも試してみたいと思います。

StackStorm の Ansible Pack で利用するPythonパッケージの追加インストール方法

はじめに

StackStorm の Ansible Pack を利用して Junos のルーターに接続しようとしたところ、 ncclient is not installed というエラーになってしまいました。

このエラー自身はAnsible 単体で利用した時にも見かけたことがあったので対処方法を知っていました。

しかし、Ansible Pack の実行環境は virtualenv で隔離されおり、パッケージのインストール方法に少しだけコツがあったので、対処した方法をご紹介します。

結論としては、以下のコマンドを利用しました。

sudo /opt/stackstorm/virtualenvs/ansible/bin/pip install ncclient jxmlease


■ やりたかったこと

以下のような Playbook で Junos のバージョン情報を表示させる処理を試そうとしました。

- hosts: junos
  gather_facts: no
  connection: netconf

  tasks:
    - name: show
      junos_command:
        commands:
          - show version
      register: result

    - name: debug
      debug:
        msg: "{{ result.stdio_lines[0] }}"

  vars:
    ansible_network_os: junos
    ansible_user: testuser
    ansible_ssh_pass: testpass

■ 遭遇したエラー

ところが、以下のようなエラーになりました。

[ec2-user@ip-172-31-4-147 ~]$ st2 run ansible.playbook playbook=/home/ec2-user/junos_show.yml inventory_file=/home/ec2-user/inventory
.
id: 5b0011b76fb12304eceb1b8b
status: failed
parameters:
  cwd: /opt/stackstorm/packs/ansible
  inventory_file: /home/ec2-user/inventory
  playbook: /home/ec2-user/junos_show.yml
result:
  failed: true
  return_code: 2
  stderr: Executed command "/opt/stackstorm/virtualenvs/ansible/bin/ansible-playbook --inventory-file=/home/ec2-user/inventory /home/ec2-user/junos_show.yml"
  stdout: "
PLAY [junos] *******************************************************************

TASK [show] ********************************************************************
fatal: [10.1.1.110]: FAILED! => {"msg": "ncclient is not installed"}
    to retry, use: --limit @/home/ec2-user/junos_show.retry

PLAY RECAP *********************************************************************
10.1.1.110               : ok=0    changed=0    unreachable=0    failed=1
"
  succeeded: false

ncclient というPythonパッケージが不足しているようです。


■ 対処

必要パッケージのインストール

Playbook 内で利用した、junos_commandモジュールのドキュメントを参照すると、ncclientjxmlease が必要とのことなのでこの2つをインストールすることにしました。

[ec2-user@ip-172-31-4-147 ~]$ sudo /opt/stackstorm/virtualenvs/ansible/bin/pip install ncclient jxmlease
(略)
Successfully installed jxmlease-1.0.1 ncclient-0.5.3

Ansible Pack を st2 pack install ansible でインストールした時に自動的に入った他のパッケージと site-packages 配下の権限を比較すると、以下のように異なります。

[ec2-user@ip-172-31-4-147 ~]$ ls -ls  /opt/stackstorm/virtualenvs/ansible/lib/python2.7/site-packages/
(略)
  4 drwxrwxr-x. 17 root st2packs   4096  5月 19 04:43 ansible
  0 drwxrwxr-x.  2 root st2packs    131  5月 19 04:43 ansible-2.5.3.dist-info
(略)
  0 drwxr-xr-x.  5 root root        243  5月 19 12:11 ncclient
  0 drwxr-xr-x.  2 root root        147  5月 19 12:11 ncclient-0.5.3.dist-info
(略)
  4 drwxrwxr-x.  2 root st2packs   4096  5月 19 04:43 paramiko
  0 drwxrwxr-x.  2 root st2packs    150  5月 19 04:43 paramiko-2.4.1.dist-info

補足

以下の方法でパッケージをインストールしても、Ansible Pack から ncclient は利用できませんでした。

# グローバル環境にインストールされ、Ansible Pack から利用できない
[ec2-user@ip-172-31-4-147 ~]$ pip install ncclient jxmlease
# 権限エラー
[ec2-user@ip-172-31-4-147 ~]$ source /opt/stackstorm/virtualenvs/ansible/bin/activate
(ansible) [ec2-user@ip-172-31-4-147 ~]$ pip install ncclient jxmlease
(略)
OSError: [Errno 13] 許可がありません: '/opt/stackstorm/virtualenvs/ansible/lib/python2.7/site-packages/ncclient'
# これもグローバル環境にインストールされてしまう
[ec2-user@ip-172-31-4-147 ~]$ source /opt/stackstorm/virtualenvs/ansible/bin/activate
(ansible) [ec2-user@ip-172-31-4-147 ~]$ sudo pip install ncclient jxmlease


■ 再実行

必要なパッケージをインストールしたので再実行します。

[ec2-user@ip-172-31-4-147 ~]$ st2 run ansible.playbook playbook=/home/ec2-user/junos_show.yml inventory_file=/home/ec2-user/inventory
........
id: 5b0014ee6fb12304eceb1b94
status: succeeded
parameters:
  cwd: /opt/stackstorm/packs/ansible
  inventory_file: /home/ec2-user/inventory
  playbook: /home/ec2-user/junos_show.yml
result:
  failed: false
  return_code: 0
  stderr: ''
  stdout: "
PLAY [junos] *******************************************************************

TASK [show] ********************************************************************
ok: [10.1.1.110]

TASK [debug] *******************************************************************
ok: [10.1.1.110] => {
    "msg": [
        "Hostname: ip-172-31-43-152",
        "Model: vmx",
        "Junos: 17.4R1-S2.2",
        "JUNOS OS Kernel 64-bit  [20180127.fdc8dfc_builder_stable_11]",
        "JUNOS OS libs [20180127.fdc8dfc_builder_stable_11]",
        "JUNOS OS runtime [20180127.fdc8dfc_builder_stable_11]",
(略)
    ]
}

PLAY RECAP *********************************************************************
10.1.1.110               : ok=2    changed=0    unreachable=0    failed=0
"
  succeeded: true

正常に実行でき、無事にJunosのバージョン情報が取得できました。


■ まとめ

StackStorm は Python で書かれているため、 virtualenv などのPythonの環境について意識する必要があると感じました。

公式ドキュメントに、この手の順が見つからなかったのですが、もしご存じの方がいらっしゃいましたら、コメントまたは twitter: @akira6592 へお知らせいただけるとうれしいです。

StackStorm と Ansible で Cisco IOS ルーターに設定を投入する(st2 run編)

■ はじめに

StackStorm と Ansible 連携を試すために、Cisco IOS ルーターに設定を投入する方法を確認しました。 この記事では、環境の準備と st2 run コマンドによる簡単な Ansible 連携の方法をご紹介します。


■ StackStorm側の準備

StackStorm 本体のインストール

今回は 以下の方法でインストールします。 tekunabe.hatenablog.jp

2.7.2 というバージョンがインストールされました。

Ansible Pack のインストール

まず st2 login コマンドでログインします。

[ec2-user@ip-172-31-4-147 ~]$ st2 login testu
Password:
Logged in as testu

Note: You didn't use --write-password option so the password hasn't been stored in the client config and you will need to login again in 24 hours when the auth token expires.
As an alternative, you can run st2 login command with the "--write-password" flag, but keep it mind this will cause it to store the password in plain-text in the client config file (~/.st2/config).

続いて、st2 pack install ansible コマンドで Ansible Pack をインストールします。

[ec2-user@ip-172-31-4-147 ~]$ st2 pack install ansible

For the "ansible" pack, the following content will be registered:

rules     |  0
sensors   |  0
triggers  |  0
actions   |  8
aliases   |  0

Installation may take a while for packs with many items.

        [ succeeded ] download pack
        [ succeeded ] make a prerun
        [ succeeded ] install pack dependencies
        [ succeeded ] register pack

+-------------+--------------------------------------------------+
| Property    | Value                                            |
+-------------+--------------------------------------------------+
| name        | ansible                                          |
| description | st2 content pack containing ansible integrations |
| version     | 0.5.4                                            |
| author      | StackStorm, Inc.                                 |
+-------------+--------------------------------------------------+

Ansible Pack がインストールされたことを確認します。

[ec2-user@ip-172-31-4-147 ~]$ st2 pack show ansible
+-------------+-----------------------------------------------------------+
| Property    | Value                                                     |
+-------------+-----------------------------------------------------------+
| name        | ansible                                                   |
| description | st2 content pack containing ansible integrations          |
| author      | StackStorm, Inc.                                          |
| content     | {                                                         |
|             |     "tests": {                                            |
|             |         "count": 1,                                       |
|             |         "resources": [                                    |
|             |             "test_actions_lib_ansiblebaserunner.py"       |
|             |         ]                                                 |
|             |     },                                                    |
|             |     "actions": {                                          |
|             |         "count": 8,                                       |
|             |         "resources": [                                    |
|             |             "playbook",                                   |
|             |             "command_local",                              |
|             |             "galaxy.list",                                |
|             |             "vault.encrypt",                              |
|             |             "galaxy.install",                             |
|             |             "command",                                    |
|             |             "vault.decrypt",                              |
|             |             "galaxy.remove"                               |
|             |         ]                                                 |
|             |     }                                                     |
|             | }                                                         |
| email       | info@stackstorm.com                                       |
| keywords    | [                                                         |
|             |     "ansible",                                            |
|             |     "cfg management",                                     |
|             |     "configuration management"                            |
|             | ]                                                         |
| ref         | ansible                                                   |
| repo_url    | https://github.com/StackStorm-Exchange/stackstorm-ansible |
| version     | 0.5.4                                                     |
+-------------+-----------------------------------------------------------+

Ansible Pack に、どのような Acition があるかを確認します。

[ec2-user@ip-172-31-4-147 ~]$ st2 action list --pack=ansible
+------------------------+---------+--------------------------------------+
| ref                    | pack    | description                          |
+------------------------+---------+--------------------------------------+
| ansible.command        | ansible | Run ad-hoc ansible command (module)  |
| ansible.command_local  | ansible | Run ad-hoc ansible command (module)  |
|                        |         | on local machine                     |
| ansible.galaxy.install | ansible | Download & Install role from ansible |
|                        |         | galaxy                               |
| ansible.galaxy.list    | ansible | Display a list of installed roles    |
|                        |         | from ansible galaxy                  |
| ansible.galaxy.remove  | ansible | Remove an installed from ansible     |
|                        |         | galaxy role                          |
| ansible.playbook       | ansible | Run ansible playbook                 |
| ansible.vault.decrypt  | ansible | Decrypt ansible data files           |
| ansible.vault.encrypt  | ansible | Encrypt ansible data files           |
+------------------------+---------+--------------------------------------+

Playbook を実行するには、 ansible.playbook という Action を利用すればよいことが分かりました。

ansible.playbook という Action の説明を参照します。

[ec2-user@ip-172-31-4-147 ~]$ st2 action get ansible.playbook
+-------------+--------------------------------------------------------------+
| Property    | Value                                                        |
+-------------+--------------------------------------------------------------+
| id          | 5affab616fb123179539c2a2                                     |
| uid         | action:ansible:playbook                                      |
| ref         | ansible.playbook                                             |
| pack        | ansible                                                      |
| name        | playbook                                                     |
| description | Run ansible playbook                                         |
| enabled     | True                                                         |
| entry_point | ansible_playbook.py                                          |
| runner_type | local-shell-script                                           |
| parameters  | {                                                            |
|             |     "help": {                                                |
|             |         "type": "boolean",                                   |
|             |         "description": "Show help message and exit [-h]"     |
|             |     },                                                       |
(略)
|             |     "playbook": {                                            |
|             |         "position": 0,                                       |
|             |         "type": "string",                                    |
|             |         "description": "Playbook file"                       |
|             |     },                                                       |
(略)
|             |     "inventory_file": {                                      |
|             |         "type": "string",                                    |
|             |         "description": "Inventory host file                  |
|             | (default=/etc/ansible/hosts) [-i]"                           |
|             |     },                                                       |
(略)
|             |     "verbose": {                                             |
|             |         "enum": [                                            |
|             |             "v",                                             |
|             |             "vv",                                            |
|             |             "vvv",                                           |
|             |             "vvvv"                                           |
|             |         ],                                                   |
(略)
|             | }                                                            |
| notify      |                                                              |
| tags        |                                                              |
+-------------+--------------------------------------------------------------+

上記は抜粋ですが、よく使いそうなパラメータとして以下のことが分かります。

  • Playbook の指定: playbook
  • インベントリファイルの指定: inventory_file
  • 詳細度の指定: verbose


■ Ansible 側の準備

Ansible 本体

Ansible自身は、Ansible Pack インストール時に virtualenv 配下にインストールされます。そのため別途インストールする必要はありません。

インストールされたバージョンを確認します。

[ec2-user@ip-172-31-4-147 ~]$ /opt/stackstorm/virtualenvs/ansible/bin/ansible --version
ansible 2.5.3
  config file = None
  configured module search path = [u'/home/ec2-user/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/stackstorm/virtualenvs/ansible/lib/python2.7/site-packages/ansible
  executable location = /opt/stackstorm/virtualenvs/ansible/bin/ansible
  python version = 2.7.5 (default, May  3 2017, 07:55:04) [GCC 4.8.5 20150623 (Red Hat 4.8.5-14)]

Playbook の作成

Cisco IOS ルーターntp server 10.1.1.1 を投入する、以下の Playbook を作成します。

ファイル名: /home/ec2-user/ios_test.yml

- hosts: ios
  gather_facts: no
  connection: network_cli

  tasks:
    - name: config_test
      ios_config:
        lines:
          - ntp server 10.1.1.1

  vars:
    ansible_network_os: ios
    ansible_user: testadmin
    ansible_ssh_pass: testpass

インベントリファイル

以下のインベントリファイルを作成します。 ファイル名: /home/ec2-user/inventory

[ios]
10.1.1.67

ansible.cfg の作成

Ansibleが初めて接続する相手のホストキーが登録されていないとエラーになりますので、今回はホストキーのチェックを無視する設定ファイルを作成します。

ファイル名: /opt/stackstorm/packs/ansible/ansible.cfg

[defaults]
host_key_checking = False


■ 実行

それでは st2 run コマンドで Ansible の Playbook を実行します。

[ec2-user@ip-172-31-4-147 ~]$ st2 run ansible.playbook playbook=/home/ec2-user/ios_test.yml inventory_file=/home/ec2-user/inventory
..
id: 5affbf066fb123179539c2e2
status: succeeded
parameters:
  cwd: /opt/stackstorm/packs/ansible
  inventory_file: /home/ec2-user/inventory
  playbook: /home/ec2-user/ios_test.yml
result:
  failed: false
  return_code: 0
  stderr: ''
  stdout: "
PLAY [ios] *********************************************************************

TASK [config_test] *************************************************************
changed: [10.1.1.67]

PLAY RECAP *********************************************************************
10.1.1.67               : ok=1    changed=1    unreachable=0    failed=0
"
  succeeded: true

正常に完了しました。

ルーター側で、設定が実際に反映されているか確認します。

Route101#sh run | inc ntp
ntp server 10.1.1.1

無事に設定が入りました。


■ まとめ

StackStorm の st2 run コマンドから Ansible 経由で Cisco IOS ルーターに設定投入できたことを確認しました。普通にAnsible 単体で利用するときと環境が別なので、たとえば、 ansible.cfg の置き場には注意が必要です。

今回は、Workflow に組み込むのではなく st2 run コマンドによる単発の実行でしたが、Ansible と連携できるのは魅力的に感じました。

Ansible 2.5.3 で ios_config モジュールのコンフィグバックアップ時に defaults オプションを参照するようになった

■ はじめに

ios_config モジュールには、コンフィグのバックアップファイルを生成する際に利用するコマンドを show running-config にするか、show running-config all または full (以降 show run all) にするかを決める、defaults というオプションがあります。 今までこのオプションは、backup: yes による、コンフィグバックアップの際は参照されませんでしたが、Ansible 2.5.3 で参照されるように仕様変更されました。

先にまとめると、backup: yes による コンフィグバックアップに利用されるコマンドは以下の様にります。

 Ansible バージョン defaults: no (デフォルト) defaults: yes
2.4.4 show run show run
2.5.2 show run all show run all
2.5.3 show run show run all

この記事ではこの仕様変更について簡単に動作検証を行います。 上記表内の 2.4.4 のログは割愛します。

  • 関連issue

Module ios_config ingores parameter defaults in combination with config backup · Issue #39724 · ansible/ansible · GitHub


■ ネットワーク機器側の事前確認

コンフィグバックアップの結果が show run の結果なのか show run all の結果なのかをあとの動産検証で判断するために、事前にネットワーク機器側で手動で両コマンドの結果を確認しておきます。

  • show run の結果
ip-172-31-38-162#sh running-config
Building configuration...

Current configuration : 3852 bytes
!
! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user
!
version 16.7
service timestamps debug datetime msec
(略)
  • show run all の結果
ip-172-31-38-162#sh running-config all
Building configuration...

Current configuration with default configurations exposed : 350668 bytes
!
! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user
!
version 16.7
downward-compatible-config 16.7
no service log backtrace
no service config
no service exec-callback
no service nagle
service slave-log
no service slave-coredump
no service pad to-xot
no service pad from-xot
no service pad cmns
service pad
no service telnet-zeroidle
no service tcp-keepalives-in
no service tcp-keepalives-out
service timestamps debug datetime mse
(略)

上記は抜粋となりますが、 show run allversion のあとに数々の no から始まるココンフィグが表示されるという特徴があることが分かりました。


■ 動作検証(Ansible 2.5.2)

仕様変更前の Ansible 2.5.2 で defaults: no と、 defaults: yes それぞれのケースで試します。

2.5.2 / defaults: no

Playbook

以下のように defaults: no (デフォルトのため指定なしと同じ) の Playbook を用意します。

- hosts: ios
  gather_facts: no
  connection: network_cli
    
  tasks:
    - name: config backup
      ios_config:
        defaults: no    # here
        backup: yes

コンフィグバックアップ結果

以下のコンフィグファイルが生成されした。

Building configuration...

Current configuration with default configurations exposed : 350668 bytes
!
! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user
!
version 16.7
downward-compatible-config 16.7
no service log backtrace
no service config
no service exec-callback
no service nagle
(略)

このように、 show run の結果となりました。

2.5.2 / defaults: yes

Playbook

以下のように defaults: yes の Playbook を用意します。

- hosts: ios
  gather_facts: no
  connection: network_cli
    
  tasks:
    - name: config backup
      ios_config:
        defaults: yes    # here
        backup: yes

コンフィグバックアップ結果

以下のコンフィグファイルが生成されした。

`` Building configuration...

Current configuration with default configurations exposed : 350668 bytes ! ! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user ! version 16.7 downward-compatible-config 16.7 no service log backtrace no service config no service exec-callback no service nagle (略) ``

このように、先ほどと同じく show run all の結果となりました。Ansible 2.5.2 では defaults オプションが参照されていないことが分かります。


■ 動作検証(Ansible 2.5.3)

次に、仕様変更前の Ansible 2.5.3 で defaults: nodefaults: yes それぞれのケースで試します。

2.5.3 / defaults: no

Playbook

以下のように defaults: no (デフォルトのため指定なしと同じ) の Playbook を用意します。

- hosts: ios
  gather_facts: no
  connection: network_cli
    
  tasks:
    - name: config backup
      ios_config:
        defaults: no    # here
        backup: yes

コンフィグバックアップ結果

以下のコンフィグファイルが生成されした。

Building configuration...

Current configuration : 3852 bytes
!
! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user
!
version 16.7
service timestamps debug datetime msec
service timestamps log datetime msec
platform qfp utilization monitor load 80
no platform punt-keepalive disable-kernel-core
platform console virtual
(略)

このように、 show run の結果となりました。

2.5.3 / defaults: yes

Playbook

以下のように defaults: yes の Playbook を用意します。

- hosts: ios
  gather_facts: no
  connection: network_cli
    
  tasks:
    - name: config backup
      ios_config:
        defaults: yes    # here
        backup: yes

コンフィグバックアップ結果

以下のコンフィグファイルが生成されした。

Building configuration...

Current configuration with default configurations exposed : 350668 bytes
!
! Last configuration change at 05:37:12 UTC Fri May 18 2018 by ec2-user
!
version 16.7
downward-compatible-config 16.7
no service log backtrace
no service config
no service exec-callback
no service nagle
(略)

このように、 show run all の結果となりました。 Ansible 2.5.3 では defaults オプションが参照されていることが分かります。


■ まとめ

Ansible 2.5.3 で ios_config モジュールのコンフィグバックアップ時に defaults オプションを参照するようになったこと確認しました。 もし、backup: yesdefaults: yes を併用されている場合は、挙動の変化にご注意いただければと思います。

参考 Ansible 2.5.3 CANGELOG

ansible/CHANGELOG-v2.5.rst at stable-2.5 · ansible/ansible · GitHub

[Ansible] ios_config モジュールで意図せず chaneged になってしまうバグが 2.5.3 で修正された

はじめに

Ansible 2.5.0 - 2.5.2 の ios_configモジュールには、コンフィグが変更されていないにもかかわらず意図せず changed になってしまうバグがありました。2018/05/18 にリリースされた Ansible 2.5.3 で修正されましたので、簡単な動作確認をします。

バグの詳細

Ansible 2.5.0 - 2.5.2 の ios_config では、lines オプションで指定したコンフィグが、すでに入っているかどうかを比較する際に、デフォルトコンフィグも明示的に表示させるる、show running-config all または、 show running-config full コマンド(以降 show run all)の結果を取得していました。

本来は、 defaults: yes なら show run all の結果と、 defaults: no なら show run の結果と比較するのですが、defaults オプションが無視され、常にshow run all の結果と比較する挙動となっていました。

問題(意図せず changed)になってしまうのは、show run で表示したときと show run all で表示したときとで、表記が変わるコマンドを投入しようとした場合です。

例えば、snmp-server community XXXXX RO というコンフィグを投入すると、

  • show run では snmp-server community XXXXX RO
  • show run all では snmp-server community XXXXXv1default RO

のように、コンフィグ表記が異なります。(私の環境調べ)

このため、実際はすでに設定されている snmp-server community XXXXX RO (show run上の表記) を ios_config モジュールで投入しようとする際、 snmp-server community XXXXX ROshow run all の結果に含まれない(snmp-server community XXXXXv1default ROとは一致しない)ため、snmp-server community XXXXX RO を再度を投入します。 結果としては、コンフィグの実質的な内容には変更ありません。

関連issue/PR

ios_config module: Always "changed" will come out. "server community" and "snmp-server community" · Issue #37550 · ansible/ansible · GitHub

Fetch ios default config is defaults is enabled by ganeshrn · Pull Request #39741 · ansible/ansible · GitHub

Fix fetching ios default running config by ganeshrn · Pull Request #39475 · ansible/ansible · GitHub

Ansible 2.5.3 CHANGELOG

ansible/CHANGELOG-v2.5.rst at stable-2.5 · ansible/ansible · GitHub


■ バグの再現(Ansible 2.5.2)

Ansible 2.5.2 でバグの再現を試みます。

事前状態確認

ネットワーク機器側の事前状態としては、show run の結果に snmp-server community XXXXX RO がすでに含まれる状態です。

csr1# show run | inc snmp-server
snmp-server community XXXXX RO

なお、show run all だと以下のように、以下のような表記になります。

csr1# show run all | inc snmp-server 
snmp-server community XXXXXv1default RO

Playbook

以下のPlaybookを用意します。

- hosts: ios
  gather_facts: no

  tasks:
    - name: config test
      ios_config:
        lines:
          - snmp-server community XXXXX RO

実行結果

(ansible252) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] *******************************************************************

TASK [config_test] ***********************************************************
changed: [10.0.8.232]

PLAY RECAP *******************************************************************
10.0.8.232               : ok=1    changed=1    unreachable=0    failed=0

このように、show run の結果に snmp-server community XXXXX RO がすでに含まれているのに changed となってしまいました。 前述のように、show run ではなく show run all の結果と比較してしまうためです。


■ バグ修正の確認(Ansible 2.5.3)

本バグが修正されたことを確認します。

Playbook

以下のPlaybookを用意します。(先ほどの 2.4.2 のときと同じです。)

- hosts: ios
  gather_facts: no

  tasks:
    - name: config test
      ios_config:
        lines:
          - snmp-server community XXXXX RO

実行結果

(ansible253) [vagrant@centos7 vagrant]$ ansible-playbook -i inventory ios_test.yml

PLAY [ios] *******************************************************************

TASK [config_test] ***********************************************************
ok: [10.0.8.232]

PLAY RECAP *******************************************************************
10.0.8.232               : ok=1    changed=0    unreachable=0    failed=0

今度は changed になりませんでした。意図通りの結果です。


■ まとめ

ios_config モジュールで意図せず chaneged になってしまうバグの再現と修正確認を行いました。 Ansible 2.5.0 - 2.5.2 をお使いでこのような現象でお困りの場合、2.5.3 へのアップデートを検討してみてはいかがでしょうか。