使用Ansible和Jinja从CSV文件创建清单文件
首先
我之前用Ansible创建了一个可以获取日志的playbook。
只需更新playbook本身的目录信息和想要获取的命令,就可以通过SSH连接到目标主机并获取日志。
在实际验证中,环境可能会经常更改。
即使有人不熟悉Ansible来操作,也可以创建一个能够生成目标主机日志获取的 inventory 文件,只需更新csv文件,就可以实现。
具体来说,我想要创建一个包含验证设备登录信息的csv文件和一个jinja模板文件。
通过使用Ansible的template模块,我将创建最终的inventory文件。
总结
-
- jinja2のテンプレートを利用してインベントリファイルを作成
-
- インプットはcsvファイルのみ
-
- アウトプットはログ取得用のインベントリファイル
-
- インベントリファイルの内に記載するもの
ホストへログインするための情報
グループ
ホストのIPアドレス
SSHユーザー
SSHパスワード
取得したいコマンドを記載するファイル名の変数
showコマンド
routeコマンド
Ansibleのtemplateモジュールをプレイブック内のタスクに記載して、インベントリファイルを生成
这次的创建中包含了playbook,但是下次以及以后只需要更新csv文件,并且通过创建的playbook来实现更新库存文件的目的。
Jinja是什么?
Jinja是Python的模板引擎,它根据给定的输入数据和模板生成新的数据。可以用它生成具有可变值的动态清单文件或生成网络设备的配置。
在中国人群中,以下是对上述内容的本土化表达方式:
需要自己制作模板本身,并创建用于使用该模板文件的playbook。
但是,一旦根据输入数据创建了适合的模板文件,今后只需要更新CSV文件即可生成统一描述的清单文件。
Ansible提供了用于利用Jinja的template模块,可以使用该模块来生成文件。
生成的文件(库存文件)
为了更好地了解可以创建哪些文件,在此先贴上创建结果的文件。我还无法很好地处理空行的问题。
[ios:children] # os種別でグループを作り、子グループがあればos種別の配下に
l2
dist
internet
[l2] # 子(なければ親)グループにホストのログイン情報を記載。SSH接続前提。
edge-sw01 ansible_host=10.10.20.172 ansible_ssh_user=cisco ansible_ssh_pass=cisco
[dist]
dist-rtr01 ansible_host=10.10.20.175 ansible_ssh_user=cisco ansible_ssh_pass=cisco
dist-rtr02 ansible_host=10.10.20.176 ansible_ssh_user=cisco ansible_ssh_pass=cisco
[internet]
internet-rtr01 ansible_host=10.10.20.181 ansible_ssh_user=cisco ansible_ssh_pass=cisco
[iosxr] # 子グループがないパターン
core-rtr01 ansible_host=10.10.20.173 ansible_ssh_user=cisco ansible_ssh_pass=cisco
core-rtr02 ansible_host=10.10.20.174 ansible_ssh_user=cisco ansible_ssh_pass=cisco
[nxos]
dist_sw01 ansible_host=10.10.20.178 ansible_ssh_user=cisco ansible_ssh_pass=cisco
dist_sw02 ansible_host=10.10.20.179 ansible_ssh_user=cisco ansible_ssh_pass=cisco
[all:vars] # これは環境によらないので、固定
ansible_connection=network_cli
[ios:vars] # 親、子グループごとに記載。親はos種別の定義がマスト
ansible_network_os=ios
[l2:vars] # 子グループがある場合は子グループの変数にコマンド記載のファイルを定義
commands_show=l2_show.txt
commands_route=l2_route.txt
[dist:vars]
commands_show=dist_show.txt
commands_route=dist_route.txt
[internet:vars]
commands_show=internet_show.txt
commands_route=internet_route.txt
[iosxr:vars]# 子グループがない場合は親グループの変数にos種別とコマンド記載のファイルを定義
ansible_network_os=iosxr
commands_show=iosxr_show.txt
commands_route=iosxr_route.txt
[nxos:vars]
ansible_network_os=nxos
commands_show=nxos_show.txt
commands_route=nxos_route.txt
脚本
1. 玩法手册
首先,我们将创建一个用于使用模板的操作手册。
---
- hosts: localhost
gather_facts: no
connection: local
vars:
template: templates/inventory.j2 # テンプレートファイルの指定
host_list: hosts_list.csv # インプットデータであるcsvファイルの指定
tasks:
- name: read inventory csv
read_csv: # read_csvモジュールでcsvファイルを読み込み
path: "{{ host_list }}"
run_once: yes
register: inventory_requests # 読み込んだ結果をレジスターに格納
- name: display
debug:
msg: "{{ inventory_requests.list }}" # 読み込んだデータはリスト
run_once: yes
- name: set command requests
set_fact:
lists: "{{ inventory_requests.list }}" # 使用するリスト部分のみを変数に格納
run_once: yes
- name: create inventory
template: # テンプレートモジュールにてjinjaを利用
src: "{{ template }}" # 利用するテンプレートファイルの指定
dest: "dynamic_inventory.ini" # 生成されるファイル名
lstrip_blocks: yes # 先頭のスペース除去
2. csv 文件
这是用作输入数据的CSV文件。
-
- “os”はios,iosxr,nxosのみ
-
- “os”が親グループになり、”group”があれば子グループとして認識
-
- ”os”、”group”は並び替えられている前提
- 例によってDEVNET sandboxのCisco Modeling LabsのMulti Platform Networkのラボの情報
(親グループ)group
(子グループ)hostUsernamePasswordipiosl2edge-sw01ciscocisco10.10.20.172iosdistdist-rtr01ciscocisco10.10.20.175iosdistdist-rtr02ciscocisco10.10.20.176iosinternetinternet-rtr01ciscocisco10.10.20.181nxos
dist_sw01ciscocisco10.10.20.178nxos
dist_sw02ciscocisco10.10.20.179iosxr
core-rtr01ciscocisco10.10.20.173iosxr
core-rtr02ciscocisco10.10.20.174
os,group,host,Username,Password,ip
ios,l2,edge-sw01,cisco,cisco,10.10.20.172
ios,dist,dist-rtr01,cisco,cisco,10.10.20.175
ios,dist,dist-rtr02,cisco,cisco,10.10.20.176
ios,internet,internet-rtr01,cisco,cisco,10.10.20.181
nxos,,dist_sw01,cisco,cisco,10.10.20.178
nxos,,dist_sw02,cisco,cisco,10.10.20.179
iosxr,,core-rtr01,cisco,cisco,10.10.20.173
iosxr,,core-rtr02,cisco,cisco,10.10.20.174
3. 模板文件
非常长。只考虑了iOS、iOS XR和NX-OS这三种。“基本上分别列出了这三种”,所以变得很长。但还是希望能更简洁地写。
-
- jinja2構文
コメント {# … #}
制御構文 {% …%}
変数 {{ … }}
変数を使用しなければベタ書き
空行、改行の制御にはWhitespace Control
{# create variable for parent-children relations #}
{# for ios #}
{% set ns = namespace(str='') %} # for分の中では変数がif文外で利用できないためfor文外で宣言
{% for item in lists %}
{% if item.os == 'ios' %}
{% if item.group != '' %}
{% if ns.str == '' -%}
[ios:children]
{% set ns.str = item.group -%} # if文外でも変数が有効になるようにnamespaceを利用
{{ item.group }}
{% else %}
{% if ns.str != item.group -%}
{{ item.group }}
{% set ns.str = item.group %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{# for iosxr #}
{% set ns = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'iosxr' %}
{% if item.group != '' %}
{% if ns.str == '' -%}
[iosxr:children]
{% set ns.str = item.group -%}
{{ item.group }}
{% else %}
{% if ns.str != item.group -%}
{{ item.group }}
{% set ns.str = item.group %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{# for nxos #}
{% set ns = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'nxos' %}
{% if item.group != '' %}
{% if ns.str == '' -%}
[nxos:children]
{% set ns.str = item.group -%}
{{ item.group }}
{% else %}
{% if ns.str != item.group -%}
{{ item.group }}
{% set ns.str = item.group %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{# create variables for child group #}
{# for ios #}
{% set ns2 = namespace(str='') %}
{% set ns3 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'ios' %}
{% if item.group != '' %}
{% if ns2.str == '' -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str == item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str != item.group -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% elif item.group == '' %}
{% if ns3.str == '' -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str == item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str != item.os -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{# for iosxr #}
{% set ns2 = namespace(str='') %}
{% set ns3 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'iosxr' %}
{% if item.group != '' %}
{% if ns2.str == '' -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str == item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str != item.group -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% elif item.group == '' %}
{% if ns3.str == '' -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str == item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str != item.os -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{# for nxos #}
{% set ns2 = namespace(str='') %}
{% set ns3 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'nxos' %}
{% if item.group != '' %}
{% if ns2.str == '' -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str == item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns2.str != item.group -%}
{{ '[' + item.group + ']' }}
{% set ns2.str = item.group -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% elif item.group == '' %}
{% if ns3.str == '' -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str == item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% elif ns3.str != item.os -%}
{{ '[' + item.os + ']' }}
{% set ns3.str = item.os -%}
{{ item.host + ' ansible_host=' + item.ip + ' ansible_ssh_user=' + item.Username + ' ansible_ssh_pass=' + item.Password }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
[all:vars]
ansible_connection=network_cli
{# create [ios:vars] #}
{% set ns4 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'ios' -%}
{% if item.group == '' -%}
{% if ns4.str == '' -%}
[ios:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=ios
{% endif %}
{% if ns4.str == '' -%}
commands_show=ios_show.txt
{% endif %}
{% if ns4.str == '' -%}
commands_route=ios_route.txt
{% endif %}
{% set ns4.str = item.os -%}
{% elif item.group != '' %}
{% if ns4.str == '' -%}
[ios:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=ios
{% endif %}
{% if ns4.str != item.group -%}
{{ '[' + item.group + ':vars]' }}
{% endif %}
{% if ns4.str != item.group -%}
{{ 'commands_show=' + item.group + '_show.txt' }}
{% endif %}
{% if ns4.str != item.group -%}
{{ 'commands_route=' + item.group + '_route.txt' }}
{% endif %}
{% set ns4.str = item.group -%}
{% endif %}
{% endif %}
{% endfor %}
{# create [iosxr:vars] #}
{% set ns4 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'iosxr' -%}
{% if item.group == '' -%}
{% if ns4.str == '' -%}
[iosxr:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=iosxr
{% endif %}
{% if ns4.str == '' -%}
commands_show=iosxr_show.txt
{% endif %}
{% if ns4.str == '' -%}
commands_route=iosxr_route.txt
{% endif %}
{% set ns4.str = item.os -%}
{% elif item.group != '' %}
{% if ns4.str == '' -%}
[iosxr:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=iosxr
{% endif %}
{% if ns4.str != item.group -%}
{{ '[' + item.group + ':vars]' }}
{% endif %}
{% if ns4.str != item.group -%}
{{ 'commands_show=' + item.group + '_show.txt' }}
{% endif %}
{% if ns4.str != item.group -%}
{{ 'commands_route=' + item.group + '_route.txt' }}
{% endif %}
{% set ns4.str = item.group -%}
{% endif %}
{% endif %}
{% endfor %}
{# create [nxos:vars] #}
{% set ns4 = namespace(str='') %}
{% for item in lists %}
{% if item.os == 'nxos' -%}
{% if item.group == '' -%}
{% if ns4.str == '' -%}
[nxos:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=nxos
{% endif %}
{% if ns4.str == '' -%}
commands_show=nxos_show.txt
{% endif %}
{% if ns4.str == '' -%}
commands_route=nxos_route.txt
{% endif %}
{% set ns4.str = item.os -%}
{% elif item.group != '' %}
{% if ns4.str == '' -%}
[nxos:vars]
{% endif %}
{% if ns4.str == '' -%}
ansible_network_os=nxos
{% endif %}
{% if ns4.str != item.group -%}
{{ '[' + item.group + ':vars]' }}
{% if ns4.str != item.group -%}
{{ 'commands_show=' + item.group + '_show.txt' }}
{% endif %}
{% if ns4.str != item.group -%}
{{ 'commands_route=' + item.group + '_route.txt' }}
{% endif %}
{% set ns4.str = item.group -%}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
执行结果
附上执行Ansible-playbook命令的日志,因为已经生成了文件并已填写。
❯ ansible-playbook -i localhost template.yml
[WARNING]: Unable to parse /Users/tomohidekimura/Documents/ansible/template-test/health_check/localhost as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *********************************************************************************************************************************************
TASK [read inventory csv] ************************************************************************************************************************************
ok: [localhost]
TASK [display] ***********************************************************************************************************************************************
ok: [localhost] => {
"msg": [
{
"Password": "cisco",
"Username": "cisco",
"group": "l2",
"host": "edge-sw01",
"ip": "10.10.20.172",
"os": "ios"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "dist",
"host": "dist-rtr01",
"ip": "10.10.20.175",
"os": "ios"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "dist",
"host": "dist-rtr02",
"ip": "10.10.20.176",
"os": "ios"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "internet",
"host": "internet-rtr01",
"ip": "10.10.20.181",
"os": "ios"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "",
"host": "dist_sw01",
"ip": "10.10.20.178",
"os": "nxos"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "",
"host": "dist_sw02",
"ip": "10.10.20.179",
"os": "nxos"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "",
"host": "core-rtr01",
"ip": "10.10.20.173",
"os": "iosxr"
},
{
"Password": "cisco",
"Username": "cisco",
"group": "",
"host": "core-rtr02",
"ip": "10.10.20.174",
"os": "iosxr"
}
]
}
TASK [set command requests] **********************************************************************************************************************************
ok: [localhost]
TASK [create inventory] **************************************************************************************************************************************
changed: [localhost]
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
一切顺利完成了!
最后
尽管外观不对称,但我成功地创建了所需的清单文件。
因为我没有预料到会变得这么长,就鲁莽地开始了工作,结果生成了一堆杂乱无章的文件。
在过程中,我发现模板文件内的变量还有作用域问题,并且无法运行与if语句结合的loop.index等等,
我花了很多时间解决这些意外情况…因为不知道如何进行调试,所以非常困难。
未来,我希望创建一个生成组变量模板和获取命令列表文件的Playbook。