てくなべ (tekunabe)

ansible / network automation / 学習メモ

DevNet Express for DNA [Tokyo] イベント参加レポート: Python と Postman と API たち

f:id:akira6592:20191212002250p:plain:w300

■ はじめに

2019/12/10-11 に開催された、DevNet Express for DNA [Tokyo] に参加してきました。 ネットワークプログラマビリティをテーマにした、ハンズオンを含むインベントです。

「for DNA」とついているのは、Cisco DNA Center という製品を中心としたエンタープライズ領域向けという位置づけのようです。とはいえデータセンターなど他の領域でも共通の部分は多分にありました。

Python の基礎からはじまり、様々な製品の API に触れられるとても楽しいイベントでした。

ざっくり言うと、DevNet の以下の日本語コンテンツにそったハンズオンです(CMXは対象外)。

developer.cisco.com

コードはこちら

github.com

この記事では、学習したことの概要や感じたことなどをまとめます。

参加したいと思った理由

モデル駆動型プログラマビリティに興味があったり、Meraki や DNA Center を触るハンズオンをしたかったので参加させていただきました。また、2020/02/24 のシスコ認定資格の大改訂と同時に始まる DevNet Associate Exam v1.0(200-901) などの、ネットワークプログラマビリティ・自動化型の認定試験の資格のとっかかかりとしても有用なのではと思い、参加しました。

実際は応募が多数で抽選制だったようです。運が良かったです。ありがたい・・。


■ 日程

こちらのPDFからタイトル抜粋)

1 日目: 12/10
09:00 ~ 09:30 受付
09:30 ~ 10:00 オープニング
10:00 ~ 13:00 REST, プログラミング, Python の基礎と概要
13:00 ~ 14:00 昼食
14:00 ~ 15:30 コントローラを使ってデバイスを管理する: DNA Center の概要
15:30 ~ 16:00 DNA Center: ミッション
16:00 ~ 17:30 デバイスを直接管理する:モデル駆動型プログラマビリティの概要
17:30 ~ 18:00 モデル駆動型プログラマビリティ: ミッション
18:00 ~ 20:00 懇親会

2 日目: 12/11
09:30 ~ 09:55 受付
09:55 ~ 10:00 オープニング
10:00 ~ 11:30 Guest Shell を使ってユーティリティと Python スクリプトをルータとスイッチ上で直接実行する
11:30 ~ 12:00 Guest Shell: ミッション
12:00 ~ 13:00 昼食
13:00 ~ 14:30 NFVIS を使ってネットワーク機能の仮想化 (NFVIS) を実現する
14:30 ~ 15:00 NFVIS: ミッション
15:00 ~ 16:30 Meraki API を使って、Meraki ネットワークの設定、管理、モニタリングを自動化する
16:30 ~ 17:00 Meraki API: ミッション
17:00 ~ 17:30 クロージング


■ 1日目にやったこと・感想

環境の確認

利用するツール類は、Python、git、Postman、ブラウザあたりです。ひとりずつ専用のラボ環境が用意されいて、そこに一通りいたので、そこに RDP で入るか、同等の手元の環境を使うか、といった感じでした。

Python 基礎

Python については、Hello, World の表示から始まり、リストやディクショナリなどのデータ構造、if や for などの制御分の説明もあったのっで、プログラミング初心者にもやさしい内容だったと思います。

git も少し触りましたが、確か git clone のみだったので難易度は高くないです。

API の基礎

API でよく利用されるデータフォーマットである、JSON の説明もありました。データをうまく料理するには、読み方はちゃんとおさえておきたいところですよね。

API を叩くには Postman を利用しました。プログラムや curl コマンドではなく、このようなツールを利用するのはとっつきやすいと思いました。

DNA Center と API

いままで画面をちらっと見たくらいでしかなかった DNA Center の API を Postman や Python から叩くハンズオンを行いました。

Python の方は「ミッション」と呼ばれる、穴埋め課題も用意されていました。 完成して実行してうまくいくと、Webex Teams に投稿される様になっていたので「えっ、誰かもうできたの!?」と思ったり、ちょっとゲーム要素がありました。

この方式のミッションはこのハンズオンでもたびたび出てきました。

f:id:akira6592:20191211225938p:plain
DNA Center の API ドキュメント画面

モデル駆動型プログラマビリティ

NETCONF、YANG、RESTCONF の概要を座学で学んだあと、NETCONF の Python クライアントである ncclient を使用したスクリプトを実行したりしました。

NETCONF の各オペレーション(<get><edit-config> など)と、RESTCONF の各メソッド(GETPOST など)の対応の説明があり、頭の整理ができて良かったです。

また、NETCONF については、モデルを調べるのに便利ANX (Advanced NETCONF Explorer)というツールがあることを知れました。


■ 2日目にやったこと・感想

Guest Shell

ネットワーク機器上で LXC コンテナが動き、その中で Python スクリプトなどを実行できる仕組み。 EEM と組み合わせて、コンフィグに変更があったときに、Webex Teams に差分を通知するという Python スクリプトを仕上げたりしました。

f:id:akira6592:20191212003145p:plain
無事にコンフィグ差分が Webex Teams に投稿された様子

正直、いままでは使いみちがピンとこなかったのですが、ネットワーク機器自身に自律的になにかやってほしいときには良いのかなと思いました。

NFVIS と API

ネットワーク機器に特化したハイパーバイザ、NFVIS。初めて知りました。これもやっぱり API を備えていて、Python から情報を取得したり、VMをデプロイしたりしました。

