ふらふらエンジニアのメモ帳

セキュリティおべんきょちゅ( ˘ω˘ )スヤァ…

Ansible cli_commandモジュールのprompt/answerオプション動作確認

Ansibleのcli_commandモジュールは、ネットワーク機器でshowコマンド等を実行できるマルチOS対応のモジュールです。

docs.ansible.com

本モジュールは、Cisco IOS、NX-OS、Juniper等、OSの違いを意識せずに使えるのが最大のメリットですが、他にも、コマンド実行後に "複数の" 確認メッセージが出る場合に、あらかじめ指定した回答を返すことが出来ます。
後者については、Red HatのAnsibleブログでも、Ciscoルータの再起動(reload)の例が紹介されています。

www.ansible.com

今回は、Ansibleブログと同じくCisco IOSで、様々な確認メッセージ(promptオプション)と回答(answerオプション)のパターンで動作確認してみました。
Ansibleは2.8.4、NW機器はCatalyst3560、12.2台を使用しました。

CLIで再起動した時の確認メッセージ

Cisco IOSの場合、前回の設定保存以降に一度でもグローバルコンフィグレーションモードに移行した場合、設定変更していなくても保存するか聞かれます。

Router#reload

System configuration has been modified. Save? [yes/no]:

②その後、本当に再起動して問題ないか聞かれます。

Proceed with reload? [confirm]

OKパターン1:promptオプションを①Save?、②confirmの順番で定義

Playbook

promptオプションに、メッセージの表示順通り、リスト形式で①Save\?、②confirmを定義しました。(①は?正規表現として扱われないよう、\エスケープ処理しています。)
各メッセージに対する回答は、いずれもyにしています。
check_allオプションはデフォルトでFalseが採用され、①②いずれかに回答したタイミングでそれ以降のメッセージ確認/回答は行いません。そのため今回はTrueを指定し、①②両方に回答するようにしています。Trueの場合、設定保存済みで①が表示されないケースでは、①のプロンプトを待ち続け、コマンドタイムアウトでエラー終了となってしまうので注意が必要です。

---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: reboot ios device
      cli_command:
        command: reload
        check_all: True
        prompt:
          - 'Save\?'
          - 'confirm'
        answer:
          - 'y'
          - 'y'

実行結果

問題なく再起動できました。

$ ansible-playbook -i inventory_3560 playbook_cli_command1.yml

PLAY [cisco] *******************************************************************************

TASK [reboot ios device] *******************************************************************
ok: [Router]

PLAY RECAP *********************************************************************************
Router                    : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

OKパターン2:promptオプションを②confirm、①Save?の順番で定義

Playbook

OKパターン1のpromptオプションのリストデータを逆にした場合に、再起動できるか確認してみました。

---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: reboot ios device
      cli_command:
        command: reload
        check_all: True
        prompt:
          - 'confirm'
          - 'Save\?'
        answer:
          - 'y'
          - 'y'

実行結果

出力内容は割愛しますが、問題なく再起動できました。定義の順番は関係なさそうです。

NGパターン1:Save?に対しシングルクォーテーションなしのyesで回答

Playbook

設定保存するかSave? [yes/no]:で聞かれた時、シングルクォーテーションなしのyesで回答してみました。yesnoで答えてくれと言われているので、このパターンは一見問題なさそうです。

---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: reboot ios device
      cli_command:
        command: reload
        check_all: True
        prompt:
          - 'Save\?'
          - 'confirm'
        answer:
          - yes   # yからyesに変更。かつシングルクォーテーションを外す。
          - 'y'

実行結果

コマンドタイムアウトでエラー終了してしまいました。デバッグモードの途中結果を見ると、yestrueに変換されています。別の方法でも確認したところ、どうやらyesの代わりにTrueで回答し、そこで止まってしまったようです。

$ ansible-playbook -i inventory_3560 playbook_cli_command1.yml -vvv
(省略)
fatal: [Router]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "answer": [
                true,
                "y"
            ],
            "check_all": true,
            "command": "reload",
            "prompt": [
                "Save\\?",
                "confirm"
            ],
            "sendonly": false
        }
    },
    "msg": "command timeout triggered, timeout value is 30 secs.\nSee the timeout setting options in the Network Debug and Troubleshooting Guide."
}

PLAY RECAP *********************************************************************************
Router                    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

