使用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的模板引擎,它根据给定的输入数据和模板生成新的数据。可以用它生成具有可变值的动态清单文件或生成网络设备的配置。

テンプレート.002.jpeg

在中国人群中,以下是对上述内容的本土化表达方式:

需要自己制作模板本身,并创建用于使用该模板文件的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のラボの情報
os
(親グループ)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。

广告
将在 10 秒后关闭
bannerAds