Cisco には NFV 向けの Enterorise Network Compute System(ENCS) というハードウェアがあることも初めて知りました。ネットワーク機器とサーバーの間のようなインターフェースを持つ機器のようです。

MerakiAPI

大きく分けて、Dashboard API、Scanning API、Captive Portal APIがって、前者2つを触りました。

APIドキュメントはこちらで、実際に試せました。

最後のハンズオンということもあり、少し慣れてきました。


■ まとめ

丸2日間、たっぷり楽しめました。あまり知らない製品もありましたが、概要の説明があってからのハンズオンだったので、なにをやっているのかがイメージしやすかったです。

さまざまな製品の API を叩きましたが、API ドキュメントの調べ方は少し難しかったです。調べ方がうまくできるようにあると、もっと作業が捗るんだろうなと思いました。

また「ネットワークエンジニアもプログラミングが求められる時代」となんとかく言われる雰囲気はありますが、Python の基礎を延々と繰り返したり、Webアプリケーションを作ったりするよりも、今回のようにネットワークエンジニアに馴染みのある機器の操作するほうがとっつきやすいかも知れません。

ありがたいことに、DevNet Sandbox や、dCloud によって個人で無料で触れる環境があるので、今後もいろいろ触ってみたいと思います。

主催の Cisco のみなさま、ありがとうございました!

おまけ

f:id:akira6592:20191211224314p:plain:w400
参加者プレゼントのTシャツと、ミッションクリア上位者向けのタンブラーとワイヤレス充電器

f:id:akira6592:20191211224455p:plain:w400
ミッションクリアの証、「みました!」がなんだか懐かしい

[Ansible] Network Resource Module の state: overridden の使い所を考えてみた


これは Ansible 3 Advent Calendar 2019 の9日目の記事です。


■ はじめに

Ansible 2.9 から Network Resource Module という新しいタイプのモジュールが続々と追加されています。

config オプションで設定内容を指定し、state というオプションで4種類の操作種別(mergedreplacedoverriddendeleted、今後増えるかも)を指定します。

中でも state: overriddenconfig で指定した設定以外は削除されるという強力な仕様でした。いくつかのイベントでこの件を説明した際に「overridden の使い所は?」という質問を受けました。

overridden は宣言的に指定できるため、パラーメーターシートとの相性が良いのではと私は思います。

この記事では、前回の記事「[Ansible] Junos のスタティックルート設定モジュール junos_static_routes の概要と state オプションの仕様 - てくなべ (tekunabe)」でご紹介した junos_static_routes モジュールを例にして説明します。


■ パラメーターシート と state: overridden の組み合わせた作業の流れ

前提

例えば、パラメーターシート(ここでは都合良くYAMLで表現、一応 Excel的なイメージも載せます)で以下のように、スタティックルートが定義されていて、実機でも同じ設定されているとします。

パラメーターシート

---
config_routes:
  - address_families:
    - afi: 'ipv4'
      routes:
        - dest: 0.0.0.0/0
          next_hop:
            - forward_router_address: 100.123.0.1

f:id:akira6592:20191208222755p:plain:w400
最初の状態

実機状態

jcluser@vMX-addr-0> show configuration routing-options static | display set 
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1

ルートの追加時

スタティックルートを追加する要件が出てきたとします。このとき、パラーメーターシートにスタティックルートを追加します。

パラメーターシート

---
config_routes:
  - address_families:
    - afi: 'ipv4'
      routes:
        - dest: 0.0.0.0/0
          next_hop:
            - forward_router_address: 100.123.0.1
        - dest: 10.0.1.0/24     # 追加
          next_hop:
            - forward_router_address: 172.16.0.1

f:id:akira6592:20191208222848p:plain:w400
ルートの追加

Playbook

パラメータシート内の config_routes 変数を、junos_static_routes モジュールのconfig オプションに割り当ててるPlaybookを用意します。以降のシナリオでも同様の Playbook を利用します。

---
- hosts: vlabs
  gather_facts: no

  tasks:
    - name: static routes test
      junos_static_routes:
        config: "{{ config_routes }}"  # パラメータシートの内容を利用
        state: overridden

実機状態(Playbook 実行後)

10.0.1.0/24 あてのルートが追加されます。

jcluser@vMX-addr-0> show configuration routing-options static | display set 
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1
set routing-options static route 10.0.1.0/24 next-hop 172.16.0.1

もし仮に、同じパラメータを merged で実行しても同じ結果になります。そのため、このケースでは overridden で何が嬉しいのか、まだ分かりにくいかもしれません。

ルート削除

では、パラメータシートからルートを削除した場合はどうでしょう。このケースでは overridden が活きます。

パラメーターシート

---
config_routes:
  - address_families:
    - afi: 'ipv4'
      routes:
        - dest: 0.0.0.0/0
          next_hop:
            - forward_router_address: 100.123.0.1
        # - dest: 10.0.1.0/24   # わかりやすいようにコメントアウトで削除
        #   next_hop:
        #     - forward_router_address: 172.16.0.1

f:id:akira6592:20191208222912p:plain:w400
ルートの削除

実機状態(Playbook 実行後)

10.0.1.0/24 あてのルートが削除されます。

user@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1

もし仮に、merged で実行すると、パラメーターシートから削除した 10.0.1.0/24 あてのルートは削除されずに残ります。マージは足し算のイメージです。

ルート追加・変更・削除

先ほどの状態(デフォルトルートのみ)から、ルートがガラッと変わる(追加・変更・削除)ケースも overridden が活きます。

パラメーターシート

