Dockerコンテナ上に立てたStacksormのWorkflowからAnsibleのPlaybookの結果をslackに通知する[junos_commandモジュール活用]

## はじめに
StackstormのWorkflowからAnsibleのPlaybookが`Juniper機器のQFX`に対して実行できるか確認する必要があったので、~~(正月休みを吹き飛ばして)~~調べました。


![st2_deumo_junos.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/635ddc48-6295-2f8e-a3fe-32d7b9d2efa6.gif)



今回Juniper機器を`無料で`サクッと検証できる`Juniper vLabs`を使ってみたのですが、若干設定に苦労したので、設定備忘録としても本記事に書き留めておきます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Dockerコンテナ上に立てたStacksormのWorkflowからAnsibleのPlaybookをJuniper QFXに対して実行し、<br>その結果をslackに飛ばせるかどうか雑に調べたけどできそう。 <a href="https://t.co/dCBBuUGRZZ">pic.twitter.com/dCBBuUGRZZ</a></p>&mdash; gkz (@gkzvoice) <a href="https://twitter.com/gkzvoice/status/1213093837104566273?ref_src=twsrc%5Etfw">January 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

本記事を進めるとslackにAnsibleのPlaybookの結果を通知することができるようになります。

![post_msg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/2fdac6f1-e200-504c-eabe-1298b7449bb1.png)




## 1. 目次

- 2. 構成図/環境/バージョン情報
- 3. Juniper vLabsの設定
- 4. Ansible環境の構築
- 5. AnsibleのPlaybookの結果をslackに通知するWorkflowの解説 

Ansible環境の構築は、Juniper機器のIPアドレスなどをsedコマンドで置換してAnsibleの`inventory/hosts`らをまとめて作成するので、便宜上`Juniper vLabs`の設定からお話しします。
ご自身で任意のネットワーク機器をお持ちの場合は、適宜読み替えてください。

## 2. 構成図/環境/バージョン情報

### 構成図
![st2_docker_ansible.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/5bdf8478-f8fe-0696-b47b-02ecc8b800ca.png)

