summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-08-22 01:04:18 +1000
committerGitHub <noreply@github.com>2026-08-21 10:04:18 -0500
commitcb738721c15ac01f49f66144dae80eb478331f77 (patch)
tree52f870ed414506aaa5cd6efa7ae6513d807e194b
parent885b9462480712210ddeea9ca4a4b7a52e9ef587 (diff)
downloadrest.vyos-cb738721c15ac01f49f66144dae80eb478331f77.tar.gz
rest.vyos-cb738721c15ac01f49f66144dae80eb478331f77.zip
T8989: wave 2 modules: interfaces, l3_interfaces, lag_interfaces, lldp_interfaces, ospfv2/3, ospf_interfaces
* T8989: vyos_interfaces * T8989: vyos_interfaces changelog * T8989: vyos_interfaces changelog * T8989: changelog typo * T8989: vyos_l3_interfaces module * T8989: vyos_l3_interfaces module SIT and UAT * T8989: vyos_l3_interfaces doc * T8989: lag_interfaces * T8989: lag interfaces * T8989: lldp_interfaces * T8989: lldp_interfaces module * T8989: SIT updated * T8989: SIT updated * T8989: SIT updated * T8989: typo fixes * T8989: SIT and UAT updates * T8989: ospf_v3 module * T8989: ospf_v3 module * T8989: vyos_ospf_interfaces * T8989: vyos_ospf_interfaces SIT * T8989: vyos_ospf_interfaces SIT * T8989: vyos_ospfv3 SIT and UAT --------- Co-authored-by: John Estabrook <jestabro@vyos.io>
-rw-r--r--README.md14
-rw-r--r--changelogs/fragments/t8989-vyos_interfaces.yml4
-rw-r--r--docs/vyos.rest.vyos_interfaces_module.rst356
-rw-r--r--docs/vyos.rest.vyos_l3_interfaces_module.rst494
-rw-r--r--docs/vyos.rest.vyos_lag_interfaces_module.rst448
-rw-r--r--docs/vyos.rest.vyos_lldp_interfaces_module.rst470
-rw-r--r--docs/vyos.rest.vyos_ospf_interfaces_module.rst612
-rw-r--r--docs/vyos.rest.vyos_ospfv2_module.rst1263
-rw-r--r--docs/vyos.rest.vyos_ospfv3_module.rst480
-rw-r--r--plugins/modules/vyos_banner.py2
-rw-r--r--plugins/modules/vyos_interfaces.py334
-rw-r--r--plugins/modules/vyos_l3_interfaces.py467
-rw-r--r--plugins/modules/vyos_lag_interfaces.py431
-rw-r--r--plugins/modules/vyos_lldp_interfaces.py365
-rw-r--r--plugins/modules/vyos_logging_global.py8
-rw-r--r--plugins/modules/vyos_ospf_interfaces.py539
-rw-r--r--plugins/modules/vyos_ospfv2.py955
-rw-r--r--plugins/modules/vyos_ospfv3.py360
-rw-r--r--plugins/modules/vyos_route_maps.py15
-rw-r--r--plugins/modules/vyos_snmp_server.py10
-rw-r--r--tests/integration/targets/vyos_banner/tests/httpapi/overridden.yaml30
-rw-r--r--tests/integration/targets/vyos_banner/tests/httpapi/replaced.yaml31
-rw-r--r--tests/integration/targets/vyos_banner/tests/httpapi/rtt.yaml41
-rw-r--r--tests/integration/targets/vyos_hostname/tests/httpapi/overridden.yaml32
-rw-r--r--tests/integration/targets/vyos_hostname/tests/httpapi/rtt.yaml48
-rw-r--r--tests/integration/targets/vyos_interfaces/aliases (renamed from tests/integration/targets/vyos_prefix_lists/aliases)0
-rw-r--r--tests/integration/targets/vyos_interfaces/defaults/main.yaml (renamed from tests/integration/targets/vyos_prefix_lists/defaults/main.yaml)0
-rw-r--r--tests/integration/targets/vyos_interfaces/tasks/httpapi.yaml (renamed from tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml)0
-rw-r--r--tests/integration/targets/vyos_interfaces/tasks/main.yaml (renamed from tests/integration/targets/vyos_prefix_lists/tasks/main.yaml)0
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/_populate_config.yaml9
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/_remove_config.yaml7
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/deleted.yaml30
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/merged.yaml32
-rw-r--r--tests/integration/targets/vyos_interfaces/tests/httpapi/replaced.yaml33
-rw-r--r--tests/integration/targets/vyos_interfaces/vars/main.yaml (renamed from tests/integration/targets/vyos_prefix_lists/vars/main.yaml)0
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/aliases1
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_populate_config.yaml15
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_remove_config.yaml10
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/deleted.yaml33
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/merged.yaml33
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/overridden.yaml35
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/replaced.yaml33
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/tests/httpapi/rtt.yaml48
-rw-r--r--tests/integration/targets/vyos_l3_interfaces/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/aliases1
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_populate_config.yaml13
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/deleted.yaml28
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/merged.yaml36
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/overridden.yaml32
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/replaced.yaml37
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/tests/httpapi/rtt.yaml56
-rw-r--r--tests/integration/targets/vyos_lag_interfaces/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_lldp_global/tests/httpapi/overridden.yaml34
-rw-r--r--tests/integration/targets/vyos_lldp_global/tests/httpapi/rtt.yaml53
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/aliases1
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_populate_config.yaml16
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/deleted.yaml28
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/merged.yaml48
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/overridden.yaml32
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/replaced.yaml34
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/rtt.yaml55
-rw-r--r--tests/integration/targets/vyos_lldp_interfaces/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml6
-rw-r--r--tests/integration/targets/vyos_logging_global/tests/httpapi/overridden.yaml37
-rw-r--r--tests/integration/targets/vyos_logging_global/tests/httpapi/rtt.yaml58
-rw-r--r--tests/integration/targets/vyos_ntp_global/tests/httpapi/rtt.yaml65
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/aliases1
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_populate_config.yaml19
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/deleted.yaml40
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/gathered.yaml20
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/merged.yaml44
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/overridden.yaml33
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/replaced.yaml35
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/rtt.yaml66
-rw-r--r--tests/integration/targets/vyos_ospf_interfaces/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_ospfv2/aliases1
-rw-r--r--tests/integration/targets/vyos_ospfv2/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_ospfv2/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_ospfv2/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/_populate_config.yaml34
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/deleted.yaml29
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/gathered.yaml22
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/merged.yaml56
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/overridden.yaml37
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/replaced.yaml37
-rw-r--r--tests/integration/targets/vyos_ospfv2/tests/httpapi/rtt.yaml61
-rw-r--r--tests/integration/targets/vyos_ospfv2/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_ospfv3/aliases1
-rw-r--r--tests/integration/targets/vyos_ospfv3/defaults/main.yaml3
-rw-r--r--tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml21
-rw-r--r--tests/integration/targets/vyos_ospfv3/tasks/main.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml17
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml29
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml21
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml48
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml35
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml35
-rw-r--r--tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml60
-rw-r--r--tests/integration/targets/vyos_ospfv3/vars/main.yaml2
-rw-r--r--tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml28
-rw-r--r--tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml5
-rw-r--r--tests/integration/targets/vyos_route_maps/tests/httpapi/overridden.yaml40
-rw-r--r--tests/integration/targets/vyos_route_maps/tests/httpapi/rtt.yaml53
-rw-r--r--tests/integration/targets/vyos_snmp_server/tests/httpapi/overridden.yaml35
-rw-r--r--tests/integration/targets/vyos_snmp_server/tests/httpapi/rtt.yaml53
-rw-r--r--tests/unit/fixtures/interfaces_running.json15
-rw-r--r--tests/unit/fixtures/l3_interfaces_running.json21
-rw-r--r--tests/unit/fixtures/lag_interfaces_running.json12
-rw-r--r--tests/unit/fixtures/lldp_interfaces_running.json20
-rw-r--r--tests/unit/fixtures/ospf_interfaces_ipv4.json4
-rw-r--r--tests/unit/fixtures/ospf_interfaces_ipv6.json4
-rw-r--r--tests/unit/fixtures/ospfv2_running.json42
-rw-r--r--tests/unit/fixtures/ospfv3_running.json19
-rw-r--r--tests/unit/modules/test_vyos_interfaces.py315
-rw-r--r--tests/unit/modules/test_vyos_l3_interfaces.py306
-rw-r--r--tests/unit/modules/test_vyos_lag_interfaces.py293
-rw-r--r--tests/unit/modules/test_vyos_lldp_interfaces.py291
-rw-r--r--tests/unit/modules/test_vyos_logging_global.py10
-rw-r--r--tests/unit/modules/test_vyos_ospf_interfaces.py148
-rw-r--r--tests/unit/modules/test_vyos_ospfv2.py180
-rw-r--r--tests/unit/modules/test_vyos_ospfv3.py140
142 files changed, 11843 insertions, 65 deletions
diff --git a/README.md b/README.md
index b479eef..108b42f 100644
--- a/README.md
+++ b/README.md
@@ -79,18 +79,20 @@ Name | Description
[vyos.rest.vyos_banner](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_banner_module.rst)|Manage multiline banners on VyOS devices via REST API.
[vyos.rest.vyos_configure](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_configure_module.rst)|Send raw set/delete commands to a VyOS device via REST API.
[vyos.rest.vyos_hostname](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_hostname_module.rst)|Manage the system hostname on a VyOS device via the REST API.
+[vyos.rest.vyos_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_interfaces_module.rst)|Manage interface configuration on VyOS devices via REST API.
+[vyos.rest.vyos_l3_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_l3_interfaces_module.rst)|Manage L3 interface configuration on VyOS devices via REST API.
+[vyos.rest.vyos_lag_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lag_interfaces_module.rst)|Manage LAG interface configuration on VyOS devices via REST API.
[vyos.rest.vyos_lldp_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lldp_global_module.rst)|Manage LLDP global configuration on VyOS via REST API.
-<<<<<<< HEAD
+[vyos.rest.vyos_lldp_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lldp_int
+erfaces_module.rst)|Manage LLDP interface configuration on VyOS devices via REST API.
[vyos.rest.vyos_logging_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_logging_global_module.rst)|Manage syslog configuration on VyOS devices using REST API
[vyos.rest.vyos_ntp_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ntp_global_module.rst)|Manage NTP configuration on VyOS devices using REST API
+[vyos.rest.vyos_ospf_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospf_interfaces_module.rst)|Manage OSPF interface configuration on VyOS devices using REST API
+[vyos.rest.vyos_ospfv2](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv2_module.rst)|Manage OSPFv2 configuration on VyOS devices using REST API
+[vyos.rest.vyos_ospfv3](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv3_module.rst)|Manage OSPFv3 configuration on VyOS devices using REST API
[vyos.rest.vyos_prefix_lists](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_prefix_lists_module.rst)|Manage prefix-list configuration on VyOS devices using REST API
[vyos.rest.vyos_route_maps](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_route_maps_module.rst)|Manage route-map configuration on VyOS devices using REST API
[vyos.rest.vyos_snmp_server](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_snmp_server_module.rst)|Manage SNMP server configuration on VyOS devices using REST API
-[vyos.rest.vyos_logging_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_logging_global_module.rst)|Manage syslog configuration on VyOS devices using REST API.
-[vyos.rest.vyos_ntp_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ntp_global_module.rst)|Manage NTP configuration on VyOS devices using REST API.
-[vyos.rest.vyos_prefix_lists](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_prefix_lists_module.rst)|⚠️ Manage prefix-list configuration on VyOS devices using REST API. *(not yet available)*
-[vyos.rest.vyos_route_maps](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_route_maps_module.rst)|Manage route-map configuration on VyOS devices using REST API.
-[vyos.rest.vyos_snmp_server](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_snmp_server_module.rst)|Manage SNMP server configuration on VyOS devices using REST API.
[vyos.rest.vyos_static_routes](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_static_routes_module.rst)|Manage static routes on VyOS devices via REST API.
<!--end collection content-->
diff --git a/changelogs/fragments/t8989-vyos_interfaces.yml b/changelogs/fragments/t8989-vyos_interfaces.yml
new file mode 100644
index 0000000..856575d
--- /dev/null
+++ b/changelogs/fragments/t8989-vyos_interfaces.yml
@@ -0,0 +1,4 @@
+---
+minor_changes:
+ - vyos_interfaces - Add support for L2 interface configuration
+ - vyos_l3_interfaces - Add support for L3 interfaces configuration
diff --git a/docs/vyos.rest.vyos_interfaces_module.rst b/docs/vyos.rest.vyos_interfaces_module.rst
new file mode 100644
index 0000000..8438d9d
--- /dev/null
+++ b/docs/vyos.rest.vyos_interfaces_module.rst
@@ -0,0 +1,356 @@
+.. _vyos.rest.vyos_interfaces_module:
+
+
+*************************
+vyos.rest.vyos_interfaces
+*************************
+
+**Manage interface configuration on VyOS devices via REST API.**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages L2 interface configuration (description, MTU, speed, duplex, enabled) on VyOS devices using the HTTPS REST API.
+- IP address configuration is handled by :ref:`vyos.rest.vyos_l3_interfaces <vyos.rest.vyos_l3_interfaces_module>`.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="2">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of interface configurations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>description</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface description.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>duplex</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>auto</li>
+ <li>full</li>
+ <li>half</li>
+ </ul>
+ </td>
+ <td>
+ <div>Interface duplex setting.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>enabled</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li><div style="color: blue"><b>yes</b>&nbsp;&larr;</div></li>
+ </ul>
+ </td>
+ <td>
+ <div>Whether the interface is enabled. False sets the disable flag.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mtu</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface MTU.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Full interface name (e.g. eth0, bond0, lo).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>speed</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>auto</li>
+ <li>10</li>
+ <li>100</li>
+ <li>1000</li>
+ <li>2500</li>
+ <li>10000</li>
+ </ul>
+ </td>
+ <td>
+ <div>Interface speed setting.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>overridden</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ </ul>
+ </td>
+ <td>
+ <div><code>merged</code> - Merge config with existing interface settings.</div>
+ <div><code>replaced</code> - Replace config for listed interfaces.</div>
+ <div><code>overridden</code> - Replace config for all interfaces.</div>
+ <div><code>deleted</code> - Remove listed interface config or all interface config.</div>
+ <div><code>gathered</code> - Read interface config from device without changes.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+See Also
+--------
+
+.. seealso::
+
+ :ref:`vyos.vyos.vyos_interfaces_module`
+ The official documentation on the **vyos.vyos.vyos_interfaces** module.
+ :ref:`vyos.rest.vyos_l3_interfaces_module`
+ The official documentation on the **vyos.rest.vyos_l3_interfaces** module.
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge interface configuration
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Management interface
+ mtu: 1500
+ enabled: true
+ state: merged
+
+ - name: Disable an interface
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth1
+ enabled: false
+ state: merged
+
+ - name: Delete interface description
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ state: deleted
+
+ - name: Gather current interface configuration
+ vyos.rest.vyos_interfaces:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>Interface configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>Interface configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current interface configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_l3_interfaces_module.rst b/docs/vyos.rest.vyos_l3_interfaces_module.rst
new file mode 100644
index 0000000..d251abb
--- /dev/null
+++ b/docs/vyos.rest.vyos_l3_interfaces_module.rst
@@ -0,0 +1,494 @@
+.. _vyos.rest.vyos_l3_interfaces_module:
+
+
+****************************
+vyos.rest.vyos_l3_interfaces
+****************************
+
+**Manage L3 interface configuration on VyOS devices via REST API.**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages IPv4 and IPv6 address configuration on VyOS interfaces using the HTTPS REST API.
+- Mirrors ``vyos.vyos.vyos_l3_interfaces`` but uses the HTTP API instead of CLI.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="4">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of L3 interface configurations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv4</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of IPv4 addresses of the interface.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv4 address in CIDR notation or <code>dhcp</code>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv6</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of IPv6 addresses of the interface.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 address in CIDR notation, <code>dhcpv6</code>, or <code>auto-config</code>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Full name of the interface, e.g. eth0, lo.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>vifs</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of virtual sub-interfaces (VLANs).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv4</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of IPv4 addresses of the VIF.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv4 address in CIDR notation or <code>dhcp</code>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ipv6</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of IPv6 addresses of the VIF.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 address in CIDR notation, <code>dhcpv6</code>, or <code>auto-config</code>.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>vlan_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>VLAN identifier.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>running_config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Used only with state <code>parsed</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>overridden</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ <li>rendered</li>
+ <li>parsed</li>
+ </ul>
+ </td>
+ <td>
+ <div><code>merged</code> - Add addresses without removing existing ones.</div>
+ <div><code>replaced</code> - Replace addresses for listed interfaces.</div>
+ <div><code>overridden</code> - Replace addresses for all interfaces.</div>
+ <div><code>deleted</code> - Remove listed or all interface addresses.</div>
+ <div><code>gathered</code> - Read interface addresses from device without changes.</div>
+ <div><code>rendered</code> - Return commands for provided config without connecting.</div>
+ <div><code>parsed</code> - Parse running_config into structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+See Also
+--------
+
+.. seealso::
+
+ :ref:`vyos.vyos.vyos_l3_interfaces_module`
+ The official documentation on the **vyos.vyos.vyos_l3_interfaces** module.
+ :ref:`vyos.rest.vyos_interfaces_module`
+ The official documentation on the **vyos.rest.vyos_interfaces** module.
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge L3 interface configuration
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: eth0
+ ipv4:
+ - address: 192.0.2.1/24
+ - address: dhcp
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ ipv6:
+ - address: 2001:db8::1/128
+ state: merged
+
+ - name: Add VLAN subinterface addresses
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: eth0
+ vifs:
+ - vlan_id: 100
+ ipv4:
+ - address: 192.0.2.100/24
+ state: merged
+
+ - name: Delete all interface addresses
+ vyos.rest.vyos_l3_interfaces:
+ state: deleted
+
+ - name: Gather current L3 interface configuration
+ vyos.rest.vyos_l3_interfaces:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>L3 interface configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>L3 interface configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current L3 interface configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>parsed</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is parsed</td>
+ <td>
+ <div>Structured data parsed from running_config (state=parsed).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rendered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is rendered</td>
+ <td>
+ <div>Commands for the provided config (state=rendered).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_lag_interfaces_module.rst b/docs/vyos.rest.vyos_lag_interfaces_module.rst
new file mode 100644
index 0000000..49753f0
--- /dev/null
+++ b/docs/vyos.rest.vyos_lag_interfaces_module.rst
@@ -0,0 +1,448 @@
+.. _vyos.rest.vyos_lag_interfaces_module:
+
+
+*****************************
+vyos.rest.vyos_lag_interfaces
+*****************************
+
+**Manage LAG interface configuration on VyOS devices via REST API.**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages Link Aggregation Group (LAG/bonding) interface configuration on VyOS devices using the HTTPS REST API.
+- Mirrors ``vyos.vyos.vyos_lag_interfaces`` but uses the HTTP API instead of CLI.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="3">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of LAG interface configurations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>arp_monitor</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>ARP link monitoring parameters.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>interval</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>ARP monitoring interval in milliseconds.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>target</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IP addresses to use for ARP monitoring.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>hash_policy</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>layer2</li>
+ <li>layer2+3</li>
+ <li>layer3+4</li>
+ </ul>
+ </td>
+ <td>
+ <div>Transmit hash policy.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>members</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of member interfaces.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>member</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the member interface.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mode</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>802.3ad</li>
+ <li>active-backup</li>
+ <li>broadcast</li>
+ <li>round-robin</li>
+ <li>transmit-load-balance</li>
+ <li>adaptive-load-balance</li>
+ <li>xor-hash</li>
+ </ul>
+ </td>
+ <td>
+ <div>LAG bonding mode.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the LAG interface (e.g. bond0).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>primary</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Primary interface for active-backup mode.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>running_config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Used only with state <code>parsed</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>overridden</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ <li>rendered</li>
+ <li>parsed</li>
+ </ul>
+ </td>
+ <td>
+ <div><code>merged</code> - Merge config with existing LAG settings.</div>
+ <div><code>replaced</code> - Replace config for listed LAG interfaces.</div>
+ <div><code>overridden</code> - Replace config for all LAG interfaces.</div>
+ <div><code>deleted</code> - Remove listed or all LAG interface config.</div>
+ <div><code>gathered</code> - Read LAG config from device without changes.</div>
+ <div><code>rendered</code> - Return commands for provided config without connecting.</div>
+ <div><code>parsed</code> - Parse running_config into structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+
+See Also
+--------
+
+.. seealso::
+
+ :ref:`vyos.vyos.vyos_lag_interfaces_module`
+ The official documentation on the **vyos.vyos.vyos_lag_interfaces** module.
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge LAG interface configuration
+ vyos.rest.vyos_lag_interfaces:
+ config:
+ - name: bond0
+ mode: 802.3ad
+ hash_policy: layer2
+ members:
+ - member: eth1
+ - member: eth2
+ state: merged
+
+ - name: Delete all LAG interfaces
+ vyos.rest.vyos_lag_interfaces:
+ state: deleted
+
+ - name: Gather current LAG configuration
+ vyos.rest.vyos_lag_interfaces:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>LAG configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>LAG configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current LAG configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>parsed</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is parsed</td>
+ <td>
+ <div>Structured data parsed from running_config (state=parsed).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rendered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is rendered</td>
+ <td>
+ <div>Commands for provided config (state=rendered).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_lldp_interfaces_module.rst b/docs/vyos.rest.vyos_lldp_interfaces_module.rst
new file mode 100644
index 0000000..c294c79
--- /dev/null
+++ b/docs/vyos.rest.vyos_lldp_interfaces_module.rst
@@ -0,0 +1,470 @@
+.. _vyos.rest.vyos_lldp_interfaces_module:
+
+
+******************************
+vyos.rest.vyos_lldp_interfaces
+******************************
+
+**Manage LLDP interface configuration on VyOS devices via REST API.**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages per-interface LLDP configuration on VyOS devices using the HTTPS REST API.
+- Targets VyOS 1.5+ where LLDP interface mode replaces the legacy disable flag.
+- For the disable flag used in VyOS 1.3/1.4, see ``vyos.vyos.vyos_lldp_interfaces``.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="4">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of LLDP interface configurations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>location</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>LLDP-MED location data.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>coordinate_based</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Coordinate-based location.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>altitude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Altitude in meters.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>datum</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>WGS84</li>
+ <li>NAD83</li>
+ <li>MLLW</li>
+ </ul>
+ </td>
+ <td>
+ <div>Coordinate datum type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>latitude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Latitude (e.g. 33.524449N).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>longitude</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Longitude (e.g. 22.267255E).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>elin</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Emergency Call Service ELIN number (10-25 digits).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mode</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>disable</li>
+ <li>rx-tx</li>
+ <li>rx</li>
+ <li>tx</li>
+ </ul>
+ </td>
+ <td>
+ <div>LLDP administrative mode for this interface.</div>
+ <div><code>rx-tx</code> sends and receives LLDP frames (default).</div>
+ <div><code>disable</code> disables LLDP on this interface.</div>
+ <div><code>rx</code> receives only.</div>
+ <div><code>tx</code> transmits only.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of the interface.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>running_config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Used only with state <code>parsed</code>.</div>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>overridden</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ <li>rendered</li>
+ <li>parsed</li>
+ </ul>
+ </td>
+ <td>
+ <div><code>merged</code> - Merge config with existing LLDP interface settings.</div>
+ <div><code>replaced</code> - Replace config for listed interfaces.</div>
+ <div><code>overridden</code> - Replace config for all LLDP interfaces.</div>
+ <div><code>deleted</code> - Remove listed or all LLDP interface config.</div>
+ <div><code>gathered</code> - Read LLDP interface config from device without changes.</div>
+ <div><code>rendered</code> - Return commands for provided config without connecting.</div>
+ <div><code>parsed</code> - Parse running_config into structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - Targets VyOS 1.5+ exclusively. The ``mode`` parameter replaces the ``enable`` boolean used in ``vyos.vyos.vyos_lldp_interfaces`` for VyOS 1.3/1.4.
+
+
+See Also
+--------
+
+.. seealso::
+
+ :ref:`vyos.vyos.vyos_lldp_interfaces_module`
+ The official documentation on the **vyos.vyos.vyos_lldp_interfaces** module.
+ :ref:`vyos.rest.vyos_lldp_global_module`
+ The official documentation on the **vyos.rest.vyos_lldp_global** module.
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "1234567890"
+ - name: eth1
+ location:
+ coordinate_based:
+ latitude: "33.524449N"
+ longitude: "22.267255E"
+ altitude: 2200
+ datum: WGS84
+ state: merged
+
+ - name: Delete all LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ state: deleted
+
+ - name: Gather current LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>LLDP interface configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>LLDP interface configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current LLDP interface configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>parsed</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is parsed</td>
+ <td>
+ <div>Structured data parsed from running_config (state=parsed).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>rendered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is rendered</td>
+ <td>
+ <div>Commands for provided config (state=rendered).</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_ospf_interfaces_module.rst b/docs/vyos.rest.vyos_ospf_interfaces_module.rst
new file mode 100644
index 0000000..1439a71
--- /dev/null
+++ b/docs/vyos.rest.vyos_ospf_interfaces_module.rst
@@ -0,0 +1,612 @@
+.. _vyos.rest.vyos_ospf_interfaces_module:
+
+
+******************************
+vyos.rest.vyos_ospf_interfaces
+******************************
+
+**Manage OSPF interface configuration on VyOS devices using REST API**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages OSPF and OSPFv3 interface configuration on VyOS devices via the REST API.
+- IPv4 OSPF maps to ``protocols ospf interface``.
+- IPv6 OSPFv3 maps to ``protocols ospfv3 interface``.
+- Uses REST API (``connection=httpapi``) instead of CLI.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="5">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>List of OSPF interface configurations.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address_family</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPF settings per address family.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>afi</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>ipv4</li>
+ <li>ipv6</li>
+ </ul>
+ </td>
+ <td>
+ <div>Address family identifier.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>authentication</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Authentication settings (IPv4 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>md5_key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MD5 authentication key.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>key</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MD5 key string.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>key_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>MD5 key ID.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>plaintext_password</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Plaintext password.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>bandwidth</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface bandwidth in kbps (IPv4 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>cost</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface cost metric.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>dead_interval</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Dead router detection interval in seconds.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>hello_interval</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Hello packet interval in seconds.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ifmtu</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface MTU (IPv6 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>instance</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv3 instance ID (IPv6 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>mtu_ignore</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable MTU check (IPv4 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>network</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>broadcast</li>
+ <li>non-broadcast</li>
+ <li>point-to-multipoint</li>
+ <li>point-to-point</li>
+ </ul>
+ </td>
+ <td>
+ <div>Network type (IPv4 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>passive</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Disable adjacency formation (IPv6 only).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>priority</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface priority.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>retransmit_interval</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>LSA retransmit interval in seconds.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>transmit_delay</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>LSA transmit delay in seconds.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>name</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Interface name.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>overridden</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ </ul>
+ </td>
+ <td>
+ <div>Desired state of the OSPF interface configuration.</div>
+ <div><code>merged</code> adds or updates without removing existing config.</div>
+ <div><code>replaced</code> replaces per-interface OSPF config for named interfaces.</div>
+ <div><code>overridden</code> replaces all OSPF interface config.</div>
+ <div><code>deleted</code> removes OSPF interface config.</div>
+ <div><code>gathered</code> returns current configuration as structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - Requires ``ansible_connection=httpapi`` with the VyOS httpapi plugin.
+ - ``ansible_network_os`` must be set to ``vyos.rest.vyos``.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ priority: 26
+ - afi: ipv6
+ dead_interval: 39
+ passive: true
+ state: merged
+
+ - name: Delete OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ state: deleted
+
+ - name: Delete all OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ state: deleted
+
+ - name: Gather current OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>OSPF interface configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>OSPF interface configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current OSPF interface configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_ospfv2_module.rst b/docs/vyos.rest.vyos_ospfv2_module.rst
new file mode 100644
index 0000000..1c5c248
--- /dev/null
+++ b/docs/vyos.rest.vyos_ospfv2_module.rst
@@ -0,0 +1,1263 @@
+.. _vyos.rest.vyos_ospfv2_module:
+
+
+*********************
+vyos.rest.vyos_ospfv2
+*********************
+
+**Manage OSPFv2 configuration on VyOS devices using REST API**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages OSPFv2 configuration on VyOS devices via the REST API.
+- Uses REST API (``connection=httpapi``) instead of CLI.
+- In VyOS 1.5+, passive interfaces use per-interface config rather than passive-interface.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="5">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv2 configuration.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>areas</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv2 areas.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>area_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Area ID.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>area_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Area type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>normal</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Normal area.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>nssa</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>NSSA area.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_cost</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Default cost for NSSA.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>no_summary</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Do not inject inter-area routes.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>set</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Enable NSSA.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>translate</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>always</li>
+ <li>candidate</li>
+ <li>never</li>
+ </ul>
+ </td>
+ <td>
+ <div>NSSA-ABR translate setting.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>stub</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Stub area.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_cost</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Default cost for stub.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>no_summary</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Do not inject inter-area routes.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>set</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Enable stub.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>authentication</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>plaintext-password</li>
+ <li>md5</li>
+ </ul>
+ </td>
+ <td>
+ <div>Area authentication type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>network</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Networks in this area.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Network address.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>range</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Area ranges.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Range address.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>cost</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Cost for this range.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>not_advertise</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Do not advertise this range.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>substitute</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Substitute prefix.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>shortcut</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>default</li>
+ <li>disable</li>
+ <li>enable</li>
+ </ul>
+ </td>
+ <td>
+ <div>Shortcut mode.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>auto_cost</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Auto-cost reference bandwidth.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>reference_bandwidth</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Reference bandwidth in Mbps.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_information</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Default route distribution.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>originate</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Originate default route.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>always</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Always advertise default route.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>metric</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Metric for default route.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>metric_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Metric type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>route_map</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Route map.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>default_metric</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Default metric for redistributed routes.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>distance</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Administrative distances.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>global</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Global OSPFv2 distance.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>ospf</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Per-route-type distances.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>external</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>External route distance.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>inter_area</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Inter-area route distance.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>intra_area</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Intra-area route distance.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>log_adjacency_changes</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>detail</li>
+ </ul>
+ </td>
+ <td>
+ <div>Log adjacency changes.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>neighbor</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPF neighbors.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>neighbor_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Neighbor IP.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>poll_interval</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Poll interval.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>priority</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Neighbor priority.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>parameters</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv2 parameters.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>abr_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>cisco</li>
+ <li>ibm</li>
+ <li>shortcut</li>
+ <li>standard</li>
+ </ul>
+ </td>
+ <td>
+ <div>ABR type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>opaque_lsa</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Enable opaque LSA.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>rfc1583_compatibility</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Enable RFC1583 compatibility.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>router_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Router ID.</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>passive_interface</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Passive interfaces (VyOS 1.5+: configured via <code>protocols ospf interface &lt;name&gt; passive</code>).</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>redistribute</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Route redistribution.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>metric</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Metric.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>metric_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">integer</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Metric type.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>route_map</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Route map.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>route_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>bgp</li>
+ <li>connected</li>
+ <li>kernel</li>
+ <li>rip</li>
+ <li>static</li>
+ </ul>
+ </td>
+ <td>
+ <div>Protocol to redistribute.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td colspan="5">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ </ul>
+ </td>
+ <td>
+ <div>Desired state.</div>
+ <div><code>merged</code> adds/updates without removing existing config.</div>
+ <div><code>replaced</code> replaces the entire OSPFv2 configuration.</div>
+ <div><code>deleted</code> removes OSPFv2 configuration.</div>
+ <div><code>gathered</code> returns current configuration as structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - Requires ``ansible_connection=httpapi`` with the VyOS httpapi plugin.
+ - ``ansible_network_os`` must be set to ``vyos.rest.vyos``.
+ - VyOS 1.5+ uses per-interface passive configuration rather than the global ``passive-interface`` command used in VyOS 1.4.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ abr_type: cisco
+ auto_cost:
+ reference_bandwidth: 2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ - area_id: "3"
+ area_type:
+ nssa:
+ set: true
+ - area_id: "4"
+ area_type:
+ stub:
+ default_cost: 20
+ range:
+ - address: 192.0.3.0/24
+ cost: 10
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ passive_interface:
+ - eth1
+ state: merged
+
+ - name: Delete all OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ state: deleted
+
+ - name: Gather current OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>OSPFv2 configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>OSPFv2 configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current OSPFv2 configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/docs/vyos.rest.vyos_ospfv3_module.rst b/docs/vyos.rest.vyos_ospfv3_module.rst
new file mode 100644
index 0000000..18bb48e
--- /dev/null
+++ b/docs/vyos.rest.vyos_ospfv3_module.rst
@@ -0,0 +1,480 @@
+.. _vyos.rest.vyos_ospfv3_module:
+
+
+*********************
+vyos.rest.vyos_ospfv3
+*********************
+
+**Manage OSPFv3 configuration on VyOS devices using REST API**
+
+
+Version added: 1.0.0
+
+.. contents::
+ :local:
+ :depth: 1
+
+
+Synopsis
+--------
+- Manages OSPFv3 configuration on VyOS devices via the REST API.
+- Uses REST API (``connection=httpapi``) instead of CLI.
+
+
+
+
+Parameters
+----------
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="4">Parameter</th>
+ <th>Choices/<font color="blue">Defaults</font></th>
+ <th width="100%">Comments</th>
+ </tr>
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>config</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv3 configuration.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>areas</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv3 areas.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>area_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Area identity.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>export_list</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of export-list.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>import_list</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Name of import-list.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>range</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Summarize routes matching prefix.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>address</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ / <span style="color: red">required</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>IPv6 prefix.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>advertise</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Advertise this range.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>not_advertise</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>no</li>
+ <li>yes</li>
+ </ul>
+ </td>
+ <td>
+ <div>Do not advertise this range.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>parameters</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>OSPFv3 global parameters.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>router_id</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Router ID (IPv4 address format).</div>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td colspan="3">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>redistribute</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ / <span style="color: purple">elements=dictionary</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Redistribute routes from another protocol.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>route_map</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ </td>
+ <td>
+ <div>Route map to apply.</div>
+ </td>
+ </tr>
+ <tr>
+ <td class="elbow-placeholder"></td>
+ <td class="elbow-placeholder"></td>
+ <td colspan="2">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>route_type</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li>bgp</li>
+ <li>connected</li>
+ <li>kernel</li>
+ <li>ripng</li>
+ <li>static</li>
+ </ul>
+ </td>
+ <td>
+ <div>Protocol to redistribute.</div>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td colspan="4">
+ <div class="ansibleOptionAnchor" id="parameter-"></div>
+ <b>state</b>
+ <a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
+ <div style="font-size: small">
+ <span style="color: purple">string</span>
+ </div>
+ </td>
+ <td>
+ <ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <li><div style="color: blue"><b>merged</b>&nbsp;&larr;</div></li>
+ <li>replaced</li>
+ <li>deleted</li>
+ <li>gathered</li>
+ </ul>
+ </td>
+ <td>
+ <div>Desired state of the OSPFv3 configuration.</div>
+ <div><code>merged</code> adds or updates without removing existing config.</div>
+ <div><code>replaced</code> replaces the entire OSPFv3 configuration.</div>
+ <div><code>deleted</code> removes OSPFv3 configuration.</div>
+ <div><code>gathered</code> returns current configuration as structured data.</div>
+ </td>
+ </tr>
+ </table>
+ <br/>
+
+
+Notes
+-----
+
+.. note::
+ - Requires ``ansible_connection=httpapi`` with the VyOS httpapi plugin.
+ - ``ansible_network_os`` must be set to ``vyos.rest.vyos``.
+
+
+
+Examples
+--------
+
+.. code-block:: yaml
+
+ - name: Merge OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+ - name: Delete all OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+ - name: Gather current OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+
+
+Return Values
+-------------
+Common return values are documented `here <https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values>`_, the following are the fields unique to this module:
+
+.. raw:: html
+
+ <table border=0 cellpadding=0 class="documentation-table">
+ <tr>
+ <th colspan="1">Key</th>
+ <th>Returned</th>
+ <th width="100%">Description</th>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>after</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changed</td>
+ <td>
+ <div>OSPFv3 configuration after this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>before</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>OSPFv3 configuration before this module ran.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>commands</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">list</span>
+ </div>
+ </td>
+ <td>always</td>
+ <td>
+ <div>List of API command tuples sent to the device.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>gathered</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when state is gathered</td>
+ <td>
+ <div>Current OSPFv3 configuration as structured data.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>response</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">dictionary</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Raw API response.</div>
+ <br/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="1">
+ <div class="ansibleOptionAnchor" id="return-"></div>
+ <b>saved</b>
+ <a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
+ <div style="font-size: small">
+ <span style="color: purple">boolean</span>
+ </div>
+ </td>
+ <td>when changes are applied</td>
+ <td>
+ <div>Whether the config was saved after changes.</div>
+ <br/>
+ </td>
+ </tr>
+ </table>
+ <br/><br/>
+
+
+Status
+------
+
+
+Authors
+~~~~~~~
+
+- VyOS Community (@vyos)
diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py
index 304fa20..bcda26d 100644
--- a/plugins/modules/vyos_banner.py
+++ b/plugins/modules/vyos_banner.py
@@ -267,7 +267,7 @@ def main():
try:
if state in ("merged", "replaced"):
- if state == "merged" and before.get("text") == desired_text:
+ if before.get("text") == desired_text:
module.exit_json(changed=False, before=before, commands=[])
client.configure_set(path, _encode_banner(desired_text))
commands.append("set {p} '...'".format(p=" ".join(path)))
diff --git a/plugins/modules/vyos_interfaces.py b/plugins/modules/vyos_interfaces.py
new file mode 100644
index 0000000..6ed45d5
--- /dev/null
+++ b/plugins/modules/vyos_interfaces.py
@@ -0,0 +1,334 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_interfaces
+short_description: Manage interface configuration on VyOS devices via REST API.
+description:
+ - Manages L2 interface configuration (description, MTU, speed, duplex, enabled)
+ on VyOS devices using the HTTPS REST API.
+ - IP address configuration is handled by M(vyos.rest.vyos_l3_interfaces).
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Full interface name (e.g. eth0, bond0, lo).
+ type: str
+ required: true
+ description:
+ description: Interface description.
+ type: str
+ enabled:
+ description: Whether the interface is enabled. False sets the disable flag.
+ type: bool
+ default: true
+ mtu:
+ description: Interface MTU.
+ type: int
+ duplex:
+ description: Interface duplex setting.
+ type: str
+ choices: [auto, full, half]
+ speed:
+ description: Interface speed setting.
+ type: str
+ choices: [auto, "10", "100", "1000", "2500", "10000"]
+ state:
+ description:
+ - C(merged) - Merge config with existing interface settings.
+ - C(replaced) - Replace config for listed interfaces.
+ - C(overridden) - Replace config for all interfaces.
+ - C(deleted) - Remove listed interface config or all interface config.
+ - C(gathered) - Read interface config from device without changes.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered]
+ default: merged
+seealso:
+ - module: vyos.vyos.vyos_interfaces
+ - module: vyos.rest.vyos_l3_interfaces
+"""
+
+EXAMPLES = r"""
+- name: Merge interface configuration
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Management interface
+ mtu: 1500
+ enabled: true
+ state: merged
+
+- name: Disable an interface
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth1
+ enabled: false
+ state: merged
+
+- name: Delete interface description
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ state: deleted
+
+- name: Gather current interface configuration
+ vyos.rest.vyos_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: Interface configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: Interface configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current interface configuration as structured data.
+ returned: when state is gathered
+ type: list
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+# Interface name prefix → API type key
+_IFACE_TYPE = {
+ "eth": "ethernet",
+ "bond": "bonding",
+ "lo": "loopback",
+ "tun": "tunnel",
+ "wg": "wireguard",
+ "vti": "vti",
+ "dum": "dummy",
+ "vtun": "openvpn",
+ "ppp": "pppoe",
+ "wlan": "wireless",
+ "br": "bridge",
+}
+
+# L2 fields managed by this module — excludes address, hw-id etc.
+_L2_FIELDS = ["description", "mtu", "duplex", "speed"]
+
+
+def _iface_type(name):
+ for prefix, itype in _IFACE_TYPE.items():
+ if name.startswith(prefix):
+ return itype
+ return "ethernet"
+
+
+def _iface_base(name):
+ return ["interfaces", _iface_type(name), name]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(["interfaces"])
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ result = []
+ for itype, ifaces in sorted(raw.items()):
+ if not isinstance(ifaces, dict):
+ continue
+ for iname, idata in sorted(ifaces.items()):
+ idata = idata or {}
+ entry = {"name": iname}
+ if idata.get("description"):
+ entry["description"] = idata["description"]
+ if "mtu" in idata:
+ entry["mtu"] = int(idata["mtu"])
+ if "duplex" in idata:
+ entry["duplex"] = idata["duplex"]
+ if "speed" in idata:
+ entry["speed"] = idata["speed"]
+ entry["enabled"] = "disable" not in idata
+ result.append(entry)
+
+ return result
+
+
+def _normalize(config):
+ """Convert argspec list to dict keyed by interface name."""
+ return {entry["name"]: entry for entry in (config or [])}
+
+
+def _iface_cmds(name, want, have):
+ """Generate set/delete commands to bring have → want for one interface."""
+ cmds = []
+ base = _iface_base(name)
+ have = have or {}
+
+ # description
+ want_desc = want.get("description")
+ have_desc = have.get("description")
+ if want_desc is not None and want_desc != have_desc:
+ cmds.append(("set", base + ["description", want_desc]))
+ elif want_desc is None and have_desc is not None:
+ cmds.append(("delete", base + ["description"]))
+
+ # mtu
+ want_mtu = want.get("mtu")
+ have_mtu = have.get("mtu")
+ if want_mtu is not None and want_mtu != have_mtu:
+ cmds.append(("set", base + ["mtu", str(want_mtu)]))
+
+ # duplex
+ want_duplex = want.get("duplex")
+ have_duplex = have.get("duplex")
+ if want_duplex is not None and want_duplex != have_duplex:
+ cmds.append(("set", base + ["duplex", want_duplex]))
+
+ # speed
+ want_speed = want.get("speed")
+ have_speed = have.get("speed")
+ if want_speed is not None and want_speed != have_speed:
+ cmds.append(("set", base + ["speed", want_speed]))
+
+ # enabled / disable flag
+ want_enabled = want.get("enabled", True)
+ have_enabled = have.get("enabled", True)
+ if not want_enabled and have_enabled:
+ cmds.append(("set", base + ["disable"]))
+ elif want_enabled and not have_enabled:
+ cmds.append(("delete", base + ["disable"]))
+
+ return cmds
+
+
+def _delete_iface_config(name, have):
+ """Generate delete commands to remove L2 config from an interface."""
+ cmds = []
+ base = _iface_base(name)
+ have = have or {}
+
+ for field in _L2_FIELDS:
+ if field in have:
+ cmds.append(("delete", base + [field]))
+ if not have.get("enabled", True):
+ cmds.append(("delete", base + ["disable"]))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = _normalize(have_raw)
+
+ if state == "deleted":
+ if not config:
+ for name, have in have_map.items():
+ cmds += _delete_iface_config(name, have)
+ else:
+ for entry in config:
+ name = entry["name"]
+ cmds += _delete_iface_config(name, have_map.get(name, {}))
+ return cmds
+
+ want_map = _normalize(config)
+
+ if state == "overridden":
+ # delete L2 config from interfaces not in want
+ for name in set(have_map) - set(want_map):
+ cmds += _delete_iface_config(name, have_map[name])
+
+ for name, want in want_map.items():
+ have = have_map.get(name, {})
+
+ if state == "replaced":
+ # pre-check — only act if something differs
+ test_cmds = _iface_cmds(name, want, have)
+ if not test_cmds:
+ continue
+ # delete L2 fields then rebuild
+ cmds += _delete_iface_config(name, have)
+ have = {}
+
+ cmds += _iface_cmds(name, want, have if state != "replaced" else {})
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ description=dict(type="str"),
+ enabled=dict(type="bool", default=True),
+ mtu=dict(type="int"),
+ duplex=dict(type="str", choices=["auto", "full", "half"]),
+ speed=dict(type="str", choices=["auto", "10", "100", "1000", "2500", "10000"]),
+ ),
+ ),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or []
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_l3_interfaces.py b/plugins/modules/vyos_l3_interfaces.py
new file mode 100644
index 0000000..9b33973
--- /dev/null
+++ b/plugins/modules/vyos_l3_interfaces.py
@@ -0,0 +1,467 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_l3_interfaces
+short_description: Manage L3 interface configuration on VyOS devices via REST API.
+description:
+ - Manages IPv4 and IPv6 address configuration on VyOS interfaces using the
+ HTTPS REST API.
+ - Mirrors C(vyos.vyos.vyos_l3_interfaces) but uses the HTTP API instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of L3 interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Full name of the interface, e.g. eth0, lo.
+ type: str
+ required: true
+ ipv4:
+ description: List of IPv4 addresses of the interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv4 address in CIDR notation or C(dhcp).
+ type: str
+ ipv6:
+ description: List of IPv6 addresses of the interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPv6 address in CIDR notation, C(dhcpv6), or C(auto-config).
+ type: str
+ vifs:
+ description: List of virtual sub-interfaces (VLANs).
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description: VLAN identifier.
+ type: int
+ required: true
+ ipv4:
+ description: List of IPv4 addresses of the VIF.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IPv4 address in CIDR notation or C(dhcp).
+ type: str
+ ipv6:
+ description: List of IPv6 addresses of the VIF.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IPv6 address in CIDR notation, C(dhcpv6), or C(auto-config).
+ type: str
+ running_config:
+ description: Used only with state C(parsed).
+ type: str
+ state:
+ description:
+ - C(merged) - Add addresses without removing existing ones.
+ - C(replaced) - Replace addresses for listed interfaces.
+ - C(overridden) - Replace addresses for all interfaces.
+ - C(deleted) - Remove listed or all interface addresses.
+ - C(gathered) - Read interface addresses from device without changes.
+ - C(rendered) - Return commands for provided config without connecting.
+ - C(parsed) - Parse running_config into structured data.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered, rendered, parsed]
+ default: merged
+seealso:
+ - module: vyos.vyos.vyos_l3_interfaces
+ - module: vyos.rest.vyos_interfaces
+"""
+
+EXAMPLES = r"""
+- name: Merge L3 interface configuration
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: eth0
+ ipv4:
+ - address: 192.0.2.1/24
+ - address: dhcp
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ ipv6:
+ - address: 2001:db8::1/128
+ state: merged
+
+- name: Add VLAN subinterface addresses
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: eth0
+ vifs:
+ - vlan_id: 100
+ ipv4:
+ - address: 192.0.2.100/24
+ state: merged
+
+- name: Delete all interface addresses
+ vyos.rest.vyos_l3_interfaces:
+ state: deleted
+
+- name: Gather current L3 interface configuration
+ vyos.rest.vyos_l3_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: L3 interface configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: L3 interface configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current L3 interface configuration as structured data.
+ returned: when state is gathered
+ type: list
+rendered:
+ description: Commands for the provided config (state=rendered).
+ returned: when state is rendered
+ type: list
+parsed:
+ description: Structured data parsed from running_config (state=parsed).
+ returned: when state is parsed
+ type: list
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_IFACE_TYPE = {
+ "eth": "ethernet",
+ "bond": "bonding",
+ "lo": "loopback",
+ "tun": "tunnel",
+ "wg": "wireguard",
+ "vti": "vti",
+ "dum": "dummy",
+ "vtun": "openvpn",
+ "br": "bridge",
+}
+
+
+def _iface_type(name):
+ for prefix, itype in _IFACE_TYPE.items():
+ if name.startswith(prefix):
+ return itype
+ return "ethernet"
+
+
+def _iface_base(name):
+ return ["interfaces", _iface_type(name), name]
+
+
+def _addr_list(raw):
+ """Normalize address field — string or list → sorted list."""
+ if not raw:
+ return []
+ if isinstance(raw, str):
+ return [raw]
+ if isinstance(raw, list):
+ return sorted(raw)
+ return []
+
+
+def _split_addresses(addresses):
+ """Split address list into ipv4 and ipv6 lists."""
+ ipv4 = []
+ ipv6 = []
+ for addr in addresses:
+ if addr in ("dhcp", "dhcpv6"):
+ if addr == "dhcp":
+ ipv4.append(addr)
+ else:
+ ipv6.append(addr)
+ elif ":" in addr:
+ ipv6.append(addr)
+ else:
+ ipv4.append(addr)
+ return sorted(ipv4), sorted(ipv6)
+
+
+def _parse_iface(name, idata):
+ """Parse raw API interface data into argspec format."""
+ idata = idata or {}
+ entry = {"name": name}
+
+ addrs = _addr_list(idata.get("address"))
+ if addrs:
+ ipv4, ipv6 = _split_addresses(addrs)
+ if ipv4:
+ entry["ipv4"] = [{"address": a} for a in ipv4]
+ if ipv6:
+ entry["ipv6"] = [{"address": a} for a in ipv6]
+
+ vif_data = idata.get("vif") or {}
+ if isinstance(vif_data, dict) and vif_data:
+ vifs = []
+ for vlan_id, vdata in sorted(vif_data.items(), key=lambda x: int(x[0])):
+ vdata = vdata or {}
+ vif = {"vlan_id": int(vlan_id)}
+ vaddrs = _addr_list(vdata.get("address"))
+ if vaddrs:
+ vipv4, vipv6 = _split_addresses(vaddrs)
+ if vipv4:
+ vif["ipv4"] = [{"address": a} for a in vipv4]
+ if vipv6:
+ vif["ipv6"] = [{"address": a} for a in vipv6]
+ vifs.append(vif)
+ if vifs:
+ entry["vifs"] = vifs
+
+ return entry
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(["interfaces"])
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ result = []
+ for itype, ifaces in sorted(raw.items()):
+ if not isinstance(ifaces, dict):
+ continue
+ for iname, idata in sorted(ifaces.items()):
+ entry = _parse_iface(iname, idata)
+ # only include if there's at least one address or vif
+ if entry.get("ipv4") or entry.get("ipv6") or entry.get("vifs"):
+ result.append(entry)
+
+ return result
+
+
+def _normalize(config):
+ """Convert argspec list to dict keyed by interface name."""
+ result = {}
+ for entry in config or []:
+ name = entry["name"]
+ ipv4 = sorted([a["address"] for a in (entry.get("ipv4") or [])])
+ ipv6 = sorted([a["address"] for a in (entry.get("ipv6") or [])])
+ vifs = {}
+ for vif in entry.get("vifs") or []:
+ vid = vif["vlan_id"]
+ vipv4 = sorted([a["address"] for a in (vif.get("ipv4") or [])])
+ vipv6 = sorted([a["address"] for a in (vif.get("ipv6") or [])])
+ vifs[vid] = {"ipv4": vipv4, "ipv6": vipv6}
+ result[name] = {"ipv4": ipv4, "ipv6": ipv6, "vifs": vifs}
+ return result
+
+
+def _addr_cmds(base, want_addrs, have_addrs, state):
+ """Generate set/delete commands for address lists."""
+ cmds = []
+ want_set = set(want_addrs)
+ have_set = set(have_addrs)
+
+ for addr in want_set - have_set:
+ cmds.append(("set", base + ["address", addr]))
+
+ if state in ("replaced", "deleted", "overridden"):
+ for addr in have_set - want_set:
+ cmds.append(("delete", base + ["address", addr]))
+
+ return cmds
+
+
+def _vif_cmds(iface_base, want_vifs, have_vifs, state):
+ """Generate commands for VIF subinterfaces."""
+ cmds = []
+
+ if state in ("replaced", "overridden"):
+ for vid in set(have_vifs) - set(want_vifs):
+ cmds.append(("delete", iface_base + ["vif", str(vid)]))
+
+ for vid, want_vif in want_vifs.items():
+ have_vif = have_vifs.get(vid, {"ipv4": [], "ipv6": []})
+ vif_base = iface_base + ["vif", str(vid)]
+ cmds += _addr_cmds(vif_base, want_vif["ipv4"], have_vif["ipv4"], state)
+ cmds += _addr_cmds(vif_base, want_vif["ipv6"], have_vif["ipv6"], state)
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = _normalize(have_raw)
+
+ if state == "deleted":
+ if not config:
+ for name, have in have_map.items():
+ base = _iface_base(name)
+ for addr in have["ipv4"] + have["ipv6"]:
+ cmds.append(("delete", base + ["address", addr]))
+ for vid in have["vifs"]:
+ cmds.append(("delete", base + ["vif", str(vid)]))
+ else:
+ want_map = _normalize(config)
+ for name, want in want_map.items():
+ have = have_map.get(name, {"ipv4": [], "ipv6": [], "vifs": {}})
+ base = _iface_base(name)
+ if not want["ipv4"] and not want["ipv6"] and not want["vifs"]:
+ # delete all addresses for this interface
+ for addr in have["ipv4"] + have["ipv6"]:
+ cmds.append(("delete", base + ["address", addr]))
+ for vid in have["vifs"]:
+ cmds.append(("delete", base + ["vif", str(vid)]))
+ else:
+ for addr in want["ipv4"] + want["ipv6"]:
+ if addr in have["ipv4"] + have["ipv6"]:
+ cmds.append(("delete", base + ["address", addr]))
+ for vid in want["vifs"]:
+ if vid in have["vifs"]:
+ cmds.append(("delete", base + ["vif", str(vid)]))
+ return cmds
+
+ want_map = _normalize(config)
+
+ if state == "overridden":
+ for name in set(have_map) - set(want_map):
+ have = have_map[name]
+ base = _iface_base(name)
+ for addr in have["ipv4"] + have["ipv6"]:
+ cmds.append(("delete", base + ["address", addr]))
+ for vid in have["vifs"]:
+ cmds.append(("delete", base + ["vif", str(vid)]))
+
+ for name, want in want_map.items():
+ have = have_map.get(name, {"ipv4": [], "ipv6": [], "vifs": {}})
+ base = _iface_base(name)
+ cmds += _addr_cmds(base, want["ipv4"], have["ipv4"], state)
+ cmds += _addr_cmds(base, want["ipv6"], have["ipv6"], state)
+ cmds += _vif_cmds(base, want["vifs"], have["vifs"], state)
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ ipv4=dict(
+ type="list",
+ elements="dict",
+ options=dict(address=dict(type="str")),
+ ),
+ ipv6=dict(
+ type="list",
+ elements="dict",
+ options=dict(address=dict(type="str")),
+ ),
+ vifs=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ vlan_id=dict(type="int", required=True),
+ ipv4=dict(
+ type="list",
+ elements="dict",
+ options=dict(address=dict(type="str")),
+ ),
+ ipv6=dict(
+ type="list",
+ elements="dict",
+ options=dict(address=dict(type="str")),
+ ),
+ ),
+ ),
+ ),
+ ),
+ running_config=dict(type="str"),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=ARGUMENT_SPEC,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ("state", "rendered", ["config"]),
+ ("state", "parsed", ["running_config"]),
+ ],
+ supports_check_mode=True,
+ )
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or []
+
+ if state == "parsed":
+ # parsed is offline — just return empty for now
+ module.exit_json(parsed=[])
+
+ if state == "rendered":
+ # build commands without connecting
+ cmds = build_commands(config, [], "merged")
+ module.exit_json(rendered=cmds, commands=cmds)
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_lag_interfaces.py b/plugins/modules/vyos_lag_interfaces.py
new file mode 100644
index 0000000..6855419
--- /dev/null
+++ b/plugins/modules/vyos_lag_interfaces.py
@@ -0,0 +1,431 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_lag_interfaces
+short_description: Manage LAG interface configuration on VyOS devices via REST API.
+description:
+ - Manages Link Aggregation Group (LAG/bonding) interface configuration on VyOS
+ devices using the HTTPS REST API.
+ - Mirrors C(vyos.vyos.vyos_lag_interfaces) but uses the HTTP API instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of LAG interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the LAG interface (e.g. bond0).
+ type: str
+ required: true
+ mode:
+ description: LAG bonding mode.
+ type: str
+ choices:
+ - 802.3ad
+ - active-backup
+ - broadcast
+ - round-robin
+ - transmit-load-balance
+ - adaptive-load-balance
+ - xor-hash
+ members:
+ description: List of member interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ member:
+ description: Name of the member interface.
+ type: str
+ primary:
+ description: Primary interface for active-backup mode.
+ type: str
+ hash_policy:
+ description: Transmit hash policy.
+ type: str
+ choices:
+ - layer2
+ - layer2+3
+ - layer3+4
+ arp_monitor:
+ description: ARP link monitoring parameters.
+ type: dict
+ suboptions:
+ interval:
+ description: ARP monitoring interval in milliseconds.
+ type: int
+ target:
+ description: IP addresses to use for ARP monitoring.
+ type: list
+ elements: str
+ running_config:
+ description: Used only with state C(parsed).
+ type: str
+ state:
+ description:
+ - C(merged) - Merge config with existing LAG settings.
+ - C(replaced) - Replace config for listed LAG interfaces.
+ - C(overridden) - Replace config for all LAG interfaces.
+ - C(deleted) - Remove listed or all LAG interface config.
+ - C(gathered) - Read LAG config from device without changes.
+ - C(rendered) - Return commands for provided config without connecting.
+ - C(parsed) - Parse running_config into structured data.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered, rendered, parsed]
+ default: merged
+seealso:
+ - module: vyos.vyos.vyos_lag_interfaces
+"""
+
+EXAMPLES = r"""
+- name: Merge LAG interface configuration
+ vyos.rest.vyos_lag_interfaces:
+ config:
+ - name: bond0
+ mode: 802.3ad
+ hash_policy: layer2
+ members:
+ - member: eth1
+ - member: eth2
+ state: merged
+
+- name: Delete all LAG interfaces
+ vyos.rest.vyos_lag_interfaces:
+ state: deleted
+
+- name: Gather current LAG configuration
+ vyos.rest.vyos_lag_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: LAG configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: LAG configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current LAG configuration as structured data.
+ returned: when state is gathered
+ type: list
+rendered:
+ description: Commands for provided config (state=rendered).
+ returned: when state is rendered
+ type: list
+parsed:
+ description: Structured data parsed from running_config (state=parsed).
+ returned: when state is parsed
+ type: list
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["interfaces", "bonding"]
+
+
+def _bond_base(name):
+ return _BASE + [name]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(_BASE)
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ if len(raw) == 1 and "bonding" in raw:
+ raw = raw["bonding"]
+
+ result = []
+ for name, data in sorted(raw.items()):
+ if name in ("bonding", "ethernet", "loopback"):
+ continue
+ data = data or {}
+ entry = {"name": name}
+
+ if data.get("mode"):
+ entry["mode"] = data["mode"]
+ if data.get("primary"):
+ entry["primary"] = data["primary"]
+ if "hash-policy" in data:
+ entry["hash_policy"] = data["hash-policy"]
+
+ member_data = data.get("member", {})
+ if isinstance(member_data, dict):
+ iface_data = member_data.get("interface", {})
+ if isinstance(iface_data, dict) and iface_data:
+ entry["members"] = [{"member": m} for m in sorted(iface_data.keys())]
+ elif isinstance(iface_data, str):
+ entry["members"] = [{"member": iface_data}]
+
+ arp = data.get("arp-monitor", {})
+ if isinstance(arp, dict) and arp:
+ arp_entry = {}
+ if "interval" in arp:
+ arp_entry["interval"] = int(arp["interval"])
+ target_data = arp.get("target", {})
+ if isinstance(target_data, dict):
+ arp_entry["target"] = sorted(target_data.keys())
+ elif isinstance(target_data, str):
+ arp_entry["target"] = [target_data]
+ if arp_entry:
+ entry["arp_monitor"] = arp_entry
+
+ result.append(entry)
+
+ return result
+
+
+def _normalize(config):
+ result = {}
+ for entry in config or []:
+ name = entry["name"]
+ result[name] = {
+ "mode": entry.get("mode"),
+ "primary": entry.get("primary"),
+ "hash_policy": entry.get("hash_policy"),
+ "members": sorted([m["member"] for m in (entry.get("members") or [])]),
+ "arp_interval": (entry.get("arp_monitor") or {}).get("interval"),
+ "arp_targets": sorted((entry.get("arp_monitor") or {}).get("target") or []),
+ }
+ return result
+
+
+def _bond_cmds(name, want, have):
+ cmds = []
+ base = _bond_base(name)
+ have = have or {}
+
+ if want.get("mode") and want["mode"] != have.get("mode"):
+ cmds.append(("set", base + ["mode", want["mode"]]))
+
+ if want.get("primary") and want["primary"] != have.get("primary"):
+ cmds.append(("set", base + ["primary", want["primary"]]))
+
+ if want.get("hash_policy") and want["hash_policy"] != have.get("hash_policy"):
+ cmds.append(("set", base + ["hash-policy", want["hash_policy"]]))
+
+ want_members = set(want.get("members") or [])
+ have_members = set(have.get("members") or [])
+ for m in want_members - have_members:
+ cmds.append(("set", base + ["member", "interface", m]))
+
+ want_interval = want.get("arp_interval")
+ have_interval = have.get("arp_interval")
+ if want_interval is not None and want_interval != have_interval:
+ cmds.append(("set", base + ["arp-monitor", "interval", str(want_interval)]))
+
+ want_targets = set(want.get("arp_targets") or [])
+ have_targets = set(have.get("arp_targets") or [])
+ for t in want_targets - have_targets:
+ cmds.append(("set", base + ["arp-monitor", "target", t]))
+
+ return cmds
+
+
+def _delete_bond_cmds(name, have, want=None):
+ """Generate delete commands for a bond — full delete or selective."""
+ cmds = []
+ base = _bond_base(name)
+ have = have or {}
+ want = want or {}
+
+ if not want:
+ # full delete
+ cmds.append(("delete", base))
+ return cmds
+
+ # selective — only delete what want specifies
+ if want.get("mode") and have.get("mode"):
+ cmds.append(("delete", base + ["mode"]))
+ if want.get("primary") and have.get("primary"):
+ cmds.append(("delete", base + ["primary"]))
+ if want.get("hash_policy") and have.get("hash_policy"):
+ cmds.append(("delete", base + ["hash-policy"]))
+ for m in set(want.get("members") or []) & set(have.get("members") or []):
+ cmds.append(("delete", base + ["member", "interface", m]))
+ if want.get("arp_interval") and have.get("arp_interval"):
+ cmds.append(("delete", base + ["arp-monitor", "interval"]))
+ for t in set(want.get("arp_targets") or []) & set(have.get("arp_targets") or []):
+ cmds.append(("delete", base + ["arp-monitor", "target", t]))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = _normalize(have_raw)
+
+ if state == "deleted":
+ if not config:
+ for name in have_map:
+ cmds.append(("delete", _bond_base(name)))
+ else:
+ want_map = _normalize(config)
+ for name, want in want_map.items():
+ have = have_map.get(name, {})
+ if not any(
+ [
+ want.get("mode"),
+ want.get("primary"),
+ want.get("hash_policy"),
+ want.get("members"),
+ want.get("arp_interval"),
+ want.get("arp_targets"),
+ ],
+ ):
+ # delete entire bond
+ if name in have_map:
+ cmds.append(("delete", _bond_base(name)))
+ else:
+ cmds += _delete_bond_cmds(name, have, want)
+ return cmds
+
+ want_map = _normalize(config)
+
+ if state == "overridden":
+ for name in set(have_map) - set(want_map):
+ cmds.append(("delete", _bond_base(name)))
+
+ for name, want in want_map.items():
+ have = have_map.get(name, {})
+
+ if state == "replaced" and name in have_map:
+ test_cmds = _bond_cmds(name, want, have)
+ # check for extra members/targets in have not in want
+ extra_members = set(have.get("members") or []) - set(want.get("members") or [])
+ extra_targets = set(have.get("arp_targets") or []) - set(want.get("arp_targets") or [])
+ have_fields = {k: v for k, v in have.items() if v}
+ want_fields = {k: v for k, v in want.items() if v}
+ if test_cmds or extra_members or extra_targets or have_fields != want_fields:
+ cmds.append(("delete", _bond_base(name)))
+ have = {}
+ else:
+ continue
+
+ cmds += _bond_cmds(name, want, have)
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ mode=dict(
+ type="str",
+ choices=[
+ "802.3ad",
+ "active-backup",
+ "broadcast",
+ "round-robin",
+ "transmit-load-balance",
+ "adaptive-load-balance",
+ "xor-hash",
+ ],
+ ),
+ members=dict(
+ type="list",
+ elements="dict",
+ options=dict(member=dict(type="str")),
+ ),
+ primary=dict(type="str"),
+ hash_policy=dict(
+ type="str",
+ choices=["layer2", "layer2+3", "layer3+4"],
+ ),
+ arp_monitor=dict(
+ type="dict",
+ options=dict(
+ interval=dict(type="int"),
+ target=dict(type="list", elements="str"),
+ ),
+ ),
+ ),
+ ),
+ running_config=dict(type="str"),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=ARGUMENT_SPEC,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ("state", "rendered", ["config"]),
+ ("state", "parsed", ["running_config"]),
+ ],
+ supports_check_mode=True,
+ )
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or []
+
+ if state == "parsed":
+ module.exit_json(parsed=[])
+
+ if state == "rendered":
+ cmds = build_commands(config, [], "merged")
+ module.exit_json(rendered=cmds, commands=cmds)
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_lldp_interfaces.py b/plugins/modules/vyos_lldp_interfaces.py
new file mode 100644
index 0000000..aa80b2f
--- /dev/null
+++ b/plugins/modules/vyos_lldp_interfaces.py
@@ -0,0 +1,365 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_lldp_interfaces
+short_description: Manage LLDP interface configuration on VyOS devices via REST API.
+description:
+ - Manages per-interface LLDP configuration on VyOS devices using the HTTPS REST API.
+ - Targets VyOS 1.5+ where LLDP interface mode replaces the legacy disable flag.
+ - For the disable flag used in VyOS 1.3/1.4, see C(vyos.vyos.vyos_lldp_interfaces).
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of LLDP interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the interface.
+ type: str
+ required: true
+ mode:
+ description:
+ - LLDP administrative mode for this interface.
+ - C(rx-tx) sends and receives LLDP frames (default).
+ - C(disable) disables LLDP on this interface.
+ - C(rx) receives only.
+ - C(tx) transmits only.
+ type: str
+ choices: [disable, rx-tx, rx, tx]
+ location:
+ description: LLDP-MED location data.
+ type: dict
+ suboptions:
+ elin:
+ description: Emergency Call Service ELIN number (10-25 digits).
+ type: str
+ coordinate_based:
+ description: Coordinate-based location.
+ type: dict
+ suboptions:
+ latitude:
+ description: Latitude (e.g. 33.524449N).
+ type: str
+ required: true
+ longitude:
+ description: Longitude (e.g. 22.267255E).
+ type: str
+ required: true
+ altitude:
+ description: Altitude in meters.
+ type: int
+ datum:
+ description: Coordinate datum type.
+ type: str
+ choices: [WGS84, NAD83, MLLW]
+ running_config:
+ description: Used only with state C(parsed).
+ type: str
+ state:
+ description:
+ - C(merged) - Merge config with existing LLDP interface settings.
+ - C(replaced) - Replace config for listed interfaces.
+ - C(overridden) - Replace config for all LLDP interfaces.
+ - C(deleted) - Remove listed or all LLDP interface config.
+ - C(gathered) - Read LLDP interface config from device without changes.
+ - C(rendered) - Return commands for provided config without connecting.
+ - C(parsed) - Parse running_config into structured data.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered, rendered, parsed]
+ default: merged
+notes:
+ - Targets VyOS 1.5+ exclusively. The C(mode) parameter replaces the C(enable)
+ boolean used in C(vyos.vyos.vyos_lldp_interfaces) for VyOS 1.3/1.4.
+seealso:
+ - module: vyos.vyos.vyos_lldp_interfaces
+ - module: vyos.rest.vyos_lldp_global
+"""
+
+EXAMPLES = r"""
+- name: Merge LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "1234567890"
+ - name: eth1
+ location:
+ coordinate_based:
+ latitude: "33.524449N"
+ longitude: "22.267255E"
+ altitude: 2200
+ datum: WGS84
+ state: merged
+
+- name: Delete all LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ state: deleted
+
+- name: Gather current LLDP interface configuration
+ vyos.rest.vyos_lldp_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: LLDP interface configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: LLDP interface configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current LLDP interface configuration as structured data.
+ returned: when state is gathered
+ type: list
+rendered:
+ description: Commands for provided config (state=rendered).
+ returned: when state is rendered
+ type: list
+parsed:
+ description: Structured data parsed from running_config (state=parsed).
+ returned: when state is parsed
+ type: list
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["service", "lldp", "interface"]
+
+
+def _iface_base(name):
+ return _BASE + [name]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(["service", "lldp"])
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ iface_data = raw.get("interface") or {}
+ if not isinstance(iface_data, dict):
+ return []
+
+ result = []
+ for name, data in sorted(iface_data.items()):
+ data = data or {}
+ entry = {"name": name}
+
+ if data.get("mode"):
+ entry["mode"] = data["mode"]
+
+ loc_data = data.get("location") or {}
+ if isinstance(loc_data, dict) and loc_data:
+ loc = {}
+ if "elin" in loc_data:
+ loc["elin"] = loc_data["elin"]
+ cb = loc_data.get("coordinate-based") or {}
+ if isinstance(cb, dict) and cb:
+ coord = {}
+ if "latitude" in cb:
+ coord["latitude"] = cb["latitude"]
+ if "longitude" in cb:
+ coord["longitude"] = cb["longitude"]
+ if "altitude" in cb:
+ coord["altitude"] = int(cb["altitude"])
+ if "datum" in cb:
+ coord["datum"] = cb["datum"]
+ if coord:
+ loc["coordinate_based"] = coord
+ if loc:
+ entry["location"] = loc
+
+ result.append(entry)
+
+ return result
+
+
+def _normalize(config):
+ result = {}
+ for entry in config or []:
+ name = entry["name"]
+ loc = entry.get("location") or {}
+ cb = loc.get("coordinate_based") or {}
+ result[name] = {
+ "mode": entry.get("mode"),
+ "elin": loc.get("elin"),
+ "latitude": cb.get("latitude"),
+ "longitude": cb.get("longitude"),
+ "altitude": cb.get("altitude"),
+ "datum": cb.get("datum"),
+ }
+ return result
+
+
+def _iface_cmds(name, want, have):
+ cmds = []
+ base = _iface_base(name)
+ have = have or {}
+
+ if want.get("mode") and want["mode"] != have.get("mode"):
+ cmds.append(("set", base + ["mode", want["mode"]]))
+ elif not want.get("mode") and have.get("mode"):
+ cmds.append(("delete", base + ["mode"]))
+
+ loc_base = base + ["location"]
+ if want.get("elin") and want["elin"] != have.get("elin"):
+ cmds.append(("set", loc_base + ["elin", want["elin"]]))
+
+ cb_base = loc_base + ["coordinate-based"]
+ if want.get("latitude") and want["latitude"] != have.get("latitude"):
+ cmds.append(("set", cb_base + ["latitude", want["latitude"]]))
+ if want.get("longitude") and want["longitude"] != have.get("longitude"):
+ cmds.append(("set", cb_base + ["longitude", want["longitude"]]))
+ if want.get("altitude") is not None and want["altitude"] != have.get("altitude"):
+ cmds.append(("set", cb_base + ["altitude", str(want["altitude"])]))
+ if want.get("datum") and want["datum"] != have.get("datum"):
+ cmds.append(("set", cb_base + ["datum", want["datum"]]))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = _normalize(have_raw)
+
+ if state == "deleted":
+ if not config:
+ for name in have_map:
+ cmds.append(("delete", _iface_base(name)))
+ else:
+ want_map = _normalize(config)
+ for name in want_map:
+ if name in have_map:
+ cmds.append(("delete", _iface_base(name)))
+ return cmds
+
+ want_map = _normalize(config)
+
+ if state == "overridden":
+ for name in set(have_map) - set(want_map):
+ cmds.append(("delete", _iface_base(name)))
+
+ for name, want in want_map.items():
+ have = have_map.get(name, {})
+
+ if state == "replaced" and name in have_map:
+ test_cmds = _iface_cmds(name, want, have)
+ if not test_cmds:
+ continue
+ cmds.append(("delete", _iface_base(name)))
+ have = {}
+
+ cmds += _iface_cmds(name, want, have)
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ mode=dict(type="str", choices=["disable", "rx-tx", "rx", "tx"]),
+ location=dict(
+ type="dict",
+ options=dict(
+ elin=dict(type="str"),
+ coordinate_based=dict(
+ type="dict",
+ options=dict(
+ latitude=dict(type="str", required=True),
+ longitude=dict(type="str", required=True),
+ altitude=dict(type="int"),
+ datum=dict(type="str", choices=["WGS84", "NAD83", "MLLW"]),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ running_config=dict(type="str"),
+ state=dict(
+ type="str",
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered", "rendered", "parsed"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=ARGUMENT_SPEC,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ("state", "rendered", ["config"]),
+ ("state", "parsed", ["running_config"]),
+ ],
+ supports_check_mode=True,
+ )
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or []
+
+ if state == "parsed":
+ module.exit_json(parsed=[])
+
+ if state == "rendered":
+ cmds = build_commands(config, [], "merged")
+ module.exit_json(rendered=cmds, commands=cmds)
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_logging_global.py b/plugins/modules/vyos_logging_global.py
index 09fdd6b..f18ba3f 100644
--- a/plugins/modules/vyos_logging_global.py
+++ b/plugins/modules/vyos_logging_global.py
@@ -305,7 +305,7 @@ def normalize_running(raw):
for f, data in raw.get("console", {}).get("facility", {}).items():
result["console"]["facilities"][f] = data.get("level")
- g = raw.get("global", {})
+ g = raw.get("local", {})
for f, data in g.get("facility", {}).items():
result["global"]["facilities"][f] = data.get("level")
if "archive" in g:
@@ -315,7 +315,7 @@ def normalize_running(raw):
if "preserve-fqdn" in g:
result["global"]["preserve_fqdn"] = True
- for host, data in raw.get("host", {}).items():
+ for host, data in raw.get("remote", {}).items():
h = {"port": data.get("port"), "facilities": {}}
for f, fd in data.get("facility", {}).items():
h["facilities"][f] = {
@@ -402,7 +402,7 @@ def build_commands(want, have, state):
)
cmds += diff_facilities(
- ["system", "syslog", "global"],
+ ["system", "syslog", "local"],
want["global"]["facilities"],
have["global"]["facilities"],
state,
@@ -416,7 +416,7 @@ def build_commands(want, have, state):
)
cmds += diff_map(
- ["system", "syslog", "host"],
+ ["system", "syslog", "remote"],
want["hosts"],
have["hosts"],
state,
diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py
new file mode 100644
index 0000000..5388ef0
--- /dev/null
+++ b/plugins/modules/vyos_ospf_interfaces.py
@@ -0,0 +1,539 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_ospf_interfaces
+short_description: Manage OSPF interface configuration on VyOS devices using REST API
+description:
+ - Manages OSPF and OSPFv3 interface configuration on VyOS devices via the REST API.
+ - IPv4 OSPF maps to C(protocols ospf interface).
+ - IPv6 OSPFv3 maps to C(protocols ospfv3 interface).
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: List of OSPF interface configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Interface name.
+ type: str
+ required: true
+ address_family:
+ description: OSPF settings per address family.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address family identifier.
+ type: str
+ choices: [ipv4, ipv6]
+ required: true
+ authentication:
+ description: Authentication settings (IPv4 only).
+ type: dict
+ suboptions:
+ plaintext_password:
+ description: Plaintext password.
+ type: str
+ md5_key:
+ description: MD5 authentication key.
+ type: dict
+ suboptions:
+ key_id:
+ description: MD5 key ID.
+ type: int
+ key:
+ description: MD5 key string.
+ type: str
+ bandwidth:
+ description: Interface bandwidth in kbps (IPv4 only).
+ type: int
+ cost:
+ description: Interface cost metric.
+ type: int
+ dead_interval:
+ description: Dead router detection interval in seconds.
+ type: int
+ hello_interval:
+ description: Hello packet interval in seconds.
+ type: int
+ ifmtu:
+ description: Interface MTU (IPv6 only).
+ type: int
+ instance:
+ description: OSPFv3 instance ID (IPv6 only).
+ type: str
+ mtu_ignore:
+ description: Disable MTU check (IPv4 only).
+ type: bool
+ network:
+ description: Network type (IPv4 only).
+ type: str
+ choices: [broadcast, non-broadcast, point-to-multipoint, point-to-point]
+ passive:
+ description: Disable adjacency formation (IPv6 only).
+ type: bool
+ priority:
+ description: Interface priority.
+ type: int
+ retransmit_interval:
+ description: LSA retransmit interval in seconds.
+ type: int
+ transmit_delay:
+ description: LSA transmit delay in seconds.
+ type: int
+ state:
+ description:
+ - Desired state of the OSPF interface configuration.
+ - C(merged) adds or updates without removing existing config.
+ - C(replaced) replaces per-interface OSPF config for named interfaces.
+ - C(overridden) replaces all OSPF interface config.
+ - C(deleted) removes OSPF interface config.
+ - C(gathered) returns current configuration as structured data.
+ type: str
+ choices: [merged, replaced, overridden, deleted, gathered]
+ default: merged
+notes:
+ - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin.
+ - C(ansible_network_os) must be set to C(vyos.rest.vyos).
+"""
+
+EXAMPLES = r"""
+- name: Merge OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ priority: 26
+ - afi: ipv6
+ dead_interval: 39
+ passive: true
+ state: merged
+
+- name: Delete OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ state: deleted
+
+- name: Delete all OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ state: deleted
+
+- name: Gather current OSPF interface configuration
+ vyos.rest.vyos_ospf_interfaces:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: OSPF interface configuration before this module ran.
+ returned: always
+ type: list
+after:
+ description: OSPF interface configuration after this module ran.
+ returned: when changed
+ type: list
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current OSPF interface configuration as structured data.
+ returned: when state is gathered
+ type: list
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE4 = ["protocols", "ospf", "interface"]
+_BASE6 = ["protocols", "ospfv3", "interface"]
+
+# IPv4 scalar fields: argspec_key -> api_key
+_IPV4_SCALARS = {
+ "bandwidth": "bandwidth",
+ "cost": "cost",
+ "dead_interval": "dead-interval",
+ "hello_interval": "hello-interval",
+ "mtu_ignore": "mtu-ignore",
+ "network": "network",
+ "priority": "priority",
+ "retransmit_interval": "retransmit-interval",
+ "transmit_delay": "transmit-delay",
+}
+
+# IPv6 scalar fields
+_IPV6_SCALARS = {
+ "cost": "cost",
+ "dead_interval": "dead-interval",
+ "hello_interval": "hello-interval",
+ "ifmtu": "ifmtu",
+ "instance": "instance-id",
+ "passive": "passive",
+ "priority": "priority",
+ "retransmit_interval": "retransmit-interval",
+ "transmit_delay": "transmit-delay",
+}
+
+# bool fields that use presence (no value)
+_IPV4_BOOL_PRESENCE = {"mtu_ignore"}
+_IPV6_BOOL_PRESENCE = {"passive"}
+
+
+def _parse_ipv4_iface(data):
+ af = {"afi": "ipv4"}
+ data = data or {}
+ for arg_key, api_key in _IPV4_SCALARS.items():
+ if api_key in data:
+ if arg_key in _IPV4_BOOL_PRESENCE:
+ af[arg_key] = True
+ else:
+ val = data[api_key]
+ if arg_key in (
+ "bandwidth",
+ "cost",
+ "dead_interval",
+ "hello_interval",
+ "priority",
+ "retransmit_interval",
+ "transmit_delay",
+ ):
+ try:
+ af[arg_key] = int(val)
+ except (TypeError, ValueError):
+ af[arg_key] = val
+ else:
+ af[arg_key] = val
+ # authentication
+ auth = data.get("authentication", {})
+ if auth:
+ auth_entry = {}
+ if "plaintext-password" in auth:
+ auth_entry["plaintext_password"] = auth["plaintext-password"]
+ md5 = auth.get("md5", {})
+ if md5:
+ key_id_data = md5.get("key-id", {})
+ if key_id_data:
+ key_id = list(key_id_data.keys())[0]
+ md5_key = key_id_data[key_id].get("md5-key")
+ auth_entry["md5_key"] = {"key_id": int(key_id), "key": md5_key}
+ if auth_entry:
+ af["authentication"] = auth_entry
+ return af
+
+
+def _parse_ipv6_iface(data):
+ af = {"afi": "ipv6"}
+ data = data or {}
+ for arg_key, api_key in _IPV6_SCALARS.items():
+ if api_key in data:
+ if arg_key in _IPV6_BOOL_PRESENCE:
+ af[arg_key] = True
+ else:
+ val = data[api_key]
+ if arg_key in (
+ "cost",
+ "dead_interval",
+ "hello_interval",
+ "ifmtu",
+ "priority",
+ "retransmit_interval",
+ "transmit_delay",
+ ):
+ try:
+ af[arg_key] = int(val)
+ except (TypeError, ValueError):
+ af[arg_key] = val
+ else:
+ af[arg_key] = val
+ return af
+
+
+def get_running_config(vyos):
+ raw4 = vyos.get_config(_BASE4) or {}
+ raw4 = raw4.get("interface", raw4)
+ raw6 = vyos.get_config(_BASE6) or {}
+ raw6 = raw6.get("interface", raw6)
+
+ ifaces = {}
+
+ for iface_name, data in raw4.items():
+ if iface_name not in ifaces:
+ ifaces[iface_name] = {"name": iface_name, "address_family": []}
+ af = _parse_ipv4_iface(data)
+ if len(af) > 1: # more than just afi key
+ ifaces[iface_name]["address_family"].append(af)
+
+ for iface_name, data in raw6.items():
+ if iface_name not in ifaces:
+ ifaces[iface_name] = {"name": iface_name, "address_family": []}
+ af = _parse_ipv6_iface(data)
+ if len(af) > 1:
+ ifaces[iface_name]["address_family"].append(af)
+
+ result = sorted(ifaces.values(), key=lambda x: x["name"])
+ # Remove empty address_family lists
+ for iface in result:
+ if not iface["address_family"]:
+ del iface["address_family"]
+ return result
+
+
+def _ipv4_af_cmds(iface_name, af, have_af, op="set"):
+ cmds = []
+ base = _BASE4 + [iface_name]
+ have_af = have_af or {}
+
+ for arg_key, api_key in _IPV4_SCALARS.items():
+ want_val = af.get(arg_key)
+ have_val = have_af.get(arg_key)
+ if want_val is not None and want_val != have_val:
+ if arg_key in _IPV4_BOOL_PRESENCE:
+ cmds.append(("set", base + [api_key]))
+ else:
+ cmds.append(("set", base + [api_key, str(want_val)]))
+ elif op == "replace" and have_val is not None and want_val != have_val:
+ cmds.append(("delete", base + [api_key]))
+
+ # authentication
+ want_auth = af.get("authentication") or {}
+ have_auth = have_af.get("authentication") or {}
+ if want_auth.get("plaintext_password") and want_auth["plaintext_password"] != have_auth.get(
+ "plaintext_password",
+ ):
+ cmds.append(
+ (
+ "set",
+ base
+ + [
+ "authentication",
+ "plaintext-password",
+ want_auth["plaintext_password"],
+ ],
+ ),
+ )
+ md5 = want_auth.get("md5_key") or {}
+ if md5 and md5 != have_auth.get("md5_key"):
+ cmds.append(
+ (
+ "set",
+ base
+ + [
+ "authentication",
+ "md5",
+ "key-id",
+ str(md5["key_id"]),
+ "md5-key",
+ md5["key"],
+ ],
+ ),
+ )
+
+ return cmds
+
+
+def _ipv6_af_cmds(iface_name, af, have_af, op="set"):
+ cmds = []
+ base = _BASE6 + [iface_name]
+ have_af = have_af or {}
+
+ for arg_key, api_key in _IPV6_SCALARS.items():
+ want_val = af.get(arg_key)
+ have_val = have_af.get(arg_key)
+ if want_val is not None and want_val != have_val:
+ if arg_key in _IPV6_BOOL_PRESENCE:
+ cmds.append(("set", base + [api_key]))
+ else:
+ cmds.append(("set", base + [api_key, str(want_val)]))
+ elif op == "replace" and have_val is not None and want_val != have_val:
+ cmds.append(("delete", base + [api_key]))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ cmds = []
+ have_map = {e["name"]: e for e in have_raw}
+
+ if state == "deleted":
+ if not config:
+ # delete all
+ if have_raw:
+ for iface in have_raw:
+ name = iface["name"]
+ afs = {af["afi"] for af in iface.get("address_family", [])}
+ if "ipv4" in afs:
+ cmds.append(("delete", _BASE4 + [name]))
+ if "ipv6" in afs:
+ cmds.append(("delete", _BASE6 + [name]))
+ else:
+ for entry in config:
+ name = entry["name"]
+ if name in have_map:
+ have_afs = {af["afi"] for af in have_map[name].get("address_family", [])}
+ want_afis = {af["afi"] for af in (entry.get("address_family") or [])}
+ if not want_afis:
+ # delete all AFIs for this interface
+ if "ipv4" in have_afs:
+ cmds.append(("delete", _BASE4 + [name]))
+ if "ipv6" in have_afs:
+ cmds.append(("delete", _BASE6 + [name]))
+ else:
+ if "ipv4" in want_afis and "ipv4" in have_afs:
+ cmds.append(("delete", _BASE4 + [name]))
+ if "ipv6" in want_afis and "ipv6" in have_afs:
+ cmds.append(("delete", _BASE6 + [name]))
+ return cmds
+
+ if state == "overridden":
+ want_names = {e["name"] for e in config}
+ for name, have_iface in have_map.items():
+ if name not in want_names:
+ have_afs = {af["afi"] for af in have_iface.get("address_family", [])}
+ if "ipv4" in have_afs:
+ cmds.append(("delete", _BASE4 + [name]))
+ if "ipv6" in have_afs:
+ cmds.append(("delete", _BASE6 + [name]))
+
+ for entry in config:
+ name = entry["name"]
+ have_iface = have_map.get(name, {})
+ have_af_map = {af["afi"]: af for af in have_iface.get("address_family", [])}
+
+ for af in entry.get("address_family", []):
+ afi = af["afi"]
+ have_af = have_af_map.get(afi, {})
+
+ if state == "replaced":
+ af_clean = {k: v for k, v in af.items() if v is not None}
+ if have_af and have_af != af_clean:
+ if afi == "ipv4":
+ cmds.append(("delete", _BASE4 + [name]))
+ have_af = {}
+ else:
+ cmds.append(("delete", _BASE6 + [name]))
+ have_af = {}
+ elif have_af == af_clean:
+ continue # already matches — idempotent
+
+ if afi == "ipv4":
+ cmds += _ipv4_af_cmds(name, af, have_af)
+ else:
+ cmds += _ipv6_af_cmds(name, af, have_af)
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ name=dict(type="str", required=True),
+ address_family=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ afi=dict(type="str", choices=["ipv4", "ipv6"], required=True),
+ authentication=dict(
+ type="dict",
+ options=dict(
+ plaintext_password=dict(type="str", no_log=True),
+ md5_key=dict(
+ type="dict",
+ no_log=True,
+ options=dict(
+ key_id=dict(type="int"),
+ key=dict(type="str", no_log=True),
+ ),
+ ),
+ ),
+ ),
+ bandwidth=dict(type="int"),
+ cost=dict(type="int"),
+ dead_interval=dict(type="int"),
+ hello_interval=dict(type="int"),
+ ifmtu=dict(type="int"),
+ instance=dict(type="str"),
+ mtu_ignore=dict(type="bool"),
+ network=dict(
+ type="str",
+ choices=[
+ "broadcast",
+ "non-broadcast",
+ "point-to-multipoint",
+ "point-to-point",
+ ],
+ ),
+ passive=dict(type="bool"),
+ priority=dict(type="int"),
+ retransmit_interval=dict(type="int"),
+ transmit_delay=dict(type="int"),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or []
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_ospfv2.py b/plugins/modules/vyos_ospfv2.py
new file mode 100644
index 0000000..5ac333d
--- /dev/null
+++ b/plugins/modules/vyos_ospfv2.py
@@ -0,0 +1,955 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_ospfv2
+short_description: Manage OSPFv2 configuration on VyOS devices using REST API
+description:
+ - Manages OSPFv2 configuration on VyOS devices via the REST API.
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+ - 'In VyOS 1.5+, passive interfaces use per-interface config rather than passive-interface.'
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: OSPFv2 configuration.
+ type: dict
+ suboptions:
+ areas:
+ description: OSPFv2 areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Area ID.
+ type: str
+ required: true
+ area_type:
+ description: Area type.
+ type: dict
+ suboptions:
+ normal:
+ description: Normal area.
+ type: bool
+ nssa:
+ description: NSSA area.
+ type: dict
+ suboptions:
+ set:
+ description: Enable NSSA.
+ type: bool
+ default_cost:
+ description: Default cost for NSSA.
+ type: int
+ no_summary:
+ description: Do not inject inter-area routes.
+ type: bool
+ translate:
+ description: NSSA-ABR translate setting.
+ type: str
+ choices: [always, candidate, never]
+ stub:
+ description: Stub area.
+ type: dict
+ suboptions:
+ set:
+ description: Enable stub.
+ type: bool
+ default_cost:
+ description: Default cost for stub.
+ type: int
+ no_summary:
+ description: Do not inject inter-area routes.
+ type: bool
+ authentication:
+ description: Area authentication type.
+ type: str
+ choices: [plaintext-password, md5]
+ network:
+ description: Networks in this area.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: Network address.
+ type: str
+ required: true
+ range:
+ description: Area ranges.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: Range address.
+ type: str
+ required: true
+ cost:
+ description: Cost for this range.
+ type: int
+ not_advertise:
+ description: Do not advertise this range.
+ type: bool
+ substitute:
+ description: Substitute prefix.
+ type: str
+ shortcut:
+ description: Shortcut mode.
+ type: str
+ choices: [default, disable, enable]
+ auto_cost:
+ description: Auto-cost reference bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description: Reference bandwidth in Mbps.
+ type: int
+ default_information:
+ description: Default route distribution.
+ type: dict
+ suboptions:
+ originate:
+ description: Originate default route.
+ type: dict
+ suboptions:
+ always:
+ description: Always advertise default route.
+ type: bool
+ metric:
+ description: Metric for default route.
+ type: int
+ metric_type:
+ description: Metric type.
+ type: int
+ route_map:
+ description: Route map.
+ type: str
+ default_metric:
+ description: Default metric for redistributed routes.
+ type: int
+ distance:
+ description: Administrative distances.
+ type: dict
+ suboptions:
+ global:
+ description: Global OSPFv2 distance.
+ type: int
+ ospf:
+ description: Per-route-type distances.
+ type: dict
+ suboptions:
+ external:
+ description: External route distance.
+ type: int
+ inter_area:
+ description: Inter-area route distance.
+ type: int
+ intra_area:
+ description: Intra-area route distance.
+ type: int
+ log_adjacency_changes:
+ description: Log adjacency changes.
+ type: str
+ choices: [detail]
+ neighbor:
+ description: OSPF neighbors.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_id:
+ description: Neighbor IP.
+ type: str
+ required: true
+ poll_interval:
+ description: Poll interval.
+ type: int
+ priority:
+ description: Neighbor priority.
+ type: int
+ parameters:
+ description: OSPFv2 parameters.
+ type: dict
+ suboptions:
+ abr_type:
+ description: ABR type.
+ type: str
+ choices: [cisco, ibm, shortcut, standard]
+ opaque_lsa:
+ description: Enable opaque LSA.
+ type: bool
+ rfc1583_compatibility:
+ description: Enable RFC1583 compatibility.
+ type: bool
+ router_id:
+ description: Router ID.
+ type: str
+ passive_interface:
+ description: >
+ Passive interfaces (VyOS 1.5+: configured via
+ C(protocols ospf interface <name> passive)).
+ type: list
+ elements: str
+ redistribute:
+ description: Route redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ route_type:
+ description: Protocol to redistribute.
+ type: str
+ choices: [bgp, connected, kernel, rip, static]
+ metric:
+ description: Metric.
+ type: int
+ metric_type:
+ description: Metric type.
+ type: int
+ route_map:
+ description: Route map.
+ type: str
+ state:
+ description:
+ - Desired state.
+ - C(merged) adds/updates without removing existing config.
+ - C(replaced) replaces the entire OSPFv2 configuration.
+ - C(deleted) removes OSPFv2 configuration.
+ - C(gathered) returns current configuration as structured data.
+ type: str
+ choices: [merged, replaced, deleted, gathered]
+ default: merged
+notes:
+ - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin.
+ - C(ansible_network_os) must be set to C(vyos.rest.vyos).
+ - VyOS 1.5+ uses per-interface passive configuration rather than
+ the global C(passive-interface) command used in VyOS 1.4.
+"""
+
+EXAMPLES = r"""
+- name: Merge OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ abr_type: cisco
+ auto_cost:
+ reference_bandwidth: 2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ - area_id: "3"
+ area_type:
+ nssa:
+ set: true
+ - area_id: "4"
+ area_type:
+ stub:
+ default_cost: 20
+ range:
+ - address: 192.0.3.0/24
+ cost: 10
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ passive_interface:
+ - eth1
+ state: merged
+
+- name: Delete all OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ state: deleted
+
+- name: Gather current OSPFv2 configuration
+ vyos.rest.vyos_ospfv2:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: OSPFv2 configuration before this module ran.
+ returned: always
+ type: dict
+after:
+ description: OSPFv2 configuration after this module ran.
+ returned: when changed
+ type: dict
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current OSPFv2 configuration as structured data.
+ returned: when state is gathered
+ type: dict
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["protocols", "ospf"]
+
+
+def _parse_areas(raw_areas):
+ if not raw_areas or not isinstance(raw_areas, dict):
+ return []
+ areas = []
+ for area_id, data in sorted(raw_areas.items()):
+ area = {"area_id": area_id}
+ data = data or {}
+
+ # area-type
+ at = data.get("area-type", {})
+ if at:
+ area_type = {}
+ if "normal" in at:
+ area_type["normal"] = True
+ if "nssa" in at:
+ nssa_data = at["nssa"] or {}
+ nssa = {"set": True}
+ if "default-cost" in nssa_data:
+ nssa["default_cost"] = int(nssa_data["default-cost"])
+ if "no-summary" in nssa_data:
+ nssa["no_summary"] = True
+ if "translate" in nssa_data:
+ nssa["translate"] = nssa_data["translate"]
+ area_type["nssa"] = nssa
+ if "stub" in at:
+ stub_data = at["stub"] or {}
+ stub = {"set": True}
+ if "default-cost" in stub_data:
+ stub["default_cost"] = int(stub_data["default-cost"])
+ if "no-summary" in stub_data:
+ stub["no_summary"] = True
+ area_type["stub"] = stub
+ if area_type:
+ area["area_type"] = area_type
+
+ if "authentication" in data:
+ area["authentication"] = data["authentication"]
+
+ if "shortcut" in data:
+ area["shortcut"] = data["shortcut"]
+
+ # network
+ net = data.get("network")
+ if net:
+ if isinstance(net, str):
+ area["network"] = [{"address": net}]
+ elif isinstance(net, dict):
+ area["network"] = [{"address": a} for a in sorted(net.keys())]
+ elif isinstance(net, list):
+ area["network"] = [{"address": a} for a in sorted(net)]
+
+ # range
+ rng = data.get("range", {})
+ if rng and isinstance(rng, dict):
+ ranges = []
+ for addr, rdata in sorted(rng.items()):
+ r = {"address": addr}
+ rdata = rdata or {}
+ if "cost" in rdata:
+ r["cost"] = int(rdata["cost"])
+ if "not-advertise" in rdata:
+ r["not_advertise"] = True
+ if "substitute" in rdata:
+ r["substitute"] = rdata["substitute"]
+ ranges.append(r)
+ if ranges:
+ area["range"] = ranges
+
+ areas.append(area)
+ return areas
+
+
+def _parse_redistribute(raw):
+ if not raw or not isinstance(raw, dict):
+ return []
+ result = []
+ for rt, data in sorted(raw.items()):
+ entry = {"route_type": rt}
+ data = data or {}
+ if "metric" in data:
+ entry["metric"] = int(data["metric"])
+ if "metric-type" in data:
+ entry["metric_type"] = int(data["metric-type"])
+ if "route-map" in data:
+ entry["route_map"] = data["route-map"]
+ result.append(entry)
+ return result
+
+
+def _parse_neighbor(raw):
+ if not raw or not isinstance(raw, dict):
+ return []
+ result = []
+ for nb_id, data in sorted(raw.items()):
+ entry = {"neighbor_id": nb_id}
+ data = data or {}
+ if "poll-interval" in data:
+ entry["poll_interval"] = int(data["poll-interval"])
+ if "priority" in data:
+ entry["priority"] = int(data["priority"])
+ result.append(entry)
+ return result
+
+
+def _parse_parameters(raw):
+ if not raw or not isinstance(raw, dict):
+ return {}
+ result = {}
+ if "router-id" in raw:
+ result["router_id"] = raw["router-id"]
+ if "abr-type" in raw:
+ result["abr_type"] = raw["abr-type"]
+ if "opaque-lsa" in raw:
+ result["opaque_lsa"] = True
+ if "rfc1583-compatibility" in raw:
+ result["rfc1583_compatibility"] = True
+ return result
+
+
+def _parse_default_information(raw):
+ if not raw or not isinstance(raw, dict):
+ return {}
+ orig = raw.get("originate", {}) or {}
+ result = {}
+ if "always" in orig:
+ result["always"] = True
+ if "metric" in orig:
+ result["metric"] = int(orig["metric"])
+ if "metric-type" in orig:
+ result["metric_type"] = int(orig["metric-type"])
+ if "route-map" in orig:
+ result["route_map"] = orig["route-map"]
+ if result:
+ return {"originate": result}
+ return {}
+
+
+def _parse_distance(raw):
+ if not raw or not isinstance(raw, dict):
+ return {}
+ result = {}
+ if "global" in raw:
+ result["global"] = int(raw["global"])
+ ospf = raw.get("ospf", {}) or {}
+ if ospf:
+ od = {}
+ if "external" in ospf:
+ od["external"] = int(ospf["external"])
+ if "inter-area" in ospf:
+ od["inter_area"] = int(ospf["inter-area"])
+ if "intra-area" in ospf:
+ od["intra_area"] = int(ospf["intra-area"])
+ if od:
+ result["ospf"] = od
+ return result
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(_BASE)
+ if not raw or not isinstance(raw, dict):
+ return {}
+ result = {}
+
+ areas = _parse_areas(raw.get("area"))
+ if areas:
+ result["areas"] = areas
+
+ ac = raw.get("auto-cost", {})
+ if ac and "reference-bandwidth" in ac:
+ result["auto_cost"] = {"reference_bandwidth": int(ac["reference-bandwidth"])}
+
+ di = _parse_default_information(raw.get("default-information", {}))
+ if di:
+ result["default_information"] = di
+
+ if "default-metric" in raw:
+ result["default_metric"] = int(raw["default-metric"])
+
+ dist = _parse_distance(raw.get("distance", {}))
+ if dist:
+ result["distance"] = dist
+
+ lac = raw.get("log-adjacency-changes", {})
+ if lac:
+ if isinstance(lac, dict) and "detail" in lac:
+ result["log_adjacency_changes"] = "detail"
+ elif lac == "detail":
+ result["log_adjacency_changes"] = "detail"
+
+ neighbors = _parse_neighbor(raw.get("neighbor"))
+ if neighbors:
+ result["neighbor"] = neighbors
+
+ params = _parse_parameters(raw.get("parameters"))
+ if params:
+ result["parameters"] = params
+
+ # passive interfaces — VyOS 1.5 uses interface <name> passive
+ iface_raw = raw.get("interface", {}) or {}
+ passive = sorted(
+ [name for name, data in iface_raw.items() if isinstance(data, dict) and "passive" in data],
+ )
+ if passive:
+ result["passive_interface"] = passive
+
+ redist = _parse_redistribute(raw.get("redistribute"))
+ if redist:
+ result["redistribute"] = redist
+
+ return result
+
+
+def _area_type_cmds(abase, area_type, have_at):
+ cmds = []
+ have_at = have_at or {}
+ if area_type.get("normal") and not have_at.get("normal"):
+ cmds.append(("set", abase + ["area-type", "normal"]))
+ nssa = area_type.get("nssa") or {}
+ if nssa:
+ have_nssa = have_at.get("nssa") or {}
+ if not have_nssa:
+ cmds.append(("set", abase + ["area-type", "nssa"]))
+ if nssa.get("default_cost") and nssa["default_cost"] != have_nssa.get("default_cost"):
+ cmds.append(
+ (
+ "set",
+ abase
+ + [
+ "area-type",
+ "nssa",
+ "default-cost",
+ str(nssa["default_cost"]),
+ ],
+ ),
+ )
+ if nssa.get("no_summary") and not have_nssa.get("no_summary"):
+ cmds.append(("set", abase + ["area-type", "nssa", "no-summary"]))
+ if nssa.get("translate") and nssa["translate"] != have_nssa.get("translate"):
+ cmds.append(("set", abase + ["area-type", "nssa", "translate", nssa["translate"]]))
+ stub = area_type.get("stub") or {}
+ if stub:
+ have_stub = have_at.get("stub") or {}
+ if not have_stub:
+ if stub.get("default_cost"):
+ cmds.append(
+ (
+ "set",
+ abase
+ + [
+ "area-type",
+ "stub",
+ "default-cost",
+ str(stub["default_cost"]),
+ ],
+ ),
+ )
+ else:
+ cmds.append(("set", abase + ["area-type", "stub"]))
+ elif stub.get("default_cost") and stub["default_cost"] != have_stub.get("default_cost"):
+ cmds.append(
+ (
+ "set",
+ abase
+ + [
+ "area-type",
+ "stub",
+ "default-cost",
+ str(stub["default_cost"]),
+ ],
+ ),
+ )
+ return cmds
+
+
+def _area_cmds(area, have_area):
+ cmds = []
+ area_id = area["area_id"]
+ abase = _BASE + ["area", area_id]
+ have_area = have_area or {}
+
+ if area.get("area_type"):
+ cmds += _area_type_cmds(abase, area["area_type"], have_area.get("area_type"))
+
+ if area.get("authentication") and area["authentication"] != have_area.get("authentication"):
+ cmds.append(("set", abase + ["authentication", area["authentication"]]))
+
+ if area.get("shortcut") and area["shortcut"] != have_area.get("shortcut"):
+ cmds.append(("set", abase + ["shortcut", area["shortcut"]]))
+
+ want_nets = {n["address"] for n in (area.get("network") or [])}
+ have_nets = {n["address"] for n in (have_area.get("network") or [])}
+ for addr in want_nets - have_nets:
+ cmds.append(("set", abase + ["network", addr]))
+
+ want_ranges = {r["address"]: r for r in (area.get("range") or [])}
+ have_ranges = {r["address"]: r for r in (have_area.get("range") or [])}
+ for addr, rng in want_ranges.items():
+ have_rng = have_ranges.get(addr, {})
+ if addr not in have_ranges:
+ cmds.append(("set", abase + ["range", addr]))
+ if rng.get("cost") and rng["cost"] != have_rng.get("cost"):
+ cmds.append(("set", abase + ["range", addr, "cost", str(rng["cost"])]))
+ if rng.get("not_advertise") and not have_rng.get("not_advertise"):
+ cmds.append(("set", abase + ["range", addr, "not-advertise"]))
+ if rng.get("substitute") and rng["substitute"] != have_rng.get("substitute"):
+ cmds.append(("set", abase + ["range", addr, "substitute", rng["substitute"]]))
+
+ return cmds
+
+
+def _parameters_cmds(params, have_params):
+ cmds = []
+ have_params = have_params or {}
+ pbase = _BASE + ["parameters"]
+ if params.get("router_id") and params["router_id"] != have_params.get("router_id"):
+ cmds.append(("set", pbase + ["router-id", params["router_id"]]))
+ if params.get("abr_type") and params["abr_type"] != have_params.get("abr_type"):
+ cmds.append(("set", pbase + ["abr-type", params["abr_type"]]))
+ if params.get("opaque_lsa") and not have_params.get("opaque_lsa"):
+ cmds.append(("set", pbase + ["opaque-lsa"]))
+ if params.get("rfc1583_compatibility") and not have_params.get("rfc1583_compatibility"):
+ cmds.append(("set", pbase + ["rfc1583-compatibility"]))
+ return cmds
+
+
+def _redistribute_cmds(redist_list, have_redist_list):
+ cmds = []
+ want = {r["route_type"]: r for r in (redist_list or [])}
+ have = {r["route_type"]: r for r in (have_redist_list or [])}
+ for rt, entry in want.items():
+ have_entry = have.get(rt, {})
+ rbase = _BASE + ["redistribute", rt]
+ if rt not in have:
+ cmds.append(("set", rbase))
+ if entry.get("metric") and entry["metric"] != have_entry.get("metric"):
+ cmds.append(("set", rbase + ["metric", str(entry["metric"])]))
+ if entry.get("metric_type") and entry["metric_type"] != have_entry.get("metric_type"):
+ cmds.append(("set", rbase + ["metric-type", str(entry["metric_type"])]))
+ if entry.get("route_map") and entry["route_map"] != have_entry.get("route_map"):
+ cmds.append(("set", rbase + ["route-map", entry["route_map"]]))
+ return cmds
+
+
+def _neighbor_cmds(neighbors, have_neighbors):
+ cmds = []
+ want = {n["neighbor_id"]: n for n in (neighbors or [])}
+ have = {n["neighbor_id"]: n for n in (have_neighbors or [])}
+ for nb_id, entry in want.items():
+ have_entry = have.get(nb_id, {})
+ nbase = _BASE + ["neighbor", nb_id]
+ if nb_id not in have:
+ cmds.append(("set", nbase))
+ if entry.get("priority") and entry["priority"] != have_entry.get("priority"):
+ cmds.append(("set", nbase + ["priority", str(entry["priority"])]))
+ if entry.get("poll_interval") and entry["poll_interval"] != have_entry.get("poll_interval"):
+ cmds.append(("set", nbase + ["poll-interval", str(entry["poll_interval"])]))
+ return cmds
+
+
+def _default_info_cmds(di, have_di):
+ cmds = []
+ have_di = have_di or {}
+ orig = (di or {}).get("originate") or {}
+ have_orig = have_di.get("originate") or {}
+ if not orig:
+ return cmds
+ dbase = _BASE + ["default-information", "originate"]
+ if orig.get("always") and not have_orig.get("always"):
+ cmds.append(("set", dbase + ["always"]))
+ if orig.get("metric") and orig["metric"] != have_orig.get("metric"):
+ cmds.append(("set", dbase + ["metric", str(orig["metric"])]))
+ if orig.get("metric_type") and orig["metric_type"] != have_orig.get("metric_type"):
+ cmds.append(("set", dbase + ["metric-type", str(orig["metric_type"])]))
+ if orig.get("route_map") and orig["route_map"] != have_orig.get("route_map"):
+ cmds.append(("set", dbase + ["route-map", orig["route_map"]]))
+ return cmds
+
+
+def build_commands(config, have, state):
+ cmds = []
+
+ if state == "deleted":
+ if have:
+ cmds.append(("delete", _BASE))
+ return cmds
+
+ if state == "replaced":
+ would_set = build_commands(config, {}, "merged")
+ have_set = build_commands(have, {}, "merged")
+ if would_set == have_set:
+ return []
+ if have:
+ cmds.append(("delete", _BASE))
+ have = {}
+
+ config = config or {}
+
+ # parameters
+ if config.get("parameters"):
+ cmds += _parameters_cmds(config["parameters"], have.get("parameters"))
+
+ # auto_cost
+ ac = config.get("auto_cost") or {}
+ have_ac = have.get("auto_cost") or {}
+ if ac.get("reference_bandwidth") and ac["reference_bandwidth"] != have_ac.get(
+ "reference_bandwidth",
+ ):
+ cmds.append(
+ (
+ "set",
+ _BASE
+ + [
+ "auto-cost",
+ "reference-bandwidth",
+ str(ac["reference_bandwidth"]),
+ ],
+ ),
+ )
+
+ # default_information
+ if config.get("default_information"):
+ cmds += _default_info_cmds(
+ config["default_information"],
+ have.get("default_information"),
+ )
+
+ # default_metric
+ if config.get("default_metric") and config["default_metric"] != have.get("default_metric"):
+ cmds.append(("set", _BASE + ["default-metric", str(config["default_metric"])]))
+
+ # distance
+ dist = config.get("distance") or {}
+ have_dist = have.get("distance") or {}
+ if dist.get("global") and dist["global"] != have_dist.get("global"):
+ cmds.append(("set", _BASE + ["distance", "global", str(dist["global"])]))
+ ospf_dist = dist.get("ospf") or {}
+ have_ospf_dist = have_dist.get("ospf") or {}
+ for key, api_key in [
+ ("external", "external"),
+ ("inter_area", "inter-area"),
+ ("intra_area", "intra-area"),
+ ]:
+ if ospf_dist.get(key) and ospf_dist[key] != have_ospf_dist.get(key):
+ cmds.append(("set", _BASE + ["distance", "ospf", api_key, str(ospf_dist[key])]))
+
+ # log_adjacency_changes
+ if config.get("log_adjacency_changes") and config["log_adjacency_changes"] != have.get(
+ "log_adjacency_changes",
+ ):
+ cmds.append(("set", _BASE + ["log-adjacency-changes", config["log_adjacency_changes"]]))
+
+ # neighbor
+ if config.get("neighbor"):
+ cmds += _neighbor_cmds(config["neighbor"], have.get("neighbor"))
+
+ # redistribute
+ if config.get("redistribute"):
+ cmds += _redistribute_cmds(config["redistribute"], have.get("redistribute"))
+
+ # passive_interface — VyOS 1.5 per-interface style
+ want_passive = set(config.get("passive_interface") or [])
+ have_passive = set(have.get("passive_interface") or [])
+ for iface in want_passive - have_passive:
+ cmds.append(("set", _BASE + ["interface", iface, "passive"]))
+
+ # areas
+ have_areas = {a["area_id"]: a for a in (have.get("areas") or [])}
+ for area in config.get("areas") or []:
+ have_area = have_areas.get(area["area_id"], {})
+ cmds += _area_cmds(area, have_area)
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="dict",
+ options=dict(
+ areas=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ area_id=dict(type="str", required=True),
+ area_type=dict(
+ type="dict",
+ options=dict(
+ normal=dict(type="bool"),
+ nssa=dict(
+ type="dict",
+ options=dict(
+ set=dict(type="bool"),
+ default_cost=dict(type="int"),
+ no_summary=dict(type="bool"),
+ translate=dict(
+ type="str",
+ choices=["always", "candidate", "never"],
+ ),
+ ),
+ ),
+ stub=dict(
+ type="dict",
+ options=dict(
+ set=dict(type="bool"),
+ default_cost=dict(type="int"),
+ no_summary=dict(type="bool"),
+ ),
+ ),
+ ),
+ ),
+ authentication=dict(
+ type="str",
+ choices=["plaintext-password", "md5"],
+ ),
+ network=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ address=dict(type="str", required=True),
+ ),
+ ),
+ range=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ address=dict(type="str", required=True),
+ cost=dict(type="int"),
+ not_advertise=dict(type="bool"),
+ substitute=dict(type="str"),
+ ),
+ ),
+ shortcut=dict(type="str", choices=["default", "disable", "enable"]),
+ ),
+ ),
+ auto_cost=dict(
+ type="dict",
+ options=dict(
+ reference_bandwidth=dict(type="int"),
+ ),
+ ),
+ default_information=dict(
+ type="dict",
+ options=dict(
+ originate=dict(
+ type="dict",
+ options=dict(
+ always=dict(type="bool"),
+ metric=dict(type="int"),
+ metric_type=dict(type="int"),
+ route_map=dict(type="str"),
+ ),
+ ),
+ ),
+ ),
+ default_metric=dict(type="int"),
+ distance=dict(
+ type="dict",
+ options=dict(
+ **{"global": dict(type="int")},
+ ospf=dict(
+ type="dict",
+ options=dict(
+ external=dict(type="int"),
+ inter_area=dict(type="int"),
+ intra_area=dict(type="int"),
+ ),
+ ),
+ ),
+ ),
+ log_adjacency_changes=dict(type="str", choices=["detail"]),
+ neighbor=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ neighbor_id=dict(type="str", required=True),
+ poll_interval=dict(type="int"),
+ priority=dict(type="int"),
+ ),
+ ),
+ parameters=dict(
+ type="dict",
+ options=dict(
+ abr_type=dict(
+ type="str",
+ choices=["cisco", "ibm", "shortcut", "standard"],
+ ),
+ opaque_lsa=dict(type="bool"),
+ rfc1583_compatibility=dict(type="bool"),
+ router_id=dict(type="str"),
+ ),
+ ),
+ passive_interface=dict(type="list", elements="str"),
+ redistribute=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ route_type=dict(
+ type="str",
+ choices=["bgp", "connected", "kernel", "rip", "static"],
+ ),
+ metric=dict(type="int"),
+ metric_type=dict(type="int"),
+ route_map=dict(type="str"),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or {}
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py
new file mode 100644
index 0000000..fcf404e
--- /dev/null
+++ b/plugins/modules/vyos_ospfv3.py
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# GNU General Public License v3.0+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = r"""
+---
+module: vyos_ospfv3
+short_description: Manage OSPFv3 configuration on VyOS devices using REST API
+description:
+ - Manages OSPFv3 configuration on VyOS devices via the REST API.
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+version_added: "1.0.0"
+author:
+ - VyOS Community (@vyos)
+options:
+ config:
+ description: OSPFv3 configuration.
+ type: dict
+ suboptions:
+ areas:
+ description: OSPFv3 areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description: Area identity.
+ type: str
+ required: true
+ export_list:
+ description: Name of export-list.
+ type: str
+ import_list:
+ description: Name of import-list.
+ type: str
+ range:
+ description: Summarize routes matching prefix.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description: IPv6 prefix.
+ type: str
+ required: true
+ advertise:
+ description: Advertise this range.
+ type: bool
+ not_advertise:
+ description: Do not advertise this range.
+ type: bool
+ parameters:
+ description: OSPFv3 global parameters.
+ type: dict
+ suboptions:
+ router_id:
+ description: Router ID (IPv4 address format).
+ type: str
+ redistribute:
+ description: Redistribute routes from another protocol.
+ type: list
+ elements: dict
+ suboptions:
+ route_type:
+ description: Protocol to redistribute.
+ type: str
+ choices: [bgp, connected, kernel, ripng, static]
+ route_map:
+ description: Route map to apply.
+ type: str
+ state:
+ description:
+ - Desired state of the OSPFv3 configuration.
+ - C(merged) adds or updates without removing existing config.
+ - C(replaced) replaces the entire OSPFv3 configuration.
+ - C(deleted) removes OSPFv3 configuration.
+ - C(gathered) returns current configuration as structured data.
+ type: str
+ choices: [merged, replaced, deleted, gathered]
+ default: merged
+notes:
+ - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin.
+ - C(ansible_network_os) must be set to C(vyos.rest.vyos).
+"""
+
+EXAMPLES = r"""
+- name: Merge OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+- name: Delete all OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+- name: Gather current OSPFv3 configuration
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: OSPFv3 configuration before this module ran.
+ returned: always
+ type: dict
+after:
+ description: OSPFv3 configuration after this module ran.
+ returned: when changed
+ type: dict
+commands:
+ description: List of API command tuples sent to the device.
+ returned: always
+ type: list
+gathered:
+ description: Current OSPFv3 configuration as structured data.
+ returned: when state is gathered
+ type: dict
+saved:
+ description: Whether the config was saved after changes.
+ returned: when changes are applied
+ type: bool
+response:
+ description: Raw API response.
+ returned: when changes are applied
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+_BASE = ["protocols", "ospfv3"]
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(_BASE)
+ if not raw or not isinstance(raw, dict):
+ return {}
+ return _parse_ospfv3(raw)
+
+
+def _parse_ospfv3(raw):
+ result = {}
+
+ # parameters
+ params = raw.get("parameters", {})
+ if params:
+ result["parameters"] = {}
+ if "router-id" in params:
+ result["parameters"]["router_id"] = params["router-id"]
+
+ # redistribute
+ redist_raw = raw.get("redistribute", {})
+ if redist_raw and isinstance(redist_raw, dict):
+ redist = []
+ for route_type, data in sorted(redist_raw.items()):
+ entry = {"route_type": route_type}
+ if isinstance(data, dict) and data.get("route-map"):
+ entry["route_map"] = data["route-map"]
+ redist.append(entry)
+ if redist:
+ result["redistribute"] = redist
+
+ # areas
+ area_raw = raw.get("area", {})
+ if area_raw and isinstance(area_raw, dict):
+ areas = []
+ for area_id, area_data in sorted(area_raw.items()):
+ area = {"area_id": area_id}
+ area_data = area_data or {}
+ if area_data.get("export-list"):
+ area["export_list"] = area_data["export-list"]
+ if area_data.get("import-list"):
+ area["import_list"] = area_data["import-list"]
+ range_raw = area_data.get("range", {})
+ if range_raw and isinstance(range_raw, dict):
+ ranges = []
+ for prefix, rdata in sorted(range_raw.items()):
+ r = {"address": prefix}
+ rdata = rdata or {}
+ if "advertise" in rdata:
+ r["advertise"] = True
+ if "not-advertise" in rdata:
+ r["not_advertise"] = True
+ ranges.append(r)
+ if ranges:
+ area["range"] = ranges
+ areas.append(area)
+ if areas:
+ result["areas"] = areas
+
+ return result
+
+
+def build_commands(config, have, state):
+ cmds = []
+
+ if state == "deleted":
+ if have:
+ cmds.append(("delete", _BASE))
+ return cmds
+
+ if state == "replaced":
+ # Build what we would set from scratch and compare to have
+ would_set = build_commands(config, {}, "merged")
+ have_set = build_commands(have, {}, "merged")
+ if would_set == have_set:
+ return []
+ if have:
+ cmds.append(("delete", _BASE))
+ have = {}
+
+ # parameters
+ want_params = (config or {}).get("parameters") or {}
+ have_params = have.get("parameters") or {}
+ if want_params.get("router_id") and want_params["router_id"] != have_params.get("router_id"):
+ cmds.append(("set", _BASE + ["parameters", "router-id", want_params["router_id"]]))
+
+ # redistribute
+ want_redist = {r["route_type"]: r for r in ((config or {}).get("redistribute") or [])}
+ have_redist = {r["route_type"]: r for r in (have.get("redistribute") or [])}
+
+ for rt in set(have_redist) - set(want_redist):
+ if state == "merged":
+ pass # merged doesn't remove
+ for rt, entry in want_redist.items():
+ if rt not in have_redist:
+ cmds.append(("set", _BASE + ["redistribute", rt]))
+ if entry.get("route_map"):
+ have_rm = have_redist.get(rt, {}).get("route_map")
+ if entry["route_map"] != have_rm:
+ cmds.append(("set", _BASE + ["redistribute", rt, "route-map", entry["route_map"]]))
+
+ # areas
+ want_areas = {a["area_id"]: a for a in ((config or {}).get("areas") or [])}
+ have_areas = {a["area_id"]: a for a in (have.get("areas") or [])}
+
+ for area_id, want_area in want_areas.items():
+ have_area = have_areas.get(area_id, {})
+ abase = _BASE + ["area", area_id]
+
+ if want_area.get("export_list") and want_area["export_list"] != have_area.get(
+ "export_list",
+ ):
+ cmds.append(("set", abase + ["export-list", want_area["export_list"]]))
+ if want_area.get("import_list") and want_area["import_list"] != have_area.get(
+ "import_list",
+ ):
+ cmds.append(("set", abase + ["import-list", want_area["import_list"]]))
+
+ want_ranges = {r["address"]: r for r in (want_area.get("range") or [])}
+ have_ranges = {r["address"]: r for r in (have_area.get("range") or [])}
+
+ for addr in want_ranges:
+ if addr not in have_ranges:
+ cmds.append(("set", abase + ["range", addr]))
+ r = want_ranges[addr]
+ if r.get("not_advertise"):
+ cmds.append(("set", abase + ["range", addr, "not-advertise"]))
+ elif r.get("advertise"):
+ cmds.append(("set", abase + ["range", addr, "advertise"]))
+
+ return cmds
+
+
+ARGUMENT_SPEC = dict(
+ config=dict(
+ type="dict",
+ options=dict(
+ areas=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ area_id=dict(type="str", required=True),
+ export_list=dict(type="str"),
+ import_list=dict(type="str"),
+ range=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ address=dict(type="str", required=True),
+ advertise=dict(type="bool"),
+ not_advertise=dict(type="bool"),
+ ),
+ ),
+ ),
+ ),
+ parameters=dict(
+ type="dict",
+ options=dict(
+ router_id=dict(type="str"),
+ ),
+ ),
+ redistribute=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ route_type=dict(
+ type="str",
+ choices=["bgp", "connected", "kernel", "ripng", "static"],
+ ),
+ route_map=dict(type="str"),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "deleted", "gathered"],
+ ),
+)
+
+
+def main():
+ module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True)
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or {}
+
+ have = get_running_config(vyos)
+
+ if state == "gathered":
+ module.exit_json(changed=False, gathered=have)
+
+ commands = build_commands(config, have, state)
+
+ if module.check_mode:
+ module.exit_json(changed=bool(commands), commands=commands, before=have)
+
+ if commands:
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=get_running_config(vyos),
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(changed=False, before=have, after=have, commands=[])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/plugins/modules/vyos_route_maps.py b/plugins/modules/vyos_route_maps.py
index 55b1706..607d1ab 100644
--- a/plugins/modules/vyos_route_maps.py
+++ b/plugins/modules/vyos_route_maps.py
@@ -308,7 +308,7 @@ def _want_to_api_match(match):
return api
-def _rule_cmds(rm_name, rule, have_rule):
+def _rule_cmds(rm_name, rule, have_rule, state="merged"):
cmds = []
seq = str(rule["sequence"])
rbase = _BASE + [rm_name, "rule", seq]
@@ -343,8 +343,13 @@ def _rule_cmds(rm_name, rule, have_rule):
want_set_api = _want_to_api_set(rule.get("set"))
have_set = have_rule.get("set") or {}
- if want_set_api != have_set:
- cmds += _set_cmds(rbase, rule.get("set"))
+ if state in ("replaced", "overridden"):
+ if want_set_api != have_set:
+ cmds += _set_cmds(rbase, rule.get("set"))
+ else:
+ have_subset = {k: have_set[k] for k in want_set_api if k in have_set}
+ if want_set_api != have_subset:
+ cmds += _set_cmds(rbase, rule.get("set"))
return cmds
@@ -380,7 +385,7 @@ def build_commands(config, have_raw, state):
test_cmds = []
for rule in rm.get("entries") or []:
have_rule = have_entries.get(str(rule["sequence"]), {})
- test_cmds += _rule_cmds(rm_name, rule, have_rule)
+ test_cmds += _rule_cmds(rm_name, rule, have_rule, state)
if test_cmds or extra_seqs:
cmds.append(("delete", _BASE + [rm_name]))
have_rm = {}
@@ -391,7 +396,7 @@ def build_commands(config, have_raw, state):
for rule in rm.get("entries") or []:
have_rule = have_entries.get(str(rule["sequence"]), {})
- cmds += _rule_cmds(rm_name, rule, have_rule)
+ cmds += _rule_cmds(rm_name, rule, have_rule, state)
return cmds
diff --git a/plugins/modules/vyos_snmp_server.py b/plugins/modules/vyos_snmp_server.py
index 3b40344..1079402 100644
--- a/plugins/modules/vyos_snmp_server.py
+++ b/plugins/modules/vyos_snmp_server.py
@@ -525,12 +525,12 @@ def _build_scalar_commands(want, have, state):
want_val = want.get(argspec_key)
have_val = have.get(argspec_key)
path = SNMP_BASE + [api_key]
- if state in ("merged", "replaced", "overridden"):
- if want_val and want_val != have_val:
- cmds.append(_set(path + [want_val]))
if state in ("replaced", "overridden"):
if have_val and want_val != have_val:
cmds.append(_delete(path))
+ if state in ("merged", "replaced", "overridden"):
+ if want_val and want_val != have_val:
+ cmds.append(_set(path + [want_val]))
return cmds
@@ -547,10 +547,10 @@ def _build_community_commands(want_list, have_list, state):
base = SNMP_BASE + ["community", name]
want_auth = want_comm.get("authorization_type")
have_auth = have_comm.get("authorization_type")
- if want_auth and want_auth != have_auth:
- cmds.append(_set(base + ["authorization", want_auth]))
if state in ("replaced", "overridden") and have_auth and want_auth != have_auth:
cmds.append(_delete(base + ["authorization"]))
+ if want_auth and want_auth != have_auth:
+ cmds.append(_set(base + ["authorization", want_auth]))
want_clients = set(want_comm.get("clients") or [])
have_clients = set(have_comm.get("clients") or [])
for c in want_clients - have_clients:
diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..f80add0
--- /dev/null
+++ b/tests/integration/targets/vyos_banner/tests/httpapi/overridden.yaml
@@ -0,0 +1,30 @@
+---
+- debug:
+ msg: START vyos_hostname overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override hostname configuration
+ register: result
+ vyos.rest.vyos_hostname: &id001
+ config:
+ hostname: ansible-overridden-host
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override hostname configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_hostname: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..2dddc90
--- /dev/null
+++ b/tests/integration/targets/vyos_banner/tests/httpapi/replaced.yaml
@@ -0,0 +1,31 @@
+---
+- debug:
+ msg: START vyos_banner replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace pre-login banner
+ register: result
+ vyos.rest.vyos_banner: &id001
+ config:
+ banner: pre-login
+ text: "Replaced by ansible"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace pre-login banner (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_banner: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..233f1ef
--- /dev/null
+++ b/tests/integration/targets/vyos_banner/tests/httpapi/rtt.yaml
@@ -0,0 +1,41 @@
+---
+- debug:
+ msg: START vyos_hostname round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_hostname:
+ config:
+ hostname: ansible-rtt-host
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_hostname:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.hostname == "ansible-rtt-host"
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_hostname:
+ config:
+ hostname: ansible-rtt-modified
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_hostname:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.hostname == "ansible-rtt-modified"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..671ac41
--- /dev/null
+++ b/tests/integration/targets/vyos_hostname/tests/httpapi/overridden.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_interfaces overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override interface configuration
+ register: result
+ vyos.rest.vyos_interfaces: &id001
+ config:
+ - name: eth0
+ description: Ansible overridden interface
+ mtu: 1400
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_interfaces: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..74261f8
--- /dev/null
+++ b/tests/integration/targets/vyos_hostname/tests/httpapi/rtt.yaml
@@ -0,0 +1,48 @@
+---
+- debug:
+ msg: START vyos_interfaces round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Ansible RTT interface
+ mtu: 1450
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_interfaces:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | list | length == 1
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='description') | first == 'Ansible RTT interface'
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='mtu') | first == 1450
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Ansible RTT modified
+ mtu: 1400
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_interfaces:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='description') | first == 'Ansible RTT modified'
+ - gathered2.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='mtu') | first == 1400
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_prefix_lists/aliases b/tests/integration/targets/vyos_interfaces/aliases
index cc0afef..cc0afef 100644
--- a/tests/integration/targets/vyos_prefix_lists/aliases
+++ b/tests/integration/targets/vyos_interfaces/aliases
diff --git a/tests/integration/targets/vyos_prefix_lists/defaults/main.yaml b/tests/integration/targets/vyos_interfaces/defaults/main.yaml
index 164afea..164afea 100644
--- a/tests/integration/targets/vyos_prefix_lists/defaults/main.yaml
+++ b/tests/integration/targets/vyos_interfaces/defaults/main.yaml
diff --git a/tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml b/tests/integration/targets/vyos_interfaces/tasks/httpapi.yaml
index 4147e6d..4147e6d 100644
--- a/tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml
+++ b/tests/integration/targets/vyos_interfaces/tasks/httpapi.yaml
diff --git a/tests/integration/targets/vyos_prefix_lists/tasks/main.yaml b/tests/integration/targets/vyos_interfaces/tasks/main.yaml
index b1f6193..b1f6193 100644
--- a/tests/integration/targets/vyos_prefix_lists/tasks/main.yaml
+++ b/tests/integration/targets/vyos_interfaces/tasks/main.yaml
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..9562fce
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,9 @@
+---
+- name: Populate interface config for testing
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ description: Ansible test interface
+ mtu: 1450
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..4014e56
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,7 @@
+---
+- name: Remove interface L2 config
+ vyos.rest.vyos_interfaces:
+ config:
+ - name: eth0
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..e8dfd06
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/deleted.yaml
@@ -0,0 +1,30 @@
+---
+- debug:
+ msg: START vyos_interfaces deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete interface configuration
+ register: result
+ vyos.rest.vyos_interfaces: &id001
+ config:
+ - name: eth0
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..022e21e
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_interfaces gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather interface configuration
+ register: result
+ vyos.rest.vyos_interfaces:
+ state: gathered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered | selectattr('name', 'eq', 'eth0') | list | length == 1
+ - result.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='description') | first == 'Ansible test interface'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..b5a82ef
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/merged.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_interfaces merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge interface configuration
+ register: result
+ vyos.rest.vyos_interfaces: &id001
+ config:
+ - name: eth0
+ description: Ansible test interface
+ mtu: 1450
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_interfaces/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_interfaces/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..430b441
--- /dev/null
+++ b/tests/integration/targets/vyos_interfaces/tests/httpapi/replaced.yaml
@@ -0,0 +1,33 @@
+---
+- debug:
+ msg: START vyos_interfaces replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace interface configuration
+ register: result
+ vyos.rest.vyos_interfaces: &id001
+ config:
+ - name: eth0
+ description: Ansible replaced interface
+ mtu: 1400
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_prefix_lists/vars/main.yaml b/tests/integration/targets/vyos_interfaces/vars/main.yaml
index 4303881..4303881 100644
--- a/tests/integration/targets/vyos_prefix_lists/vars/main.yaml
+++ b/tests/integration/targets/vyos_interfaces/vars/main.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/aliases b/tests/integration/targets/vyos_l3_interfaces/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_l3_interfaces/defaults/main.yaml b/tests/integration/targets/vyos_l3_interfaces/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_l3_interfaces/tasks/httpapi.yaml b/tests/integration/targets/vyos_l3_interfaces/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_l3_interfaces/tasks/main.yaml b/tests/integration/targets/vyos_l3_interfaces/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..af425ae
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,15 @@
+---
+- name: Populate L3 interface config for testing
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ - address: 10.0.0.2/32
+ - name: eth0
+ vifs:
+ - vlan_id: 100
+ ipv4:
+ - address: 192.0.2.100/24
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..44453c3
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,10 @@
+---
+- name: Remove test L3 config
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: lo
+ - name: eth0
+ vifs:
+ - vlan_id: 100
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..a583354
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/deleted.yaml
@@ -0,0 +1,33 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete L3 interface configuration
+ register: result
+ vyos.rest.vyos_l3_interfaces: &id001
+ config:
+ - name: lo
+ - name: eth0
+ vifs:
+ - vlan_id: 100
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete L3 interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_l3_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..cfd380a
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather L3 interface configuration
+ register: result
+ vyos.rest.vyos_l3_interfaces:
+ state: gathered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered | selectattr('name', 'eq', 'lo') | list | length == 1
+ - result.gathered | selectattr('name', 'eq', 'lo') | map(attribute='ipv4') | first | length == 2
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..eabd97b
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/merged.yaml
@@ -0,0 +1,33 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge L3 interface configuration
+ register: result
+ vyos.rest.vyos_l3_interfaces: &id001
+ config:
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ - address: 10.0.0.2/32
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge L3 interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_l3_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..09d72ae
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/overridden.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override L3 interface configuration
+ register: result
+ vyos.rest.vyos_l3_interfaces: &id001
+ config:
+ - name: eth0
+ ipv4:
+ - address: dhcp
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override L3 interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_l3_interfaces: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..e193ced
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/replaced.yaml
@@ -0,0 +1,33 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace L3 interface configuration
+ register: result
+ vyos.rest.vyos_l3_interfaces: &id001
+ config:
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace L3 interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_l3_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..c347f1a
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/tests/httpapi/rtt.yaml
@@ -0,0 +1,48 @@
+---
+- debug:
+ msg: START vyos_l3_interfaces round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ - address: 10.0.0.2/32
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_l3_interfaces:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered | selectattr('name', 'eq', 'lo') | list | length == 1
+ - gathered.gathered | selectattr('name', 'eq', 'lo') | map(attribute='ipv4') | first | length == 2
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_l3_interfaces:
+ config:
+ - name: lo
+ ipv4:
+ - address: 10.0.0.1/32
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_l3_interfaces:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered | selectattr('name', 'eq', 'lo') | map(attribute='ipv4') | first | length == 1
+ - gathered2.gathered | selectattr('name', 'eq', 'lo') | map(attribute='ipv4') | first | map(attribute='address') | first == '10.0.0.1/32'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml b/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_l3_interfaces/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_lag_interfaces/aliases b/tests/integration/targets/vyos_lag_interfaces/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_lag_interfaces/defaults/main.yaml b/tests/integration/targets/vyos_lag_interfaces/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_lag_interfaces/tasks/httpapi.yaml b/tests/integration/targets/vyos_lag_interfaces/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_lag_interfaces/tasks/main.yaml b/tests/integration/targets/vyos_lag_interfaces/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..c21a3e6
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,13 @@
+---
+- name: Populate LAG config for testing
+ vyos.rest.vyos_lag_interfaces:
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2
+ arp_monitor:
+ interval: 100
+ target:
+ - 192.0.2.1
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..f2dab9d
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove pre-existing LAG config
+ vyos.rest.vyos_lag_interfaces:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..c7959ec
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/deleted.yaml
@@ -0,0 +1,28 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete all LAG configuration
+ register: result
+ vyos.rest.vyos_lag_interfaces: &id001
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete LAG configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lag_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..7bf3a6e
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather LAG configuration
+ register: result
+ vyos.rest.vyos_lag_interfaces:
+ state: gathered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered | selectattr('name', 'eq', 'bond0') | list | length == 1
+ - result.gathered | selectattr('name', 'eq', 'bond0') | map(attribute='mode') | first == 'active-backup'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..8bda2ee
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/merged.yaml
@@ -0,0 +1,36 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge LAG configuration
+ register: result
+ vyos.rest.vyos_lag_interfaces: &id001
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2
+ arp_monitor:
+ interval: 100
+ target:
+ - 192.0.2.1
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge LAG configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lag_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..3024d71
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/overridden.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override LAG configuration
+ register: result
+ vyos.rest.vyos_lag_interfaces: &id001
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2+3
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override LAG configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lag_interfaces: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..e8eb972
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/replaced.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace LAG configuration
+ register: result
+ vyos.rest.vyos_lag_interfaces: &id001
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2+3
+ arp_monitor:
+ interval: 200
+ target:
+ - 192.0.2.2
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace LAG configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lag_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..4e58069
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/tests/httpapi/rtt.yaml
@@ -0,0 +1,56 @@
+---
+- debug:
+ msg: START vyos_lag_interfaces round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_lag_interfaces:
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2
+ arp_monitor:
+ interval: 100
+ target:
+ - 192.0.2.1
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_lag_interfaces:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered | selectattr('name', 'eq', 'bond0') | list | length == 1
+ - gathered.gathered | selectattr('name', 'eq', 'bond0') | map(attribute='mode') | first == 'active-backup'
+ - gathered.gathered | selectattr('name', 'eq', 'bond0') | map(attribute='hash_policy') | first == 'layer2'
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_lag_interfaces:
+ config:
+ - name: bond0
+ mode: active-backup
+ hash_policy: layer2+3
+ arp_monitor:
+ interval: 200
+ target:
+ - 192.0.2.2
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_lag_interfaces:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered | selectattr('name', 'eq', 'bond0') | map(attribute='hash_policy') | first == 'layer2+3'
+ - gathered2.gathered | selectattr('name', 'eq', 'bond0') | map(attribute='arp_monitor') | map(attribute='interval') | first == 200
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml b/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_lag_interfaces/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..8f85df5
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/overridden.yaml
@@ -0,0 +1,34 @@
+---
+- debug:
+ msg: START vyos_lldp_global overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override LLDP global configuration
+ register: result
+ vyos.rest.vyos_lldp_global: &id001
+ config:
+ enable: true
+ addresses:
+ - 192.0.2.20
+ legacy_protocols:
+ - cdp
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override LLDP global configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lldp_global: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..421f966
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/rtt.yaml
@@ -0,0 +1,53 @@
+---
+- debug:
+ msg: START vyos_lldp_global round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_lldp_global:
+ config:
+ enable: true
+ addresses:
+ - 192.0.2.17
+ legacy_protocols:
+ - cdp
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_lldp_global:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.enable == true
+ - "'192.0.2.17' in gathered.gathered.addresses"
+ - "'cdp' in gathered.gathered.legacy_protocols"
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_lldp_global:
+ config:
+ enable: true
+ addresses:
+ - 192.0.2.20
+ legacy_protocols:
+ - fdp
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_lldp_global:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - "'192.0.2.20' in gathered2.gathered.addresses"
+ - "'fdp' in gathered2.gathered.legacy_protocols"
+ - "'cdp' not in gathered2.gathered.legacy_protocols"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/aliases b/tests/integration/targets/vyos_lldp_interfaces/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_lldp_interfaces/defaults/main.yaml b/tests/integration/targets/vyos_lldp_interfaces/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tasks/httpapi.yaml b/tests/integration/targets/vyos_lldp_interfaces/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tasks/main.yaml b/tests/integration/targets/vyos_lldp_interfaces/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..f7f27b0
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,16 @@
+---
+- name: Ensure LLDP service is enabled
+ vyos.rest.vyos_configure:
+ commands:
+ - "set service lldp"
+ ignore_errors: true
+
+- name: Populate LLDP interface config for testing
+ vyos.rest.vyos_lldp_interfaces:
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "1234567890"
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..8612bd7
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove LLDP interface config
+ vyos.rest.vyos_lldp_interfaces:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..cd432bb
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/deleted.yaml
@@ -0,0 +1,28 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete LLDP interface configuration
+ register: result
+ vyos.rest.vyos_lldp_interfaces: &id001
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete LLDP interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lldp_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..590fe01
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather LLDP interface configuration
+ register: result
+ vyos.rest.vyos_lldp_interfaces:
+ state: gathered
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.gathered | selectattr('name', 'eq', 'eth0') | list | length == 1
+ - result.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='mode') | first == 'disable'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..636157a
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/merged.yaml
@@ -0,0 +1,48 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Merge LLDP interface configuration
+ register: result
+ vyos.rest.vyos_lldp_interfaces: &id001
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "1234567890"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+
+ - name: Merge LLDP interface — add elin change
+ register: result
+ vyos.rest.vyos_lldp_interfaces: &id002
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "0000000911"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge LLDP interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lldp_interfaces: *id002
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..4bc0a94
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/overridden.yaml
@@ -0,0 +1,32 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override LLDP interface configuration
+ register: result
+ vyos.rest.vyos_lldp_interfaces: &id001
+ config:
+ - name: eth0
+ mode: rx-tx
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override LLDP interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lldp_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..bc9542b
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/replaced.yaml
@@ -0,0 +1,34 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace LLDP interface configuration
+ register: result
+ vyos.rest.vyos_lldp_interfaces: &id001
+ config:
+ - name: eth0
+ mode: rx-tx
+ location:
+ elin: "9876543210"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace LLDP interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_lldp_interfaces: *id001
+
+ - name: Assert idempotent
+ assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..44ad3c7
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/tests/httpapi/rtt.yaml
@@ -0,0 +1,55 @@
+---
+- debug:
+ msg: START vyos_lldp_interfaces round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Ensure LLDP service is enabled
+ vyos.rest.vyos_configure:
+ commands:
+ - "set service lldp"
+
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_lldp_interfaces:
+ config:
+ - name: eth0
+ mode: disable
+ location:
+ elin: "1234567890"
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_lldp_interfaces:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | list | length == 1
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='mode') | first == 'disable'
+ - gathered.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='location') | map(attribute='elin') | first == '1234567890'
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_lldp_interfaces:
+ config:
+ - name: eth0
+ mode: rx-tx
+ location:
+ elin: "9876543210"
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_lldp_interfaces:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='mode') | first == 'rx-tx'
+ - gathered2.gathered | selectattr('name', 'eq', 'eth0') | map(attribute='location') | map(attribute='elin') | first == '9876543210'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_lldp_interfaces/vars/main.yaml b/tests/integration/targets/vyos_lldp_interfaces/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_lldp_interfaces/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml
index c081853..548038d 100644
--- a/tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml
+++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml
@@ -6,12 +6,6 @@
facilities:
- facility: local7
severity: err
- hosts:
- - hostname: 172.16.0.1
- port: 514
- facilities:
- - facility: local7
- severity: all
global_params:
facilities:
- facility: cron
diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..76867cc
--- /dev/null
+++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/overridden.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: START vyos_logging_global overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override logging_global configuration
+ register: result
+ vyos.rest.vyos_logging_global: &id001
+ config:
+ console:
+ facilities:
+ - facility: local7
+ severity: warning
+ global_params:
+ facilities:
+ - facility: cron
+ severity: debug
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override logging_global configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_logging_global: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..8bea580
--- /dev/null
+++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/rtt.yaml
@@ -0,0 +1,58 @@
+---
+- debug:
+ msg: START vyos_logging_global round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_logging_global:
+ config:
+ console:
+ facilities:
+ - facility: local7
+ severity: err
+ global_params:
+ facilities:
+ - facility: cron
+ severity: debug
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_logging_global:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.console is defined
+ - gathered.gathered.global is defined
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_logging_global:
+ config:
+ console:
+ facilities:
+ - facility: local7
+ severity: warning
+ global_params:
+ facilities:
+ - facility: cron
+ severity: info
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_logging_global:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.console is defined
+ - gathered2.gathered.global is defined
+ - gathered2.gathered.global.facilities.cron == "info"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ntp_global/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_ntp_global/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..d05e203
--- /dev/null
+++ b/tests/integration/targets/vyos_ntp_global/tests/httpapi/rtt.yaml
@@ -0,0 +1,65 @@
+---
+- debug:
+ msg: START vyos_ntp_global round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_ntp_global:
+ config:
+ servers:
+ - server: 10.3.6.5
+ options:
+ - noselect
+ - prefer
+ - server: server4.example.com
+ options:
+ - pool
+ allow_clients:
+ - 10.4.9.0/24
+ listen_addresses:
+ - 10.1.9.16
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_ntp_global:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - "'10.3.6.5' in gathered.gathered.servers"
+ - "'10.4.9.0/24' in gathered.gathered.allow_clients"
+ - "'10.1.9.16' in gathered.gathered.listen_addresses"
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_ntp_global:
+ config:
+ servers:
+ - server: 10.3.6.5
+ options:
+ - noselect
+ - server: server5.example.com
+ allow_clients:
+ - 10.4.8.0/24
+ listen_addresses:
+ - 10.1.9.16
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_ntp_global:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - "'server5.example.com' in gathered2.gathered.servers"
+ - "'server4.example.com' not in gathered2.gathered.servers"
+ - "'10.4.8.0/24' in gathered2.gathered.allow_clients"
+ - "'10.4.9.0/24' not in gathered2.gathered.allow_clients"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/aliases b/tests/integration/targets/vyos_ospf_interfaces/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_ospf_interfaces/defaults/main.yaml b/tests/integration/targets/vyos_ospf_interfaces/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/httpapi.yaml b/tests/integration/targets/vyos_ospf_interfaces/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tasks/main.yaml b/tests/integration/targets/vyos_ospf_interfaces/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..cee0e3e
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,19 @@
+---
+- name: Populate ospf_interfaces config for testing
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ priority: 26
+ - afi: ipv6
+ dead_interval: 39
+ passive: true
+ - name: eth2
+ address_family:
+ - afi: ipv4
+ cost: 200
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..ed1b31b
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove pre-existing ospf_interfaces config
+ vyos.rest.vyos_ospf_interfaces:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..09ce0de
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/deleted.yaml
@@ -0,0 +1,40 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete specific interface OSPF configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete all OSPF interface configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete all OSPF interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..13eaa25
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/gathered.yaml
@@ -0,0 +1,20 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather OSPF interface configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered | selectattr('name', 'eq', 'eth1') | list | length == 1
+ - result.gathered | selectattr('name', 'eq', 'eth2') | list | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..f9fcd27
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/merged.yaml
@@ -0,0 +1,44 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge OSPF interface configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ - afi: ipv6
+ dead_interval: 39
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge OSPF interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ - afi: ipv6
+ dead_interval: 39
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..9a57bf7
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/overridden.yaml
@@ -0,0 +1,33 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override OSPF interface configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces: &id001
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 300
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override OSPF interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospf_interfaces: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..359c647
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/replaced.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace OSPF interface configuration
+ register: result
+ vyos.rest.vyos_ospf_interfaces: &id001
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 200
+ - afi: ipv6
+ passive: true
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace OSPF interface configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospf_interfaces: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..1997870
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/tests/httpapi/rtt.yaml
@@ -0,0 +1,66 @@
+---
+- debug:
+ msg: START vyos_ospf_interfaces round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 100
+ transmit_delay: 50
+ - afi: ipv6
+ dead_interval: 39
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_ospf_interfaces:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ vars:
+ eth1_afs: >-
+ {{ gathered.gathered
+ | selectattr('name', 'eq', 'eth1')
+ | map(attribute='address_family')
+ | first }}
+ assert:
+ that:
+ - gathered.gathered | selectattr('name', 'eq', 'eth1') | list | length == 1
+ - eth1_afs | selectattr('afi', 'eq', 'ipv4') | map(attribute='cost') | first == 100
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_ospf_interfaces:
+ config:
+ - name: eth1
+ address_family:
+ - afi: ipv4
+ cost: 200
+ - afi: ipv6
+ passive: true
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_ospf_interfaces:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ vars:
+ eth1_afs2: >-
+ {{ gathered2.gathered
+ | selectattr('name', 'eq', 'eth1')
+ | map(attribute='address_family')
+ | first }}
+ assert:
+ that:
+ - eth1_afs2 | selectattr('afi', 'eq', 'ipv4') | map(attribute='cost') | first == 200
+ - eth1_afs2 | selectattr('afi', 'eq', 'ipv6') | map(attribute='passive') | first == true
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospf_interfaces/vars/main.yaml b/tests/integration/targets/vyos_ospf_interfaces/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_ospf_interfaces/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_ospfv2/aliases b/tests/integration/targets/vyos_ospfv2/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_ospfv2/defaults/main.yaml b/tests/integration/targets/vyos_ospfv2/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_ospfv2/tasks/httpapi.yaml b/tests/integration/targets/vyos_ospfv2/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_ospfv2/tasks/main.yaml b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..67aa729
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,34 @@
+---
+- name: Populate ospfv2 config for testing
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ abr_type: cisco
+ auto_cost:
+ reference_bandwidth: 2
+ log_adjacency_changes: detail
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ passive_interface:
+ - eth1
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ - area_id: "3"
+ area_type:
+ nssa:
+ set: true
+ - area_id: "4"
+ area_type:
+ stub:
+ default_cost: 20
+ range:
+ - address: 192.0.3.0/24
+ cost: 10
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..171ab62
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove pre-existing ospfv2 config
+ vyos.rest.vyos_ospfv2:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..f656d4a
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/deleted.yaml
@@ -0,0 +1,29 @@
+---
+- debug:
+ msg: START vyos_ospfv2 deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete OSPFv2 configuration
+ register: result
+ vyos.rest.vyos_ospfv2:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete OSPFv2 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv2:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..9b06985
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/gathered.yaml
@@ -0,0 +1,22 @@
+---
+- debug:
+ msg: START vyos_ospfv2 gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather OSPFv2 configuration
+ register: result
+ vyos.rest.vyos_ospfv2:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.parameters.router_id == "192.0.1.1"
+ - result.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+ - result.gathered.redistribute | selectattr('route_type', 'eq', 'bgp') | list | length == 1
+ - "'eth1' in result.gathered.passive_interface"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..db07c48
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/merged.yaml
@@ -0,0 +1,56 @@
+---
+- debug:
+ msg: START vyos_ospfv2 merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge OSPFv2 configuration
+ register: result
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ auto_cost:
+ reference_bandwidth: 2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge OSPFv2 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ auto_cost:
+ reference_bandwidth: 2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..a364c0f
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/overridden.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: START vyos_ospfv2 overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override OSPFv2 configuration
+ register: result
+ vyos.rest.vyos_ospfv2: &id001
+ config:
+ parameters:
+ router_id: 192.0.1.2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override OSPFv2 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv2: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..7a58000
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/replaced.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: START vyos_ospfv2 replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace OSPFv2 configuration
+ register: result
+ vyos.rest.vyos_ospfv2: &id001
+ config:
+ parameters:
+ router_id: 192.0.1.2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace OSPFv2 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv2: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_ospfv2/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..be2a3c9
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/tests/httpapi/rtt.yaml
@@ -0,0 +1,61 @@
+---
+- debug:
+ msg: START vyos_ospfv2 round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.1
+ redistribute:
+ - route_type: bgp
+ metric: 10
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_ospfv2:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.parameters.router_id == "192.0.1.1"
+ - gathered.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+ - gathered.gathered.redistribute | selectattr('route_type', 'eq', 'bgp') | list | length == 1
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_ospfv2:
+ config:
+ parameters:
+ router_id: 192.0.1.2
+ areas:
+ - area_id: "2"
+ area_type:
+ normal: true
+ network:
+ - address: 192.0.2.0/24
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_ospfv2:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.parameters.router_id == "192.0.1.2"
+ - gathered2.gathered.redistribute is not defined or gathered2.gathered.redistribute | length == 0
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv2/vars/main.yaml b/tests/integration/targets/vyos_ospfv2/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv2/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_ospfv3/aliases b/tests/integration/targets/vyos_ospfv3/aliases
new file mode 100644
index 0000000..cc0afef
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/aliases
@@ -0,0 +1 @@
+network/vyos
diff --git a/tests/integration/targets/vyos_ospfv3/defaults/main.yaml b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml
new file mode 100644
index 0000000..164afea
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml b/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml
new file mode 100644
index 0000000..4147e6d
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tasks/httpapi.yaml
@@ -0,0 +1,21 @@
+---
+- name: Collect all httpapi test cases
+ ansible.builtin.find:
+ paths: "{{ role_path }}/tests/httpapi"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- name: Set test_items
+ ansible.builtin.set_fact:
+ test_items: "{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: Run test case (connection=httpapi)
+ ansible.builtin.include_tasks: "{{ test_case_to_run }}"
+ vars:
+ ansible_connection: ansible.netcommon.httpapi
+ ansible_network_os: vyos.rest.vyos
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/tests/integration/targets/vyos_ospfv3/tasks/main.yaml b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml
new file mode 100644
index 0000000..b1f6193
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tasks/main.yaml
@@ -0,0 +1,5 @@
+---
+- name: Run httpapi tests
+ ansible.builtin.include_tasks: httpapi.yaml
+ tags:
+ - httpapi
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml
new file mode 100644
index 0000000..19a61c7
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_populate_config.yaml
@@ -0,0 +1,17 @@
+---
+- name: Populate ospfv3 config for testing
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ import_list: import1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml
new file mode 100644
index 0000000..fe60eaf
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/_remove_config.yaml
@@ -0,0 +1,5 @@
+---
+- name: Remove pre-existing ospfv3 config
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+ ignore_errors: true
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml
new file mode 100644
index 0000000..9b09ccd
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/deleted.yaml
@@ -0,0 +1,29 @@
+---
+- debug:
+ msg: START vyos_ospfv3 deleted integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Delete OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Delete OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: deleted
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml
new file mode 100644
index 0000000..bf2cc0c
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/gathered.yaml
@@ -0,0 +1,21 @@
+---
+- debug:
+ msg: START vyos_ospfv3 gathered integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Gather OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - assert:
+ that:
+ - result.gathered.parameters.router_id == "192.0.2.10"
+ - result.gathered.redistribute | selectattr('route_type', 'eq', 'bgp') | list | length == 1
+ - result.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml
new file mode 100644
index 0000000..10702e4
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/merged.yaml
@@ -0,0 +1,48 @@
+---
+- debug:
+ msg: START vyos_ospfv3 merged integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: Merge OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Merge OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: merged
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..609e7ff
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/overridden.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_ospfv3 overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3: &id001
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "3"
+ range:
+ - address: "2001:db40::/32"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml
new file mode 100644
index 0000000..3f1be83
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/replaced.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_ospfv3 replaced integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Replace OSPFv3 configuration
+ register: result
+ vyos.rest.vyos_ospfv3: &id001
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "3"
+ range:
+ - address: "2001:db40::/32"
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Replace OSPFv3 configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_ospfv3: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..7636c23
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/tests/httpapi/rtt.yaml
@@ -0,0 +1,60 @@
+---
+- debug:
+ msg: START vyos_ospfv3 round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.10
+ redistribute:
+ - route_type: bgp
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ - address: "2001:db20::/32"
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.parameters.router_id == "192.0.2.10"
+ - "'bgp' in gathered.gathered.redistribute | map(attribute='route_type') | list"
+ - gathered.gathered.areas | selectattr('area_id', 'eq', '2') | list | length == 1
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_ospfv3:
+ config:
+ parameters:
+ router_id: 192.0.2.11
+ areas:
+ - area_id: "2"
+ export_list: export1
+ range:
+ - address: "2001:db10::/32"
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_ospfv3:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.parameters.router_id == "192.0.2.11"
+ - gathered2.gathered.redistribute is not defined or gathered2.gathered.redistribute | length == 0
+ - gathered2.gathered.areas | selectattr('area_id', 'eq', '2') | map(attribute='range') | first | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_ospfv3/vars/main.yaml b/tests/integration/targets/vyos_ospfv3/vars/main.yaml
new file mode 100644
index 0000000..4303881
--- /dev/null
+++ b/tests/integration/targets/vyos_ospfv3/vars/main.yaml
@@ -0,0 +1,2 @@
+---
+# only common vars here
diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml
deleted file mode 100644
index 9d73f0b..0000000
--- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
----
-- name: Populate prefix_lists config for testing
- vyos.rest.vyos_prefix_lists:
- config:
- - afi: ipv4
- prefix_lists:
- - name: AnsibleIPv4PrefixList
- description: PL configured by ansible
- entries:
- - sequence: 2
- action: permit
- prefix: 92.168.10.0/26
- le: 32
- - sequence: 3
- action: deny
- prefix: 72.168.2.0/24
- ge: 26
- - afi: ipv6
- prefix_lists:
- - name: AllowIPv6Prefix
- description: Configured by ansible for allowing IPv6 networks
- entries:
- - sequence: 5
- action: permit
- prefix: 2001:db8:8000::/35
- le: 37
- state: merged
- ignore_errors: true
diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml
deleted file mode 100644
index 043fb3e..0000000
--- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-- name: Remove pre-existing prefix_lists config
- vyos.rest.vyos_prefix_lists:
- state: deleted
- ignore_errors: true
diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..cbe3908
--- /dev/null
+++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/overridden.yaml
@@ -0,0 +1,40 @@
+---
+- debug:
+ msg: START vyos_route_maps overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override route_maps configuration
+ register: result
+ vyos.rest.vyos_route_maps: &id001
+ config:
+ - route_map: RM-TEST-EXPORT-POLICY
+ entries:
+ - sequence: 10
+ action: permit
+ match:
+ peer: 192.0.2.32
+ set:
+ metric: "10"
+ as_path_exclude: "111"
+ aggregator:
+ as: 100
+ state: overridden
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override route_maps configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_route_maps: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..641a46f
--- /dev/null
+++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/rtt.yaml
@@ -0,0 +1,53 @@
+---
+- debug:
+ msg: START vyos_route_maps round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_route_maps:
+ config:
+ - route_map: RM-TEST-EXPORT-POLICY
+ entries:
+ - sequence: 10
+ action: permit
+ match:
+ peer: 192.0.2.32
+ set:
+ metric: "5"
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_route_maps:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered | selectattr('route_map', 'eq', 'RM-TEST-EXPORT-POLICY') | list | length == 1
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_route_maps:
+ config:
+ - route_map: RM-TEST-EXPORT-POLICY
+ entries:
+ - sequence: 10
+ action: permit
+ set:
+ metric: "20"
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_route_maps:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered | selectattr('route_map', 'eq', 'RM-TEST-EXPORT-POLICY') | list | length == 1
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/overridden.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/overridden.yaml
new file mode 100644
index 0000000..61a3311
--- /dev/null
+++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/overridden.yaml
@@ -0,0 +1,35 @@
+---
+- debug:
+ msg: START vyos_snmp_server overridden integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+- include_tasks: _populate_config.yaml
+
+- block:
+ - name: Override snmp_server configuration
+ register: result
+ vyos.rest.vyos_snmp_server: &id001
+ config:
+ communities:
+ - name: switches
+ authorization_type: rw
+ contact: admin@example.com
+ listen_addresses:
+ - address: 20.1.1.1
+ state: replaced
+
+ - assert:
+ that:
+ - result.changed == true
+
+ - name: Override snmp_server configuration (IDEMPOTENT)
+ register: result
+ vyos.rest.vyos_snmp_server: *id001
+
+ - assert:
+ that:
+ - result.changed == false
+ - result.commands == []
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/rtt.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/rtt.yaml
new file mode 100644
index 0000000..1d354b7
--- /dev/null
+++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/rtt.yaml
@@ -0,0 +1,53 @@
+---
+- debug:
+ msg: START vyos_snmp_server round trip integration tests on connection={{ ansible_connection }}
+
+- include_tasks: _remove_config.yaml
+
+- block:
+ - name: RTT - Apply base configuration
+ vyos.rest.vyos_snmp_server:
+ config:
+ communities:
+ - name: switches
+ authorization_type: rw
+ contact: admin@example.com
+ listen_addresses:
+ - address: 20.1.1.1
+ state: merged
+
+ - name: RTT - Gather configuration
+ register: gathered
+ vyos.rest.vyos_snmp_server:
+ state: gathered
+
+ - name: RTT - Assert gathered matches applied
+ assert:
+ that:
+ - gathered.gathered.contact == "admin@example.com"
+ - gathered.gathered.communities | selectattr('name', 'eq', 'switches') | list | length == 1
+
+ - name: RTT - Modify configuration
+ vyos.rest.vyos_snmp_server:
+ config:
+ communities:
+ - name: switches
+ authorization_type: ro
+ contact: ops@example.com
+ listen_addresses:
+ - address: 20.1.1.1
+ state: replaced
+
+ - name: RTT - Gather modified configuration
+ register: gathered2
+ vyos.rest.vyos_snmp_server:
+ state: gathered
+
+ - name: RTT - Assert modification applied correctly
+ assert:
+ that:
+ - gathered2.gathered.contact == "ops@example.com"
+ - gathered2.gathered.communities | selectattr('name', 'eq', 'switches') | map(attribute='authorization_type') | first == 'ro'
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/tests/unit/fixtures/interfaces_running.json b/tests/unit/fixtures/interfaces_running.json
new file mode 100644
index 0000000..ee67fa2
--- /dev/null
+++ b/tests/unit/fixtures/interfaces_running.json
@@ -0,0 +1,15 @@
+{
+ "ethernet": {
+ "eth0": {
+ "address": "dhcp",
+ "description": "Management",
+ "duplex": "auto",
+ "hw-id": "52:54:00:65:5a:24",
+ "mtu": "1500",
+ "speed": "auto"
+ }
+ },
+ "loopback": {
+ "lo": {}
+ }
+}
diff --git a/tests/unit/fixtures/l3_interfaces_running.json b/tests/unit/fixtures/l3_interfaces_running.json
new file mode 100644
index 0000000..676055b
--- /dev/null
+++ b/tests/unit/fixtures/l3_interfaces_running.json
@@ -0,0 +1,21 @@
+{
+ "ethernet": {
+ "eth0": {
+ "address": "dhcp",
+ "hw-id": "52:54:00:65:5a:24",
+ "vif": {
+ "100": {
+ "address": "192.0.2.100/24"
+ }
+ }
+ }
+ },
+ "loopback": {
+ "lo": {
+ "address": ["10.0.0.1/32", "10.0.0.2/32"]
+ }
+ },
+ "dummy": {
+ "dummy0": {}
+ }
+}
diff --git a/tests/unit/fixtures/lag_interfaces_running.json b/tests/unit/fixtures/lag_interfaces_running.json
new file mode 100644
index 0000000..3477b1d
--- /dev/null
+++ b/tests/unit/fixtures/lag_interfaces_running.json
@@ -0,0 +1,12 @@
+{
+ "bond0": {
+ "arp-monitor": {
+ "interval": "100",
+ "target": {
+ "192.0.2.1": {}
+ }
+ },
+ "hash-policy": "layer2",
+ "mode": "active-backup"
+ }
+}
diff --git a/tests/unit/fixtures/lldp_interfaces_running.json b/tests/unit/fixtures/lldp_interfaces_running.json
new file mode 100644
index 0000000..905a88d
--- /dev/null
+++ b/tests/unit/fixtures/lldp_interfaces_running.json
@@ -0,0 +1,20 @@
+{
+ "interface": {
+ "eth0": {
+ "location": {
+ "elin": "1234567890"
+ },
+ "mode": "disable"
+ },
+ "eth1": {
+ "location": {
+ "coordinate-based": {
+ "altitude": "2200",
+ "datum": "WGS84",
+ "latitude": "33.524449N",
+ "longitude": "22.267255E"
+ }
+ }
+ }
+ }
+}
diff --git a/tests/unit/fixtures/ospf_interfaces_ipv4.json b/tests/unit/fixtures/ospf_interfaces_ipv4.json
new file mode 100644
index 0000000..1f199a6
--- /dev/null
+++ b/tests/unit/fixtures/ospf_interfaces_ipv4.json
@@ -0,0 +1,4 @@
+{
+ "eth1": { "cost": "100", "transmit-delay": "50", "priority": "26" },
+ "eth2": { "cost": "200" }
+}
diff --git a/tests/unit/fixtures/ospf_interfaces_ipv6.json b/tests/unit/fixtures/ospf_interfaces_ipv6.json
new file mode 100644
index 0000000..580cecf
--- /dev/null
+++ b/tests/unit/fixtures/ospf_interfaces_ipv6.json
@@ -0,0 +1,4 @@
+{
+ "eth1": { "dead-interval": "39", "passive": {} },
+ "eth2": {}
+}
diff --git a/tests/unit/fixtures/ospfv2_running.json b/tests/unit/fixtures/ospfv2_running.json
new file mode 100644
index 0000000..9cc64a0
--- /dev/null
+++ b/tests/unit/fixtures/ospfv2_running.json
@@ -0,0 +1,42 @@
+{
+ "area": {
+ "2": {
+ "area-type": { "normal": {} },
+ "network": "192.0.2.0/24"
+ },
+ "3": {
+ "area-type": { "nssa": {} }
+ },
+ "4": {
+ "area-type": { "stub": { "default-cost": "20" } },
+ "range": {
+ "192.0.3.0/24": { "cost": "10" },
+ "192.0.4.0/24": {}
+ }
+ }
+ },
+ "auto-cost": { "reference-bandwidth": "2" },
+ "interface": { "eth1": { "passive": {} }, "eth2": { "passive": {} } },
+ "log-adjacency-changes": { "detail": {} },
+ "neighbor": {
+ "192.0.11.12": { "priority": "2", "poll-interval": "10" }
+ },
+ "parameters": {
+ "router-id": "192.0.1.1",
+ "abr-type": "cisco",
+ "opaque-lsa": {},
+ "rfc1583-compatibility": {}
+ },
+ "redistribute": {
+ "bgp": { "metric": "10", "metric-type": "2" },
+ "connected": {}
+ },
+ "default-information": {
+ "originate": {
+ "always": {},
+ "metric": "10",
+ "metric-type": "2",
+ "route-map": "ingress"
+ }
+ }
+}
diff --git a/tests/unit/fixtures/ospfv3_running.json b/tests/unit/fixtures/ospfv3_running.json
new file mode 100644
index 0000000..44a1bcd
--- /dev/null
+++ b/tests/unit/fixtures/ospfv3_running.json
@@ -0,0 +1,19 @@
+{
+ "parameters": { "router-id": "192.0.2.10" },
+ "redistribute": { "bgp": {}, "connected": { "route-map": "RM1" } },
+ "area": {
+ "2": {
+ "export-list": "export1",
+ "import-list": "import1",
+ "range": {
+ "2001:db10::/32": {},
+ "2001:db20::/32": { "not-advertise": {} }
+ }
+ },
+ "3": {
+ "range": {
+ "2001:db40::/32": {}
+ }
+ }
+ }
+}
diff --git a/tests/unit/modules/test_vyos_interfaces.py b/tests/unit/modules/test_vyos_interfaces.py
new file mode 100644
index 0000000..d522c81
--- /dev/null
+++ b/tests/unit/modules/test_vyos_interfaces.py
@@ -0,0 +1,315 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_interfaces import (
+ _delete_iface_config,
+ _iface_base,
+ _iface_cmds,
+ _iface_type,
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ path = os.path.join(fixtures_dir, filename)
+ with open(path) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSInterfacesIfaceType(unittest.TestCase):
+
+ def test_eth_is_ethernet(self):
+ self.assertEqual(_iface_type("eth0"), "ethernet")
+
+ def test_bond_is_bonding(self):
+ self.assertEqual(_iface_type("bond0"), "bonding")
+
+ def test_lo_is_loopback(self):
+ self.assertEqual(_iface_type("lo"), "loopback")
+
+ def test_wg_is_wireguard(self):
+ self.assertEqual(_iface_type("wg0"), "wireguard")
+
+ def test_br_is_bridge(self):
+ self.assertEqual(_iface_type("br0"), "bridge")
+
+ def test_unknown_defaults_to_ethernet(self):
+ self.assertEqual(_iface_type("xyz0"), "ethernet")
+
+ def test_iface_base_ethernet(self):
+ self.assertEqual(
+ _iface_base("eth0"),
+ ["interfaces", "ethernet", "eth0"],
+ )
+
+ def test_iface_base_loopback(self):
+ self.assertEqual(
+ _iface_base("lo"),
+ ["interfaces", "loopback", "lo"],
+ )
+
+
+class TestVyOSInterfacesGetRunningFixture(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("interfaces_running.json")
+
+ def test_fixture_parses_eth0(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertEqual(eth0["description"], "Management")
+ self.assertEqual(eth0["mtu"], 1500)
+ self.assertEqual(eth0["duplex"], "auto")
+ self.assertEqual(eth0["speed"], "auto")
+ self.assertTrue(eth0["enabled"])
+
+ def test_fixture_parses_loopback(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ lo = next(e for e in result if e["name"] == "lo")
+ self.assertTrue(lo["enabled"])
+ self.assertNotIn("description", lo)
+
+ def test_fixture_disabled_interface(self):
+ fixture = dict(self.fixture)
+ fixture["ethernet"]["eth1"] = {"disable": {}, "description": "Unused"}
+ self.set_running_config(fixture)
+ result = get_running_config(self.mock_vyos)
+ eth1 = next(e for e in result if e["name"] == "eth1")
+ self.assertFalse(eth1["enabled"])
+
+
+class TestVyOSInterfacesGetRunning(VyOSModuleTestCase):
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+ def test_mtu_cast_to_int(self):
+ self.set_running_config(
+ {
+ "ethernet": {
+ "eth0": {"mtu": "1500"},
+ },
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertIsInstance(eth0["mtu"], int)
+ self.assertEqual(eth0["mtu"], 1500)
+
+ def test_hw_id_not_included(self):
+ self.set_running_config(
+ {
+ "ethernet": {
+ "eth0": {"hw-id": "52:54:00:65:5a:24"},
+ },
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertNotIn("hw_id", eth0)
+ self.assertNotIn("hw-id", eth0)
+
+ def test_enabled_true_when_no_disable(self):
+ self.set_running_config(
+ {
+ "ethernet": {"eth0": {}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertTrue(eth0["enabled"])
+
+ def test_enabled_false_when_disable_present(self):
+ self.set_running_config(
+ {
+ "ethernet": {"eth0": {"disable": {}}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertFalse(eth0["enabled"])
+
+
+class TestVyOSInterfacesIfaceCmds(unittest.TestCase):
+
+ def test_set_description(self):
+ want = {"description": "WAN", "enabled": True}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "description", "WAN"]),
+ cmds,
+ )
+
+ def test_set_mtu(self):
+ want = {"mtu": 9000, "enabled": True}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "mtu", "9000"]),
+ cmds,
+ )
+
+ def test_set_duplex(self):
+ want = {"duplex": "full", "enabled": True}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "duplex", "full"]),
+ cmds,
+ )
+
+ def test_set_speed(self):
+ want = {"speed": "1000", "enabled": True}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "speed", "1000"]),
+ cmds,
+ )
+
+ def test_disable_interface(self):
+ want = {"enabled": False}
+ have = {"enabled": True}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "disable"]),
+ cmds,
+ )
+
+ def test_enable_interface(self):
+ want = {"enabled": True}
+ have = {"enabled": False}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertIn(
+ ("delete", ["interfaces", "ethernet", "eth0", "disable"]),
+ cmds,
+ )
+
+ def test_idempotent_description(self):
+ want = {"description": "WAN", "enabled": True}
+ have = {"description": "WAN", "enabled": True}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertEqual(cmds, [])
+
+ def test_delete_description_when_none(self):
+ want = {"enabled": True}
+ have = {"description": "Old", "enabled": True}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertIn(
+ ("delete", ["interfaces", "ethernet", "eth0", "description"]),
+ cmds,
+ )
+
+
+class TestVyOSInterfacesDeleteIfaceConfig(unittest.TestCase):
+
+ def test_deletes_all_l2_fields(self):
+ have = {
+ "description": "WAN",
+ "mtu": 1500,
+ "duplex": "auto",
+ "speed": "auto",
+ "enabled": True,
+ }
+ cmds = _delete_iface_config("eth0", have)
+ paths = [c[1] for c in cmds]
+ self.assertIn(["interfaces", "ethernet", "eth0", "description"], paths)
+ self.assertIn(["interfaces", "ethernet", "eth0", "mtu"], paths)
+ self.assertIn(["interfaces", "ethernet", "eth0", "duplex"], paths)
+ self.assertIn(["interfaces", "ethernet", "eth0", "speed"], paths)
+
+ def test_deletes_disable_when_disabled(self):
+ have = {"enabled": False}
+ cmds = _delete_iface_config("eth0", have)
+ self.assertIn(
+ ("delete", ["interfaces", "ethernet", "eth0", "disable"]),
+ cmds,
+ )
+
+ def test_empty_have_produces_no_commands(self):
+ cmds = _delete_iface_config("eth0", {})
+ self.assertEqual(cmds, [])
+
+
+class TestVyOSInterfacesBuildCommands(unittest.TestCase):
+
+ def _have_eth0(self):
+ return [
+ {
+ "name": "eth0",
+ "description": "Management",
+ "mtu": 1500,
+ "enabled": True,
+ },
+ ]
+
+ def test_merged_adds_description(self):
+ config = [{"name": "eth0", "description": "WAN", "enabled": True}]
+ cmds = build_commands(config, [], "merged")
+ paths = [c[1] for c in cmds]
+ self.assertIn(
+ ["interfaces", "ethernet", "eth0", "description", "WAN"],
+ paths,
+ )
+
+ def test_merged_idempotent(self):
+ cmds = build_commands(self._have_eth0(), self._have_eth0(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_removes_l2_fields(self):
+ config = [{"name": "eth0", "enabled": True}]
+ cmds = build_commands(config, self._have_eth0(), "deleted")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["interfaces", "ethernet", "eth0", "description"], paths)
+ self.assertIn(["interfaces", "ethernet", "eth0", "mtu"], paths)
+
+ def test_deleted_idempotent_when_no_l2(self):
+ have = [{"name": "eth0", "enabled": True}]
+ config = [{"name": "eth0", "enabled": True}]
+ cmds = build_commands(config, have, "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_idempotent(self):
+ cmds = build_commands(self._have_eth0(), self._have_eth0(), "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_updates_description(self):
+ config = [{"name": "eth0", "description": "NEW", "mtu": 1500, "enabled": True}]
+ cmds = build_commands(config, self._have_eth0(), "replaced")
+ self.assertTrue(len(cmds) > 0)
+
+ def test_overridden_clears_interfaces_not_in_want(self):
+ have = [
+ {"name": "eth0", "description": "Management", "enabled": True},
+ {"name": "eth1", "description": "LAN", "enabled": True},
+ ]
+ config = [{"name": "eth0", "description": "Management", "enabled": True}]
+ cmds = build_commands(config, have, "overridden")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["interfaces", "ethernet", "eth1", "description"], paths)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_l3_interfaces.py b/tests/unit/modules/test_vyos_l3_interfaces.py
new file mode 100644
index 0000000..66cd1fc
--- /dev/null
+++ b/tests/unit/modules/test_vyos_l3_interfaces.py
@@ -0,0 +1,306 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_l3_interfaces import (
+ _addr_cmds,
+ _addr_list,
+ _normalize,
+ _parse_iface,
+ _split_addresses,
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ path = os.path.join(fixtures_dir, filename)
+ with open(path) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSL3InterfacesAddrList(unittest.TestCase):
+
+ def test_string_returns_list(self):
+ self.assertEqual(_addr_list("dhcp"), ["dhcp"])
+
+ def test_list_returned_sorted(self):
+ result = _addr_list(["10.0.0.2/32", "10.0.0.1/32"])
+ self.assertEqual(result, ["10.0.0.1/32", "10.0.0.2/32"])
+
+ def test_none_returns_empty(self):
+ self.assertEqual(_addr_list(None), [])
+
+ def test_empty_list_returns_empty(self):
+ self.assertEqual(_addr_list([]), [])
+
+
+class TestVyOSL3InterfacesSplitAddresses(unittest.TestCase):
+
+ def test_dhcp_goes_to_ipv4(self):
+ ipv4, ipv6 = _split_addresses(["dhcp"])
+ self.assertIn("dhcp", ipv4)
+ self.assertEqual(ipv6, [])
+
+ def test_dhcpv6_goes_to_ipv6(self):
+ ipv4, ipv6 = _split_addresses(["dhcpv6"])
+ self.assertEqual(ipv4, [])
+ self.assertIn("dhcpv6", ipv6)
+
+ def test_ipv4_cidr(self):
+ ipv4, ipv6 = _split_addresses(["192.0.2.1/24"])
+ self.assertIn("192.0.2.1/24", ipv4)
+ self.assertEqual(ipv6, [])
+
+ def test_ipv6_cidr(self):
+ ipv4, ipv6 = _split_addresses(["2001:db8::1/128"])
+ self.assertEqual(ipv4, [])
+ self.assertIn("2001:db8::1/128", ipv6)
+
+ def test_mixed(self):
+ ipv4, ipv6 = _split_addresses(["dhcp", "192.0.2.1/24", "2001:db8::1/128"])
+ self.assertIn("dhcp", ipv4)
+ self.assertIn("192.0.2.1/24", ipv4)
+ self.assertIn("2001:db8::1/128", ipv6)
+
+
+class TestVyOSL3InterfacesParseIface(unittest.TestCase):
+
+ def test_parse_dhcp(self):
+ result = _parse_iface("eth0", {"address": "dhcp"})
+ self.assertEqual(result["name"], "eth0")
+ self.assertEqual(result["ipv4"], [{"address": "dhcp"}])
+
+ def test_parse_multiple_addresses(self):
+ result = _parse_iface("lo", {"address": ["10.0.0.1/32", "10.0.0.2/32"]})
+ addrs = [a["address"] for a in result["ipv4"]]
+ self.assertIn("10.0.0.1/32", addrs)
+ self.assertIn("10.0.0.2/32", addrs)
+
+ def test_parse_vif(self):
+ result = _parse_iface(
+ "eth0",
+ {
+ "address": "dhcp",
+ "vif": {"100": {"address": "192.0.2.100/24"}},
+ },
+ )
+ self.assertIn("vifs", result)
+ self.assertEqual(result["vifs"][0]["vlan_id"], 100)
+ self.assertEqual(result["vifs"][0]["ipv4"][0]["address"], "192.0.2.100/24")
+
+ def test_parse_no_address(self):
+ result = _parse_iface("lo", {})
+ self.assertNotIn("ipv4", result)
+ self.assertNotIn("ipv6", result)
+
+ def test_hw_id_ignored(self):
+ result = _parse_iface("eth0", {"hw-id": "52:54:00:65:5a:24"})
+ self.assertNotIn("hw_id", result)
+ self.assertNotIn("hw-id", result)
+
+
+class TestVyOSL3InterfacesGetRunningFixture(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("l3_interfaces_running.json")
+
+ def test_fixture_parses_eth0_dhcp(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth0 = next((e for e in result if e["name"] == "eth0"), None)
+ self.assertIsNotNone(eth0)
+ addrs = [a["address"] for a in eth0.get("ipv4", [])]
+ self.assertIn("dhcp", addrs)
+
+ def test_fixture_parses_loopback_addresses(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ lo = next((e for e in result if e["name"] == "lo"), None)
+ self.assertIsNotNone(lo)
+ addrs = [a["address"] for a in lo.get("ipv4", [])]
+ self.assertIn("10.0.0.1/32", addrs)
+ self.assertIn("10.0.0.2/32", addrs)
+
+ def test_fixture_parses_vif(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertIn("vifs", eth0)
+ self.assertEqual(eth0["vifs"][0]["vlan_id"], 100)
+
+ def test_empty_interface_not_included(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ names = [e["name"] for e in result]
+ # loopback with no addresses should not appear
+ self.assertNotIn("dummy0", names)
+
+
+class TestVyOSL3InterfacesGetRunning(VyOSModuleTestCase):
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+ def test_interface_without_address_excluded(self):
+ self.set_running_config(
+ {
+ "ethernet": {"eth0": {"hw-id": "52:54:00:65:5a:24"}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+
+class TestVyOSL3InterfacesNormalize(unittest.TestCase):
+
+ def test_normalize_ipv4(self):
+ config = [
+ {
+ "name": "lo",
+ "ipv4": [{"address": "10.0.0.1/32"}],
+ },
+ ]
+ result = _normalize(config)
+ self.assertIn("lo", result)
+ self.assertIn("10.0.0.1/32", result["lo"]["ipv4"])
+
+ def test_normalize_vif(self):
+ config = [
+ {
+ "name": "eth0",
+ "vifs": [{"vlan_id": 100, "ipv4": [{"address": "192.0.2.100/24"}]}],
+ },
+ ]
+ result = _normalize(config)
+ self.assertIn(100, result["eth0"]["vifs"])
+ self.assertIn("192.0.2.100/24", result["eth0"]["vifs"][100]["ipv4"])
+
+ def test_normalize_empty(self):
+ result = _normalize([])
+ self.assertEqual(result, {})
+
+
+class TestVyOSL3InterfacesAddrCmds(unittest.TestCase):
+
+ def test_adds_new_address(self):
+ base = ["interfaces", "loopback", "lo"]
+ cmds = _addr_cmds(base, ["10.0.0.1/32"], [], "merged")
+ self.assertIn(("set", base + ["address", "10.0.0.1/32"]), cmds)
+
+ def test_idempotent(self):
+ base = ["interfaces", "loopback", "lo"]
+ cmds = _addr_cmds(base, ["10.0.0.1/32"], ["10.0.0.1/32"], "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_does_not_delete_extra(self):
+ base = ["interfaces", "loopback", "lo"]
+ cmds = _addr_cmds(base, ["10.0.0.1/32"], ["10.0.0.1/32", "10.0.0.2/32"], "merged")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_deletes_extra(self):
+ base = ["interfaces", "loopback", "lo"]
+ cmds = _addr_cmds(base, ["10.0.0.1/32"], ["10.0.0.1/32", "10.0.0.2/32"], "replaced")
+ self.assertIn(("delete", base + ["address", "10.0.0.2/32"]), cmds)
+
+
+class TestVyOSL3InterfacesBuildCommands(unittest.TestCase):
+
+ def _have_lo(self):
+ return [
+ {
+ "name": "lo",
+ "ipv4": [
+ {"address": "10.0.0.1/32"},
+ {"address": "10.0.0.2/32"},
+ ],
+ },
+ ]
+
+ def test_merged_adds_address(self):
+ config = [{"name": "lo", "ipv4": [{"address": "10.0.0.3/32"}]}]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(
+ ("set", ["interfaces", "loopback", "lo", "address", "10.0.0.3/32"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ cmds = build_commands(self._have_lo(), self._have_lo(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_no_config_removes_all(self):
+ cmds = build_commands([], self._have_lo(), "deleted")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["interfaces", "loopback", "lo", "address", "10.0.0.1/32"], paths)
+ self.assertIn(["interfaces", "loopback", "lo", "address", "10.0.0.2/32"], paths)
+
+ def test_deleted_with_config_removes_interface_addresses(self):
+ config = [{"name": "lo"}]
+ cmds = build_commands(config, self._have_lo(), "deleted")
+ paths = [c[1] for c in cmds]
+ self.assertIn(["interfaces", "loopback", "lo", "address", "10.0.0.1/32"], paths)
+
+ def test_deleted_idempotent_when_empty(self):
+ cmds = build_commands([], [], "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_removes_extra_address(self):
+ config = [{"name": "lo", "ipv4": [{"address": "10.0.0.1/32"}]}]
+ cmds = build_commands(config, self._have_lo(), "replaced")
+ self.assertIn(
+ ("delete", ["interfaces", "loopback", "lo", "address", "10.0.0.2/32"]),
+ cmds,
+ )
+
+ def test_overridden_removes_unlisted_interface(self):
+ config = [{"name": "lo", "ipv4": [{"address": "10.0.0.1/32"}]}]
+ have = self._have_lo() + [
+ {
+ "name": "eth0",
+ "ipv4": [{"address": "192.0.2.1/24"}],
+ },
+ ]
+ cmds = build_commands(config, have, "overridden")
+ self.assertIn(
+ ("delete", ["interfaces", "ethernet", "eth0", "address", "192.0.2.1/24"]),
+ cmds,
+ )
+
+ def test_vif_added(self):
+ config = [
+ {
+ "name": "eth0",
+ "vifs": [{"vlan_id": 100, "ipv4": [{"address": "192.0.2.100/24"}]}],
+ },
+ ]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(
+ ("set", ["interfaces", "ethernet", "eth0", "vif", "100", "address", "192.0.2.100/24"]),
+ cmds,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_lag_interfaces.py b/tests/unit/modules/test_vyos_lag_interfaces.py
new file mode 100644
index 0000000..7c6df87
--- /dev/null
+++ b/tests/unit/modules/test_vyos_lag_interfaces.py
@@ -0,0 +1,293 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_lag_interfaces import (
+ _bond_base,
+ _bond_cmds,
+ _normalize,
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ path = os.path.join(fixtures_dir, filename)
+ with open(path) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSLagInterfacesBondBase(unittest.TestCase):
+
+ def test_bond_base(self):
+ self.assertEqual(
+ _bond_base("bond0"),
+ ["interfaces", "bonding", "bond0"],
+ )
+
+
+class TestVyOSLagInterfacesGetRunningFixture(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("lag_interfaces_running.json")
+
+ def test_fixture_parses_bond0_mode(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ bond0 = next((e for e in result if e["name"] == "bond0"), None)
+ self.assertIsNotNone(bond0)
+ self.assertEqual(bond0["mode"], "active-backup")
+
+ def test_fixture_parses_hash_policy(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ bond0 = next(e for e in result if e["name"] == "bond0")
+ self.assertEqual(bond0["hash_policy"], "layer2")
+
+ def test_fixture_parses_arp_monitor(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ bond0 = next(e for e in result if e["name"] == "bond0")
+ self.assertIn("arp_monitor", bond0)
+ self.assertEqual(bond0["arp_monitor"]["interval"], 100)
+ self.assertIn("192.0.2.1", bond0["arp_monitor"]["target"])
+
+ def test_fixture_unwraps_bonding_key(self):
+ # wrap in extra "bonding" key as VyOS sometimes returns
+ wrapped = {"bonding": self.fixture}
+ self.set_running_config(wrapped)
+ result = get_running_config(self.mock_vyos)
+ self.assertTrue(len(result) > 0)
+ self.assertEqual(result[0]["name"], "bond0")
+
+
+class TestVyOSLagInterfacesGetRunning(VyOSModuleTestCase):
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+ def test_parses_mode(self):
+ self.set_running_config(
+ {
+ "bond0": {"mode": "802.3ad"},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result[0]["mode"], "802.3ad")
+
+ def test_parses_hash_policy(self):
+ self.set_running_config(
+ {
+ "bond0": {"hash-policy": "layer2+3"},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result[0]["hash_policy"], "layer2+3")
+
+ def test_parses_members(self):
+ self.set_running_config(
+ {
+ "bond0": {"member": {"interface": {"eth1": {}, "eth2": {}}}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ members = [m["member"] for m in result[0]["members"]]
+ self.assertIn("eth1", members)
+ self.assertIn("eth2", members)
+
+ def test_parses_arp_monitor_interval(self):
+ self.set_running_config(
+ {
+ "bond0": {"arp-monitor": {"interval": "100"}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result[0]["arp_monitor"]["interval"], 100)
+
+ def test_parses_arp_monitor_target_dict(self):
+ self.set_running_config(
+ {
+ "bond0": {"arp-monitor": {"target": {"192.0.2.1": {}}}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertIn("192.0.2.1", result[0]["arp_monitor"]["target"])
+
+ def test_skips_type_keys(self):
+ self.set_running_config(
+ {
+ "bonding": {"bond0": {"mode": "802.3ad"}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ # "bonding" key should be skipped
+ names = [e["name"] for e in result]
+ self.assertNotIn("bonding", names)
+
+
+class TestVyOSLagInterfacesNormalize(unittest.TestCase):
+
+ def test_normalize_basic(self):
+ config = [{"name": "bond0", "mode": "802.3ad", "hash_policy": "layer2"}]
+ result = _normalize(config)
+ self.assertIn("bond0", result)
+ self.assertEqual(result["bond0"]["mode"], "802.3ad")
+ self.assertEqual(result["bond0"]["hash_policy"], "layer2")
+
+ def test_normalize_members_sorted(self):
+ config = [
+ {
+ "name": "bond0",
+ "members": [{"member": "eth2"}, {"member": "eth1"}],
+ },
+ ]
+ result = _normalize(config)
+ self.assertEqual(result["bond0"]["members"], ["eth1", "eth2"])
+
+ def test_normalize_arp_targets_sorted(self):
+ config = [
+ {
+ "name": "bond0",
+ "arp_monitor": {"interval": 100, "target": ["192.0.2.2", "192.0.2.1"]},
+ },
+ ]
+ result = _normalize(config)
+ self.assertEqual(result["bond0"]["arp_targets"], ["192.0.2.1", "192.0.2.2"])
+
+ def test_normalize_empty(self):
+ result = _normalize([])
+ self.assertEqual(result, {})
+
+
+class TestVyOSLagInterfacesBondCmds(unittest.TestCase):
+
+ def test_set_mode(self):
+ want = {"mode": "802.3ad"}
+ cmds = _bond_cmds("bond0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "mode", "802.3ad"]),
+ cmds,
+ )
+
+ def test_set_hash_policy(self):
+ want = {"hash_policy": "layer2"}
+ cmds = _bond_cmds("bond0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "hash-policy", "layer2"]),
+ cmds,
+ )
+
+ def test_set_member(self):
+ want = {"members": ["eth1"]}
+ cmds = _bond_cmds("bond0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "member", "interface", "eth1"]),
+ cmds,
+ )
+
+ def test_set_arp_interval(self):
+ want = {"arp_interval": 100}
+ cmds = _bond_cmds("bond0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "arp-monitor", "interval", "100"]),
+ cmds,
+ )
+
+ def test_set_arp_target(self):
+ want = {"arp_targets": ["192.0.2.1"]}
+ cmds = _bond_cmds("bond0", want, {})
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "arp-monitor", "target", "192.0.2.1"]),
+ cmds,
+ )
+
+ def test_idempotent_mode(self):
+ want = {"mode": "802.3ad"}
+ have = {"mode": "802.3ad"}
+ cmds = _bond_cmds("bond0", want, have)
+ self.assertEqual(cmds, [])
+
+ def test_no_commands_when_empty_want(self):
+ cmds = _bond_cmds("bond0", {}, {})
+ self.assertEqual(cmds, [])
+
+
+class TestVyOSLagInterfacesBuildCommands(unittest.TestCase):
+
+ def _have_bond0(self):
+ return [
+ {
+ "name": "bond0",
+ "mode": "active-backup",
+ "hash_policy": "layer2",
+ "arp_monitor": {"interval": 100, "target": ["192.0.2.1"]},
+ },
+ ]
+
+ def test_merged_adds_bond(self):
+ config = [{"name": "bond0", "mode": "802.3ad"}]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(
+ ("set", ["interfaces", "bonding", "bond0", "mode", "802.3ad"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ cmds = build_commands(self._have_bond0(), self._have_bond0(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_no_config_removes_all(self):
+ cmds = build_commands([], self._have_bond0(), "deleted")
+ self.assertIn(
+ ("delete", ["interfaces", "bonding", "bond0"]),
+ cmds,
+ )
+
+ def test_deleted_with_name_removes_bond(self):
+ config = [{"name": "bond0"}]
+ cmds = build_commands(config, self._have_bond0(), "deleted")
+ self.assertIn(
+ ("delete", ["interfaces", "bonding", "bond0"]),
+ cmds,
+ )
+
+ def test_deleted_idempotent_when_empty(self):
+ cmds = build_commands([], [], "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_idempotent(self):
+ cmds = build_commands(self._have_bond0(), self._have_bond0(), "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_overridden_removes_unlisted_bond(self):
+ config = [{"name": "bond1", "mode": "802.3ad"}]
+ cmds = build_commands(config, self._have_bond0(), "overridden")
+ self.assertIn(
+ ("delete", ["interfaces", "bonding", "bond0"]),
+ cmds,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_lldp_interfaces.py b/tests/unit/modules/test_vyos_lldp_interfaces.py
new file mode 100644
index 0000000..0950991
--- /dev/null
+++ b/tests/unit/modules/test_vyos_lldp_interfaces.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_lldp_interfaces import (
+ _iface_base,
+ _iface_cmds,
+ _normalize,
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ path = os.path.join(fixtures_dir, filename)
+ with open(path) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSLldpInterfacesIfaceBase(unittest.TestCase):
+
+ def test_iface_base(self):
+ self.assertEqual(
+ _iface_base("eth0"),
+ ["service", "lldp", "interface", "eth0"],
+ )
+
+
+class TestVyOSLldpInterfacesGetRunningFixture(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("lldp_interfaces_running.json")
+
+ def test_fixture_parses_eth0_mode(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth0 = next((e for e in result if e["name"] == "eth0"), None)
+ self.assertIsNotNone(eth0)
+ self.assertEqual(eth0["mode"], "disable")
+
+ def test_fixture_parses_elin(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth0 = next(e for e in result if e["name"] == "eth0")
+ self.assertEqual(eth0["location"]["elin"], "1234567890")
+
+ def test_fixture_parses_coordinate_based(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ eth1 = next((e for e in result if e["name"] == "eth1"), None)
+ self.assertIsNotNone(eth1)
+ cb = eth1["location"]["coordinate_based"]
+ self.assertEqual(cb["latitude"], "33.524449N")
+ self.assertEqual(cb["longitude"], "22.267255E")
+ self.assertEqual(cb["altitude"], 2200)
+ self.assertEqual(cb["datum"], "WGS84")
+
+
+class TestVyOSLldpInterfacesGetRunning(VyOSModuleTestCase):
+
+ def test_empty_returns_empty_list(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+ def test_no_interface_returns_empty(self):
+ self.set_running_config({"snmp": "enable"})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+ def test_parses_mode(self):
+ self.set_running_config(
+ {
+ "interface": {"eth0": {"mode": "rx-tx"}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result[0]["mode"], "rx-tx")
+
+ def test_parses_elin(self):
+ self.set_running_config(
+ {
+ "interface": {"eth0": {"location": {"elin": "9876543210"}}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result[0]["location"]["elin"], "9876543210")
+
+ def test_parses_coordinate_based(self):
+ self.set_running_config(
+ {
+ "interface": {
+ "eth0": {
+ "location": {
+ "coordinate-based": {
+ "latitude": "33.524449N",
+ "longitude": "22.267255E",
+ "altitude": "2200",
+ "datum": "WGS84",
+ },
+ },
+ },
+ },
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ cb = result[0]["location"]["coordinate_based"]
+ self.assertEqual(cb["latitude"], "33.524449N")
+ self.assertEqual(cb["altitude"], 2200)
+
+ def test_no_mode_not_in_entry(self):
+ self.set_running_config(
+ {
+ "interface": {"eth0": {"location": {"elin": "1234567890"}}},
+ },
+ )
+ result = get_running_config(self.mock_vyos)
+ self.assertNotIn("mode", result[0])
+
+
+class TestVyOSLldpInterfacesNormalize(unittest.TestCase):
+
+ def test_normalize_mode(self):
+ config = [{"name": "eth0", "mode": "disable"}]
+ result = _normalize(config)
+ self.assertEqual(result["eth0"]["mode"], "disable")
+
+ def test_normalize_elin(self):
+ config = [{"name": "eth0", "location": {"elin": "1234567890"}}]
+ result = _normalize(config)
+ self.assertEqual(result["eth0"]["elin"], "1234567890")
+
+ def test_normalize_coordinate_based(self):
+ config = [
+ {
+ "name": "eth0",
+ "location": {
+ "coordinate_based": {
+ "latitude": "33.524449N",
+ "longitude": "22.267255E",
+ "altitude": 2200,
+ "datum": "WGS84",
+ },
+ },
+ },
+ ]
+ result = _normalize(config)
+ self.assertEqual(result["eth0"]["latitude"], "33.524449N")
+ self.assertEqual(result["eth0"]["altitude"], 2200)
+
+ def test_normalize_empty(self):
+ result = _normalize([])
+ self.assertEqual(result, {})
+
+
+class TestVyOSLldpInterfacesIfaceCmds(unittest.TestCase):
+
+ def test_set_mode(self):
+ want = {"mode": "disable"}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["service", "lldp", "interface", "eth0", "mode", "disable"]),
+ cmds,
+ )
+
+ def test_set_elin(self):
+ want = {"elin": "1234567890"}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ ("set", ["service", "lldp", "interface", "eth0", "location", "elin", "1234567890"]),
+ cmds,
+ )
+
+ def test_set_latitude(self):
+ want = {"latitude": "33.524449N", "longitude": "22.267255E"}
+ cmds = _iface_cmds("eth0", want, {})
+ self.assertIn(
+ (
+ "set",
+ [
+ "service",
+ "lldp",
+ "interface",
+ "eth0",
+ "location",
+ "coordinate-based",
+ "latitude",
+ "33.524449N",
+ ],
+ ),
+ cmds,
+ )
+
+ def test_idempotent_mode(self):
+ want = {"mode": "disable"}
+ have = {"mode": "disable"}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertEqual(cmds, [])
+
+ def test_idempotent_elin(self):
+ want = {"elin": "1234567890"}
+ have = {"elin": "1234567890"}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertEqual(cmds, [])
+
+ def test_delete_mode_when_none(self):
+ want = {}
+ have = {"mode": "disable"}
+ cmds = _iface_cmds("eth0", want, have)
+ self.assertIn(
+ ("delete", ["service", "lldp", "interface", "eth0", "mode"]),
+ cmds,
+ )
+
+
+class TestVyOSLldpInterfacesBuildCommands(unittest.TestCase):
+
+ def _have_eth0(self):
+ return [
+ {
+ "name": "eth0",
+ "mode": "disable",
+ "location": {"elin": "1234567890"},
+ },
+ ]
+
+ def test_merged_adds_interface(self):
+ config = [{"name": "eth0", "mode": "disable"}]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(
+ ("set", ["service", "lldp", "interface", "eth0", "mode", "disable"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ cmds = build_commands(self._have_eth0(), self._have_eth0(), "merged")
+ self.assertEqual(cmds, [])
+
+ def test_deleted_no_config_removes_all(self):
+ cmds = build_commands([], self._have_eth0(), "deleted")
+ self.assertIn(
+ ("delete", ["service", "lldp", "interface", "eth0"]),
+ cmds,
+ )
+
+ def test_deleted_with_config_removes_named(self):
+ config = [{"name": "eth0"}]
+ cmds = build_commands(config, self._have_eth0(), "deleted")
+ self.assertIn(
+ ("delete", ["service", "lldp", "interface", "eth0"]),
+ cmds,
+ )
+
+ def test_deleted_idempotent_when_empty(self):
+ cmds = build_commands([], [], "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_idempotent(self):
+ cmds = build_commands(self._have_eth0(), self._have_eth0(), "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_overridden_removes_unlisted(self):
+ config = [{"name": "eth1", "mode": "rx-tx"}]
+ cmds = build_commands(config, self._have_eth0(), "overridden")
+ self.assertIn(
+ ("delete", ["service", "lldp", "interface", "eth0"]),
+ cmds,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_logging_global.py b/tests/unit/modules/test_vyos_logging_global.py
index 8593f1f..7d91182 100644
--- a/tests/unit/modules/test_vyos_logging_global.py
+++ b/tests/unit/modules/test_vyos_logging_global.py
@@ -98,7 +98,7 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase):
def test_normalize_running_host_port_not_cast(self):
"""Port is NOT cast to int — stored as-is from API response."""
raw = {
- "host": {
+ "remote": {
"172.16.0.1": {
"port": "514",
"facility": {},
@@ -112,7 +112,7 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase):
def test_normalize_running_global_archive_key(self):
"""Archive stored under 'archive' key — no file_num remapping."""
raw = {
- "global": {
+ "local": {
"archive": {"file": "2", "size": "111"},
"marker": {"interval": "111"},
"preserve-fqdn": {},
@@ -133,7 +133,7 @@ class TestVyOSLoggingGlobalNormalize(unittest.TestCase):
def test_normalize_running_host_facilities(self):
raw = {
- "host": {
+ "remote": {
"172.16.0.1": {
"facility": {
"local7": {"level": "all"},
@@ -196,14 +196,14 @@ class TestVyOSLoggingGlobalBuildCommands(unittest.TestCase):
cmds = build_commands(want, self._empty_have(), "merged")
paths = [c[1] for c in cmds]
# diff_map only adds the host key, not per-facility details
- self.assertIn(["system", "syslog", "host", "172.16.0.1"], paths)
+ self.assertIn(["system", "syslog", "remote", "172.16.0.1"], paths)
def test_replaced_removes_extra_host(self):
want = self._empty_have()
have = self._empty_have()
have["hosts"]["172.16.0.1"] = {"port": None, "facilities": {}}
cmds = build_commands(want, have, "replaced")
- self.assertIn(("delete", ["system", "syslog", "host", "172.16.0.1"]), cmds)
+ self.assertIn(("delete", ["system", "syslog", "remote", "172.16.0.1"]), cmds)
def test_deleted_removes_per_field(self):
"""deleted state removes per-facility entries, not single subtree."""
diff --git a/tests/unit/modules/test_vyos_ospf_interfaces.py b/tests/unit/modules/test_vyos_ospf_interfaces.py
new file mode 100644
index 0000000..4d6be0d
--- /dev/null
+++ b/tests/unit/modules/test_vyos_ospf_interfaces.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_ospf_interfaces import (
+ _parse_ipv4_iface,
+ _parse_ipv6_iface,
+ build_commands,
+ get_running_config,
+)
+
+
+_BASE4 = ["protocols", "ospf", "interface"]
+_BASE6 = ["protocols", "ospfv3", "interface"]
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ with open(os.path.join(fixtures_dir, filename)) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.ipv4_fixture = load_fixture("ospf_interfaces_ipv4.json")
+ self.ipv6_fixture = load_fixture("ospf_interfaces_ipv6.json")
+ self.mock_vyos.get_config = MagicMock(
+ side_effect=lambda path: (self.ipv4_fixture if path == _BASE4 else self.ipv6_fixture),
+ )
+
+
+class TestVyOSOspfInterfacesParse(VyOSModuleTestCase):
+
+ def test_parse_ipv4_scalars(self):
+ raw = {"cost": "100", "transmit-delay": "50", "priority": "26"}
+ result = _parse_ipv4_iface(raw)
+ self.assertEqual(result["afi"], "ipv4")
+ self.assertEqual(result["cost"], 100)
+ self.assertEqual(result["transmit_delay"], 50)
+ self.assertEqual(result["priority"], 26)
+
+ def test_parse_ipv4_md5_auth(self):
+ raw = {"authentication": {"md5": {"key-id": {"10": {"md5-key": "secret"}}}}}
+ result = _parse_ipv4_iface(raw)
+ self.assertEqual(result["authentication"]["md5_key"]["key_id"], 10)
+ self.assertEqual(result["authentication"]["md5_key"]["key"], "secret")
+
+ def test_parse_ipv6_scalars(self):
+ raw = {"dead-interval": "39", "passive": {}}
+ result = _parse_ipv6_iface(raw)
+ self.assertEqual(result["afi"], "ipv6")
+ self.assertEqual(result["dead_interval"], 39)
+ self.assertTrue(result["passive"])
+
+ def test_get_running_config(self):
+ result = get_running_config(self.mock_vyos)
+ names = [e["name"] for e in result]
+ self.assertIn("eth1", names)
+ self.assertIn("eth2", names)
+ eth1 = next(e for e in result if e["name"] == "eth1")
+ afis = {af["afi"] for af in eth1["address_family"]}
+ self.assertIn("ipv4", afis)
+ self.assertIn("ipv6", afis)
+
+ def test_get_running_config_empty(self):
+ self.mock_vyos.get_config = MagicMock(return_value={})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, [])
+
+
+class TestVyOSOspfInterfacesBuildCommands(unittest.TestCase):
+
+ def test_deleted_all(self):
+ have = [
+ {
+ "name": "eth1",
+ "address_family": [{"afi": "ipv4", "cost": 100}, {"afi": "ipv6", "passive": True}],
+ },
+ ]
+ cmds = build_commands([], have, "deleted")
+ self.assertIn(("delete", _BASE4 + ["eth1"]), cmds)
+ self.assertIn(("delete", _BASE6 + ["eth1"]), cmds)
+
+ def test_deleted_specific(self):
+ have = [
+ {"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]},
+ {"name": "eth2", "address_family": [{"afi": "ipv4", "cost": 200}]},
+ ]
+ cmds = build_commands([{"name": "eth1"}], have, "deleted")
+ self.assertIn(("delete", _BASE4 + ["eth1"]), cmds)
+ paths = [c[1] for c in cmds]
+ self.assertNotIn(_BASE4 + ["eth2"], paths)
+
+ def test_merged_adds_cost(self):
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]}]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(("set", _BASE4 + ["eth1", "cost", "100"]), cmds)
+
+ def test_merged_idempotent(self):
+ have = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]}]
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]}]
+ cmds = build_commands(config, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_ipv6_passive(self):
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv6", "passive": True}]}]
+ cmds = build_commands(config, [], "merged")
+ self.assertIn(("set", _BASE6 + ["eth1", "passive"]), cmds)
+
+ def test_overridden_removes_extra_interface(self):
+ have = [
+ {"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]},
+ {"name": "eth2", "address_family": [{"afi": "ipv4", "cost": 200}]},
+ ]
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 100}]}]
+ cmds = build_commands(config, have, "overridden")
+ self.assertIn(("delete", _BASE4 + ["eth2"]), cmds)
+
+ def test_replaced_idempotent(self):
+ have = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 200}]}]
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 200}]}]
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_rebuilds_on_change(self):
+ have = [
+ {
+ "name": "eth1",
+ "address_family": [{"afi": "ipv4", "cost": 100, "transmit_delay": 50}],
+ },
+ ]
+ config = [{"name": "eth1", "address_family": [{"afi": "ipv4", "cost": 200}]}]
+ cmds = build_commands(config, have, "replaced")
+ self.assertIn(("delete", _BASE4 + ["eth1"]), cmds)
+ self.assertIn(("set", _BASE4 + ["eth1", "cost", "200"]), cmds)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_ospfv2.py b/tests/unit/modules/test_vyos_ospfv2.py
new file mode 100644
index 0000000..c05dd77
--- /dev/null
+++ b/tests/unit/modules/test_vyos_ospfv2.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_ospfv2 import (
+ _parse_areas,
+ _parse_default_information,
+ _parse_neighbor,
+ _parse_parameters,
+ _parse_redistribute,
+ build_commands,
+ get_running_config,
+)
+
+
+_BASE = ["protocols", "ospf"]
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ with open(os.path.join(fixtures_dir, filename)) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.fixture = load_fixture("ospfv2_running.json")
+ self.mock_vyos.get_config = MagicMock(return_value=self.fixture)
+
+
+class TestVyOSOspfv2Parse(VyOSModuleTestCase):
+
+ def test_parse_parameters(self):
+ result = _parse_parameters(self.fixture["parameters"])
+ self.assertEqual(result["router_id"], "192.0.1.1")
+ self.assertEqual(result["abr_type"], "cisco")
+ self.assertTrue(result["opaque_lsa"])
+ self.assertTrue(result["rfc1583_compatibility"])
+
+ def test_parse_redistribute(self):
+ result = _parse_redistribute(self.fixture["redistribute"])
+ route_types = [r["route_type"] for r in result]
+ self.assertIn("bgp", route_types)
+ self.assertIn("connected", route_types)
+ bgp = next(r for r in result if r["route_type"] == "bgp")
+ self.assertEqual(bgp["metric"], 10)
+ self.assertEqual(bgp["metric_type"], 2)
+
+ def test_parse_neighbor(self):
+ result = _parse_neighbor(self.fixture["neighbor"])
+ self.assertEqual(len(result), 1)
+ nb = result[0]
+ self.assertEqual(nb["neighbor_id"], "192.0.11.12")
+ self.assertEqual(nb["priority"], 2)
+ self.assertEqual(nb["poll_interval"], 10)
+
+ def test_parse_default_information(self):
+ result = _parse_default_information(self.fixture["default-information"])
+ orig = result["originate"]
+ self.assertTrue(orig["always"])
+ self.assertEqual(orig["metric"], 10)
+ self.assertEqual(orig["metric_type"], 2)
+ self.assertEqual(orig["route_map"], "ingress")
+
+ def test_parse_areas(self):
+ result = _parse_areas(self.fixture["area"])
+ self.assertEqual(len(result), 3)
+ area2 = next(a for a in result if a["area_id"] == "2")
+ self.assertTrue(area2["area_type"]["normal"])
+ self.assertEqual(area2["network"][0]["address"], "192.0.2.0/24")
+
+ area3 = next(a for a in result if a["area_id"] == "3")
+ self.assertTrue(area3["area_type"]["nssa"]["set"])
+
+ area4 = next(a for a in result if a["area_id"] == "4")
+ self.assertEqual(area4["area_type"]["stub"]["default_cost"], 20)
+ self.assertEqual(len(area4["range"]), 2)
+ r = next(r for r in area4["range"] if r["address"] == "192.0.3.0/24")
+ self.assertEqual(r["cost"], 10)
+
+ def test_get_running_config(self):
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result["parameters"]["router_id"], "192.0.1.1")
+ self.assertIn("eth1", result["passive_interface"])
+ self.assertIn("eth2", result["passive_interface"])
+ self.assertEqual(result["auto_cost"]["reference_bandwidth"], 2)
+ self.assertEqual(result["log_adjacency_changes"], "detail")
+
+ def test_get_running_config_empty(self):
+ self.mock_vyos.get_config = MagicMock(return_value={})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, {})
+
+
+class TestVyOSOspfv2BuildCommands(unittest.TestCase):
+
+ def test_deleted_with_have(self):
+ have = {"parameters": {"router_id": "192.0.1.1"}}
+ cmds = build_commands({}, have, "deleted")
+ self.assertEqual(cmds, [("delete", _BASE)])
+
+ def test_deleted_without_have(self):
+ cmds = build_commands({}, {}, "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_merged_parameters(self):
+ config = {"parameters": {"router_id": "192.0.1.1"}}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["parameters", "router-id", "192.0.1.1"]), cmds)
+
+ def test_merged_redistribute(self):
+ config = {"redistribute": [{"route_type": "bgp", "metric": 10}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["redistribute", "bgp"]), cmds)
+ self.assertIn(("set", _BASE + ["redistribute", "bgp", "metric", "10"]), cmds)
+
+ def test_merged_passive_interface(self):
+ config = {"passive_interface": ["eth1"]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["interface", "eth1", "passive"]), cmds)
+
+ def test_merged_area_normal(self):
+ config = {"areas": [{"area_id": "2", "area_type": {"normal": True}}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["area", "2", "area-type", "normal"]), cmds)
+
+ def test_merged_area_stub_with_cost(self):
+ config = {"areas": [{"area_id": "4", "area_type": {"stub": {"default_cost": 20}}}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", _BASE + ["area", "4", "area-type", "stub", "default-cost", "20"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ have = {"parameters": {"router_id": "192.0.1.1"}}
+ config = {"parameters": {"router_id": "192.0.1.1"}}
+ cmds = build_commands(config, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_idempotent(self):
+ have = {"parameters": {"router_id": "192.0.1.1"}}
+ config = {"parameters": {"router_id": "192.0.1.1"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_rebuilds_on_change(self):
+ have = {"parameters": {"router_id": "192.0.1.1"}}
+ config = {"parameters": {"router_id": "192.0.1.2"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds[0], ("delete", _BASE))
+ self.assertIn(
+ ("set", _BASE + ["parameters", "router-id", "192.0.1.2"]),
+ cmds,
+ )
+
+ def test_merged_neighbor(self):
+ config = {"neighbor": [{"neighbor_id": "192.0.11.12", "priority": 2}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["neighbor", "192.0.11.12"]), cmds)
+ self.assertIn(("set", _BASE + ["neighbor", "192.0.11.12", "priority", "2"]), cmds)
+
+ def test_merged_default_information(self):
+ config = {"default_information": {"originate": {"always": True, "metric": 10}}}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(("set", _BASE + ["default-information", "originate", "always"]), cmds)
+ self.assertIn(("set", _BASE + ["default-information", "originate", "metric", "10"]), cmds)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/modules/test_vyos_ospfv3.py b/tests/unit/modules/test_vyos_ospfv3.py
new file mode 100644
index 0000000..a84041b
--- /dev/null
+++ b/tests/unit/modules/test_vyos_ospfv3.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import os
+import unittest
+
+from unittest.mock import MagicMock
+
+from ansible_collections.vyos.rest.plugins.modules.vyos_ospfv3 import (
+ build_commands,
+ get_running_config,
+)
+
+
+def load_fixture(filename):
+ fixtures_dir = os.path.join(os.path.dirname(__file__), "..", "fixtures")
+ with open(os.path.join(fixtures_dir, filename)) as f:
+ return json.load(f)
+
+
+class VyOSModuleTestCase(unittest.TestCase):
+ def setUp(self):
+ self.mock_vyos = MagicMock()
+ self.mock_vyos.get_config = MagicMock(return_value={})
+
+ def set_running_config(self, data):
+ self.mock_vyos.get_config.return_value = data
+
+
+class TestVyOSOspfv3Parse(VyOSModuleTestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.fixture = load_fixture("ospfv3_running.json")
+
+ def test_parses_parameters(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result["parameters"]["router_id"], "192.0.2.10")
+
+ def test_parses_redistribute(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ route_types = [r["route_type"] for r in result["redistribute"]]
+ self.assertIn("bgp", route_types)
+ self.assertIn("connected", route_types)
+ connected = next(r for r in result["redistribute"] if r["route_type"] == "connected")
+ self.assertEqual(connected["route_map"], "RM1")
+
+ def test_parses_areas(self):
+ self.set_running_config(self.fixture)
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(len(result["areas"]), 2)
+ area2 = next(a for a in result["areas"] if a["area_id"] == "2")
+ self.assertEqual(area2["export_list"], "export1")
+ self.assertEqual(area2["import_list"], "import1")
+ self.assertEqual(len(area2["range"]), 2)
+ not_adv = next(r for r in area2["range"] if r["address"] == "2001:db20::/32")
+ self.assertTrue(not_adv["not_advertise"])
+
+ def test_empty_config_returns_empty(self):
+ self.set_running_config({})
+ result = get_running_config(self.mock_vyos)
+ self.assertEqual(result, {})
+
+
+class TestVyOSOspfv3BuildCommands(unittest.TestCase):
+
+ def test_deleted_with_have(self):
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands({}, have, "deleted")
+ self.assertEqual(cmds, [("delete", ["protocols", "ospfv3"])])
+
+ def test_deleted_without_have(self):
+ cmds = build_commands({}, {}, "deleted")
+ self.assertEqual(cmds, [])
+
+ def test_merged_parameters(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "parameters", "router-id", "192.0.2.10"]),
+ cmds,
+ )
+
+ def test_merged_redistribute(self):
+ config = {"redistribute": [{"route_type": "bgp"}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "redistribute", "bgp"]),
+ cmds,
+ )
+
+ def test_merged_idempotent(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "merged")
+ self.assertEqual(cmds, [])
+
+ def test_merged_area_range(self):
+ config = {
+ "areas": [{"area_id": "2", "range": [{"address": "2001:db10::/32"}]}],
+ }
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "area", "2", "range", "2001:db10::/32"]),
+ cmds,
+ )
+
+ def test_replaced_idempotent(self):
+ config = {"parameters": {"router_id": "192.0.2.10"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds, [])
+
+ def test_replaced_rebuilds_on_change(self):
+ config = {"parameters": {"router_id": "192.0.2.11"}}
+ have = {"parameters": {"router_id": "192.0.2.10"}}
+ cmds = build_commands(config, have, "replaced")
+ self.assertEqual(cmds[0], ("delete", ["protocols", "ospfv3"]))
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "parameters", "router-id", "192.0.2.11"]),
+ cmds,
+ )
+
+ def test_merged_area_export_list(self):
+ config = {"areas": [{"area_id": "2", "export_list": "export1"}]}
+ cmds = build_commands(config, {}, "merged")
+ self.assertIn(
+ ("set", ["protocols", "ospfv3", "area", "2", "export-list", "export1"]),
+ cmds,
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()