---
config_routes:
  - address_families:
    - afi: 'ipv4'
      routes:
        - dest: 0.0.0.0/0
          next_hop:
            - forward_router_address: 10.0.99.1
        - dest: 172.16.9.0/24
          next_hop:
            - forward_router_address: 172.16.0.254
        - dest: 192.168.9.0/24
          next_hop:
            - forward_router_address: 172.16.0.254

f:id:akira6592:20191208222948p:plain:w400
ガラッと変える

実機状態(Playbook 実行後)

ガラッとルートが変わりました。

jcluser@vMX-addr-0> show configuration routing-options static | display set 
set routing-options static route 0.0.0.0/0 next-hop 10.0.99.1
set routing-options static route 172.16.9.0/24 next-hop 172.16.0.254
set routing-options static route 192.168.9.0/24 next-hop 172.16.0.254

モジュールの内部では、現状の設定されているルート(have)と、config で指定したこうなってほしいルート(want)の差分を求めます。そして、余計なルートは delete コマンドで削除、不足しているルートは set コマンドで追加、といったことをしています。

Playbook やパラメーターシートを定義する側が、このあたりのコマンド生成ロジックを考えなくてもよいのが良いところです。

overridden は宣言的

他の mergedreplaceddeleted は差分、操作を意識するタイプなのに対して、overridden はあるべき状態を宣言的に指定することで、そのとおりの設定状態にしてくれるオプションです。「これはルートを追加するから merged」とか「削除だから deleted」と意識する必要もありません。

仕様を知らないと、強力すぎて危うい一面もあるかもしれませんが、うまく付き合うと便利な気がします。

モジュールの戻り値内の commands に、実際に実行されるコマンドが入って返ってくるので、チェックモードと組み合わせせて、確認しながら試すと良いかもしれません。


■ まとめ

junos_static_routes モジュールを例にして、state: overridden の使い所を考えてみました。

  • overridden は 宣言的に指定できる
  • 削除、追加コマンドの生成ロジックを気にしなくても良い
  • パラメーターシートとの相性がよさそう

参考

www.slideshare.net

[Ansible] Junos のスタティックルート設定モジュール junos_static_routes の概要と state オプションの仕様


これは Ansible 3 Advent Calendar 2019 の8日目の記事です。


■ はじめに

開発中の Ansible 2.10 では、Juniper Junos 向けのスタティックルート設定モジュール junos_static_routes モジュール (末尾 s あり)が追加されます。

Ansible 2.9 から続々と追加されている Network Resource Module のひとつです。 既存のjunos_static_route モジュール(末尾 s なし)は、DECRECATED(非推奨)扱いになります。

この記事では、junos_static_routes の概要の説明と、操作種別を示す各 state オプションの検証結果を掲載します。

一言でいうと、便利になります。


■ 環境の準備

開発版 Ansible のインストール

開発版をインストールするには、以下のように devel ブランチを指定して pip isntall します。

$ pip install git+https://github.com/ansible/ansible.git@devel
...(略)...
$ ansible --version
ansible 2.10.0.dev0
...(略)...

必要な Python モジュール のインストール

NETCONF クライアントライブラリの ncclientXML を扱う xmltodict が必要と、公式ドキュメントの Requirementsに書かれているので、インストールします。ncclient と一緒に paramiko もインストールされます。

$ pip install ncclient xmltodict

ここまでで、環境の準備ができました。


junos_static_routes モジュールの基本

junos_static_routes モジュールには、大きくけて、設定の内容を示す config オプションと、その内容をどういう状態にするかを指定する state オプションがあります。

サンプル

- name: Merge provided configuration with device configuration (default operation is merge)
  junos_static_routes:
    config:
      - address_families:
          - afi: 'ipv4'
            routes:
              - dest: 10.200.16.75/24
                next_hop:
                  - forward_router_address: 10.200.16.2
    state: merged

※ 公式ドキュメントの Examples から抜粋

雰囲気で分かる感じですが、config オプション内で更に細かく、宛先やネクストホップの指定します。

state オプション

state オプションは、以下の4種類から1つ選択できます。

state オプションの値 動作
merged config 指定の内容をマージ(デフォルト)
replaced config 指定の内容に置き換え
overridden config 指定の内容でまるごと上書き(結果的にルーティングが削除されることもあり)
deleted config 指定の内容を削除

特に orverriden の動きが個人的にかなり特徴的(強力)だと思っています。

一通り Examples に掲載されていますが、 この記事では少し違うアプローチで独自に検証します。

コネクションプラグインnetconf のみサポート

junos_static_routes モジュールは、コネクションプラグインとして、netconf のみサポートしています。networ_cli には対応していません。

そのため、ansible_connection 変数には netconf を指定して利用します。 あわせて、ncclient などをインストールした python 環境を、ansible_python_interpreter 変数で指定します。

  • 変数定義ファイルの例
ansible_connection: netconf
ansible_network_os: junos
ansible_user: testuser
ansible_password: testpassxxx
ansible_python_interpreter: /Users/ansible/envs/venvs/a210dev/bin/python


state: merged の動作

f:id:akira6592:20191208092001p:plain:w400
merged は足し算

まずは、config 指定の内容をマージする state: merged(デフォルト) を検証します。

事前状態

Playbook 実行前は以下とおり、デフォルトルートが1つある状態です。

jcluser@vMX-addr-0> show configuration routing-options static | display set 
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1

Playbook 作成

3つのスタティックルートをマージする Playbook を作成します。

