Ansibleのcommand系ネットワークモジュールを自作した話の続き

# はじめに

本記事は、前回投稿した [Ansibleのcommand系ネットワークモジュールを自作した話](https://qiita.com/tech_kitara/items/16f45d9a4d207bf35a1c) の続きになります。

# 前回のお話

前回の記事の中で、`neko_command`という自作モジュールをご紹介しました。本モジュールは、`ios_command`や`cli_command`と同じく、ネットワーク機器でshowコマンド等を取得するためのモジュールです。
通常は、InventoryやVariableファイル内で、`ansible_network_os`というパラメータにOSタイプを指定する必要があります。一方、自作モジュールではダミーの(適当な)OSタイプを指定してあげれば、OSの違いを意識することなくコマンドを取得できました。

ただし、このモジュールには以下の課題がありました。

1. `ansible_network_os`で関係ないOSタイプを指定するのがイケていない。
2. 汎用性に欠ける。(設定変更系のモジュールであるxxx_configで同じことをやりたい場合、もう一つ個別にモジュールを作らないといけない。)
2. OS自動検出処理に時間がかかる。(1台の所要時間は30秒くらい。)

これらの内、1と2を解決するため、Connectionプラグイン`network_cli`のコードを修正し、`ansible_network_os`の値を`autodetect`にすると、NetmikoによるOS自動検出を行い、ユーザがOSタイプを意識することなく既存の`cli_command`や`cli_config`モジュールを使えるようにしてみました。


# 試した環境
Ansible 「2.7.0」で動作確認しています。Ansibleは頻繁にバージョンアップが行われており、既に`network_cli`でも改修が入っているので、最新版で動作するかは不明です。

# 修正内容

修正前は、network_cli.pyの`_connect`関数の冒頭で、`self._network_os`が`Null`(OSタイプが指定されていない)場合、「OSタイプを自動検知できないので、手動で設定して下さい」というメッセージが出てFailするようになっています。

```python:network_cli.py(修正前)
<--中略-->
    def _connect(self):
        '''
        Connects to the remote device and starts the terminal
        '''
        if not self.connected:
            if not self._network_os:
                raise AnsibleConnectionFailure(
                    'Unable to automatically determine host network os. Please '
                    'manually configure ansible_network_os value for this host'
                )
            display.display('network_os is set to %s' % self._network_os, log_only=True)
<--中略-->
```

この前段で、`self._network_os`が`autodetect`だった場合、(1) ログイン情報を取得し、(2) NetmikoでOS自動検出を行い、(3) 返ってきた値`best_match`をAnsibleが使用するOSタイプに名称変換の上、`self._network_os`に格納する処理を加えています。

```python:network_cli.py(修正後)
from netmiko import ConnectHandler
from netmiko.ssh_autodetect import SSHDetect
<--中略-->
    def _connect(self):
        '''
        Connects to the remote device and starts the terminal
        '''
        if not self.connected:
            if self._network_os == 'autodetect':
                # (1) define login information
                remote_device = {
                    'device_type': 'autodetect',
                    'ip': self._play_context.remote_addr,
                    'username': self._play_context.remote_user,
                    'password': self._play_context.password,
                    'port' : self._play_context.port,          # optional, defaults to 22
                    'secret': self._play_context.become_pass,     # optional, defaults to ''
                    'verbose': False,       # optional, defaults to False
                }

                # (2) autodetect device type
                guesser = SSHDetect(**remote_device)
                best_match = guesser.autodetect()

                # (3) convert netmiko device name to ansible_network_os name
                if best_match == 'cisco_ios':
                    self._network_os = 'ios'
                elif best_match == 'cisco_nxos':
                    self._network_os = 'nxos'
                elif best_match == 'cisco_asa':
                    self._network_os = 'asa'
                elif best_match == 'cisco_xr':
                    self._network_os = 'iosxr'
                elif best_match == 'juniper_junos':
                    self._network_os = 'junos'
                elif best_match == 'arista_eos':
                    self._network_os = 'eos'

            if not self._network_os:
                raise AnsibleConnectionFailure(
                    'Unable to automatically determine host network os. Please '
                    'manually configure ansible_network_os value for this host'
                )
            display.display('network_os is set to %s' % self._network_os, log_only=True)
<--中略-->
```

これ以降で、修正したコードでPlaybookを実行した結果を見て行きます。

# Inventoryファイル
前回同様、Cisco IOS機器`test`とNX-OS機器`sbx-n9kv-ao`に対して操作を行います。

```yaml:inventory202
[all]
test ansible_host=192.168.100.200
sbx-n9kv-ao ansible_host=sbx-nxos-mgmt.cisco.com ansible_user=admin ansible_password=Admin_1234! ansible_port=8181

[all:vars]
ansible_network_os=autodetect
ansible_connection=network_cli
ansible_user=test
ansible_password=cisco
```

# Playbook(コマンド取得)
show versionコマンドの実行および出力を行います。

```yml:playbook1-2.yml
---

- hosts: all
  gather_facts: no

  tasks:
    - name: run show command on remote devices
      cli_command:
        command: show version
      register: result

    - name: debug
      debug:
        msg: "{{ result.stdout_lines }}"
```

# 実行結果(コマンド取得)
いずれも問題なく結果が出力されています。

```yml:
[centos@localhost ansible]$ ansible-playbook -i inventory202 playbook1-2.yml

PLAY [all] *******************************************************************************************************************************************************************

TASK [run show command on remote devices] ************************************************************************************************************************************
ok: [test]
ok: [sbx-n9kv-ao]

TASK [debug] *****************************************************************************************************************************************************************
ok: [test] => {
    "msg": [
        "Cisco IOS XE Software, Version 03.17.02.S - Standard Support Release", 
        "Cisco IOS Software, CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 15.6(1)S2, RELEASE SOFTWARE (fc1)", 
        "Technical Support: http://www.cisco.com/techsupport", 
        "Copyright (c) 1986-2016 by Cisco Systems, Inc.", 
        "Compiled Mon 06-Jun-16 22:04 by mcpre", 
        <--中略-->
    ]
}
ok: [sbx-n9kv-ao] => {
    "msg": [
        "Cisco Nexus Operating System (NX-OS) Software", 
        "TAC support: http://www.cisco.com/tac", 
        "Documents: http://www.cisco.com/en/US/products/ps9372/tsd_products_support_series_home.html", 
        "Copyright (c) 2002-2018, Cisco Systems, Inc. All rights reserved.", 
        "The copyrights to certain works contained herein are owned by", 
        "other third parties and are used and distributed under license.", 
        "Some parts of this software are covered under the GNU Public", 
        "License. A copy of the license is available at", 
        "http://www.gnu.org/licenses/gpl.html.", 
        "", 
        "Nexus 9000v is a demo version of the Nexus Operating System", 
        "", 
        "Software", 
        "  BIOS: version ", 
        "  NXOS: version 9.2(1)", 
        "  BIOS compile time:  ", 
        "  NXOS image file is: bootflash:///nxos.9.2.1.bin", 
        "  NXOS compile time:  7/17/2018 16:00:00 [07/18/2018 00:21:19]", 
        <--中略-->
    ]
}

PLAY RECAP *******************************************************************************************************************************************************************
sbx-n9kv-ao                : ok=2    changed=0    unreachable=0    failed=0   
test                       : ok=2    changed=0    unreachable=0    failed=0   
```

# Playbook(設定変更)
今度は、設定変更を行ってみます。内容は、インターフェースLoopback 78を新規作成するというものです。

```yml:playbook1-2-1.yml
---

- hosts: all
  gather_facts: no

  tasks:
    - name: change configuration on remote devices
      cli_config:
        config: interface loopback 78
```

# 出力結果(設定変更)
こちらも問題なく設定変更(ok=1 changed=1)できました。ここには記載していませんが、2回実行した時の冪等性も問題なしです。

```yml:
[centos@localhost ansible]$ ansible-playbook -i inventory202 playbook1-2-1.yml

PLAY [all] *******************************************************************************************************************************************************************

TASK [change configuration on remote devices] ********************************************************************************************************************************
changed: [test]
changed: [sbx-n9kv-ao]

PLAY RECAP *******************************************************************************************************************************************************************
sbx-n9kv-ao                : ok=1    changed=1    unreachable=0    failed=0   
test                       : ok=1    changed=1    unreachable=0    failed=0   
```

# 変更後のConfig(設定変更)
Config上でも設定反映されていることが確認できました。

```text:
test#sh run interface loopback 78
Building configuration...

Current configuration : 43 bytes
!
interface Loopback78
 no ip address
end

sbx-n9kv-ao# sh run interface loopback 78

!Command: show running-config interface loopback78
!Running configuration last done at: Sat Dec 15 14:44:11 2018
!Time: Sat Dec 15 14:49:41 2018

version 9.2(1) Bios:version

interface loopback78

```

# 最後に
一応、Cisco IOSとNX-OSでは問題なく動作しました。前回よりはおススメできるものになったと思います。
はい、めでたしめでたし！
～ 完 ～