AnsibleのaclsモジュールでACL設定変更を自動化してみた

# はじめに

以前の記事で、CSV形式のACL要件資料とJinja2テンプレートから「名前付き拡張ACL」のConfigファイルを生成し、設定変更を行ってみました。

[AnsibleでACL Config作成＆設定変更を自動化してみた](https://qiita.com/tech_kitara/items/c098306c7cfcdeb4496f)

一方その後、Ansible Collectionで`xxx_acls`モジュールがリリースされ、Configを用意しなくても、オプション内でIPアドレスやポート番号を指定すれば、名前付き・番号付き、標準・拡張ACLのConfig作成と設定変更が出来るようになりました。

[[Ansible] cisco.ios collection 0.0.3 リリース、ACL や OSPF などのモジュールなどが追加](https://tekunabe.hatenablog.jp/entry/2020/06/21/ansible_cisco.ios.0.0.3#%E8%BF%BD%E5%8A%A0%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB)

本記事は、Cisco IOS、NX-OS、ASA用の以下モジュールで動作確認した時のメモです。

[cisco.ios.ios_acls – ACLs resource module](https://docs.ansible.com/ansible/2.10/collections/cisco/ios/ios_acls_module.html)
[cisco.nxos.nxos_acls – ACLs resource module](https://docs.ansible.com/ansible/latest/collections/cisco/nxos/nxos_acls_module.html)
[cisco.asa.asa_acls – Access-Lists resource module](https://docs.ansible.com/ansible/2.10/collections/cisco/asa/asa_acls_module.html)


# 1. 用意した環境

### 1-1. Ansible
CentOS上にAnsible2.10.1をインストールの上、Ansible Galaxyから以下のCollectionをインストールしました。

```
$ ansible-galaxy collection list

# /home/centos/.ansible/collections/ansible_collections
Collection        Version   
----------------- ----------
ansible.netcommon 1.5.1-dev9
cisco.asa         1.0.5-dev4
cisco.ios         1.3.1-dev5
cisco.nxos        1.4.1-dev9
```

### 1-2. NW機器
Cisco CML-P 2.1の仮想環境上に、IOS機器`iosvl2-0` `iosvl2-1`、NX-OS機器`nxos-0`、ASA機器`asav-1`を同一セグメント内に構築し、外部環境のCentOSと`ext-conn-0`経由で接続しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/81ad5578-93d9-fa19-7280-1603f0eef5ff.png)


# 2. ACL要件資料
各OS機器で、名前付き拡張ACLと番号付き拡張ACLをそれぞれ3要件ずつ、計18要件を追加してみます。

- acl_requests4.csv
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/52358a46-69fe-4745-9025-db6aef86a807.png)

<details><summary>acl_requests4.csv(生データ)</summary><div>

```
hostname,change,acl_type,acl_name,seq_no,action,protocol,src_addr,src_oper,src_port,dest_addr,dest_oper,dest_port,modifier,description
iosvl2-0,add,name_extended,TEST1,110,deny,udp,192.168.1.1/32,,,192.168.2.1/32,eq,ntp,,ACL_TEST1
iosvl2-0,add,name_extended,TEST1,120,deny,tcp,192.168.1.2/32,gt,1023,192.168.2.0/24,eq,www,,
iosvl2-0,add,name_extended,TEST1,130,permit,ip,192.168.1.0/24,,,192.168.2.0/24,,,log,
iosvl2-0,add,number_extended,100,110,deny,udp,any,,,any,eq,ntp,,ACL_100
iosvl2-0,add,number_extended,100,120,deny,tcp,,gt,1023,,eq,www,,
iosvl2-0,add,number_extended,100,130,permit,ip,any,,,192.168.2.0/24,,,log,
nxos-0,add,name_extended,TEST1,110,deny,udp,192.168.1.1/32,,,192.168.2.1/32,eq,ntp,,ACL_TEST1
nxos-0,add,name_extended,TEST1,120,deny,tcp,192.168.1.2/32,range,1024-65535,192.168.2.0/24,eq,www,,
nxos-0,add,name_extended,TEST1,130,permit,ip,192.168.1.0/24,,,192.168.2.0/24,,,log,
nxos-0,add,number_extended,100,110,deny,udp,any,,,any,eq,ntp,,ACL_100
nxos-0,add,number_extended,100,120,deny,tcp,,range,1024-65535,,eq,www,,
nxos-0,add,number_extended,100,130,permit,ip,any,,,192.168.2.0/24,,,log,
asav-1,add,name_extended,TEST1,1,deny,udp,192.168.1.1/32,,,192.168.2.1/32,eq,ntp,,ACL_TEST1
asav-1,add,name_extended,TEST1,2,deny,tcp,192.168.1.2/32,gt,1023,192.168.2.0/24,eq,www,,
asav-1,add,name_extended,TEST1,3,permit,ip,192.168.1.0/24,,,192.168.2.0/24,,,log,
asav-1,add,number_extended,100,1,deny,udp,any,,,any,eq,ntp,,ACL_100
asav-1,add,number_extended,100,2,deny,tcp,,gt,1023,,eq,www,,
asav-1,add,number_extended,100,3,permit,ip,any,,,192.168.2.0/24,,,log,
```
</div></details>

各項目の内容は以下の通りです。

| 項目名       |  内容 |
|:-----------------|:------------------|
| hostname | 対象機器のホスト名 |
| change | 設定変更種別　例：add（追加）、delete（削除） |
| acl_type | ACL種別　例：name_extended（名前付き拡張）、name_standard（名前付き標準）、number_extended（番号付き拡張）、number_standard（番号付き標準）※今回の実装対象はname_extended、number_extendedのみ |
| acl_name | ACL名 or ACL番号 |
| seq_no | シーケンス番号 |
| action | 通信許可 or 拒否　例：permit、deny |
| protocol | プロトコル　例：ip、tcp、udp |
| src_addr | 送信元アドレス　アドレス/プレフィックス長の形式で指定 |
| src_oper | 送信元演算子（任意）　例：eq、gt、lt、range |
| src_port | 送信元ポート番号（任意）　rangeの場合、FromとToをハイフンで区切って指定 |
| dest_addr | 宛先アドレス　アドレス/プレフィックス長の形式で指定 |
| dest_oper | 宛先演算子（任意）　例：eq、neq、gt、lt、range |
| dest_port | 宛先ポート番号（任意）　rangeの場合、FromとToをハイフンで区切って指定 |


# 3. Playbook
各タスクの内容は以下の通りです。

1. `read_csv`モジュールを使い、ACL要件資料の各行を`[{ "hostname": "xx", "acl_name": "aa", ～ }, { "hostname": "yy", "acl_name": "bb", ～ }, ～]`のように、リスト内の要素として定義。
2. `debug`モジュールで、タスク1のリストを表示。
3. `set_fact`モジュールで、上記のリストを変数`acls`に格納。ここまでは機器毎の実行は不要なため`run_once: yes`で1回のみ実行。
4. `template`モジュールで、変数`acls`と後述のJinja2テンプレートをレンダリングし、`xxx_acls`モジュールのアーギュメントとして読み込めるJSON形式に変換してファイル出力。
5. `include_vars`モジュールで、タスク4のJSONファイルを読み込み、変数`acl_option`に格納。
6. `debug`モジュールで、タスク5の内容を表示。
7. ～9. `cisco.ios.ios_acls` `cisco.nxos.nxos_acls` `cisco.asa.asa_acls`モジュールで、各OS機器のACL Config生成と設定変更を実施。`config`、`state`アーギュメントは、タスク5の変数をループで読み取り。


```yaml:playbook_acl_extend.yml
---

- hosts: cisco
  gather_facts: no

  vars:
    template: acl_module_template3.j2
    acl_list: acl_requests4.csv
    acl_option_file: acl_option_{{ inventory_hostname }}.json

  tasks:
    - name: read acl requests from CSV file and return a list   # (1)
      read_csv:
        path: "{{ acl_list }}"
      run_once: yes
      register: acl_requests

    - name: display acl requests   # (2)
      debug:
        msg: "{{ acl_requests.list }}"
      run_once: yes

    - name: set acl requests   # (3)
      set_fact:
        acls: "{{ acl_requests.list }}"
      run_once: yes

    - name: build template   # (4)
      template:
        src: "{{ template }}"
        dest: "{{ acl_option_file }}"

    - name: set acl options   # (5)
      include_vars:
        file: "{{ acl_option_file }}"
        name: acl_option

    - name: display acl options   # (6)
      debug:
        msg: "{{ acl_option }}"

    - name: merge acl config (ios)   # (7)
      cisco.ios.ios_acls:
        config: "{{ item.config }}"
        state: "{{ item.state }}"
      loop: "{{ acl_option.acl_config }}"
      when: ansible_network_os == 'cisco.ios.ios'

    - name: merge acl config (nxos)   # (8)
      cisco.nxos.nxos_acls:
        config: "{{ item.config }}"
        state: "{{ item.state }}"
      loop: "{{ acl_option.acl_config }}"
      when: ansible_network_os == 'cisco.nxos.nxos'

    - name: merge acl config (asa)   # (9)
      cisco.asa.asa_acls:
        config: "{{ item.config[0] }}"
        state: "{{ item.state }}"
      loop: "{{ acl_option.acl_config }}"
      when: ansible_network_os == 'cisco.asa.asa'
```

# 4. Jinja2テンプレート
詳細は割愛しますが、大まかなポイントは以下の通りです。

<details><summary>acl_module_template3.j2</summary><div>

```jinja:
{
    "acl_config": [
    {% for item in acls -%}
        {% if item.hostname == inventory_hostname and item.change == 'add' -%}
            {% set src_prefix = item.src_addr | ansible.netcommon.ipaddr('prefix') -%}
            {% set dest_prefix = item.dest_addr | ansible.netcommon.ipaddr('prefix') -%}
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            {% if item.dest_addr == '' or item.dest_addr == 'any' -%}
                                            "any": yes,
                                            {% elif dest_prefix == 32 -%}
                                            "host": "{{ item.dest_addr | ansible.netcommon.ipaddr('network') }}",
                                            {% elif dest_prefix < 32 -%}
                                            "address": "{{ item.dest_addr | ansible.netcommon.ipaddr('network') }}",
                                            {% if ansible_network_os == 'cisco.asa.asa' -%}
                                            "netmask": "{{ item.dest_addr | ansible.netcommon.ipaddr('netmask') }}",
                                            {% else -%}
                                            "wildcard_bits": "{{ item.dest_addr | ansible.netcommon.ipaddr('wildcard') }}",
                                            {% endif -%}
                                            {% endif -%}
                                            {% if item.dest_oper == 'range' -%}                                        
                                            "port_protocol": {
                                                "range": {
                                                    {% if ansible_network_os == 'cisco.nxos.nxos' -%}
                                                    "end": "{{ item.dest_port.split('-')[1] }}",
                                                    "start": "{{ item.dest_port.split('-')[0] }}"
                                                    {% else -%}
                                                    "end": {{ item.dest_port.split('-')[1] }},
                                                    "start": {{ item.dest_port.split('-')[0] }}
                                                    {% endif -%}
                                                }
                                            }
                                            {% elif item.dest_oper != '' and item.dest_oper != 'any' -%}
                                            "port_protocol": {
                                                "{{ item.dest_oper }}": "{{ item.dest_port }}"
                                            }
                                            {% endif -%}
                                        },
                                        "grant": "{{ item.action }}",
                                        "protocol": "{{ item.protocol }}",
                                        {% if ansible_network_os == 'cisco.asa.asa' -%}
                                        "line": {{ item.seq_no }},
                                        {% else -%}   
                                        "sequence": {{ item.seq_no }},
                                        {% endif -%}
                                        "source": {
                                            {% if item.src_addr == '' or item.src_addr == 'any' -%}
                                            "any": yes,
                                            {% elif src_prefix == 32 -%}
                                            "host": "{{ item.src_addr | ansible.netcommon.ipaddr('network') }}",
                                            {% elif src_prefix < 32 -%}
                                            "address": "{{ item.src_addr | ansible.netcommon.ipaddr('network') }}",
                                            {% if ansible_network_os == 'cisco.asa.asa' -%}
                                            "netmask": "{{ item.src_addr | ansible.netcommon.ipaddr('netmask') }}",
                                            {% else -%}
                                            "wildcard_bits": "{{ item.src_addr | ansible.netcommon.ipaddr('wildcard') }}",
                                            {% endif -%}
                                            {% endif -%}
                                            {% if item.src_oper == 'range' -%}                                        
                                            "port_protocol": {
                                                "range": {
                                                    {% if ansible_network_os == 'cisco.nxos.nxos' -%}
                                                    "end": "{{ item.src_port.split('-')[1] }}",
                                                    "start": "{{ item.src_port.split('-')[0] }}"
                                                    {% else -%}
                                                    "end": {{ item.src_port.split('-')[1] }},
                                                    "start": {{ item.src_port.split('-')[0] }}
                                                    {% endif -%}
                                                }
                                            }
                                            {% elif item.src_oper != '' and item.src_oper != 'any' -%}
                                            "port_protocol": {
                                                "{{ item.src_oper }}": "{{ item.src_port }}"
                                            }
                                            {% endif -%}
                                        }
                                    }
                                ],
                                {% if ansible_network_os != 'cisco.nxos.nxos' -%}
                                "acl_type": "{{ item.acl_type | regex_replace('^(name|number)_(.*)$', '\\2') }}",
                                {% endif -%}
                                "name": "{{ item.acl_name }}"
                            }
                        ],
                        {% if ansible_network_os != 'cisco.asa.asa' -%}
                        "afi": "ipv4"
                        {% endif -%}
                    }
                ],
                "state": {% if item.change == 'add' -%}"merged"{% elif item.change == 'delete' -%}"deleted"{% endif -%}
            },
        {% endif -%}
    {% endfor -%}
    ]
}
```
</div></details>


- テンプレートのフォーマットはJSONを使用。当初、Playbookのフォーマットと合わせてYAMLで作っていましたが、余分なWhitespaceがYAML内でインデントとして扱われてしまう可能性を考慮し、途中で修正しました:sweat_smile:

- 冒頭の`{% if item.hostname == inventory_hostname and item.change == 'add' -%}`で、ACL追加対象が自分自身の要件のみレンダリングを行い、機器毎にファイル`acl_option_{{ inventory_hostname }}.json`が生成されるようにした。※inventory_hostname=Ansible実行対象のホスト名

- 1つの機器で要件が複数ある場合、リスト形式で要素を追加していき、Playbook内で読み込んだ時にループで回せるようにした。

- IPアドレス(ホスト/ネットワーク/any)、ポート番号(eq/neq/gt/lt/range)、OS(IOS/NX-OS/ASA)で利用するアーギュメントやデータ型が異なるため、テンプレート内で条件分岐させた。
例えば、IOS/NX-OSでは、送信元/宛先ネットワークをワイルドカードマスク用のアーギュメント`wildcard_bits`で指定するが、ASAではネットマスク用のアーギュメント`netmask`で指定する。



# 5. 実行結果
こちらも詳細は割愛しますが、3台ともFailせず完了しています。もう1台の、Inventoryファイルでは指定したものの、ACL設定要件がなかった`iosvl2-1`も、`changed=0`で完了しています。

<details><summary>実行結果</summary><div>

```
$ ansible-playbook -i inventory_cml.ini playbook_acl_extend.yml

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

TASK [read acl requests from CSV file and return a list] *********************************************************
ok: [iosvl2-0]

TASK [display acl requests] **************************************************************************************
ok: [iosvl2-0] => {
    "msg": [
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosvl2-0",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosvl2-0",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "gt",
            "src_port": "1023"
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosvl2-0",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_100",
            "dest_addr": "any",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "iosvl2-0",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "iosvl2-0",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "",
            "src_oper": "gt",
            "src_port": "1023"
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "iosvl2-0",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nxos-0",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nxos-0",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "192.168.1.2/32",
            "src_oper": "range",
            "src_port": "1024-65535"
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nxos-0",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_100",
            "dest_addr": "any",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "nxos-0",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "110",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "nxos-0",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "120",
            "src_addr": "",
            "src_oper": "range",
            "src_port": "1024-65535"
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "nxos-0",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "130",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_TEST1",
            "dest_addr": "192.168.2.1/32",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "1",
            "src_addr": "192.168.1.1/32",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "2",
            "src_addr": "192.168.1.2/32",
            "src_oper": "gt",
            "src_port": "1023"
        },
        {
            "acl_name": "TEST1",
            "acl_type": "name_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "3",
            "src_addr": "192.168.1.0/24",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "ACL_100",
            "dest_addr": "any",
            "dest_oper": "eq",
            "dest_port": "ntp",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "udp",
            "seq_no": "1",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "deny",
            "change": "add",
            "description": "",
            "dest_addr": "",
            "dest_oper": "eq",
            "dest_port": "www",
            "hostname": "asav-1",
            "modifier": "",
            "protocol": "tcp",
            "seq_no": "2",
            "src_addr": "",
            "src_oper": "gt",
            "src_port": "1023"
        },
        {
            "acl_name": "100",
            "acl_type": "number_extended",
            "action": "permit",
            "change": "add",
            "description": "",
            "dest_addr": "192.168.2.0/24",
            "dest_oper": "",
            "dest_port": "",
            "hostname": "asav-1",
            "modifier": "log",
            "protocol": "ip",
            "seq_no": "3",
            "src_addr": "any",
            "src_oper": "",
            "src_port": ""
        }
    ]
}

TASK [set acl requests] ******************************************************************************************
ok: [iosvl2-0]

TASK [build template] ********************************************************************************************
[WARNING]: The value '' is not a valid IP address or network, passing this value to ipaddr filter might result in
breaking change in future.
[WARNING]: The value '' is not a valid IP address or network, passing this value to ipaddr filter might result in
breaking change in future.
[WARNING]: The value '' is not a valid IP address or network, passing this value to ipaddr filter might result in
breaking change in future.
ok: [iosvl2-1]
changed: [nxos-0]
changed: [asav-1]
changed: [iosvl2-0]

TASK [set acl options] *******************************************************************************************
ok: [iosvl2-0]
ok: [nxos-0]
ok: [asav-1]
ok: [iosvl2-1]

TASK [display acl options] ***************************************************************************************
ok: [nxos-0] => {
    "msg": {
        "acl_config": [
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "host": "192.168.2.1",
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "udp",
                                        "sequence": 110,
                                        "source": {
                                            "host": "192.168.1.1"
                                        }
                                    }
                                ],
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "port_protocol": {
                                                "eq": "www"
                                            },
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "deny",
                                        "protocol": "tcp",
                                        "sequence": 120,
                                        "source": {
                                            "host": "192.168.1.2",
                                            "port_protocol": {
                                                "range": {
                                                    "end": "65535",
                                                    "start": "1024"
                                                }
                                            }
                                        }
                                    }
                                ],
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "permit",
                                        "protocol": "ip",
                                        "sequence": 130,
                                        "source": {
                                            "address": "192.168.1.0",
                                            "wildcard_bits": "0.0.0.255"
                                        }
                                    }
                                ],
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "udp",
                                        "sequence": 110,
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "www"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "tcp",
                                        "sequence": 120,
                                        "source": {
                                            "any": true,
                                            "port_protocol": {
                                                "range": {
                                                    "end": "65535",
                                                    "start": "1024"
                                                }
                                            }
                                        }
                                    }
                                ],
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "permit",
                                        "protocol": "ip",
                                        "sequence": 130,
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            }
        ]
    }
}
ok: [asav-1] => {
    "msg": {
        "acl_config": [
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "host": "192.168.2.1",
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "line": 1,
                                        "protocol": "udp",
                                        "source": {
                                            "host": "192.168.1.1"
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ]
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "netmask": "255.255.255.0",
                                            "port_protocol": {
                                                "eq": "www"
                                            }
                                        },
                                        "grant": "deny",
                                        "line": 2,
                                        "protocol": "tcp",
                                        "source": {
                                            "host": "192.168.1.2",
                                            "port_protocol": {
                                                "gt": "1023"
                                            }
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ]
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "netmask": "255.255.255.0"
                                        },
                                        "grant": "permit",
                                        "line": 3,
                                        "protocol": "ip",
                                        "source": {
                                            "address": "192.168.1.0",
                                            "netmask": "255.255.255.0"
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ]
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "line": 1,
                                        "protocol": "udp",
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ]
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "www"
                                            }
                                        },
                                        "grant": "deny",
                                        "line": 2,
                                        "protocol": "tcp",
                                        "source": {
                                            "any": true,
                                            "port_protocol": {
                                                "gt": "1023"
                                            }
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ]
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "netmask": "255.255.255.0"
                                        },
                                        "grant": "permit",
                                        "line": 3,
                                        "protocol": "ip",
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ]
                    }
                ],
                "state": "merged"
            }
        ]
    }
}
ok: [iosvl2-0] => {
    "msg": {
        "acl_config": [
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "host": "192.168.2.1",
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "udp",
                                        "sequence": 110,
                                        "source": {
                                            "host": "192.168.1.1"
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "port_protocol": {
                                                "eq": "www"
                                            },
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "deny",
                                        "protocol": "tcp",
                                        "sequence": 120,
                                        "source": {
                                            "host": "192.168.1.2",
                                            "port_protocol": {
                                                "gt": "1023"
                                            }
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "permit",
                                        "protocol": "ip",
                                        "sequence": 130,
                                        "source": {
                                            "address": "192.168.1.0",
                                            "wildcard_bits": "0.0.0.255"
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "TEST1"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "ntp"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "udp",
                                        "sequence": 110,
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "any": true,
                                            "port_protocol": {
                                                "eq": "www"
                                            }
                                        },
                                        "grant": "deny",
                                        "protocol": "tcp",
                                        "sequence": 120,
                                        "source": {
                                            "any": true,
                                            "port_protocol": {
                                                "gt": "1023"
                                            }
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            },
            {
                "config": [
                    {
                        "acls": [
                            {
                                "aces": [
                                    {
                                        "destination": {
                                            "address": "192.168.2.0",
                                            "wildcard_bits": "0.0.0.255"
                                        },
                                        "grant": "permit",
                                        "protocol": "ip",
                                        "sequence": 130,
                                        "source": {
                                            "any": true
                                        }
                                    }
                                ],
                                "acl_type": "extended",
                                "name": "100"
                            }
                        ],
                        "afi": "ipv4"
                    }
                ],
                "state": "merged"
            }
        ]
    }
}
ok: [iosvl2-1] => {
    "msg": {
        "acl_config": []
    }
}

TASK [merge acl config (ios)] ************************************************************************************
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'address': '192.168.1.0', 'netmask': '255.255.255.0'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})

TASK [merge acl config (nxos)] ***********************************************************************************
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'address': '192.168.1.0', 'netmask': '255.255.255.0'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
skipping: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'}) 
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})
changed: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'})

TASK [merge acl config (asa)] ************************************************************************************
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'host': '192.168.1.1'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'host': '192.168.1.2', 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'address': '192.168.1.0', 'wildcard_bits': '0.0.0.255'}}], 'acl_type': 'extended', 'name': 'TEST1'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'sequence': 110, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'sequence': 120, 'source': {'any': True, 'port_protocol': {'range': {'end': '65535', 'start': '1024'}}}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [iosvl2-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
skipping: [nxos-0] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'wildcard_bits': '0.0.0.255'}, 'grant': 'permit', 'protocol': 'ip', 'sequence': 130, 'source': {'any': True}}], 'name': '100'}], 'afi': 'ipv4'}], 'state': 'merged'}) 
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'host': '192.168.2.1', 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'host': '192.168.1.1'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'})
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0', 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'host': '192.168.1.2', 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'})
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'address': '192.168.1.0', 'netmask': '255.255.255.0'}}], 'acl_type': 'extended', 'name': 'TEST1'}]}], 'state': 'merged'})
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'ntp'}}, 'grant': 'deny', 'protocol': 'udp', 'line': 1, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'})
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'any': True, 'port_protocol': {'eq': 'www'}}, 'grant': 'deny', 'protocol': 'tcp', 'line': 2, 'source': {'any': True, 'port_protocol': {'gt': '1023'}}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'})
changed: [asav-1] => (item={'config': [{'acls': [{'aces': [{'destination': {'address': '192.168.2.0', 'netmask': '255.255.255.0'}, 'grant': 'permit', 'protocol': 'ip', 'line': 3, 'source': {'any': True}}], 'acl_type': 'extended', 'name': '100'}]}], 'state': 'merged'})

PLAY RECAP *******************************************************************************************************
asav-1                     : ok=4    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
iosvl2-0                   : ok=7    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
iosvl2-1                   : ok=3    changed=0    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0   
nxos-0                     : ok=4    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
```
</div></details>


# 6. Config確認

### 6-1. IOS機器
6行のACL要件に対し、4行しか設定されていませんでした。入っていないのは名前付きACLの120番と番号付きACLの130番です。

```text:show_run
ip access-list extended TEST1
 deny   udp host 192.168.1.1 host 192.168.2.1 eq ntp
 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255
!
access-list 100 deny   udp any any eq ntp
access-list 100 deny   tcp any gt 1023 any eq www
```

```text:show_ip_access-list
iosvl2-0#sh ip access-list
Extended IP access list 100
    110 deny udp any any eq ntp
    120 deny tcp any gt 1023 any eq www
Extended IP access list TEST1
    110 deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
    130 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255
```

切り分けしたところ、送信元と宛先でアーギュメント指定方法が異なり、かつ宛先がネットワークだとうまく行きませんでした。（前者はホスト/ネットワーク、後者はany/ネットワークの混在。）
以下URLのcisco.iosのConfig生成用コードを見ると、送信元と宛先共通の関数の中で、本来`**config_data[type]`とすべきところが、`**config_data["source"]`となっていました。ここを修正したら6行出力されました。
[GitHub - cisco.ios/plugins/module_utils/network/ios/rm_templates/acls.py
](https://github.com/ansible-collections/cisco.ios/blob/main/plugins/module_utils/network/ios/rm_templates/acls.py#L146:7)

また、ここには記載していませんが、ポート番号をrange指定するケースでも、Configを生成する処理に失敗し、Failedになりました。上記URLのコードを見る限り、rangeは未実装のようです。

現状Configにコマンド`ip access-list TEST1`のみが設定され、エントリが1行も存在しない状態でPlaybookを実行したケースでもFailedとなりました。


### 6-2. NX-OS機器
問題なく設定されていました。

```text:show_run
ip access-list 100
  110 deny udp any any eq ntp
  120 deny tcp any range 1024 65535 any eq www
  130 permit ip any 192.168.2.0/24
ip access-list TEST1
  110 deny udp 192.168.1.1/32 192.168.2.1/32 eq ntp
  120 deny tcp 192.168.1.2/32 range 1024 65535 192.168.2.0/24 eq www
  130 permit ip 192.168.1.0/24 192.168.2.0/24
```

### 6-3. ASA機器
今回の要件では特に問題ありませんでしたが、IOS機器と同様range指定がFailedとなりました。

```text:show_run
access-list TEST1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp
access-list TEST1 extended deny tcp host 192.168.1.2 gt 1023 192.168.2.0 255.255.255.0 eq www
access-list TEST1 extended permit ip 192.168.1.0 255.255.255.0 192.168.2.0 255.255.255.0
access-list 100 extended deny udp any any eq ntp
access-list 100 extended deny tcp any gt 1023 any eq www
access-list 100 extended permit ip any 192.168.2.0 255.255.255.0
```

# 7. 冪等性確認
同じPlaybookを再度実行したところ、IOS、NX-OS機器の一部要件、ASA機器の全要件で`changed`になり、冪等性が担保されませんでした。
どのようなConfigが再投入されているか、`xxx_acls`モジュールの実行結果をregister変数に格納し、変数内の`commands`を確認してみました。

### 7-1. IOS機器
同じシーケンス番号の既存Configを削除しようとしているようですが、コマンドが途切れています。もしこれが既存Configとして扱われているとしたら、追加Configと異なるので再投入されてしまうのも説明が付く気がします。

```
"ip access-list extended TEST1",
"no 120 deny tcp host 192.168.1.2",
"120 deny tcp host 192.168.1.2 gt 1023 192.168.2.0 0.0.0.255 eq www"

"ip access-list extended 100",
"no 120 deny tcp any",
"120 deny tcp any gt 1023 any eq www"
```

### 7-2. NX-OS機器
既存Configは`IPアドレス/プレフィックス`で表示され、追加Configは`host ホストアドレス`もしくは`ネットワークアドレス ワイルドカード`の形で表示されるせいかと思いましたが、名前付き拡張ACLの110番の冪等性が担保される理由が付きませんね。謎ですw

```
"ip access-list TEST1",
"no 120",
"120 deny tcp host 192.168.1.2 range 1024 65535 192.168.2.0 0.0.0.255 eq www"

"ip access-list TEST1",
"no 130",
"130 permit ip 192.168.1.0 0.0.0.255 192.168.2.0 0.0.0.255"

"ip access-list 100",
"no 120",
"120 deny tcp any range 1024 65535 any eq www"

"ip access-list 100",
"no 130",
"130 permit ip any 192.168.2.0 0.0.0.255"
```

### 7-3. ASA機器
既存Configの削除は行われず、新規の扱いでした。これも謎ですw

```
"access-list TEST1 line 1 extended deny udp host 192.168.1.1 host 192.168.2.1 eq ntp"
"access-list TEST1 line 2 extended deny tcp host 192.168.1.2 gt 1023 192.168.2.0 255.255.255.0 eq www"
(省略)
```

# 最後に
ACL設定変更のような単純作業とAnsibleは相性が良く、かつ`xxx_acls`はサポートするアーギュメントの種類が多いため、かなり有用なモジュールだと思います。ただ、今回の動作確認でいくつか想定通りの結果にならない点もあったので、本番作業で使えるのはもう少し先かなと感じました。今回見つかったバグは、GitHubにIssueを上げたいと思います。