- hosts: vlabs
  gather_facts: no

  tasks:
    - name: static routes test
      junos_static_routes:
        config:
          - address_families:
            - afi: 'ipv4'
              routes:
                - dest: 10.0.1.0/24
                  next_hop:
                    - forward_router_address: 192.168.0.1
                - dest: 10.0.2.0/24
                  next_hop:
                    - forward_router_address: 192.168.0.1
                - dest: 10.0.3.0/24
                  next_hop:
                    - forward_router_address: 192.168.0.1
        state: merged    # ポイント

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini static_routes.yml 

PLAY [vlabs] ***************************************************************************************************

TASK [static routes test] **************************************************************************************
changed: [vmx]

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

なお、内部では実際に実行されるコマンドは junos_static_routes モジュールの戻り値の commands で確認できます。

事後状態

以下の通り、スタティックルートが3つ 追加 されました。もともとあったデフォルトルートはそのままです。

jcluser@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1
set routing-options static route 10.0.1.0/24 next-hop 192.168.0.1
set routing-options static route 10.0.2.0/24 next-hop 192.168.0.1
set routing-options static route 10.0.3.0/24 next-hop 192.168.0.1

ここまでで、state: merged で、指定したルートがマージされることが検証できました。


state: replaced の動作

f:id:akira6592:20191208092304p:plain:w400
replaced は特定のエントリの置き換え
次に、config 指定の内容に置き換える state: replaced を検証します。

事前状態

先ほど検証した state: merged の事後状態から始めます。

Playbook 作成

10.0.1.0/24 あてのネクストホップを置き換える Playbook を作成します。

- hosts: vlabs
  gather_facts: no

  tasks:
    - name: static routes test
      junos_static_routes:
        config:
          - address_families:
            - afi: 'ipv4'
              routes:
                - dest: 10.0.1.0/24
                  next_hop:
                    - forward_router_address: 172.16.0.1  # ネクストホップ置き換え
        state: replaced  # ポイント

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini static_routes.yml 
...(略)...

事後状態

以下の通り、10.0.1.0/24 あてのネクストホップが置き換わりました。

jcluser@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1
set routing-options static route 10.0.2.0/24 next-hop 192.168.0.1
set routing-options static route 10.0.3.0/24 next-hop 192.168.0.1
set routing-options static route 10.0.1.0/24 next-hop 172.16.0.1

ここまでで、state: replaced で、指定したルートが置き換えされることが検証できました。

なお、同じ config 内容で state: merged にすると、

jcluser@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1
set routing-options static route 10.0.1.0/24 next-hop 192.168.0.1   # 同じNWあて
set routing-options static route 10.0.1.0/24 next-hop 172.16.0.1    # 同じNWあて
set routing-options static route 10.0.2.0/24 next-hop 192.168.0.1
set routing-options static route 10.0.3.0/24 next-hop 192.168.0.1

のように、同じ 10.0.1.0/24 あての別のエントリができます。


state: overridden の動作

f:id:akira6592:20191208092547p:plain:w400
overridden は丸ごと上書き
一番気になっている指定です。

config 指定の内容でまるごと上書きする state: overridden を検証します。

事前状態

先ほど検証した state: replaced の事後状態から始めます。

Playbook 作成

デフォルトルートと、10.0.99.0/24 あてのみにする Playbook を作成します。 指定した2エントリ以外のルーティングは削除されます。

- hosts: vlabs
  gather_facts: no

  tasks:
    - name: static routes test
      junos_static_routes:
        config:
          - address_families:
            - afi: 'ipv4'
              routes:
                - dest: 0.0.0.0/0
                  next_hop:
                    - forward_router_address: 100.123.0.1
                - dest: 10.0.99.0/24
                  next_hop:
                    - forward_router_address: 192.168.99.1
        state: overridden   # ポイント

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini static_routes.yml 
...(略)...

事後状態

以下の通り、デフォルトルートと、10.0.99.0/24 あてのみになりました。 指定した2エントリ以外のルーティングは削除されました。

jcluser@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1
set routing-options static route 10.0.99.0/24 next-hop 192.168.99.1

他の指定とは異なり、 config で指定したルーティング以外は削除される 点が、便利でもあり、注意点でもあります。

ここまでで、state: overridden で、指定の内容でまるごと上書きされることが検証できました。


state: deleted の動作

f:id:akira6592:20191208092702p:plain:w400
deleted は特定エントリの削除
最後のオプション、config 指定の内容を削除する state: deleted を検証します。

事前状態

先ほど検証した state: overridden の事後状態から始めます。

Playbook 作成

10.0.1.0/24 あてルートを削除そする Playbook を作成します。

- hosts: vlabs
  gather_facts: no

  tasks:
    - name: static routes test
      junos_static_routes:
        config:
          - address_families:
            - afi: 'ipv4'
              routes:
                - dest: 10.0.99.0/24
                  next_hop:
                    - forward_router_address: 192.168.99.1
        state: deleted   # ポイント

Playbook 実行

Playbook を実行します。

$ ansible-playbook -i ../inventory.ini static_routes.yml 
...(略)...

事後状態

以下の通り、10.0.1.0/24 あてのルートが削除され、デフォルトルートのみになりました。

jcluser@vMX-addr-0> show configuration routing-options static | display set    
set routing-options static route 0.0.0.0/0 next-hop 100.123.0.1

ここまでで、state: deleted で、指定したルートが削除されることが検証できました。


■ まとめ

junos_static_routes モジュールは、以下のような特徴がありました。

  • コマンド(set / delete 〜など)は直接指定しない
  • config オプションに設定パラメータを指定する
  • state オプションの値よって挙動が異なる
  • state: mergedstate: replacedstate: deleted 手続きベースの指定
  • state: overridden は、config で指定したルート以外は削除され、宣言的な指定ができる