[draw.io](https://www.draw.io/?splash=0&libs=aws4)にて作成

### 環境/バージョン情報

私が投稿した下記の記事から抜粋しました。
-  [Dockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する)](https://qiita.com/gkzz/items/4329f0846c873dbce9cf)

>- AWS (EC2, VPC, EIP, etc)
  - `Ubuntu 18.04.3 LTS`
  - t2.large
  - パブリックサブネット1つのみ 
  - パブリックIP有効化(Auto-assign Public IP Enable)
  - ストレージはGeneral Purpose SSD (gp2)、8size (t2.largeを選択した場合の初期値)
  - セキュリティグループはsshはローカルのみ接続許可、HTTPとHTTPSはフルオープン
  　(HTTPはアプリコンテナ、HTTPSはStackstormをwebからそれぞれ閲覧できるように許可)

>(図など略)

>- Stackstormらバージョン
  - `Stackstorm: 3.1.0, on Python 2.7.6`  #Stackstormでorquestaを使うためにはバージョンが3.0以上である必要があります。
  - RabbitMQ: 3.6
  - MongoDB: 3.4
  - Redis: 4.0
  - Postgresql: 9.6
  - Docker version 19.03.5, build 633a0ea838
  - docker-compose version 1.24.1, build 4667896b
  

- 今回扱うトポロジーの`Standalone vQFX Light`
  - Single vQFX (RE only)
  - Junos OS 15.1X53-D63.9

![valbs_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/d7fcc90b-dffe-e49e-1280-c5da851434fb.png) 
   

##  3. Juniper vLabsの設定
- ルーティング、スイッチング、セキュリティを`無料で`検証することができるJuniper社のサービス
  - [Juniper vLabs - Juniper Networks ログインURL](https://jlabs.juniper.net/vlabs/)
- 利用するうえで必要なことはアカウントの開設と検証したいトポロジーの選択だけ
- トポロジーの選択画面から選択した機器に対して`3時間だけ`sshコンソール画面から、あるいは手元のPCからssh接続できる



### 基本的な設定について
[@akira6592](https://twitter.com/akira6592)さんの記事が参考になるので、ぜひ！ 
~~書くのがめんくどさい　~~

- [Juniper 機器が検証できるリモートラボサービス「Juniper vLabs」の利用方法](https://tekunabe.hatenablog.jp/entry/2019/01/01/juniper_vlabs)

## アクセス先の機器のユーザー名とパスワードを知る方法
- トポロジーを選択すると届く、`Your Juniper vLab "Standalone vQFX-Light" is now available`という表題のメールに下記のとおり記載されている
 - `Device login credentials: <username> / <password>`
  　


## 手元のpcからsshするうえで必要な設定
- 手元のpcからsshする場合、`Add Allowed Network Prefixes`に`接続時に使うアクセス元IPアドレス`(xxx.xxx.xxx.xxx/32)などと入力

![juniper_valbs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/bb049b1b-da9f-eb94-658b-be138da31988.png)

- 上のとおり、IPアドレスを入力すると届く、`Update about Direct Access to the resources in your vLab "Standalone vQFX-Light"`という表題のメールに下記のとおり記載されている

 - Abstract Resource Name Protocol Public IP Address  Public Port     
 - vQFX_Light             SSH	 \<ip\>                \<port\>    
 - vQFX_Light             NETCONF  yyy.yyy.yyy.yyy    bbbb   
 

### ターミナルからssh接続してみる

```
$ ssh <username>@<ip> -p <port>
(略)
Password:
--- JUNOS 15.1X53-D63.9 built 2017-04-01 20:45:26 UTC
{master:0}
jcluser@Template-vQFX-Light> show version | no-more 
fpc0:
--------------------------------------------------------------------------
Hostname: Template-vQFX-Light
Model: vqfx-10000
Junos: 15.1X53-D63.9
JUNOS Base OS boot [15.1X53-D63.9]
JUNOS Base OS Software Suite [15.1X53-D63.9]
JUNOS Online Documentation [15.1X53-D63.9]
JUNOS Crypto Software Suite [15.1X53-D63.9]
JUNOS Packet Forwarding Engine Support (qfx-10-f) [15.1X53-D63.9]
JUNOS Kernel Software Suite [15.1X53-D63.9]
JUNOS Web Management [15.1X53-D63.9]
JUNOS Enterprise Software Suite [15.1X53-D63.9]
JUNOS SDN Software Suite [15.1X53-D63.9]
JUNOS Routing Software Suite [15.1X53-D63.9]
JUNOS py-base-i386 [15.1X53-D63.9]

{master:0}
jcluser@Template-vQFX-Light> 

```     
	                	                

## 4. Ansible環境の構築

本記事では`Dockerコンテナ上に立てたStacksormのWorkflowからAnsibleのPlaybook`を実行するので、私が投稿した下記の記事に沿って環境構築を進めますが、Stackstormを使わず、Ansibleだけで実行する場合、直接`inventory/hosts`らを設定してください。

-  [Dockerで始めるStackstorm再入門1/3(環境構築からOrquestaで書いたWorkflowの結果をslackに通知する)](https://qiita.com/gkzz/items/4329f0846c873dbce9cf)


### Ansibleを使う上で必要なファイル

- /opt/stackstorm/packs/ansible/inventory/hosts
  - `/st2-docker/opt/reload.sh`内でsedコマンドで書き換えている

```
[localhost]
127.0.0.1

[junos]
<JUNOS_IP>　　# export JUNOS_IP='<ip>'
```

- /opt/stackstorm/packs/ansible/inventory/group_vars/junos
  - `/st2-docker/opt/reload.sh`内でsedコマンドで書き換えている

```
---
ansible_connection: network_cli 
ansible_network_os: junos
ansible_port: <JUNOS_PORT>       # export JUNOS_PORT='<port>'
ansible_ssh_user: <JUNOS_USER>   # export JUNOS_USER='<username>'
ansible_ssh_pass: <JUNOS_PASS>   # JUNOS_PASS='<password>'
```

-  /opt/stackstorm/packs/ansible/ansible.cfg
  - `https://github.com/gkzz/st2-docker-gkz.git`をクローンしていれば、設定済
 
```/opt/stackstorm/packs/ansible/ansible.cfg
[defaults]
host_key_checking = False
```

-  /opt/stackstorm/packs/ansible/playbook/gather_facts_junos.yaml
  - `https://github.com/gkzz/st2-docker-gkz.git`をクローンしていれば、設定済

```/opt/stackstorm/packs/ansible/playbook/gather_facts_junos.yaml 
---
- hosts: junos
  connection: network_cli
  gather_facts: no
  become: yes

  tasks:
    - name: show version
      junos_command:
        commands:
          - show version
          - show interface terse
      when: ansible_network_os == "junos"
      register: result
    - debug: 
        msg: "{{ result.stdout_lines }}"

```


### st2コマンドでAnsibleのPlaybookを実行
```
root@$HOSTNAME:/# st2 run ansible.playbook \
inventory_file=/opt/stackstorm/packs/ansible/inventory/hosts \
playbook=/opt/stackstorm/packs/ansible/playbook/gather_facts_junos.yaml
```

## 5. AnsibleのPlaybookの結果をslackに通知するWorkflowの解説

- Workflow

```/opt/stackstorm/packs/mydemo_pack/actions/workflows/playbook-demo-junos.yaml 


description: playbook-demo-junos

input:
  - playbook
  - inventory_file

output:
  - failed: <% ctx().failed %>
  - action_name: <% ctx().action_name %>

tasks:
  init:
    action: core.noop
    next:
    - publish:
        - failed: False
        - action_name: 'playbook-demo-junos'
      do: playbook

  playbook:
    action: ansible.playbook
    input:
      playbook: <% ctx().playbook %>
      inventory_file: <% ctx().inventory_file %>
    next:
      - when: <% succeeded() and (result().stderr = '') %>     
        do: last
        publish:
          - action_result: |-
              [succeeded] <% result().succeeded %>
              [return_code] <% result().return_code %>
              [stdout] <% result().stdout %>
              [stderr] <% result().stderr %>
      - when: <% succeeded() and (result().stderr != '') %>　# AnsibleからのWARNINGメッセージなどが出力されることがあったので、stderr != ''のときもlackに通知するようにした
        do: post_msg
        publish:
          - action_result: |-
              [succeeded] <% result().succeeded %>
              [return_code] <% result().return_code %>
              [stdout] <% result().stdout %>
              [stderr] <% result().stderr %>
      - when: <% failed() %>
        do: post_msg
        publish:
          - failed: True
          - action_result: |-  
              [succeeded] <% result().succeeded %>
              [return_code] <% result().return_code %>
              [stdout] <% result().stdout %>
              [stderr] <% result().stderr %>
  
  post_msg:
    action: slack.post_message
    input:
      message: <% ctx().action_result %>
    next:
      - do: last
  
  last:
    action: core.noop
    next:
      - when: <% ctx().failed %>
        do: fail
```

- Workflowのメタファイル(Workflowを実行するうえで必要な引数やエントリーポイントの定義ファイル)

```/opt/stackstorm/packs/mydemo_pack/actions/playbook-demo-junos.meta.yaml
---
name: "playbook-demo-junos"
pack: "mydemo_pack"
description: "Playbook Demo of junos"
runner_type: "orquesta"
entry_point: "workflows/playbook-demo-junos.yaml"
enabled: true
parameters:
  playbook:
    type: "string"
    required: true
    default: "/opt/stackstorm/packs/ansible/playbook/gather_facts_junos.yaml"
  inventory_file:
    type: "string"
    required: true
    default: "/opt/stackstorm/packs/ansible/inventory/hosts"

```

```
root@$HOSTNAME:/# st2 run mydemo_pack.playbook-demo-junos
.......
id: 5e0f63c87b2fca012e129850
action.ref: mydemo_pack.playbook-demo-junos
parameters: None
status: succeeded
start_timestamp: Fri, 03 Jan 2020 15:54:48 UTC
end_timestamp: Fri, 03 Jan 2020 15:55:00 UTC
result: 
  output:
    action_name: playbook-demo-junos
    failed: false
+--------------------------+-------------------------+----------+------------+-----------------+
| id                       | status                  | task     | action     | start_timestamp |
+--------------------------+-------------------------+----------+------------+-----------------+
| 5e0f63c87b2fca00372f716d | succeeded (0s elapsed)  | init     | core.noop  | Fri, 03 Jan     |
|                          |                         |          |            | 2020 15:54:48   |
|                          |                         |          |            | UTC             |
| 5e0f63c97b2fca00372f7170 | succeeded (10s elapsed) | playbook | ansible.pl | Fri, 03 Jan     |
|                          |                         |          | aybook     | 2020 15:54:48   |
|                          |                         |          |            | UTC             |
| 5e0f63d37b2fca00372f7173 | succeeded (0s elapsed)  | post_msg | slack.post | Fri, 03 Jan     |
|                          |                         |          | _message   | 2020 15:54:59   |
|                          |                         |          |            | UTC             |
| 5e0f63d47b2fca00372f7176 | succeeded (0s elapsed)  | last     | core.noop  | Fri, 03 Jan     |
|                          |                         |          |            | 2020 15:55:00   |
|                          |                         |          |            | UTC             |
+--------------------------+-------------------------+----------+------------+-----------------+
```

- ansible.playbookの結果を`st2 execution get $ID`で確認
  - `stderr`にpython2.7のサポート終了に伴うバージョン引き上げの注意メッセージが出力されていることが確認できる。
   

> stderr: "/opt/stackstorm/virtualenvs/ansible/lib/python2.7/site-packages/cryptography/hazmat/primitives/constant_time.py:26: CryptographyDeprecationWarning: Support for your Python version is deprecated. `The next version of cryptography will remove support. Please upgrade to a release (2.7.7+)` that supports hmac.compare_digest as soon as possible.
  utils.PersistentlyDeprecated2018,` 

```
root@c5f23c2ec76d:~# st2 execution get 5e0f63c97b2fca00372f7170
id: 5e0f63c97b2fca00372f7170
status: succeeded (10s elapsed)
parameters: 
  cwd: /opt/stackstorm/packs/mydemo_pack
  inventory_file: /opt/stackstorm/packs/ansible/inventory/hosts
  playbook: /opt/stackstorm/packs/ansible/playbook/gather_facts_junos.yaml
result: 
  failed: false
  return_code: 0
  stderr: "/opt/stackstorm/virtualenvs/ansible/lib/python2.7/site-packages/cryptography/hazmat/primitives/constant_time.py:26: CryptographyDeprecationWarning: Support for your Python version is deprecated. The next version of cryptography will remove support. Please upgrade to a release (2.7.7+) that supports hmac.compare_digest as soon as possible.
  utils.PersistentlyDeprecated2018,
[WARNING]: arguments wait_for, match, rpcs are not supported when using
transport=cli"
  stdout: "
PLAY [junos] *******************************************************************

TASK [show version] ************************************************************
ok: [<ip>]

TASK [debug] *******************************************************************
ok: [<ip>] => {
    "msg": [
        [
            "fpc0:", 
            "--------------------------------------------------------------------------", 
            "Hostname: Template-vQFX-Light", 
            "Model: vqfx-10000", 
            "Junos: 15.1X53-D63.9", 
            (略)
            "", 
            "{master:0}"
        ], 
        [
            "show interfaces terse ", 
            "Interface               Admin Link Proto    Local                 Remote", 
            "gr-0/0/0                up    up", 
            "bme0                    up    up", 
            "bme0.0                  up    up   inet     128.0.0.1/2     ", 
            (略)

            "{master:0}"
        ]
    ]
}

PLAY RECAP *********************************************************************
66.129.235.11              : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
"
  succeeded: true
```


## 参考

- [Actions StackStorm 3.1.0 documentation](https://docs.stackstorm.com/actions.html)

- orqeustaのサンプルコード

  - [github.com/StackStorm/st2/blob/master/contrib/examples/actions/workflows/orquesta-streaming-demo.yaml]
(https://github.com/StackStorm/st2/blob/master/contrib/examples/actions/workflows/orquesta-streaming-demo.yaml)

  - [github.com/StackStorm/st2/blob/master/contrib/examples/actions/local_command_runner_print_to_stdout_and_stderr.yaml](https://github.com/StackStorm/st2/blob/master/contrib/examples/actions/local_command_runner_print_to_stdout_and_stderr.yaml)

- Ansible pack
  - [StackStorm と Ansible で Cisco IOS ルーターに設定を投入する（st2 run編）](https://tekunabe.hatenablog.jp/entry/2018/05/19/st2_run_ansible_ios)

- Slack pack
  - [StackStorm 経由で Slack に投稿する（準備 ～ st2 run編）](https://tekunabe.hatenablog.jp/entry/2018/05/28/st2_run_slack)


- 本記事で扱ったStackstormのサンプルコード
  - [gkzz/st2-docker-gkz](https://github.com/gkzz/st2-docker-gkz)

- ActionとWorkflowのサンプルコード
  - [gkzz/mydemo_pack](https://github.com/gkzz/mydemo_pack)


- Ansible Junos module
  - [junos_command – Run arbitrary commands on an Juniper JUNOS device &mdash; Ansible Documentation](https://docs.ansible.com/ansible/latest/modules/junos_command_module.html)


## P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)
[@gkzvoice](https://twitter.com/gkzvoice)

## P.P.S. StackstormでLTしました！

[StackstormというIFTTT的なツールをDockerコンテナに載せた小話](https://speakerdeck.com/gkzz/stackstormtoiuiftttde-naturuwodockerkontenanizai-setaxiao-hua)