NGパターン2:Save?をダブルクォーテーションで定義

Playbook

これまでのPlaybookはすべての値をシングルクォーテーションで囲っていましたが、代わりにSave?をダブルクォーテーションで囲ってみます。多くのケースでは、どちらで囲っても問題なく、何となく選んでいるケースが多いと思います。

---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: reboot ios device
      cli_command:
        command: reload
        check_all: True
        prompt:
          - "Save\?"   # シングルクォーテーションからダブルクォーテーションに変更
          - 'confirm'
        answer:
          - 'y'
          - 'y'

実行結果

シンタックスエラーとなってしまいました。ダブルクォーテーション内でエスケープ処理する場合、"Save\\?"とすれば問題なさそうです。(使い分けがややこしいですね!)

$ ansible-playbook -i inventory_3560 playbook_cli_command1.yml -vvv
(省略)
ERROR! Syntax Error while loading YAML.
  found unknown escape character '?'

The error appears to be in '/home/centos/venv/ansible/playbook_cli_command1.yml': line 13, column 19, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

        prompt:
          - "Save\?"
                  ^ here

そもそもの話になってしまいますが、再起動は、直前に設定保存すればプロンプト処理は1回で済むため、わざわざcli_commandモジュールを使う必要はないかもしれません。
他の応用例として、拡張Tracerouteの実行例もメモしておきます。

おまけ:拡張Tracerouteの実行例

CLIで実行した時のメッセージ

Router#traceroute
Protocol [ip]: ip
Target IP address: 192.168.100.1
Source address: 192.168.100.24
Numeric display [n]: n
Timeout in seconds [3]: 3
Probe count [3]: 3
Minimum Time to Live [1]: 1
Maximum Time to Live [30]: 10
Port Number [33434]: 33434
Loose, Strict, Record, Timestamp, Verbose[none]:
Type escape sequence to abort.
Tracing the route to 192.168.100.1

  1 192.168.100.1 0 msec 0 msec 0 msec

Playbook

---

- hosts: cisco
  gather_facts: no
  connection: network_cli

  tasks:
    - name: issue extended traceroute command
      cli_command:
        command: traceroute
        check_all: True
        prompt:
          - 'Protocol'
          - 'Target'
          - 'Source'
          - 'Numeric'
          - 'Timeout'
          - 'Probe'
          - 'Minimum'
          - 'Maximum'
          - 'Port'
          - 'Loose'
        answer:
          - 'ip'
          - '192.168.100.1'
          - '192.168.100.24'
          - 'n'
          - '3'
          - '3'
          - '1'
          - '10'
          - '33434'
          - ''
      register: result

    - name: debug
      debug:
        msg: "{{ result }}"

実行結果

$ ansible-playbook -i inventory_3560 playbook_cli_traceroute.yml

PLAY [cisco] *******************************************************************************

TASK [issue extended traceroute command] *******************************************************************
ok: [Router]

TASK [debug] *******************************************************************************
ok: [Router] => {
    "msg": {
        "changed": false,
        "failed": false,
        "stdout": "Protocol [ip]: ip\nTarget IP address: 192.168.100.1\nSource address: 192.168.100.24\nNumeric display [n]: n\nTimeout in seconds [3]: 3\nProbe count [3]: 3\nMinimum Time to Live [1]: 1\nMaximum Time to Live [30]: 10\nPort Number [33434]: 33434\nLoose, Strict, Record, Timestamp, Verbose[none]: \nType escape sequence to abort.\nTracing the route to 192.168.100.1\n\n  1 192.168.100.1 0 msec 0 msec 0 msec",
        "stdout_lines": [
            "Protocol [ip]: ip",
            "Target IP address: 192.168.100.1",
            "Source address: 192.168.100.24",
            "Numeric display [n]: n",
            "Timeout in seconds [3]: 3",
            "Probe count [3]: 3",
            "Minimum Time to Live [1]: 1",
            "Maximum Time to Live [30]: 10",
            "Port Number [33434]: 33434",
            "Loose, Strict, Record, Timestamp, Verbose[none]: ",
            "Type escape sequence to abort.",
            "Tracing the route to 192.168.100.1",
            "",
            "  1 192.168.100.1 0 msec 0 msec 0 msec"
        ]
    }
}

PLAY RECAP *********************************************************************************
Router                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0