なお、Network: 2.10 Roadmapによると、Ansible 2.10 では、以下の *_static_routes モジュールの開発が予定されているようです。

  • vyos_static_routes
  • ios_static_routes
  • iosxr_static_routes
  • junos_static_routes
  • eos_static_routes
  • nxos_static_routes

[2019/12/09 追記] 特に特徴的な挙動だった、overridden については別途使い所を考えた記事をアップしました。

tekunabe.hatenablog.jp

[Ansible] YAML のキー重複時の挙動を DUPLICATE_YAML_DICT_KEY で設定する


これは Ansible 2 Advent Calendar 2019 の7日目の記事です。

はじめに

Playbook は YAML というフォーマットで記述します。ときどき、ミスによって YAML のディクショナリのキーの重複して書いてしまうこともあるかと思います。 これまではキーの重複時は、警告が表示された上で処理が継続するという仕様で固定でした。そいのため、あとから定義した値が優先されて実行されるなどの想定外の結果を引き起こしてしまう恐れがありました。個人的には、いっそのことエラーにしてほしいなと思っていました。

Ansible 2.9 で追加された、DUPLICATE_YAML_DICT_KEY という設定で選べるようになりました。

この記事では、簡単な例をもとに説明します。

  • 動作確認環境
    • Ansible 2.9.1


■ 選べる警告類の種類

DUPLICATE_YAML_DICT_KEYでは、キーの重複時の警告類を3種類の中から選べます

設定値 動作
error エラーを表示して、Playbook の実行を中断。
warn 警告(WARNING)表示して、Playbook の実行を続行。最後に定義した値を利用。デフォルトの設定。
False 特に何も表示せずに、Playbook の実行を続行。最後に定義した値を利用。


■ 検証

errorwarnFalse をそれぞれ検証します。

検証用 Playbook

以下の 2 種類の Playbook を検証に利用します。

Playbook1: モジュールオプションの重複

debug モジュールの msg オプションを意図的に重複させています。

- hosts: localhost
  gather_facts: no

  tasks:
    - name: debug test
      debug:
        msg: ryukin
        msg: demekin

Playbook2: 変数定義の重複

変数 debug の定義を意図的に重複させています。

- hosts: localhost
  gather_facts: no

  vars:
    kind: ryukin
    kind: demekin

  tasks:
    - name: debug test
      debug:
        msg: "{{ kind }}"

検証1: error の場合

DUPLICATE_YAML_DICT_KEYerror に指定する場合です。

キーの重複を検出した場合はエラーで処理が止まる設定です。

ansible.cfg で以下の指定します。

[defaults]
duplicate_dict_key = error

以下、各 Playbook の実行結果です。

  • 「Playbook1: モジュールオプションの重複」の実行
$ ansible-playbook -i localhost, duplicate.yml
ERROR! Unexpected Exception, this is probably a bug: 'NoneType' object has no attribute 'line'

ちょっとピンとこないメッセージですが、ERROR といえば ERROR です。

  • 「Playbook2: 変数定義の重複」の実行
$ ansible-playbook -i localhost, duplicate.yml
ERROR! Unexpected Exception, this is probably a bug: 'NoneType' object has no attribute 'line'

こちらも Playbook 1 と同じでした。

設定を判断してるのはこのあたりのコードようです

ここまでが、 error の検証でした。

検証2: warn の場合 (デフォルト)

DUPLICATE_YAML_DICT_KEYwarn に指定する場合です。デフォルトがこれなので、特に指定しなくても同じです。

キーの重複を検出した場合は WARNING が表示され、処理は継続する設定です。最後に定義した値が利用されます。

今回は明示的に ansible.cfg で以下の指定します。

[defaults]
duplicate_dict_key = warn
  • 「Playbook1: モジュールオプションの重複」の実行
$ ansible-playbook -i localhost, key_dup_option.yml
[WARNING]: While constructing a mapping from /vagrant/a29/key_dup_option.yml, line 7, column 9, found a duplicate dict key (msg). Using last defined value only.


PLAY [localhost] **********************************************************************************

TASK [debug test] *********************************************************************************
ok: [localhost] => {
    "msg": "demekin"     ← 最後に定義した msg の値
}

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

msg が重複していたので、最後に定義した値が使われたという WARNING が表示され、処理自体は実行されました。

  • 「Playbook2: 変数定義の重複」の実行
]$ ansible-playbook -i localhost, key_dup_var.yml
[WARNING]: While constructing a mapping from /vagrant/a29/key_dup_var.yml, line 6, column 5, found a duplicate dict key (kind). Using last defined value only.


PLAY [localhost] **********************************************************************************

TASK [debug test] *********************************************************************************
ok: [localhost] => {
    "msg": "demekin"    ← 最後に定義した kind 変数の値
}

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

こちらも Playbook 1 と同じでした。

ここまでが、 warn (デフォルト) の検証でした。

検証3:False の場合

最後に、DUPLICATE_YAML_DICT_KEYFalse に指定する場合です。

キーの重複を検出しても、エラーにもならず WARNING も表示されず、何事もなかったように処理をする設定です。最後に定義した値が利用されます。

ansible.cfg で以下の指定します。

[defaults]
duplicate_dict_key = False
  • 「Playbook1: モジュールオプションの重複」の実行
$ ansible-playbook -i localhost, key_dup_option.yml

PLAY [localhost] **********************************************************************************

TASK [debug test] *********************************************************************************
ok: [localhost] => {
    "msg": "demekin"    ← 最後に定義した msg の値
}

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

