この記事は Ansible Advent Calendar 2021 の12日目の記事です。
はじめに
Cisco IOS の機器などで、インターフェースへのトランクVLANの割り当て時に以下のように、範囲指定を含めて指定ができます。
interface GigabitEthernet1/3
switchport trunk allowed vlan 1,10-13,100
上記の例の場合は、VLAN 1
、10
、11
、12
、13
、100
を割り当てていることが読み取れます。
Ansibleで、この読み取り方の変換をする ansible.netcommon.vlan_expander
フィルターを今年作りました。-
による範囲表記を含むカンマ区切りの数字を、連番にバラして数字のリストに変換します。
ansible.netcommon
collection の 2.3.0 から利用できます。
もともとは ansible.netcommon.vlan_parser
というフィルターがあり、VLAN のリスト(例: [1, 10, 11, 12, 13, 100]
を、["1,10-13,100"]
にというコンフィグっぽい方向に変換ができたのですが、これの逆をしたかった次第です。
サンプル
簡単なサンプル Playbook をご紹介します。(ansible.netcommon
collection 2.4.0 で確認)
文字列 '1,10-13,100'
を ansible.netcommon.vlan_expander
フィルターにかけます。
---
- hosts: localhost
gather_facts: false
connection: local
tasks:
- name: vlan_expander test
ansible.builtin.debug:
msg: "{{ '1,10-13,100' | ansible.netcommon.vlan_expander }}"
実行結果(抜粋)です。 [1, 10, 11, 12, 13, 100]
のリストにバラけます。
TASK [vlan_expander test] ****************************
ok: [localhost] => {
"msg": [
1,
10,
11,
12,
13,
100
]
}
コンフィグとしてはないと思いますが、元の文字列が '1, 10-13, 100' のように ,
の後にスペースがあっても変わりません。
また、'10-13,1,100'
のように、順番がバラバラでも結果のリストは昇順ソートします(上記結果と同じ)。
ユースケース: allowed vlan に特定の VLAN が入ってるかチェック
ansible.netcommon.vlan_expander
フィルターと、assert
モジュールを組み合わせると、allowed vlan
に特定の VLAN が 入ってるかどうかをチェックできます。
応用サンプルでは、下準備としてansible/utils/cli_parse
モジュールと、パーサー ntc-templates
を組みあせて、show interfaces switchport
の結果をパースされた構造化データとして取得します。allowed vlan
された vlan の情報は、構造化データ内の trunking_vlans
配下にありますので、それに対して ansible.netcommon.vlan_expander
フィルターをかけると、リストにバラせます。その後、assert
モジュールで in
を使ってチェックすれば目的のことができます。
なお、パーサーとして ntc-templates
を利用するため、あらかじめ pip install ntc-templates
でインストールが必要です。
Playbook
以下の Playbook では、Gi1/3 の allowed vlan に 11
があることをチェックします。途中、デバッグのためのタスクも挟んでいます。
---
- hosts: sw01
gather_facts: false
tasks:
- name: exec and parse show interfaces switchport
ansible.utils.cli_parse:
command: show interfaces switchport
parser:
name: ansible.netcommon.ntc_templates
register: result_switchport
- name: set_fact for Gi1/3
ansible.builtin.set_fact:
parsed_gi1_3: "{{ result_switchport.parsed | selectattr('interface', '==', 'Gi1/3') | first }}"
- name: debug parsed_gi1_3
ansible.builtin.debug:
msg: "{{ parsed_gi1_3 }}"
- name: assert allowed vlan
ansible.builtin.assert:
that:
- 11 in (parsed_gi1_3.trunking_vlans | first | ansible.netcommon.vlan_expander)
実行例
正常(Gi1/3 の allowed vlan に 11
がある)の場合、以下のような実行結果になります。
PLAY [sw01] ********************************************************************
TASK [exec and parse show interfaces switchport] *******************************
ok: [sw01]
TASK [set_fact for Gi1/3] ******************************************************
ok: [sw01]
TASK [debug parsed_gi1_3] ******************************************************
ok: [sw01] => {
"msg": {
"access_vlan": "1",
"admin_mode": "trunk",
"interface": "Gi1/3",
"mode": "trunk",
"native_vlan": "1",
"switchport": "Enabled",
"switchport_monitor": "",
"switchport_negotiation": "On",
"trunking_vlans": [
"1,10-13,100"
],
"voice_vlan": "none"
}
}
TASK [assert allowed vlan] *****************************************************
ok: [sw01] => {
"changed": false,
"msg": "All assertions passed"
}
PLAY RECAP *********************************************************************
sw01 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
assert
モジュールの that
に与える条件式のバリエーションとしては
含まれることを期待する VLAN をリストで指定する場合は subset
で
- "[1, 12] is subset(parsed_gi1_3.trunking_vlans | first | ansible.netcommon.vlan_expander)"
完全一致を期待する場合は
- "[1, 10, 11, 12, 13, 100] == (parsed_gi1_3.trunking_vlans | first | ansible.netcommon.vlan_expander)"
おわりに
既存のフィルターを駆使しても類似の処理はできそうではありますが、今回はフィルターを作って ansible.netcommon
collection にマージしていただきました。(ただ、CI環境の変更の都合で別の方にプルリクを出し直していただいた関係で私の名前はどこか彼方へ)
やはり1つのフィルターとして実装すると再利用しやすくて便利だなと思いました。