何事もなかったように実行されました。最後に定義した msg: demekin が表示されました。

  • 「Playbook2: 変数定義の重複」の実行
$ ansible-playbook -i localhost, key_dup_var.yml

PLAY [localhost] **********************************************************************************

TASK [debug test] *********************************************************************************
ok: [localhost] => {
    "msg": "demekin"    ← 最後に定義した kind 変数の値
}

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

こちらも Playbook 1 と同じでした。

なお、コードを見た感じは、実際は False だけでなく。「error でも warn でもない値」が設定された場合に、何も表示しないといった仕様のようです。ドキュメント上

These warnings can be silenced by adjusting this setting to False. と記載があったので False で検証しました。

ここまでが、 False の検証でした。


■ まとめ

YAML のキーが重複している場合の、挙動を設定できる DUPLICATE_YAML_DICT_KEY について検証しました。

-(再掲)

設定値 動作
error エラーを表示して、Playbook の実行を中断。
warn 警告(WARNING)表示して、Playbook の実行を続行。最後に定義した値を利用。デフォルトの設定。
False 特に何も表示せずに、Playbook の実行を続行。最後に定義した値を利用。

補足

できることなら、この設定に頼らず、YAML のキーの重複は実行前に気づいて修正しておきたいものです。 たとえば VSCode など多機能なエディタを利用すれば、以下のように気づかせてくれます。

f:id:akira6592:20191129175238p:plain
VSCode 上で 重複キーを検出

[Ansible] リスト内の要素の重複を排除してユニークにする unique フィルター


これは Ansible 2 Advent Calendar 2019 の5日目の記事です。

はじめに

Ansible には、値に対して様々な処理をするフィルターがあります。

unique フィルターは、リスト内の要素の重複を排除してユニークにするフィルターです。

簡単なサンプルでご紹介します。


サンプル

Playbook

タスク unique test 1 では、 1 が2つ、3 が3つあります。それぞれの重複は排除されます。

タスク unique test 2 も似ていますが、 数字の 1 と、クォーテーションで囲った文字 '1' があります。1'1' は別物なので、重複排除されずに特に変更は加えられません。

- hosts: localhost
  gather_facts: no

  tasks:
    - name: unique test 1
      debug:
        msg: "{{ [1, 1, 2, 3, 3, 3, 4, 5] | unique }}"

    - name: unique test 2
      debug:
        msg: "{{ [1, '1', 2, 3, 3, 3, 4, 5] | unique }}"

Playbook の実行

Playbook を実行します。

$ ansible-playbook -i localhost, filter_test.yml 

PLAY [localhost] ***************************************************************

TASK [unique test 1] ***********************************************************
ok: [localhost] => {
    "msg": [
        1,
        2,
        3,
        4,
        5
    ]
}

TASK [unique test 2] ***********************************************************
ok: [localhost] => {
    "msg": [
        1,
        "1",
        2,
        3,
        4,
        5
    ]
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • unique test 1 では
    • [1, 1, 2, 3, 3, 3, 4, 5]
    • [1, 2, 3, 4, 5]
  • unique test 2 では
    • [1, '1', 2, 3, 3, 3, 4, 5]
    • [1, "1", 2, 3, 4, 5]

になりました。


少しだけ応用: count フィルターと組み合わせる

count と組み合わせると、 重複排除の件数をカウント出来ます。 SQLdistinctcount と組合わえたようなイメージです。

具体的な例としては

msg: "{{ [1, 1, 2, 3, 3, 3, 4, 5] | unique | count }}"

の結果は、

    "msg": "5"

になります。


まとめ

リスト内の要素の重複を排除してユニークにする unique フィルターをご紹介しました。

他にも色々なフィルターがあるので、公式ドキュメントを眺めてみると、新たな発見があるかもしれません。

netmiko で Juniper ScreenOS(SSG)の状態取得と設定変更を試してみる


これは ネットワーク自動化 Advent Calendar 2019 の4日目の記事です。

■ はじめに

ネットワーク自動化 Python ライブラリ netmiko は、Cisco、Juniper、Arista など様々なネットワークOSに対応しています。

開発状況を眺めていると、Juniper ScreenOS (SSG) に対応する PR が devel ブランチにマージされていました。

まだ、安定バージョン(次期は netmiko 3?)には組み込まれていませんが、まさか対応するとは思ってなかったので、驚きついでに検証します。

  • 環境
    • 2019/12/03 時点の netmiko のこのコミット までの develop ブランチ
      • 開発途中で仕様が変更される場合があります
    • Python 3.6.7
      • 次期リリースから、Python 2 系のサポートがなくなり、Python 3.6 以上のみサポートになることが開発者から案内されています
    • SSG5 ScreenOS 6.1.0r2.0

[2020/02/20 追記] Netmiko 3.0.0 のリリースで本機能が取り込まれました Release Netmiko 3.0.0 Release · ktbyers/netmiko · GitHub


■ 開発版のインストール

開発版をインストールするため、 pip install netmiko ではなく、以下の方法でインストールします。

$ git clone https://github.com/ktbyers/netmiko.git
$ git checkout develop
$ cd netmiko/
$ pip3 install -r requirements.txt

これで開発版の netmiko が利用できるようになります。


■ 状態取得

まずは、簡単な情報取得(get コマンド)を試します。

get route コマンドを実行して表示する以下のコードを利用します。 一番のポイントは device_typejuniper_screenos にすることです。

from netmiko import Netmiko

device = {
    "host": "10.0.0.254",
    "username": "netscreen",
    "password": "testpassword",
    "device_type": "juniper_screenos"
}

# 接続
net_connect = Netmiko(**device)

# コマンド実行
output = net_connect.send_command("get route")

print(output)

# 切断
net_connect.disconnect()
  • 実行結果
$ python ssg_get.py 
IPv4 Dest-Routes for <untrust-vr> (0 entries)
--------------------------------------------------------------------------------------
H: Host C: Connected S: Static A: Auto-Exported
I: Imported R: RIP P: Permanent D: Auto-Discovered
N: NHRP
iB: IBGP eB: EBGP O: OSPF E1: OSPF external type 1
E2: OSPF external type 2 trailing B: backup route


IPv4 Dest-Routes for <trust-vr> (3 entries)
--------------------------------------------------------------------------------------
         ID          IP-Prefix      Interface         Gateway   P Pref    Mtr     Vsys
--------------------------------------------------------------------------------------
*         3          0.0.0.0/0         eth0/6     192.168.1.1   S   20      1     Root
*         2     10.0.0.254/32         eth0/6         0.0.0.0   H    0      0     Root
*         1     192.168.1.0/24         eth0/6         0.0.0.0   C    0      0     Root

$

非常に簡単な例ですが、get route コマンドの結果が表示されました。

補足


■ 設定

次に、設定変更(set コマンド)を試します。

実行したいコマンドをコード内に定義する方法と、実行したいコマンドを記述した別ファイルを読み込む方法をそれぞれ試します。

方法1: コマンドをコード内に定義して実行

send_config_set に、実行したいコマンドのリストを渡して実行します。 ここでは アドレスオブジェクト3つを追加するコマンドを実行します。

save_configsave config コマンドが実行されます。

from netmiko import Netmiko

device = {
    "host": "10.0.0.254",
    "username": "netscreen",
    "password": "testpassword",
    "device_type": "juniper_screenos"
}
config_commands = [
   'set address "Trust" "web1" 192.168.1.81 255.255.255.255',
   'set address "Trust" "web2" 192.168.1.82 255.255.255.255',
   'set address "Trust" "web3" 192.168.1.83 255.255.255.255'
]
net_connect = Netmiko(**device)

# 設定コマンド実行
net_connect.send_config_set(config_commands)

# コンフィグの保存(save config コマンドの実行)
net_connect.save_config()   

# 切断
net_connect.disconnect()
  • 実行結果
$ python ssg_config.py 
$ 

実機を確認します。

SSG5-> get config | inc "set address"
get config | inc "set address"
set address "Trust" "web1" 192.168.1.81 255.255.255.255
set address "Trust" "web2" 192.168.1.82 255.255.255.255
set address "Trust" "web3" 192.168.1.83 255.255.255.255
SSG5-> 

無事に、web1web2web3 が追加され、設定が反映されました。

方法2: コマンドをファイルに定義して実行

今度は、実行したいコマンドを記述した別ファイルを用意し、send_config_from_file で読み込んで実行します。

from netmiko import Netmiko

device = {
    "host": "10.0.0.254",
    "username": "netscreen",
    "password": "testpassword",
    "device_type": "juniper_screenos"
}

net_connect = Netmiko(**device)

# config file
net_connect.send_config_from_file("config.txt")

# コンフィグの保存(save config コマンドの実行)
net_connect.save_config()

# 切断
net_connect.disconnect()

実行したいコマンドのファイル(ここでは config.txt)は以下のとおりです。

set address "Trust" "web4" 192.168.1.84 255.255.255.255
set address "Trust" "web5" 192.168.1.85 255.255.255.255
  • 実行結果
$ python ssg_config_from_file.py 
$ 

実機を確認します。

SSG5-> get config | inc "set address"
set address "Trust" "web1" 192.168.1.81 255.255.255.255
set address "Trust" "web2" 192.168.1.82 255.255.255.255
set address "Trust" "web3" 192.168.1.83 255.255.255.255
set address "Trust" "web4" 192.168.1.84 255.255.255.255
set address "Trust" "web5" 192.168.1.85 255.255.255.255
SSG5-> 

無事に、web4web5 が追加され、設定が反映されました。


■ まとめ

開発版の netmiko で SSG (Juniper ScreenOS)に対して、情報取得と設定変更を試しました。 いずれも、device_typejuniper_screenos にすることがポイントでした。

おそらくこのまま、次期バージョンにもマージされると思います。

この手のツールで、ScreenOS に対応しているものは珍しいと思うので、実は待ってた、という方もいらっしゃるのではないかなと思いました。

[Ansible] 変数の値が指定の範囲内の数値であることを assert モジュールでチェックする


これは Ansible 2 Advent Calendar 2019 の3日目の記事です。

■ はじめに

assert モジュールthat オプションで >=<= などを利用することで、数値の範囲をチェックできます。

この記事では、簡単なサンプルをもとに説明します。



■ 【要件1】 1〜1000 であることをチェックする (int も float も ok)

まずは、1〜1000 の数値( int も float も ok)であることをチェックする方法です。

数値 x のをチェックしたい場合、assert モジュールの that には以下のように指定します。

- name: range check
  assert:
    that: 
      - 1 <= x
      - x <= 1000

1 <= item <= 1000 のように書きたくなるかもしれませんが、残念ながらできません。その代わり、複数の AND 条件として指定します。

x の各値と assert の結果は以下のようになります。

x の値 assert の判定 備考
1 ok
1.0 ok float
999.9 ok float
1000 ok
1_000 ok 桁区切りリテラル
1000.0 ok
0b1111101000 ok 1000 の 2進数表記
01750 ok 1000 の 8進数表記
0x3E8 ok 1000 の 16進数表記
-1 failed
0 failed
0.9 failed float
1000.1 failed float
1001 failed float
0b1111101001 failed 1001 の 2進数表記
01751 failed 1001 の 8進数表記
0x3E9 failed 1001 の 16進数表記

検証

実態に試してみます。

Playbook

以下の Playbook を利用します。 数値のパターンをいろい試すため、ok になる想定の値と、failed になる想定の値をそれぞれリストにして、それぞれ loop でチェックします。

- hosts: localhost
  gather_facts: no

  vars:
    ok_patterns:
      - 1
      - 1.0
      - 999.9
      - 1000
      - 1_000
      - 1000.0
      - 0b1111101000   # 1000
      - 01750          # 1000
      - 0x3E8          # 1000 
    failed_patterns:
      - -1
      - 0
      - 0.9
      - 1000.1
      - 1001
      - 0b1111101001   # 1001
      - 01751          # 1001
      - 0x3E9          # 1001

  tasks:
    - name: ok pattern
      assert:
        that: 
          - 1 <= item
          - item <= 1000
      loop: "{{ ok_patterns }}"
      
    - name: failed patterns
      assert:
        that:
          - 1 <= item
          - item <= 1000
      loop: "{{ failed_patterns }}"

実行結果

Playbook を実行します。failed 時は、どの条件に引っかかったか表示されます。

$ ansible-playbook -i localhost, validate/range1.yml 

PLAY [localhost] ********************************************************************************************************

TASK [ok pattern] *******************************************************************************************************
ok: [localhost] => (item=1) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1.0) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1.0,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=999.9) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 999.9,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000.0) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000.0,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}

TASK [failed patterns] **************************************************************************************************
failed: [localhost] (item=-1) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": -1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.1) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}

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

1〜1000 の数値( float も ok)であることをチェックできました。


■ 【要件2】 1〜1000 の int であることをチェックする

次の要件は、数値の範囲に加えて、int であることのチェックもします。

assert の書き方としては、要件1の条件に加えて、type_debug フィルターを利用して型チェックをします。

- name: range check
  assert:
    that: 
      - (item | type_debug ) == "int"  # 型チェック
      - 1 <= x
      - x <= 1000

これにより、要件1 のときは異なり、数値的には範囲内だが int でない 1.0 のような値は failed になります。

x の各値と assert の結果は以下のようになります。

x の値 assert の判定 備考
1 ok
1000 ok
1_000 ok 桁区切りリテラル
0b1111101000 ok 1000 の 2進数表記
01750 ok 1000 の 8進数表記
0x3E8 ok 1000 の 16進数表記
-1 failed
0 failed
0.9 failed float
1.0 failed float、範囲内だが型が違う
999.9 failed float、範囲内だが型が違う
1000.0 failed 範囲内だが型が違う
1000.1 failed float
1001 failed float
0b1111101001 failed 1001 の 2進数表記
01751 failed 1001 の 8進数表記
0x3E9 failed 1001 の 16進数表記

検証

実態に試してみます。

Playbook

以下の Playbook を利用します。要件1のときと似たような方式です。

ok_patternsfailed_patterns の内容が異なる点と、assertthat オプションに、型チェックの条件を加えてます。

- hosts: localhost
  gather_facts: no

  vars:
    ok_patterns:
      - 1
      - 1000
      - 1_000
      - 0b1111101000   # 1000
      - 01750          # 1000
      - 0x3E8          # 1000 
    failed_patterns:
      - -1
      - 0
      - 0.9
      - 1.0
      - 999.9
      - 1000.0
      - 1000.1
      - 1001
      - 0b1111101001   # 1001
      - 01751          # 1001
      - 0x3E9          # 1001

  tasks:
    - name: ok pattern
      assert:
        that: 
          - (item | type_debug) == "int"  # 型チェック
          - 1 <= item
          - item <= 1000
      loop: "{{ ok_patterns }}"
      
    - name: failed pattern
      assert:
        that:
          - (item | type_debug) == "int"  # 型チェック
          - 1 <= item
          - item <= 1000
      loop: "{{ failed_patterns }}"

実行結果

Playbook を実行します。

$ ansible-playbook -i localhost, validate/range2.yml 

PLAY [localhost] ********************************************************************************************************

TASK [ok pattern] *******************************************************************************************************
ok: [localhost] => (item=1) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}
ok: [localhost] => (item=1000) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": 1000,
    "msg": "All assertions passed"
}

TASK [failed pattern] ***************************************************************************************************
failed: [localhost] (item=-1) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": -1,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0) => {
    "ansible_loop_var": "item",
    "assertion": "1 <= item",
    "changed": false,
    "evaluated_to": false,
    "item": 0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=0.9) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 0.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1.0) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1.0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=999.9) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 999.9,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.0) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.0,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1000.1) => {
    "ansible_loop_var": "item",
    "assertion": "(item | type_debug ) == \"int\"",
    "changed": false,
    "evaluated_to": false,
    "item": 1000.1,
    "msg": "Assertion failed"
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}
failed: [localhost] (item=1001) => {
    "ansible_loop_var": "item",
    "assertion": "item <= 1000",
    "changed": false,
    "evaluated_to": false,
    "item": 1001,
    "msg": "Assertion failed"      // failed
}

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

1〜1000 の数値( int のみ ok)であることをチェックできました。


■ まとめ

数値の範囲を assert モジュールでチェックする方法を確認しました。

  • assert モジュールの that オプションに複数の条件を指定して、範囲を表現する
  • 型チェックも加える場合は、type_debug フィルターを利用する