From 92b7323c353f473c9d5bdb7930e9490a5dafcd41 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 19 May 2026 05:35:04 +1000 Subject: lldp_global --- plugins/modules/vyos_info.py | 115 ------------------------------------ plugins/modules/vyos_lldp_global.py | 32 +++++++--- 2 files changed, 25 insertions(+), 122 deletions(-) delete mode 100644 plugins/modules/vyos_info.py (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_info.py b/plugins/modules/vyos_info.py deleted file mode 100644 index 8946868..0000000 --- a/plugins/modules/vyos_info.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/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_info -short_description: Retrieve system information from a VyOS device via the REST API. -description: - - Queries the public C(/info) endpoint (HTTP GET) which requires no - authentication and returns the VyOS version, hostname, and a welcome banner. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: > - API key. Not required for this module (the /info endpoint is public) - but kept in the spec for consistency with other modules in this collection. - type: str - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 10 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Get VyOS system info - vyos.rest.vyos_info: - hostname: 192.168.1.1 - register: sys_info - - - name: Print VyOS version - ansible.builtin.debug: - msg: "Running VyOS {{ sys_info.version }}" -""" - -RETURN = r""" -version: - description: VyOS version string. - returned: success - type: str - sample: "1.4-rolling-202401010000" -hostname: - description: System hostname. - returned: success - type: str - sample: vyos-router -banner: - description: Welcome banner text. - returned: success - type: str - sample: "Welcome to VyOS" -info: - description: Full info dict as returned by the device. - returned: success - type: dict -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict() - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - # api_key not strictly required for /info - argument_spec["api_key"]["required"] = False - argument_spec["api_key"]["default"] = "" - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - try: - result = client.info() - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - data = result.get("data", {}) - module.exit_json( - changed=False, - version=data.get("version", ""), - hostname=data.get("hostname", ""), - banner=data.get("banner", ""), - info=data, - ) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_lldp_global.py b/plugins/modules/vyos_lldp_global.py index 0a9618d..1eb10bd 100644 --- a/plugins/modules/vyos_lldp_global.py +++ b/plugins/modules/vyos_lldp_global.py @@ -74,35 +74,53 @@ def _get(vyos): if "management-address" in raw: v = raw["management-address"] result["addresses"] = ( - list(v.keys()) if isinstance(v, dict) else ([v] if isinstance(v, str) else list(v)) + sorted(list(v.keys())) + if isinstance(v, dict) + else ([v] if isinstance(v, str) else sorted(list(v))) ) if "snmp" in raw: result["snmp"] = "enable" if "legacy-protocols" in raw: v = raw["legacy-protocols"] result["legacy_protocols"] = ( - list(v.keys()) if isinstance(v, dict) else ([v] if isinstance(v, str) else list(v)) + sorted(list(v.keys())) + if isinstance(v, dict) + else ([v] if isinstance(v, str) else sorted(list(v))) ) return result def _build(want, have, state): cmds = [] + if state in ("replaced", "deleted") and have: cmds.append(("delete", _BASE)) if state == "deleted": return cmds - if want.get("enable") is not False: + + # enable — only set if not already enabled + if want.get("enable") is not False and not have.get("enable"): cmds.append(("set", _BASE)) + + # addresses — only add missing ones + have_addrs = set(have.get("addresses") or []) for addr in want.get("addresses") or []: - cmds.append(("set", _BASE + ["management-address", addr])) + if addr not in have_addrs: + cmds.append(("set", _BASE + ["management-address", addr])) + + # snmp if want.get("snmp"): - if want["snmp"] == "disable": + if want["snmp"] == "disable" and have.get("snmp"): cmds.append(("delete", _BASE + ["snmp"])) - else: + elif want["snmp"] != "disable" and not have.get("snmp"): cmds.append(("set", _BASE + ["snmp"])) + + # legacy_protocols — only add missing ones + have_protos = set(have.get("legacy_protocols") or []) for p in want.get("legacy_protocols") or []: - cmds.append(("set", _BASE + ["legacy-protocols", p])) + if p not in have_protos: + cmds.append(("set", _BASE + ["legacy-protocols", p])) + return cmds -- cgit v1.2.3 From 60866aeb2f743a404e045ce64a2c0912c689c6a9 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 19 May 2026 08:44:50 +1000 Subject: route_map --- README.md | 4 +- docs/vyos.rest.vyos_prefix_lists_module.rst | 191 ++++--- docs/vyos.rest.vyos_route_maps_module.rst | 802 +++++----------------------- plugins/modules/vyos_prefix_lists.py | 483 +++++++++++------ plugins/modules/vyos_route_maps.py | 699 ++++++++++++++---------- 5 files changed, 963 insertions(+), 1216 deletions(-) (limited to 'plugins/modules') diff --git a/README.md b/README.md index d6dd4d5..ccc953b 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ Name | Description [vyos.rest.vyos_ospfv2](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv2_module.rst)|OSPFv2 resource module via REST API. [vyos.rest.vyos_ospfv3](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv3_module.rst)|OSPFv3 resource module via REST API. [vyos.rest.vyos_ping](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ping_module.rst)|Test reachability using ping via VyOS REST API. -[vyos.rest.vyos_prefix_lists](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_prefix_lists_module.rst)|Prefix-Lists resource module via 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_reset](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_reset_module.rst)|Execute reset commands on a VyOS device via REST API. -[vyos.rest.vyos_route_maps](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_route_maps_module.rst)|Route Map resource module via 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_show](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_show_module.rst)|Execute op-mode show commands on a VyOS device via 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 via the REST API. diff --git a/docs/vyos.rest.vyos_prefix_lists_module.rst b/docs/vyos.rest.vyos_prefix_lists_module.rst index 6e1d288..53321c9 100644 --- a/docs/vyos.rest.vyos_prefix_lists_module.rst +++ b/docs/vyos.rest.vyos_prefix_lists_module.rst @@ -5,7 +5,7 @@ vyos.rest.vyos_prefix_lists *************************** -**Prefix-Lists resource module via REST API.** +**Manage prefix-list configuration on VyOS devices using REST API** Version added: 1.0.0 @@ -17,7 +17,8 @@ Version added: 1.0.0 Synopsis -------- -- Manages IPv4 and IPv6 prefix lists on VyOS via the HTTPS REST API. +- Manages IPv4 and IPv6 prefix lists on VyOS via the REST API. +- Uses REST API (``connection=httpapi``) instead of CLI. @@ -33,21 +34,6 @@ Parameters Choices/Defaults Comments - - -
- api_key - -
- string - / required -
- - - - - -
@@ -61,7 +47,7 @@ Parameters -
List of prefix-list configurations.
+
List of prefix-list configurations grouped by address family.
@@ -82,7 +68,7 @@ Parameters -
Address family.
+
Address family identifier.
@@ -116,7 +102,7 @@ Parameters -
Description.
+
Prefix list description.
@@ -156,7 +142,7 @@ Parameters -
permit or deny.
+
Permit or deny.
@@ -174,6 +160,7 @@ Parameters +
Rule description.
@@ -270,36 +257,6 @@ Parameters - - -
- hostname - -
- string - / required -
- - - - - - - - -
- port - -
- integer -
- - - Default:
443
- - - -
@@ -319,49 +276,67 @@ Parameters - - - - -
- timeout - -
- integer -
- - - Default:
30
- - - - - - -
- verify_ssl - -
- boolean -
- - - - - +
Desired state of the prefix-list configuration.
+
merged adds or updates entries without removing existing ones.
+
replaced replaces each named prefix list mentioned in config.
+
overridden replaces all prefix lists for the given AFIs.
+
deleted removes prefix lists. Without config removes all.
+
gathered returns current configuration as structured data.

+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 prefix list configuration + 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 + entries: + - sequence: 5 + action: permit + prefix: 2001:db8:8000::/35 + le: 37 + state: merged + + - name: Delete all prefix lists + vyos.rest.vyos_prefix_lists: + state: deleted + + - name: Gather current prefix list configuration + vyos.rest.vyos_prefix_lists: + state: gathered + + Return Values ------------- @@ -386,6 +361,7 @@ Common return values are documented `here when changed +
Prefix list configuration after this module ran.

@@ -400,6 +376,7 @@ Common return values are documented `here always +
Prefix list configuration before this module ran.

@@ -414,6 +391,52 @@ Common return values are documented `here always +
List of API command tuples sent to the device.
+
+ + + + +
+ gathered + +
+ list +
+ + when state is gathered + +
Current prefix list configuration as structured data.
+
+ + + + +
+ response + +
+ dictionary +
+ + when changes are applied + +
Raw API response.
+
+ + + + +
+ saved + +
+ boolean +
+ + when changes are applied + +
Whether the config was saved after changes.

diff --git a/docs/vyos.rest.vyos_route_maps_module.rst b/docs/vyos.rest.vyos_route_maps_module.rst index 0f41805..4fc0801 100644 --- a/docs/vyos.rest.vyos_route_maps_module.rst +++ b/docs/vyos.rest.vyos_route_maps_module.rst @@ -5,7 +5,7 @@ vyos.rest.vyos_route_maps ************************* -**Route Map resource module via REST API.** +**Manage route-map configuration on VyOS devices using REST API** Version added: 1.0.0 @@ -17,7 +17,8 @@ Version added: 1.0.0 Synopsis -------- -- Manages route maps on VyOS via the HTTPS REST API. +- Manages route maps on VyOS via the REST API. +- Uses REST API (``connection=httpapi``) instead of CLI. @@ -29,583 +30,66 @@ Parameters - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterParameter Choices/Defaults Comments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- list - / elements=dictionary -
-
- -
List of route-map configurations.
-
-
- entries - -
- list - / elements=dictionary -
-
- -
Route map rules.
-
-
- action - -
- string -
-
-
    Choices: -
  • permit
  • -
  • deny
  • -
-
-
permit or deny.
-
-
- call - -
- string -
-
- -
Call another route map.
-
-
- continue_sequence - -
- integer -
-
- -
Continue at a different sequence.
-
-
- description - -
- string -
-
- -
-
- match - -
- dictionary -
-
- -
Match conditions.
-
-
- interface - -
- string -
-
- -
-
- ip - -
- dictionary -
-
- -
-
- nexthop_address - -
- string -
-
- -
-
- nexthop_prefix_list - -
- string -
-
- -
-
- ipv6 - -
- dictionary -
-
- -
-
- nexthop_address - -
- string -
-
- -
-
- metric - -
- integer -
-
- -
-
- origin - -
- string -
-
- -
-
- prefix_list - -
- string -
-
- -
-
- prefix_list6 - -
- string -
-
- -
-
- sequence - -
- integer -
-
- -
Rule number (1-65535).
-
-
- set - -
- dictionary -
-
- -
Route parameters to set.
-
-
- aggregator - -
- dictionary -
-
- -
-
- as - -
- string -
-
- -
-
- ip - -
- string -
-
- -
-
- as_path_prepend - -
- string -
-
- -
-
- atomic_aggregate - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- bgp_extcommunity_rt - -
- string -
-
- -
+
- community + config
- dictionary + list + / elements=dictionary
+
List of route-map configurations.
-
- value - -
- string -
-
- -
-
- ip_next_hop - -
- string -
-
- -
- ipv6_next_hop + entries
- dictionary + list + / elements=dictionary
+
Route map rules.
- ip_type + action
string
-
    Choices: -
  • global
  • -
  • local
  • -
+
Permit or deny.
- value - -
- string -
-
- -
-
- large_community + call
string @@ -614,15 +98,15 @@ Parameters
+
Call another route map.
+
- local_preference + continue_sequence
integer @@ -631,66 +115,15 @@ Parameters
+
Continue at a different sequence number.
-
- metric - -
- string -
-
- -
-
- metric_type - -
- string -
-
- -
-
- origin - -
- string -
-
- -
+
- originator_id + description
string @@ -699,69 +132,71 @@ Parameters
+
Rule description.
+
- src + match
- string + dictionary
+
Match conditions (passed through to VyOS API).
+
- tag + sequence
- string + integer + / required
+
Rule sequence number (1-65535).
+
- weight + set
- integer + dictionary
+
Route parameters to set (passed through to VyOS API).
+
route_map
string + / required
@@ -772,37 +207,7 @@ Parameters
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
+
state @@ -820,47 +225,62 @@ Parameters
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
+
Desired state of the route-map configuration.
+
merged adds or updates entries without removing existing ones.
+
replaced replaces each named route map mentioned in config.
+
overridden replaces all route maps.
+
deleted removes route maps. Without config removes all.
+
gathered returns current configuration as structured data.

+Notes +----- + +.. note:: + - Requires ``ansible_connection=httpapi`` with the VyOS httpapi plugin. + - ``ansible_network_os`` must be set to ``vyos.rest.vyos``. + - Input validation is delegated to the VyOS API. + +Examples +-------- + +.. code-block:: yaml + + - name: Merge route map 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" + as_path_exclude: "111" + aggregator: + as: 100 + state: merged + + - name: Delete all route maps + vyos.rest.vyos_route_maps: + state: deleted + + - name: Delete a specific route map + vyos.rest.vyos_route_maps: + config: + - route_map: RM-TEST-EXPORT-POLICY + state: deleted + + - name: Gather current route map configuration + vyos.rest.vyos_route_maps: + state: gathered @@ -887,6 +307,7 @@ Common return values are documented `here when changed +
Route map configuration after this module ran.

@@ -901,6 +322,7 @@ Common return values are documented `here always +
Route map configuration before this module ran.

@@ -915,6 +337,52 @@ Common return values are documented `here always +
List of API command tuples sent to the device.
+
+ + + + +
+ gathered + +
+ list +
+ + when state is gathered + +
Current route map configuration as structured data.
+
+ + + + +
+ response + +
+ dictionary +
+ + when changes are applied + +
Raw API response.
+
+ + + + +
+ saved + +
+ boolean +
+ + when changes are applied + +
Whether the config was saved after changes.

diff --git a/plugins/modules/vyos_prefix_lists.py b/plugins/modules/vyos_prefix_lists.py index 63fa584..5334e7a 100644 --- a/plugins/modules/vyos_prefix_lists.py +++ b/plugins/modules/vyos_prefix_lists.py @@ -1,6 +1,5 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# GNU General Public License v3.0+ from __future__ import absolute_import, division, print_function @@ -10,20 +9,22 @@ __metaclass__ = type DOCUMENTATION = r""" --- module: vyos_prefix_lists -short_description: Prefix-Lists resource module via REST API. +short_description: Manage prefix-list configuration on VyOS devices using REST API description: - - Manages IPv4 and IPv6 prefix lists on VyOS via the HTTPS REST API. + - Manages IPv4 and IPv6 prefix lists on VyOS 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: List of prefix-list configurations. + description: List of prefix-list configurations grouped by address family. type: list elements: dict suboptions: afi: - description: Address family. + description: Address family identifier. type: str choices: [ipv4, ipv6] required: true @@ -37,7 +38,7 @@ options: type: str required: true description: - description: Description. + description: Prefix list description. type: str entries: description: Prefix list rules. @@ -49,9 +50,10 @@ options: type: int required: true description: + description: Rule description. type: str action: - description: permit or deny. + description: Permit or deny. type: str choices: [permit, deny] ge: @@ -63,204 +65,347 @@ options: prefix: description: Network prefix to match. type: str + state: + description: + - Desired state of the prefix-list configuration. + - C(merged) adds or updates entries without removing existing ones. + - C(replaced) replaces each named prefix list mentioned in config. + - C(overridden) replaces all prefix lists for the given AFIs. + - C(deleted) removes prefix lists. Without config removes all. + - C(gathered) returns current configuration as structured data. type: str choices: [merged, replaced, overridden, deleted, gathered] default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false + +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 prefix list configuration + 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 + entries: + - sequence: 5 + action: permit + prefix: 2001:db8:8000::/35 + le: 37 + state: merged + +- name: Delete all prefix lists + vyos.rest.vyos_prefix_lists: + state: deleted + +- name: Gather current prefix list configuration + vyos.rest.vyos_prefix_lists: + state: gathered """ RETURN = r""" before: + description: Prefix list configuration before this module ran. returned: always type: list + after: + description: Prefix list 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 prefix list 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_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) +from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule +# AFI → API key mapping _PL_KEY = {"ipv4": "prefix-list", "ipv6": "prefix-list6"} -def _get(client): - try: - r = client.retrieve_show_config(["policy"]) - data = r.get("data") or {} - out = [] - for afi, key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: - pl_data = data.get(key) or {} - if not isinstance(pl_data, dict): - continue - pls = [] - for pl_name, pl_info in pl_data.items(): - entry = {"name": pl_name} - if isinstance(pl_info, dict): - if "description" in pl_info: - entry["description"] = pl_info["description"] - rules = [] - for seq, rdata in (pl_info.get("rule") or {}).items(): - if isinstance(rdata, dict): - rules.append({"sequence": int(seq), **rdata}) - if rules: - entry["entries"] = rules - pls.append(entry) - if pls: - out.append({"afi": afi, "prefix_lists": pls}) - return out - except VyOSRestError: +# ------------------------------------------------------------ +# Parsing: API response → argspec list +# ------------------------------------------------------------ + + +def get_running_config(vyos): + """ + Fetch current prefix list config and return in argspec list format: + [{"afi": "ipv4", "prefix_lists": [...]}, {"afi": "ipv6", ...}] + """ + raw = vyos.get_config(["policy"]) + if not raw or not isinstance(raw, dict): return [] + result = [] + for afi, api_key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: + pl_data = raw.get(api_key) or {} + if not isinstance(pl_data, dict): + continue + + pls = [] + for pl_name, pl_info in sorted(pl_data.items()): + entry = {"name": pl_name} + pl_info = pl_info or {} + + if pl_info.get("description"): + entry["description"] = pl_info["description"] + + rules = [] + for seq, rdata in sorted( + (pl_info.get("rule") or {}).items(), + key=lambda x: int(x[0]), + ): + rdata = rdata or {} + rule = {"sequence": int(seq)} + if rdata.get("action"): + rule["action"] = rdata["action"] + if rdata.get("prefix"): + rule["prefix"] = rdata["prefix"] + if rdata.get("ge") is not None: + rule["ge"] = int(rdata["ge"]) + if rdata.get("le") is not None: + rule["le"] = int(rdata["le"]) + if rdata.get("description"): + rule["description"] = rdata["description"] + rules.append(rule) + + if rules: + entry["entries"] = rules + pls.append(entry) + + if pls: + result.append({"afi": afi, "prefix_lists": pls}) + + return result + + +# ------------------------------------------------------------ +# Internal normalization: argspec list → nested dicts for diffing +# ------------------------------------------------------------ + + +def _normalize(config): + """ + Convert argspec list to nested dict keyed by afi → pl_name → rule_seq. + { + "ipv4": { + "AnsibleIPv4PrefixList": { + "description": "...", + "rules": {2: {...}, 3: {...}} + } + }, + "ipv6": {...} + } + """ + result = {"ipv4": {}, "ipv6": {}} + for entry in config or []: + afi = entry.get("afi") + if afi not in result: + continue + for pl in entry.get("prefix_lists") or []: + name = pl["name"] + rules = {} + for r in pl.get("entries") or []: + seq = r["sequence"] + rules[seq] = {k: v for k, v in r.items() if k != "sequence" and v is not None} + result[afi][name] = { + "description": pl.get("description"), + "rules": rules, + } + return result + + +# ------------------------------------------------------------ +# Command builders +# ------------------------------------------------------------ + + +def _rule_cmds(base, seq, want_rule, have_rule, state): + """Build commands for a single rule entry.""" + cmds = [] + rbase = base + ["rule", str(seq)] + + if seq not in (have_rule or {}): + # New rule + for field, api_key in [ + ("action", "action"), + ("prefix", "prefix"), + ("description", "description"), + ]: + if want_rule.get(field): + cmds.append(("set", rbase + [api_key, want_rule[field]])) + if want_rule.get("ge") is not None: + cmds.append(("set", rbase + ["ge", str(want_rule["ge"])])) + if want_rule.get("le") is not None: + cmds.append(("set", rbase + ["le", str(want_rule["le"])])) + else: + # Existing rule — update changed fields + h = have_rule[seq] + for field, api_key in [ + ("action", "action"), + ("prefix", "prefix"), + ("description", "description"), + ]: + if want_rule.get(field) and want_rule[field] != h.get(field): + cmds.append(("set", rbase + [api_key, want_rule[field]])) + for field in ("ge", "le"): + if want_rule.get(field) is not None and want_rule[field] != h.get(field): + cmds.append(("set", rbase + [field, str(want_rule[field])])) -def _apply(client, entry, commands): - afi = entry["afi"] - key = _PL_KEY[afi] - for pl in entry.get("prefix_lists") or []: - base = ["policy", key, pl["name"]] - client.configure_set(base) - commands.append("set policy {k} {n}".format(k=key, n=pl["name"])) - if pl.get("description"): - client.configure_set(base + ["description"], pl["description"]) - for rule in pl.get("entries") or []: - rb = base + ["rule", str(rule["sequence"])] - client.configure_set(rb) - commands.append( - "set policy {k} {n} rule {s}".format( - k=key, - n=pl["name"], - s=rule["sequence"], - ), - ) - if rule.get("action"): - client.configure_set(rb + ["action"], rule["action"]) - commands.append( - "set policy {k} {n} rule {s} action {a}".format( - k=key, - n=pl["name"], - s=rule["sequence"], - a=rule["action"], - ), - ) - if rule.get("prefix"): - client.configure_set(rb + ["prefix"], rule["prefix"]) - if rule.get("ge") is not None: - client.configure_set(rb + ["ge"], str(rule["ge"])) - if rule.get("le") is not None: - client.configure_set(rb + ["le"], str(rule["le"])) - if rule.get("description"): - client.configure_set(rb + ["description"], rule["description"]) + return cmds + + +def build_commands(config, have_raw, state): + """ + Build command tuples to move from have → want. + """ + cmds = [] + + if state == "deleted": + if not config: + # Delete all prefix lists for both AFIs + for afi, api_key in _PL_KEY.items(): + # Only delete if something exists + if any(e.get("afi") == afi for e in have_raw): + cmds.append(("delete", ["policy", api_key])) + else: + # Delete only specified prefix lists + for entry in config: + afi = entry["afi"] + api_key = _PL_KEY[afi] + for pl in entry.get("prefix_lists") or []: + cmds.append(("delete", ["policy", api_key, pl["name"]])) + return cmds + + want = _normalize(config) + have = _normalize(have_raw) + + for afi, api_key in _PL_KEY.items(): + want_afi = want.get(afi, {}) + have_afi = have.get(afi, {}) + + if state == "overridden": + # Delete PLs present on device but not in want + for pl_name in set(have_afi) - set(want_afi): + cmds.append(("delete", ["policy", api_key, pl_name])) + + for pl_name, want_pl in want_afi.items(): + have_pl = have_afi.get(pl_name, {}) + base = ["policy", api_key, pl_name] + + if state == "replaced" and pl_name in have_afi: + # Full replacement — delete existing then re-set + cmds.append(("delete", ["policy", api_key, pl_name])) + have_pl = {} + + # Description + if want_pl.get("description") and want_pl["description"] != have_pl.get("description"): + cmds.append(("set", base + ["description", want_pl["description"]])) + + # Rules + want_rules = want_pl.get("rules", {}) + have_rules = have_pl.get("rules", {}) + + if state == "replaced": + # have_rules is now empty after delete above + have_rules = {} + + for seq, want_rule in want_rules.items(): + cmds += _rule_cmds(base, seq, want_rule, have_rules, state) + + # merged: leave extra have rules alone + # replaced: they were deleted with the parent node above + + return cmds + + +# ------------------------------------------------------------ +# Argument spec +# ------------------------------------------------------------ + +ARGUMENT_SPEC = dict( + config=dict(type="list", elements="dict"), + state=dict( + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), +) + + +# ------------------------------------------------------------ +# Main +# ------------------------------------------------------------ def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - prefix_lists=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - entries=dict( - type="list", - elements="dict", - options=dict( - sequence=dict(type="int", required=True), - description=dict(type="str"), - action=dict(type="str", choices=["permit", "deny"]), - ge=dict(type="int"), - le=dict(type="int"), - prefix=dict(type="str"), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) + + module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True) + + vyos = VyOSModule(module) + state = module.params["state"] config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) + + have = get_running_config(vyos) if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) + module.exit_json(changed=False, gathered=have) + + commands = build_commands(config, have, state) + if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted" and not config: - for afi, key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: - try: - client.configure_delete(["policy", key]) - commands.append("delete policy {k}".format(k=key)) - except VyOSRestError: - pass - changed = True - elif state == "deleted" and config: - for entry in config: - key = _PL_KEY[entry["afi"]] - for pl in entry.get("prefix_lists") or []: - try: - client.configure_delete(["policy", key, pl["name"]]) - commands.append("delete policy {k} {n}".format(k=key, n=pl["name"])) - changed = True - except VyOSRestError: - pass - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - for entry in config: - key = _PL_KEY[entry["afi"]] - for pl in entry.get("prefix_lists") or []: - try: - client.configure_delete(["policy", key, pl["name"]]) - except VyOSRestError: - pass - for entry in config: - _apply(client, entry, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) + 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, + ) - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) + module.exit_json(changed=False, before=have, after=have, commands=[]) if __name__ == "__main__": diff --git a/plugins/modules/vyos_route_maps.py b/plugins/modules/vyos_route_maps.py index d13a88d..5bb1916 100644 --- a/plugins/modules/vyos_route_maps.py +++ b/plugins/modules/vyos_route_maps.py @@ -1,6 +1,5 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# GNU General Public License v3.0+ from __future__ import absolute_import, division, print_function @@ -10,12 +9,14 @@ __metaclass__ = type DOCUMENTATION = r""" --- module: vyos_route_maps -short_description: Route Map resource module via REST API. +short_description: Manage route-map configuration on VyOS devices using REST API description: - - Manages route maps on VyOS via the HTTPS REST API. + - Manages route maps on VyOS 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: List of route-map configurations. @@ -25,351 +26,461 @@ options: route_map: description: Route map name. type: str + required: true entries: description: Route map rules. type: list elements: dict suboptions: sequence: - description: Rule number (1-65535). + description: Rule sequence number (1-65535). type: int + required: true action: - description: permit or deny. + description: Permit or deny. type: str - choices: [permit, deny] description: + description: Rule description. type: str call: description: Call another route map. type: str continue_sequence: - description: Continue at a different sequence. + description: Continue at a different sequence number. type: int match: - description: Match conditions. + description: Match conditions (passed through to VyOS API). type: dict - suboptions: - prefix_list: - type: str - prefix_list6: - type: str - interface: - type: str - metric: - type: int - origin: - type: str - ip: - type: dict - suboptions: - nexthop_address: - type: str - nexthop_prefix_list: - type: str - ipv6: - type: dict - suboptions: - nexthop_address: - type: str set: - description: Route parameters to set. + description: Route parameters to set (passed through to VyOS API). type: dict - suboptions: - aggregator: - type: dict - suboptions: - as: - type: str - ip: - type: str - as_path_prepend: - type: str - atomic_aggregate: - type: bool - bgp_extcommunity_rt: - type: str - community: - type: dict - suboptions: - value: - type: str - ip_next_hop: - type: str - ipv6_next_hop: - type: dict - suboptions: - ip_type: - type: str - choices: [global, local] - value: - type: str - large_community: - type: str - local_preference: - type: int - metric: - type: str - metric_type: - type: str - origin: - type: str - originator_id: - type: str - src: - type: str - tag: - type: str - weight: - type: int + state: + description: + - Desired state of the route-map configuration. + - C(merged) adds or updates entries without removing existing ones. + - C(replaced) replaces each named route map mentioned in config. + - C(overridden) replaces all route maps. + - C(deleted) removes route maps. Without config removes all. + - C(gathered) returns current configuration as structured data. type: str choices: [merged, replaced, overridden, deleted, gathered] default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false + +notes: + - Requires C(ansible_connection=httpapi) with the VyOS httpapi plugin. + - C(ansible_network_os) must be set to C(vyos.rest.vyos). + - Input validation is delegated to the VyOS API. +""" + +EXAMPLES = r""" +- name: Merge route map 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" + as_path_exclude: "111" + aggregator: + as: 100 + state: merged + +- name: Delete all route maps + vyos.rest.vyos_route_maps: + state: deleted + +- name: Delete a specific route map + vyos.rest.vyos_route_maps: + config: + - route_map: RM-TEST-EXPORT-POLICY + state: deleted + +- name: Gather current route map configuration + vyos.rest.vyos_route_maps: + state: gathered """ RETURN = r""" before: + description: Route map configuration before this module ran. returned: always type: list + after: + description: Route map 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 route map 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_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) +from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule + + +_BASE = ["policy", "route-map"] + +# Argspec key → API path segments under "set" +_SET_MAP = { + "as_path_prepend": ["as-path", "prepend"], + "as_path_exclude": ["as-path", "exclude"], + "as_path_prepend_last_as": ["as-path", "prepend-last-as"], + "ip_next_hop": ["ip-next-hop"], + "local_preference": ["local-preference"], + "metric": ["metric"], + "metric_type": ["metric-type"], + "origin": ["origin"], + "originator_id": ["originator-id"], + "src": ["src"], + "tag": ["tag"], + "weight": ["weight"], + "distance": ["distance"], + "table": ["table"], +} +# Argspec key → API path segments under "match" +_MATCH_MAP = { + "interface": ["interface"], + "metric": ["metric"], + "origin": ["origin"], + "peer": ["peer"], + "protocol": ["protocol"], +} -def _get(client): - try: - r = client.retrieve_show_config(["policy", "route-map"]) - data = r.get("data") or {} - return [{"route_map": name, "raw": rdata} for name, rdata in data.items()] - except VyOSRestError: + +# ------------------------------------------------------------ +# Parsing: API response → argspec list +# ------------------------------------------------------------ + + +def get_running_config(vyos): + """ + Fetch current route map config in argspec list format. + + API returns: {"route-map": {"RM-NAME": {"rule": {"10": {...}}}}} + Note the extra "route-map" nesting level that must be unwrapped. + """ + raw = vyos.get_config(["policy", "route-map"]) + if not raw or not isinstance(raw, dict): + return [] + + # API wraps everything under a "route-map" key — unwrap it + rm_data = raw.get("route-map", raw) + if not isinstance(rm_data, dict): return [] + result = [] + for rm_name, rm_info in sorted(rm_data.items()): + entry = {"route_map": rm_name, "entries": []} + rm_info = rm_info or {} + + for seq, rule_data in sorted( + (rm_info.get("rule") or {}).items(), + key=lambda x: int(x[0]), + ): + rule_data = rule_data or {} + rule = {"sequence": int(seq)} + if rule_data.get("action"): + rule["action"] = rule_data["action"] + if rule_data.get("description"): + rule["description"] = rule_data["description"] + if rule_data.get("call"): + rule["call"] = rule_data["call"] + if rule_data.get("continue"): + rule["continue_sequence"] = int(rule_data["continue"]) + # match and set returned as raw API dicts + if rule_data.get("match"): + rule["match"] = rule_data["match"] + if rule_data.get("set"): + rule["set"] = rule_data["set"] + entry["entries"].append(rule) + + result.append(entry) + + return result + + +# ------------------------------------------------------------ +# Command builders +# ------------------------------------------------------------ + + +def _match_cmds(rbase, match): + """Convert match dict to API path commands.""" + cmds = [] + if not match: + return cmds + + mbase = rbase + ["match"] + + for key, path_suffix in _MATCH_MAP.items(): + if match.get(key) is not None: + cmds.append(("set", mbase + path_suffix + [str(match[key])])) + + if match.get("prefix_list"): + cmds.append(("set", mbase + ["ip", "address", "prefix-list", match["prefix_list"]])) + if match.get("prefix_list6"): + cmds.append(("set", mbase + ["ipv6", "address", "prefix-list", match["prefix_list6"]])) + + ip = match.get("ip") or {} + if ip.get("nexthop_address"): + cmds.append(("set", mbase + ["ip", "nexthop", "address", ip["nexthop_address"]])) + if ip.get("nexthop_prefix_list"): + cmds.append(("set", mbase + ["ip", "nexthop", "prefix-list", ip["nexthop_prefix_list"]])) + + ipv6 = match.get("ipv6") or {} + if ipv6.get("nexthop_address"): + cmds.append(("set", mbase + ["ipv6", "nexthop", "address", ipv6["nexthop_address"]])) + + return cmds + + +def _set_cmds(rbase, setv): + """Convert set dict to API path commands.""" + cmds = [] + if not setv: + return cmds + + sbase = rbase + ["set"] -def _apply(client, rm_cfg, commands): - rm_name = rm_cfg["route_map"] - for rule in rm_cfg.get("entries") or []: - seq = str(rule["sequence"]) - base = ["policy", "route-map", rm_name, "rule", seq] - client.configure_set(base) - commands.append("set policy route-map {n} rule {s}".format(n=rm_name, s=seq)) - - if rule.get("action"): - client.configure_set(base + ["action"], rule["action"]) - commands.append( - "set policy route-map {n} rule {s} action {a}".format( - n=rm_name, - s=seq, - a=rule["action"], - ), - ) - if rule.get("description"): - client.configure_set(base + ["description"], rule["description"]) - if rule.get("call"): - client.configure_set(base + ["call"], rule["call"]) - if rule.get("continue_sequence"): - client.configure_set(base + ["continue"], str(rule["continue_sequence"])) - - # Match conditions + for key, path_suffix in _SET_MAP.items(): + if setv.get(key) is not None: + cmds.append(("set", sbase + path_suffix + [str(setv[key])])) + + if setv.get("atomic_aggregate"): + cmds.append(("set", sbase + ["atomic-aggregate"])) + + comm = setv.get("community") or {} + if comm.get("value"): + cmds.append(("set", sbase + ["community", comm["value"]])) + + large_comm = setv.get("large_community") + if large_comm is not None: + cmds.append(("set", sbase + ["large-community", str(large_comm)])) + + agg = setv.get("aggregator") or {} + agg_as = agg.get("as") or agg.get("as_") + if agg_as and agg.get("ip"): + cmds.append(("set", sbase + ["aggregator", "as", str(agg_as), "address", agg["ip"]])) + elif agg_as: + cmds.append(("set", sbase + ["aggregator", "as", str(agg_as)])) + + nh6 = setv.get("ipv6_next_hop") or {} + if nh6.get("value"): + ip_type = nh6.get("ip_type") or "global" + cmds.append(("set", sbase + ["ipv6-next-hop", ip_type, nh6["value"]])) + + return cmds + + +def _want_to_api_set(setv): + """ + Convert argspec set dict to the nested API shape for comparison with have. + + API stores: + as_path_exclude "111" → {"as-path": {"exclude": "111"}} + metric "5" → {"metric": "5"} + aggregator.as 100 → {"aggregator": {"as": "100"}} + """ + if not setv: + return {} + api = {} + for key, path in _SET_MAP.items(): + if setv.get(key) is not None: + d = api + for p in path[:-1]: + d = d.setdefault(p, {}) + d[path[-1]] = str(setv[key]) + agg = setv.get("aggregator") or {} + agg_as = agg.get("as") or agg.get("as_") + if agg_as: + api.setdefault("aggregator", {})["as"] = str(agg_as) + large_comm = setv.get("large_community") + if large_comm is not None: + api["large-community"] = {str(large_comm): {}} + return api + + +def _want_to_api_match(match): + """ + Convert argspec match dict to API shape for comparison with have. + Flat fields map directly; prefix_list nests under ip.address. + """ + if not match: + return {} + api = {} + for key in _MATCH_MAP: + if match.get(key) is not None: + api[key] = str(match[key]) + if match.get("prefix_list"): + api.setdefault("ip", {}).setdefault("address", {})["prefix-list"] = match["prefix_list"] + if match.get("prefix_list6"): + api.setdefault("ipv6", {}).setdefault("address", {})["prefix-list"] = match["prefix_list6"] + return api + + +def _rule_cmds(rm_name, rule, have_rule): + """ + Build commands for a single rule, comparing against have_rule to skip + fields that are already correctly configured (idempotency). + """ + cmds = [] + seq = str(rule["sequence"]) + rbase = _BASE + [rm_name, "rule", seq] + + # Scalar fields + if rule.get("action") and rule["action"] != have_rule.get("action"): + cmds.append(("set", rbase + ["action", rule["action"]])) + if rule.get("description") and rule["description"] != have_rule.get("description"): + cmds.append(("set", rbase + ["description", rule["description"]])) + if rule.get("call") and rule["call"] != have_rule.get("call"): + cmds.append(("set", rbase + ["call", rule["call"]])) + if rule.get("continue_sequence") is not None and rule["continue_sequence"] != have_rule.get( + "continue_sequence", + ): + cmds.append(("set", rbase + ["continue", str(rule["continue_sequence"])])) + + # match — convert want to API shape, compare top-level keys against have + want_match_api = _want_to_api_match(rule.get("match")) + have_match = have_rule.get("match") or {} + changed_match = {k: v for k, v in want_match_api.items() if have_match.get(k) != v} + if changed_match: + # rebuild from original argspec for changed keys only match = rule.get("match") or {} - if match.get("prefix_list"): - client.configure_set( - base + ["match", "ip", "address", "prefix-list"], - match["prefix_list"], - ) - if match.get("interface"): - client.configure_set(base + ["match", "interface"], match["interface"]) - if match.get("metric") is not None: - client.configure_set(base + ["match", "metric"], str(match["metric"])) - ip_match = match.get("ip") or {} - if ip_match.get("nexthop_address"): - client.configure_set( - base + ["match", "ip", "nexthop", "address"], - ip_match["nexthop_address"], - ) - - # Set actions - setv = rule.get("set") or {} - if setv.get("ip_next_hop"): - client.configure_set(base + ["set", "ip-next-hop"], setv["ip_next_hop"]) - commands.append( - "set policy route-map {n} rule {s} set ip-next-hop {nh}".format( - n=rm_name, - s=seq, - nh=setv["ip_next_hop"], - ), - ) - if setv.get("local_preference") is not None: - client.configure_set( - base + ["set", "local-preference"], - str(setv["local_preference"]), - ) - if setv.get("metric"): - client.configure_set(base + ["set", "metric"], str(setv["metric"])) - if setv.get("origin"): - client.configure_set(base + ["set", "origin"], setv["origin"]) - if setv.get("weight") is not None: - client.configure_set(base + ["set", "weight"], str(setv["weight"])) - if setv.get("as_path_prepend"): - client.configure_set(base + ["set", "as-path-prepend"], setv["as_path_prepend"]) - comm = setv.get("community") or {} - if comm.get("value"): - client.configure_set(base + ["set", "community", comm["value"]]) - if setv.get("tag"): - client.configure_set(base + ["set", "tag"], setv["tag"]) - if setv.get("src"): - client.configure_set(base + ["set", "src"], setv["src"]) - nh6 = setv.get("ipv6_next_hop") or {} - if nh6.get("value"): - ip_type = nh6.get("ip_type", "global") - client.configure_set(base + ["set", "ipv6-next-hop", ip_type], nh6["value"]) + changed_keys = set(changed_match.keys()) + partial_match = { + k: v + for k, v in match.items() + if k in changed_keys + or (k == "prefix_list" and "ip" in changed_keys) + or (k == "prefix_list6" and "ipv6" in changed_keys) + } + if not partial_match: + partial_match = match + cmds += _match_cmds(rbase, partial_match) + + # set — convert want to API shape, compare top-level keys against have + want_set_api = _want_to_api_set(rule.get("set")) + have_set = have_rule.get("set") or {} + if want_set_api != have_set: + # something changed — regenerate all set commands + # (simpler than surgical per-key diff for deeply nested structures) + cmds += _set_cmds(rbase, rule.get("set")) + + return cmds + + +def build_commands(config, have_raw, state): + """Build command tuples to move from have → want.""" + cmds = [] + + if state == "deleted": + if not config: + if have_raw: + cmds.append(("delete", _BASE)) + else: + for rm in config: + cmds.append(("delete", _BASE + [rm["route_map"]])) + return cmds + + have_map = {e["route_map"]: e for e in have_raw} + + if state == "overridden": + want_names = {rm["route_map"] for rm in config} + for name in set(have_map) - want_names: + cmds.append(("delete", _BASE + [name])) + + for rm in config: + rm_name = rm["route_map"] + have_rm = have_map.get(rm_name, {}) + + if state == "replaced" and rm_name in have_map: + cmds.append(("delete", _BASE + [rm_name])) + have_rm = {} + + # Index have entries by sequence for O(1) lookup + have_entries = {str(r["sequence"]): r for r in (have_rm.get("entries") or [])} + + for rule in rm.get("entries") or []: + have_rule = have_entries.get(str(rule["sequence"]), {}) + cmds += _rule_cmds(rm_name, rule, have_rule) + + return cmds + + +# ------------------------------------------------------------ +# Argument spec — minimal, VyOS API validates field values +# ------------------------------------------------------------ + +ARGUMENT_SPEC = dict( + config=dict(type="list", elements="dict"), + state=dict( + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), +) + + +# ------------------------------------------------------------ +# Main +# ------------------------------------------------------------ def main(): - set_spec = dict( - type="dict", - options=dict( - aggregator=dict(type="dict", options=dict(as_=dict(type="str"), ip=dict(type="str"))), - as_path_prepend=dict(type="str"), - atomic_aggregate=dict(type="bool"), - bgp_extcommunity_rt=dict(type="str"), - community=dict(type="dict", options=dict(value=dict(type="str"))), - ip_next_hop=dict(type="str"), - ipv6_next_hop=dict( - type="dict", - options=dict( - ip_type=dict(type="str", choices=["global", "local"]), - value=dict(type="str"), - ), - ), - large_community=dict(type="str"), - local_preference=dict(type="int"), - metric=dict(type="str"), - metric_type=dict(type="str"), - origin=dict(type="str"), - originator_id=dict(type="str"), - src=dict(type="str"), - tag=dict(type="str"), - weight=dict(type="int"), - ), - ) - match_spec = dict( - type="dict", - options=dict( - prefix_list=dict(type="str"), - prefix_list6=dict(type="str"), - interface=dict(type="str"), - metric=dict(type="int"), - origin=dict(type="str"), - ip=dict( - type="dict", - options=dict( - nexthop_address=dict(type="str"), - nexthop_prefix_list=dict(type="str"), - ), - ), - ipv6=dict(type="dict", options=dict(nexthop_address=dict(type="str"))), - ), - ) - - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - route_map=dict(type="str"), - entries=dict( - type="list", - elements="dict", - options=dict( - sequence=dict(type="int"), - action=dict(type="str", choices=["permit", "deny"]), - description=dict(type="str"), - call=dict(type="str"), - continue_sequence=dict(type="int"), - match=match_spec, - set=set_spec, - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) + + module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True) + + vyos = VyOSModule(module) + state = module.params["state"] config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) + + have = get_running_config(vyos) if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) + module.exit_json(changed=False, gathered=have) + + commands = build_commands(config, have, state) + if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted" and not config: - try: - client.configure_delete(["policy", "route-map"]) - commands.append("delete policy route-map") - changed = True - except VyOSRestError: - pass - elif state == "deleted" and config: - for rm in config: - try: - client.configure_delete(["policy", "route-map", rm["route_map"]]) - commands.append("delete policy route-map {n}".format(n=rm["route_map"])) - changed = True - except VyOSRestError: - pass - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - for rm in config: - try: - client.configure_delete(["policy", "route-map", rm["route_map"]]) - except VyOSRestError: - pass - for rm in config: - _apply(client, rm, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) + 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, + ) - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) + module.exit_json(changed=False, before=have, after=have, commands=[]) if __name__ == "__main__": -- cgit v1.2.3 From 5a6f6976b49fd5ca8e964b2342493bc5ceee85d5 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 19 May 2026 08:55:26 +1000 Subject: marking preview --- plugins/modules/vyos_bgp_address_family.py | 354 --------------- plugins/modules/vyos_bgp_address_family.py.preview | 354 +++++++++++++++ plugins/modules/vyos_bgp_global.py | 365 --------------- plugins/modules/vyos_bgp_global.py.preview | 365 +++++++++++++++ plugins/modules/vyos_config_file.py | 130 ------ plugins/modules/vyos_config_file.py.preview | 130 ++++++ plugins/modules/vyos_configure.py | 259 ----------- plugins/modules/vyos_configure.py.preview | 259 +++++++++++ plugins/modules/vyos_facts.py | 161 ------- plugins/modules/vyos_facts.py.preview | 161 +++++++ plugins/modules/vyos_firewall_global.py | 360 --------------- plugins/modules/vyos_firewall_global.py.preview | 360 +++++++++++++++ plugins/modules/vyos_firewall_interfaces.py | 244 ---------- .../modules/vyos_firewall_interfaces.py.preview | 244 ++++++++++ plugins/modules/vyos_firewall_rules.py | 492 --------------------- plugins/modules/vyos_firewall_rules.py.preview | 492 +++++++++++++++++++++ plugins/modules/vyos_generate.py | 102 ----- plugins/modules/vyos_generate.py.preview | 102 +++++ plugins/modules/vyos_image.py | 131 ------ plugins/modules/vyos_image.py.preview | 131 ++++++ plugins/modules/vyos_interfaces.py | 401 ----------------- plugins/modules/vyos_interfaces.py.preview | 401 +++++++++++++++++ plugins/modules/vyos_l3_interfaces.py | 381 ---------------- plugins/modules/vyos_l3_interfaces.py.preview | 381 ++++++++++++++++ plugins/modules/vyos_lag_interfaces.py | 228 ---------- plugins/modules/vyos_lag_interfaces.py.preview | 228 ++++++++++ plugins/modules/vyos_lldp_interfaces.py | 227 ---------- plugins/modules/vyos_lldp_interfaces.py.preview | 227 ++++++++++ plugins/modules/vyos_ospf_interfaces.py | 290 ------------ plugins/modules/vyos_ospf_interfaces.py.preview | 290 ++++++++++++ plugins/modules/vyos_ospfv2.py | 487 -------------------- plugins/modules/vyos_ospfv2.py.preview | 487 ++++++++++++++++++++ plugins/modules/vyos_ospfv3.py | 257 ----------- plugins/modules/vyos_ospfv3.py.preview | 257 +++++++++++ plugins/modules/vyos_ping.py | 180 -------- plugins/modules/vyos_ping.py.preview | 180 ++++++++ plugins/modules/vyos_prefix_lists.py | 412 ----------------- plugins/modules/vyos_prefix_lists.py.preview | 412 +++++++++++++++++ plugins/modules/vyos_reset.py | 108 ----- plugins/modules/vyos_reset.py.preview | 108 +++++ plugins/modules/vyos_show.py | 119 ----- plugins/modules/vyos_show.py.preview | 119 +++++ plugins/modules/vyos_static_routes.py | 397 ----------------- plugins/modules/vyos_static_routes.py.preview | 397 +++++++++++++++++ plugins/modules/vyos_system.py | 147 ------ plugins/modules/vyos_system.py.preview | 147 ++++++ plugins/modules/vyos_user.py | 282 ------------ plugins/modules/vyos_user.py.preview | 282 ++++++++++++ plugins/modules/vyos_vlan.py | 197 --------- plugins/modules/vyos_vlan.py.preview | 197 +++++++++ 50 files changed, 6711 insertions(+), 6711 deletions(-) delete mode 100644 plugins/modules/vyos_bgp_address_family.py create mode 100644 plugins/modules/vyos_bgp_address_family.py.preview delete mode 100644 plugins/modules/vyos_bgp_global.py create mode 100644 plugins/modules/vyos_bgp_global.py.preview delete mode 100644 plugins/modules/vyos_config_file.py create mode 100644 plugins/modules/vyos_config_file.py.preview delete mode 100644 plugins/modules/vyos_configure.py create mode 100644 plugins/modules/vyos_configure.py.preview delete mode 100644 plugins/modules/vyos_facts.py create mode 100644 plugins/modules/vyos_facts.py.preview delete mode 100644 plugins/modules/vyos_firewall_global.py create mode 100644 plugins/modules/vyos_firewall_global.py.preview delete mode 100644 plugins/modules/vyos_firewall_interfaces.py create mode 100644 plugins/modules/vyos_firewall_interfaces.py.preview delete mode 100644 plugins/modules/vyos_firewall_rules.py create mode 100644 plugins/modules/vyos_firewall_rules.py.preview delete mode 100644 plugins/modules/vyos_generate.py create mode 100644 plugins/modules/vyos_generate.py.preview delete mode 100644 plugins/modules/vyos_image.py create mode 100644 plugins/modules/vyos_image.py.preview delete mode 100644 plugins/modules/vyos_interfaces.py create mode 100644 plugins/modules/vyos_interfaces.py.preview delete mode 100644 plugins/modules/vyos_l3_interfaces.py create mode 100644 plugins/modules/vyos_l3_interfaces.py.preview delete mode 100644 plugins/modules/vyos_lag_interfaces.py create mode 100644 plugins/modules/vyos_lag_interfaces.py.preview delete mode 100644 plugins/modules/vyos_lldp_interfaces.py create mode 100644 plugins/modules/vyos_lldp_interfaces.py.preview delete mode 100644 plugins/modules/vyos_ospf_interfaces.py create mode 100644 plugins/modules/vyos_ospf_interfaces.py.preview delete mode 100644 plugins/modules/vyos_ospfv2.py create mode 100644 plugins/modules/vyos_ospfv2.py.preview delete mode 100644 plugins/modules/vyos_ospfv3.py create mode 100644 plugins/modules/vyos_ospfv3.py.preview delete mode 100644 plugins/modules/vyos_ping.py create mode 100644 plugins/modules/vyos_ping.py.preview delete mode 100644 plugins/modules/vyos_prefix_lists.py create mode 100644 plugins/modules/vyos_prefix_lists.py.preview delete mode 100644 plugins/modules/vyos_reset.py create mode 100644 plugins/modules/vyos_reset.py.preview delete mode 100644 plugins/modules/vyos_show.py create mode 100644 plugins/modules/vyos_show.py.preview delete mode 100644 plugins/modules/vyos_static_routes.py create mode 100644 plugins/modules/vyos_static_routes.py.preview delete mode 100644 plugins/modules/vyos_system.py create mode 100644 plugins/modules/vyos_system.py.preview delete mode 100644 plugins/modules/vyos_user.py create mode 100644 plugins/modules/vyos_user.py.preview delete mode 100644 plugins/modules/vyos_vlan.py create mode 100644 plugins/modules/vyos_vlan.py.preview (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_bgp_address_family.py b/plugins/modules/vyos_bgp_address_family.py deleted file mode 100644 index ff80053..0000000 --- a/plugins/modules/vyos_bgp_address_family.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/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_bgp_address_family -short_description: BGP Address Family resource module via REST API. -description: - - Manages BGP address-family configuration (networks, redistribute, - aggregate-address, per-neighbor AFI settings) via the VyOS HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: BGP address-family configuration. - type: dict - suboptions: - as_number: - description: Local AS number. - type: int - required: true - address_family: - description: Global address-family parameters. - type: list - elements: dict - suboptions: - afi: - description: Address family (ipv4 or ipv6). - type: str - choices: [ipv4, ipv6] - aggregate_address: - description: List of BGP aggregate networks. - type: list - elements: dict - suboptions: - prefix: - type: str - as_set: - type: bool - summary_only: - type: bool - networks: - description: Networks to originate. - type: list - elements: dict - suboptions: - prefix: - type: str - path_limit: - type: int - backdoor: - type: bool - route_map: - type: str - redistribute: - description: Protocols to redistribute. - type: list - elements: dict - suboptions: - protocol: - type: str - choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] - table: - type: str - route_map: - type: str - metric: - type: int - neighbors: - description: Per-neighbor address-family settings. - type: list - elements: dict - suboptions: - neighbor_address: - description: Neighbor IP. - type: str - address_family: - type: list - elements: dict - suboptions: - afi: - type: str - choices: [ipv4, ipv6] - allowas_in: - type: int - as_override: - type: bool - route_map: - description: Route-map settings. - type: dict - suboptions: - import_map: - type: str - export_map: - type: str - soft_reconfiguration: - type: bool - nexthop_self: - type: bool - remove_private_as: - type: bool - state: - description: Desired state. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - description: Device IP or FQDN. - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - description: Config before module ran. - returned: always - type: dict -after: - description: Config after module ran. - returned: when changed - type: dict -commands: - description: Commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _get_bgp(client): - try: - result = client.retrieve_show_config(["protocols", "bgp"]) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def _afi_unicast_key(afi): - return "ipv4-unicast" if afi == "ipv4" else "ipv6-unicast" - - -def _apply(client, config, commands): - asn = str(config["as_number"]) - base = ["protocols", "bgp", asn] - - for af in config.get("address_family") or []: - afi = af["afi"] - af_base = base + ["address-family", _afi_unicast_key(afi)] - - for agg in af.get("aggregate_address") or []: - p = agg["prefix"] - agg_base = af_base + ["aggregate-address", p] - client.configure_set(agg_base) - commands.append("set {b}".format(b=" ".join(agg_base))) - if agg.get("as_set"): - client.configure_set(agg_base + ["as-set"]) - if agg.get("summary_only"): - client.configure_set(agg_base + ["summary-only"]) - - for net in af.get("networks") or []: - net_base = af_base + ["network", net["prefix"]] - client.configure_set(net_base) - commands.append("set {b}".format(b=" ".join(net_base))) - if net.get("route_map"): - client.configure_set(net_base + ["route-map"], net["route_map"]) - if net.get("backdoor"): - client.configure_set(net_base + ["backdoor"]) - - for redist in af.get("redistribute") or []: - r_base = af_base + ["redistribute", redist["protocol"]] - client.configure_set(r_base) - commands.append("set {b}".format(b=" ".join(r_base))) - if redist.get("metric"): - client.configure_set(r_base + ["metric"], str(redist["metric"])) - if redist.get("route_map"): - client.configure_set(r_base + ["route-map"], redist["route_map"]) - - for nbr in config.get("neighbors") or []: - n_base = base + ["neighbor", nbr["neighbor_address"]] - for af in nbr.get("address_family") or []: - afi = af["afi"] - n_af_base = n_base + ["address-family", _afi_unicast_key(afi)] - client.configure_set(n_af_base) - commands.append("set {b}".format(b=" ".join(n_af_base))) - if af.get("allowas_in"): - client.configure_set(n_af_base + ["allowas-in", "number"], str(af["allowas_in"])) - if af.get("as_override"): - client.configure_set(n_af_base + ["as-override"]) - if af.get("nexthop_self"): - client.configure_set(n_af_base + ["nexthop-self"]) - if af.get("remove_private_as"): - client.configure_set(n_af_base + ["remove-private-as"]) - if af.get("soft_reconfiguration"): - client.configure_set(n_af_base + ["soft-reconfiguration", "inbound"]) - rm = af.get("route_map") or {} - if rm.get("import_map"): - client.configure_set(n_af_base + ["route-map", "import"], rm["import_map"]) - if rm.get("export_map"): - client.configure_set(n_af_base + ["route-map", "export"], rm["export_map"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - as_number=dict(type="int", required=True), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", choices=["ipv4", "ipv6"]), - aggregate_address=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str"), - as_set=dict(type="bool"), - summary_only=dict(type="bool"), - ), - ), - networks=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str"), - path_limit=dict(type="int"), - backdoor=dict(type="bool"), - route_map=dict(type="str"), - ), - ), - redistribute=dict( - type="list", - elements="dict", - options=dict( - protocol=dict( - type="str", - choices=[ - "connected", - "kernel", - "ospf", - "ospfv3", - "rip", - "ripng", - "static", - ], - ), - table=dict(type="str"), - route_map=dict(type="str"), - metric=dict(type="int"), - ), - ), - ), - ), - neighbors=dict( - type="list", - elements="dict", - options=dict( - neighbor_address=dict(type="str"), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", choices=["ipv4", "ipv6"]), - allowas_in=dict(type="int"), - as_override=dict(type="bool"), - nexthop_self=dict(type="bool"), - remove_private_as=dict(type="bool"), - soft_reconfiguration=dict(type="bool"), - route_map=dict( - type="dict", - options=dict( - import_map=dict(type="str"), - export_map=dict(type="str"), - ), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get_bgp(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(["protocols", "bgp"]) - commands.append("delete protocols bgp") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(["protocols", "bgp"]) - commands.append("delete protocols bgp") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_bgp(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_bgp_address_family.py.preview b/plugins/modules/vyos_bgp_address_family.py.preview new file mode 100644 index 0000000..ff80053 --- /dev/null +++ b/plugins/modules/vyos_bgp_address_family.py.preview @@ -0,0 +1,354 @@ +#!/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_bgp_address_family +short_description: BGP Address Family resource module via REST API. +description: + - Manages BGP address-family configuration (networks, redistribute, + aggregate-address, per-neighbor AFI settings) via the VyOS HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: BGP address-family configuration. + type: dict + suboptions: + as_number: + description: Local AS number. + type: int + required: true + address_family: + description: Global address-family parameters. + type: list + elements: dict + suboptions: + afi: + description: Address family (ipv4 or ipv6). + type: str + choices: [ipv4, ipv6] + aggregate_address: + description: List of BGP aggregate networks. + type: list + elements: dict + suboptions: + prefix: + type: str + as_set: + type: bool + summary_only: + type: bool + networks: + description: Networks to originate. + type: list + elements: dict + suboptions: + prefix: + type: str + path_limit: + type: int + backdoor: + type: bool + route_map: + type: str + redistribute: + description: Protocols to redistribute. + type: list + elements: dict + suboptions: + protocol: + type: str + choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] + table: + type: str + route_map: + type: str + metric: + type: int + neighbors: + description: Per-neighbor address-family settings. + type: list + elements: dict + suboptions: + neighbor_address: + description: Neighbor IP. + type: str + address_family: + type: list + elements: dict + suboptions: + afi: + type: str + choices: [ipv4, ipv6] + allowas_in: + type: int + as_override: + type: bool + route_map: + description: Route-map settings. + type: dict + suboptions: + import_map: + type: str + export_map: + type: str + soft_reconfiguration: + type: bool + nexthop_self: + type: bool + remove_private_as: + type: bool + state: + description: Desired state. + type: str + choices: [merged, replaced, deleted, gathered] + default: merged + hostname: + description: Device IP or FQDN. + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + description: Config before module ran. + returned: always + type: dict +after: + description: Config after module ran. + returned: when changed + type: dict +commands: + description: Commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def _get_bgp(client): + try: + result = client.retrieve_show_config(["protocols", "bgp"]) + return result.get("data") or {} + except VyOSRestError: + return {} + + +def _afi_unicast_key(afi): + return "ipv4-unicast" if afi == "ipv4" else "ipv6-unicast" + + +def _apply(client, config, commands): + asn = str(config["as_number"]) + base = ["protocols", "bgp", asn] + + for af in config.get("address_family") or []: + afi = af["afi"] + af_base = base + ["address-family", _afi_unicast_key(afi)] + + for agg in af.get("aggregate_address") or []: + p = agg["prefix"] + agg_base = af_base + ["aggregate-address", p] + client.configure_set(agg_base) + commands.append("set {b}".format(b=" ".join(agg_base))) + if agg.get("as_set"): + client.configure_set(agg_base + ["as-set"]) + if agg.get("summary_only"): + client.configure_set(agg_base + ["summary-only"]) + + for net in af.get("networks") or []: + net_base = af_base + ["network", net["prefix"]] + client.configure_set(net_base) + commands.append("set {b}".format(b=" ".join(net_base))) + if net.get("route_map"): + client.configure_set(net_base + ["route-map"], net["route_map"]) + if net.get("backdoor"): + client.configure_set(net_base + ["backdoor"]) + + for redist in af.get("redistribute") or []: + r_base = af_base + ["redistribute", redist["protocol"]] + client.configure_set(r_base) + commands.append("set {b}".format(b=" ".join(r_base))) + if redist.get("metric"): + client.configure_set(r_base + ["metric"], str(redist["metric"])) + if redist.get("route_map"): + client.configure_set(r_base + ["route-map"], redist["route_map"]) + + for nbr in config.get("neighbors") or []: + n_base = base + ["neighbor", nbr["neighbor_address"]] + for af in nbr.get("address_family") or []: + afi = af["afi"] + n_af_base = n_base + ["address-family", _afi_unicast_key(afi)] + client.configure_set(n_af_base) + commands.append("set {b}".format(b=" ".join(n_af_base))) + if af.get("allowas_in"): + client.configure_set(n_af_base + ["allowas-in", "number"], str(af["allowas_in"])) + if af.get("as_override"): + client.configure_set(n_af_base + ["as-override"]) + if af.get("nexthop_self"): + client.configure_set(n_af_base + ["nexthop-self"]) + if af.get("remove_private_as"): + client.configure_set(n_af_base + ["remove-private-as"]) + if af.get("soft_reconfiguration"): + client.configure_set(n_af_base + ["soft-reconfiguration", "inbound"]) + rm = af.get("route_map") or {} + if rm.get("import_map"): + client.configure_set(n_af_base + ["route-map", "import"], rm["import_map"]) + if rm.get("export_map"): + client.configure_set(n_af_base + ["route-map", "export"], rm["export_map"]) + + +def main(): + argument_spec = dict( + config=dict( + type="dict", + options=dict( + as_number=dict(type="int", required=True), + address_family=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", choices=["ipv4", "ipv6"]), + aggregate_address=dict( + type="list", + elements="dict", + options=dict( + prefix=dict(type="str"), + as_set=dict(type="bool"), + summary_only=dict(type="bool"), + ), + ), + networks=dict( + type="list", + elements="dict", + options=dict( + prefix=dict(type="str"), + path_limit=dict(type="int"), + backdoor=dict(type="bool"), + route_map=dict(type="str"), + ), + ), + redistribute=dict( + type="list", + elements="dict", + options=dict( + protocol=dict( + type="str", + choices=[ + "connected", + "kernel", + "ospf", + "ospfv3", + "rip", + "ripng", + "static", + ], + ), + table=dict(type="str"), + route_map=dict(type="str"), + metric=dict(type="int"), + ), + ), + ), + ), + neighbors=dict( + type="list", + elements="dict", + options=dict( + neighbor_address=dict(type="str"), + address_family=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", choices=["ipv4", "ipv6"]), + allowas_in=dict(type="int"), + as_override=dict(type="bool"), + nexthop_self=dict(type="bool"), + remove_private_as=dict(type="bool"), + soft_reconfiguration=dict(type="bool"), + route_map=dict( + type="dict", + options=dict( + import_map=dict(type="str"), + export_map=dict(type="str"), + ), + ), + ), + ), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") + commands = [] + changed = False + before = _get_bgp(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + if before: + client.configure_delete(["protocols", "bgp"]) + commands.append("delete protocols bgp") + changed = True + elif state in ("merged", "replaced"): + if state == "replaced" and before: + client.configure_delete(["protocols", "bgp"]) + commands.append("delete protocols bgp") + _apply(client, config, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_bgp(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_bgp_global.py b/plugins/modules/vyos_bgp_global.py deleted file mode 100644 index 6a08f4f..0000000 --- a/plugins/modules/vyos_bgp_global.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/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_bgp_global -short_description: Manage BGP global configuration on VyOS via the REST API. -description: - - Manages BGP global parameters on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_bgp_global) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: BGP global configuration. - type: dict - suboptions: - as_number: - description: Local BGP AS number. - type: int - required: true - router_id: - description: BGP router ID (IPv4 address). - type: str - neighbors: - description: List of BGP neighbor configurations. - type: list - elements: dict - suboptions: - neighbor: - description: Neighbor IP address. - type: str - required: true - remote_as: - description: Neighbor's AS number. - type: int - description: - description: Neighbor description. - type: str - password: - description: MD5 authentication password. - type: str - no_log: true - update_source: - description: Local interface or address for BGP session. - type: str - ebgp_multihop: - description: Maximum hops for eBGP sessions. - type: int - shutdown: - description: Whether to administratively shut down the neighbor. - type: bool - networks: - description: List of networks to originate. - type: list - elements: dict - suboptions: - prefix: - description: Network prefix to advertise. - type: str - required: true - route_map: - description: Route map to apply. - type: str - redistribute: - description: List of protocols to redistribute into BGP. - type: list - elements: dict - suboptions: - protocol: - description: Protocol to redistribute. - type: str - choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] - required: true - metric: - description: Metric for redistributed routes. - type: int - route_map: - description: Route map to apply. - type: str - state: - description: - - C(merged): Merge BGP config with existing. - - C(replaced): Replace entire BGP config. - - C(deleted): Remove BGP configuration. - - C(gathered): Read BGP config from device. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_bgp_global -examples: | - - name: Configure BGP - vyos.rest.vyos_bgp_global: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - as_number: 65001 - router_id: 192.168.1.1 - neighbors: - - neighbor: 192.168.2.1 - remote_as: 65002 - description: "Peer AS65002" - networks: - - prefix: 10.0.0.0/8 - redistribute: - - protocol: connected - state: merged - - - name: Remove BGP configuration - vyos.rest.vyos_bgp_global: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: BGP config before the module ran. - returned: always - type: dict -after: - description: BGP config after the module ran. - returned: when changed - type: dict -gathered: - description: BGP config read from device (state=gathered). - returned: when state is gathered - type: dict -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BGP_BASE = ["protocols", "bgp"] - - -def _get_bgp(client): - try: - result = client.retrieve_show_config(_BGP_BASE) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def _apply_bgp(client, config, commands): - asn = str(config["as_number"]) - base = _BGP_BASE + [asn] - client.configure_set(base) - commands.append("set protocols bgp {a}".format(a=asn)) - - if config.get("router_id"): - client.configure_set(base + ["parameters", "router-id"], config["router_id"]) - commands.append( - "set protocols bgp {a} parameters router-id {r}".format( - a=asn, - r=config["router_id"], - ), - ) - - for nbr in config.get("neighbors") or []: - nbase = base + ["neighbor", nbr["neighbor"]] - client.configure_set(nbase) - commands.append("set protocols bgp {a} neighbor {n}".format(a=asn, n=nbr["neighbor"])) - if nbr.get("remote_as"): - client.configure_set(nbase + ["remote-as"], str(nbr["remote_as"])) - commands.append( - "set protocols bgp {a} neighbor {n} remote-as {r}".format( - a=asn, - n=nbr["neighbor"], - r=nbr["remote_as"], - ), - ) - if nbr.get("description"): - client.configure_set(nbase + ["description"], nbr["description"]) - if nbr.get("password"): - client.configure_set(nbase + ["password"], nbr["password"]) - if nbr.get("update_source"): - client.configure_set(nbase + ["update-source"], nbr["update_source"]) - if nbr.get("ebgp_multihop"): - client.configure_set(nbase + ["ebgp-multihop"], str(nbr["ebgp_multihop"])) - if nbr.get("shutdown"): - client.configure_set(nbase + ["shutdown"]) - commands.append( - "set protocols bgp {a} neighbor {n} shutdown".format( - a=asn, - n=nbr["neighbor"], - ), - ) - - for net in config.get("networks") or []: - nbase = base + ["address-family", "ipv4-unicast", "network", net["prefix"]] - client.configure_set(nbase) - commands.append( - "set protocols bgp {a} address-family ipv4-unicast network {p}".format( - a=asn, - p=net["prefix"], - ), - ) - if net.get("route_map"): - client.configure_set(nbase + ["route-map"], net["route_map"]) - - for redist in config.get("redistribute") or []: - rbase = base + [ - "address-family", - "ipv4-unicast", - "redistribute", - redist["protocol"], - ] - client.configure_set(rbase) - commands.append( - "set protocols bgp {a} address-family ipv4-unicast redistribute {p}".format( - a=asn, - p=redist["protocol"], - ), - ) - if redist.get("metric"): - client.configure_set(rbase + ["metric"], str(redist["metric"])) - if redist.get("route_map"): - client.configure_set(rbase + ["route-map"], redist["route_map"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - as_number=dict(type="int", required=True), - router_id=dict(type="str"), - neighbors=dict( - type="list", - elements="dict", - options=dict( - neighbor=dict(type="str", required=True), - remote_as=dict(type="int"), - description=dict(type="str"), - password=dict(type="str", no_log=True), - update_source=dict(type="str"), - ebgp_multihop=dict(type="int"), - shutdown=dict(type="bool"), - ), - ), - networks=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str", required=True), - route_map=dict(type="str"), - ), - ), - redistribute=dict( - type="list", - elements="dict", - options=dict( - protocol=dict( - type="str", - required=True, - choices=[ - "connected", - "kernel", - "ospf", - "ospfv3", - "rip", - "ripng", - "static", - ], - ), - metric=dict(type="int"), - route_map=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[ - ("state", "merged", ["config"]), - ("state", "replaced", ["config"]), - ], - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - - before = _get_bgp(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BGP_BASE) - commands.append("delete protocols bgp") - changed = True - - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BGP_BASE) - commands.append("delete protocols bgp") - _apply_bgp(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_bgp(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_bgp_global.py.preview b/plugins/modules/vyos_bgp_global.py.preview new file mode 100644 index 0000000..6a08f4f --- /dev/null +++ b/plugins/modules/vyos_bgp_global.py.preview @@ -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_bgp_global +short_description: Manage BGP global configuration on VyOS via the REST API. +description: + - Manages BGP global parameters on VyOS devices using the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_bgp_global) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: BGP global configuration. + type: dict + suboptions: + as_number: + description: Local BGP AS number. + type: int + required: true + router_id: + description: BGP router ID (IPv4 address). + type: str + neighbors: + description: List of BGP neighbor configurations. + type: list + elements: dict + suboptions: + neighbor: + description: Neighbor IP address. + type: str + required: true + remote_as: + description: Neighbor's AS number. + type: int + description: + description: Neighbor description. + type: str + password: + description: MD5 authentication password. + type: str + no_log: true + update_source: + description: Local interface or address for BGP session. + type: str + ebgp_multihop: + description: Maximum hops for eBGP sessions. + type: int + shutdown: + description: Whether to administratively shut down the neighbor. + type: bool + networks: + description: List of networks to originate. + type: list + elements: dict + suboptions: + prefix: + description: Network prefix to advertise. + type: str + required: true + route_map: + description: Route map to apply. + type: str + redistribute: + description: List of protocols to redistribute into BGP. + type: list + elements: dict + suboptions: + protocol: + description: Protocol to redistribute. + type: str + choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] + required: true + metric: + description: Metric for redistributed routes. + type: int + route_map: + description: Route map to apply. + type: str + state: + description: + - C(merged): Merge BGP config with existing. + - C(replaced): Replace entire BGP config. + - C(deleted): Remove BGP configuration. + - C(gathered): Read BGP config from device. + type: str + choices: [merged, replaced, deleted, gathered] + default: merged + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.vyos.vyos_bgp_global +examples: | + - name: Configure BGP + vyos.rest.vyos_bgp_global: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + as_number: 65001 + router_id: 192.168.1.1 + neighbors: + - neighbor: 192.168.2.1 + remote_as: 65002 + description: "Peer AS65002" + networks: + - prefix: 10.0.0.0/8 + redistribute: + - protocol: connected + state: merged + + - name: Remove BGP configuration + vyos.rest.vyos_bgp_global: + hostname: 192.168.1.1 + api_key: MY-KEY + state: deleted +""" + +RETURN = r""" +before: + description: BGP config before the module ran. + returned: always + type: dict +after: + description: BGP config after the module ran. + returned: when changed + type: dict +gathered: + description: BGP config read from device (state=gathered). + returned: when state is gathered + type: dict +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_BGP_BASE = ["protocols", "bgp"] + + +def _get_bgp(client): + try: + result = client.retrieve_show_config(_BGP_BASE) + return result.get("data") or {} + except VyOSRestError: + return {} + + +def _apply_bgp(client, config, commands): + asn = str(config["as_number"]) + base = _BGP_BASE + [asn] + client.configure_set(base) + commands.append("set protocols bgp {a}".format(a=asn)) + + if config.get("router_id"): + client.configure_set(base + ["parameters", "router-id"], config["router_id"]) + commands.append( + "set protocols bgp {a} parameters router-id {r}".format( + a=asn, + r=config["router_id"], + ), + ) + + for nbr in config.get("neighbors") or []: + nbase = base + ["neighbor", nbr["neighbor"]] + client.configure_set(nbase) + commands.append("set protocols bgp {a} neighbor {n}".format(a=asn, n=nbr["neighbor"])) + if nbr.get("remote_as"): + client.configure_set(nbase + ["remote-as"], str(nbr["remote_as"])) + commands.append( + "set protocols bgp {a} neighbor {n} remote-as {r}".format( + a=asn, + n=nbr["neighbor"], + r=nbr["remote_as"], + ), + ) + if nbr.get("description"): + client.configure_set(nbase + ["description"], nbr["description"]) + if nbr.get("password"): + client.configure_set(nbase + ["password"], nbr["password"]) + if nbr.get("update_source"): + client.configure_set(nbase + ["update-source"], nbr["update_source"]) + if nbr.get("ebgp_multihop"): + client.configure_set(nbase + ["ebgp-multihop"], str(nbr["ebgp_multihop"])) + if nbr.get("shutdown"): + client.configure_set(nbase + ["shutdown"]) + commands.append( + "set protocols bgp {a} neighbor {n} shutdown".format( + a=asn, + n=nbr["neighbor"], + ), + ) + + for net in config.get("networks") or []: + nbase = base + ["address-family", "ipv4-unicast", "network", net["prefix"]] + client.configure_set(nbase) + commands.append( + "set protocols bgp {a} address-family ipv4-unicast network {p}".format( + a=asn, + p=net["prefix"], + ), + ) + if net.get("route_map"): + client.configure_set(nbase + ["route-map"], net["route_map"]) + + for redist in config.get("redistribute") or []: + rbase = base + [ + "address-family", + "ipv4-unicast", + "redistribute", + redist["protocol"], + ] + client.configure_set(rbase) + commands.append( + "set protocols bgp {a} address-family ipv4-unicast redistribute {p}".format( + a=asn, + p=redist["protocol"], + ), + ) + if redist.get("metric"): + client.configure_set(rbase + ["metric"], str(redist["metric"])) + if redist.get("route_map"): + client.configure_set(rbase + ["route-map"], redist["route_map"]) + + +def main(): + argument_spec = dict( + config=dict( + type="dict", + options=dict( + as_number=dict(type="int", required=True), + router_id=dict(type="str"), + neighbors=dict( + type="list", + elements="dict", + options=dict( + neighbor=dict(type="str", required=True), + remote_as=dict(type="int"), + description=dict(type="str"), + password=dict(type="str", no_log=True), + update_source=dict(type="str"), + ebgp_multihop=dict(type="int"), + shutdown=dict(type="bool"), + ), + ), + networks=dict( + type="list", + elements="dict", + options=dict( + prefix=dict(type="str", required=True), + route_map=dict(type="str"), + ), + ), + redistribute=dict( + type="list", + elements="dict", + options=dict( + protocol=dict( + type="str", + required=True, + choices=[ + "connected", + "kernel", + "ospf", + "ospfv3", + "rip", + "ripng", + "static", + ], + ), + metric=dict(type="int"), + route_map=dict(type="str"), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "merged", ["config"]), + ("state", "replaced", ["config"]), + ], + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") + commands = [] + changed = False + + before = _get_bgp(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + if before: + client.configure_delete(_BGP_BASE) + commands.append("delete protocols bgp") + changed = True + + elif state in ("merged", "replaced"): + if state == "replaced" and before: + client.configure_delete(_BGP_BASE) + commands.append("delete protocols bgp") + _apply_bgp(client, config, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_bgp(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_config_file.py b/plugins/modules/vyos_config_file.py deleted file mode 100644 index 9d5e48a..0000000 --- a/plugins/modules/vyos_config_file.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/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_config_file -short_description: Save or load VyOS configuration files via the REST API. -description: - - Manages VyOS configuration persistence via the HTTPS REST API - (C(/config-file) endpoint). - - Use I(op=save) to write the running configuration to a file. - - Use I(op=load) to load a configuration from a file and apply it. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - op: - description: - - C(save) - Save the running configuration to I(file) or the default C(/config/config.boot). - - C(load) - Load and commit the configuration from I(file). - type: str - choices: [save, load] - default: save - file: - description: - - Path on the VyOS device for save/load target. - - If omitted for C(save), the device uses its default boot config path. - type: str - hostname: - description: - - IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: - - HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: - - API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: - - Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: - - Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+""" - -RETURN = r""" -output: - description: Text output from the config-file operation. - returned: success - type: str -""" - -EXAMPLES = r""" -- name: Save running config (default location) - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - -- name: Save running config to a custom file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - file: /config/backups/config.boot.bak - -- name: Load a configuration from file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: load - file: /config/backups/config.boot.bak -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - op=dict(type="str", default="save", choices=["save", "load"]), - file=dict(type="str"), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("op", "load", ["file"])], - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - op = module.params["op"] - file_path = module.params.get("file") - - try: - if op == "save": - result = client.config_file_save(file_path) - else: - result = client.config_file_load(file_path) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_config_file.py.preview b/plugins/modules/vyos_config_file.py.preview new file mode 100644 index 0000000..9d5e48a --- /dev/null +++ b/plugins/modules/vyos_config_file.py.preview @@ -0,0 +1,130 @@ +#!/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_config_file +short_description: Save or load VyOS configuration files via the REST API. +description: + - Manages VyOS configuration persistence via the HTTPS REST API + (C(/config-file) endpoint). + - Use I(op=save) to write the running configuration to a file. + - Use I(op=load) to load a configuration from a file and apply it. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + op: + description: + - C(save) - Save the running configuration to I(file) or the default C(/config/config.boot). + - C(load) - Load and commit the configuration from I(file). + type: str + choices: [save, load] + default: save + file: + description: + - Path on the VyOS device for save/load target. + - If omitted for C(save), the device uses its default boot config path. + type: str + hostname: + description: + - IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: + - HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: + - API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: + - Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: + - Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+""" + +RETURN = r""" +output: + description: Text output from the config-file operation. + returned: success + type: str +""" + +EXAMPLES = r""" +- name: Save running config (default location) + vyos.rest.vyos_config_file: + hostname: 192.168.1.1 + api_key: MY-KEY + op: save + +- name: Save running config to a custom file + vyos.rest.vyos_config_file: + hostname: 192.168.1.1 + api_key: MY-KEY + op: save + file: /config/backups/config.boot.bak + +- name: Load a configuration from file + vyos.rest.vyos_config_file: + hostname: 192.168.1.1 + api_key: MY-KEY + op: load + file: /config/backups/config.boot.bak +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + op=dict(type="str", default="save", choices=["save", "load"]), + file=dict(type="str"), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("op", "load", ["file"])], + supports_check_mode=False, + ) + + client = VyOSRestClient(module) + op = module.params["op"] + file_path = module.params.get("file") + + try: + if op == "save": + result = client.config_file_save(file_path) + else: + result = client.config_file_load(file_path) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=True, output=result.get("data", "")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_configure.py b/plugins/modules/vyos_configure.py deleted file mode 100644 index 355a78c..0000000 --- a/plugins/modules/vyos_configure.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/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_configure -short_description: Manage VyOS device configuration via the REST API. -description: - - Sends one or more C(set) or C(delete) configuration commands to a VyOS - device via its HTTPS REST API (C(/configure) endpoint). - - Each command in I(commands) is executed and committed atomically. - - Supports Ansible check-mode (C(check_mode=yes)) - no changes are applied. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - commands: - description: - - List of VyOS configuration commands. - - Each item may be either a plain string in VyOS CLI syntax - (e.g. C(set interfaces ethernet eth0 description 'WAN')) - or a dict with keys C(op), C(path), and optionally C(value). - type: list - elements: raw - required: true - save: - description: - - Whether to save the configuration to disk after a successful commit. - type: bool - default: false - hostname: - description: - - IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: - - HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: - - API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: - - Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: - - Validate the device's TLS certificate. - type: bool - default: false -notes: - - The VyOS HTTP API must be enabled on the device before using this module. - - Enable with C(set service https api keys id key ) and - C(set service https api rest), then C(commit && save). - - Tested against VyOS 1.3 and 1.4. -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.rest.vyos_retrieve - - module: vyos.rest.vyos_show""" - -RETURN = r""" -commands_sent: - description: The list of commands dispatched to the API. - returned: always - type: list - elements: dict - sample: - - op: set - path: ["system", "host-name"] - value: vyos-router -changed: - description: Whether the device configuration was modified. - returned: always - type: bool -saved: - description: Whether the configuration was saved to disk. - returned: always - type: bool -""" - -import re - - -EXAMPLES = r""" -- name: Set hostname via REST API - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set system host-name vyos-router" - -- name: Configure an interface description and address - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set interfaces ethernet eth1 description 'UPLINK'" - - "set interfaces ethernet eth1 address 10.0.0.1/30" - save: true - -- name: Delete a static route using dict syntax - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - op: delete - path: ["protocols", "static", "route", "192.0.2.0/24"] -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _parse_cli_command(cmd_str): - """Parse a VyOS CLI-syntax string into an API payload dict. - - Supports: - set [] - delete - comment - - Args: - cmd_str (str): e.g. "set interfaces ethernet eth0 description 'WAN'" - - Returns: - dict: e.g. {"op": "set", "path": [...], "value": "WAN"} - """ - cmd_str = cmd_str.strip() - op_match = re.match(r"^(set|delete|comment)\s+(.+)$", cmd_str, re.IGNORECASE) - if not op_match: - raise ValueError( - "Cannot parse command '{cmd}'. Must start with set/delete/comment.".format( - cmd=cmd_str, - ), - ) - op = op_match.group(1).lower() - rest = op_match.group(2).strip() - - # Extract a quoted or bare trailing value for 'set' - value = None - if op == "set": - # Look for a trailing quoted value: '...' or "..." - quoted = re.search(r"""(['"])(.*?)\1\s*$""", rest) - if quoted: - value = quoted.group(2) - rest = rest[: quoted.start()].strip() - else: - # Bare final token may be the value if the second-to-last token - # is a known leaf keyword – use a simple heuristic: if the - # last segment has no sub-tokens we treat it as value. - tokens = rest.split() - # We cannot know the schema here, so we pass the full path and - # no value; the device will decide. - path = tokens - return {"op": op, "path": path} - - path = rest.split() - payload = {"op": op, "path": path} - if value is not None: - payload["value"] = value - return payload - - -def _normalise_commands(raw_commands): - """Normalise the mixed list of str/dict commands into API dicts.""" - result = [] - for item in raw_commands: - if isinstance(item, dict): - if "op" not in item or "path" not in item: - raise ValueError( - "Command dict must have 'op' and 'path' keys: {item}".format( - item=item, - ), - ) - result.append(item) - elif isinstance(item, str): - result.append(_parse_cli_command(item)) - else: - raise ValueError( - "Command must be a string or dict, got: {t}".format(t=type(item)), - ) - return result - - -def main(): - argument_spec = dict( - commands=dict(type="list", elements="raw", required=True), - save=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - try: - commands = _normalise_commands(module.params["commands"]) - except ValueError as exc: - module.fail_json(msg=str(exc)) - - if module.check_mode: - module.exit_json( - changed=True, - commands_sent=commands, - saved=False, - msg="Check mode: no changes applied.", - ) - - client = VyOSRestClient(module) - changed = False - saved = False - - try: - for cmd in commands: - op = cmd["op"].lower() - path = cmd["path"] - value = cmd.get("value") - if op == "set": - client.configure_set(path, value) - elif op == "delete": - client.configure_delete(path) - elif op == "comment": - client.configure_comment(path, value or "") - else: - module.fail_json( - msg="Unsupported op '{op}'".format(op=op), - ) - changed = True - - if module.params["save"]: - client.config_file_save() - saved = True - - except VyOSRestError as exc: - module.fail_json(msg=str(exc), commands_sent=commands) - - module.exit_json(changed=changed, commands_sent=commands, saved=saved) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_configure.py.preview b/plugins/modules/vyos_configure.py.preview new file mode 100644 index 0000000..355a78c --- /dev/null +++ b/plugins/modules/vyos_configure.py.preview @@ -0,0 +1,259 @@ +#!/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_configure +short_description: Manage VyOS device configuration via the REST API. +description: + - Sends one or more C(set) or C(delete) configuration commands to a VyOS + device via its HTTPS REST API (C(/configure) endpoint). + - Each command in I(commands) is executed and committed atomically. + - Supports Ansible check-mode (C(check_mode=yes)) - no changes are applied. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + commands: + description: + - List of VyOS configuration commands. + - Each item may be either a plain string in VyOS CLI syntax + (e.g. C(set interfaces ethernet eth0 description 'WAN')) + or a dict with keys C(op), C(path), and optionally C(value). + type: list + elements: raw + required: true + save: + description: + - Whether to save the configuration to disk after a successful commit. + type: bool + default: false + hostname: + description: + - IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: + - HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: + - API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: + - Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: + - Validate the device's TLS certificate. + type: bool + default: false +notes: + - The VyOS HTTP API must be enabled on the device before using this module. + - Enable with C(set service https api keys id key ) and + C(set service https api rest), then C(commit && save). + - Tested against VyOS 1.3 and 1.4. +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.rest.vyos_retrieve + - module: vyos.rest.vyos_show""" + +RETURN = r""" +commands_sent: + description: The list of commands dispatched to the API. + returned: always + type: list + elements: dict + sample: + - op: set + path: ["system", "host-name"] + value: vyos-router +changed: + description: Whether the device configuration was modified. + returned: always + type: bool +saved: + description: Whether the configuration was saved to disk. + returned: always + type: bool +""" + +import re + + +EXAMPLES = r""" +- name: Set hostname via REST API + vyos.rest.vyos_configure: + hostname: 192.168.1.1 + api_key: MY-KEY + commands: + - "set system host-name vyos-router" + +- name: Configure an interface description and address + vyos.rest.vyos_configure: + hostname: 192.168.1.1 + api_key: MY-KEY + commands: + - "set interfaces ethernet eth1 description 'UPLINK'" + - "set interfaces ethernet eth1 address 10.0.0.1/30" + save: true + +- name: Delete a static route using dict syntax + vyos.rest.vyos_configure: + hostname: 192.168.1.1 + api_key: MY-KEY + commands: + - op: delete + path: ["protocols", "static", "route", "192.0.2.0/24"] +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def _parse_cli_command(cmd_str): + """Parse a VyOS CLI-syntax string into an API payload dict. + + Supports: + set [] + delete + comment + + Args: + cmd_str (str): e.g. "set interfaces ethernet eth0 description 'WAN'" + + Returns: + dict: e.g. {"op": "set", "path": [...], "value": "WAN"} + """ + cmd_str = cmd_str.strip() + op_match = re.match(r"^(set|delete|comment)\s+(.+)$", cmd_str, re.IGNORECASE) + if not op_match: + raise ValueError( + "Cannot parse command '{cmd}'. Must start with set/delete/comment.".format( + cmd=cmd_str, + ), + ) + op = op_match.group(1).lower() + rest = op_match.group(2).strip() + + # Extract a quoted or bare trailing value for 'set' + value = None + if op == "set": + # Look for a trailing quoted value: '...' or "..." + quoted = re.search(r"""(['"])(.*?)\1\s*$""", rest) + if quoted: + value = quoted.group(2) + rest = rest[: quoted.start()].strip() + else: + # Bare final token may be the value if the second-to-last token + # is a known leaf keyword – use a simple heuristic: if the + # last segment has no sub-tokens we treat it as value. + tokens = rest.split() + # We cannot know the schema here, so we pass the full path and + # no value; the device will decide. + path = tokens + return {"op": op, "path": path} + + path = rest.split() + payload = {"op": op, "path": path} + if value is not None: + payload["value"] = value + return payload + + +def _normalise_commands(raw_commands): + """Normalise the mixed list of str/dict commands into API dicts.""" + result = [] + for item in raw_commands: + if isinstance(item, dict): + if "op" not in item or "path" not in item: + raise ValueError( + "Command dict must have 'op' and 'path' keys: {item}".format( + item=item, + ), + ) + result.append(item) + elif isinstance(item, str): + result.append(_parse_cli_command(item)) + else: + raise ValueError( + "Command must be a string or dict, got: {t}".format(t=type(item)), + ) + return result + + +def main(): + argument_spec = dict( + commands=dict(type="list", elements="raw", required=True), + save=dict(type="bool", default=False), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + try: + commands = _normalise_commands(module.params["commands"]) + except ValueError as exc: + module.fail_json(msg=str(exc)) + + if module.check_mode: + module.exit_json( + changed=True, + commands_sent=commands, + saved=False, + msg="Check mode: no changes applied.", + ) + + client = VyOSRestClient(module) + changed = False + saved = False + + try: + for cmd in commands: + op = cmd["op"].lower() + path = cmd["path"] + value = cmd.get("value") + if op == "set": + client.configure_set(path, value) + elif op == "delete": + client.configure_delete(path) + elif op == "comment": + client.configure_comment(path, value or "") + else: + module.fail_json( + msg="Unsupported op '{op}'".format(op=op), + ) + changed = True + + if module.params["save"]: + client.config_file_save() + saved = True + + except VyOSRestError as exc: + module.fail_json(msg=str(exc), commands_sent=commands) + + module.exit_json(changed=changed, commands_sent=commands, saved=saved) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_facts.py b/plugins/modules/vyos_facts.py deleted file mode 100644 index 66e684b..0000000 --- a/plugins/modules/vyos_facts.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/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_facts -short_description: Get facts about VyOS devices via REST API. -description: - - Collects facts from VyOS devices using the HTTPS REST API. - - Returns system info and optionally per-resource configuration facts. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - gather_subset: - description: - - Restrict facts to a subset. Use C(all), C(default), C(config), or C(min). - type: list - elements: str - default: ["min"] - gather_network_resources: - description: - - Which network resource facts to gather. - type: list - elements: str - available_network_resources: - description: - - When true, return a list of available resource names. - type: bool - default: false - hostname: - description: - - Device IP or FQDN. - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -ansible_facts: - description: Dictionary of facts keyed by resource name. - returned: always - type: dict -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_NETWORK_RESOURCES = [ - "interfaces", - "l3_interfaces", - "lag_interfaces", - "lldp_global", - "lldp_interfaces", - "static_routes", - "firewall_rules", - "firewall_global", - "firewall_interfaces", - "ospfv2", - "ospfv3", -] - -_RESOURCE_PATHS = { - "interfaces": ["interfaces"], - "l3_interfaces": ["interfaces"], - "lag_interfaces": ["interfaces", "bonding"], - "lldp_global": ["service", "lldp"], - "lldp_interfaces": ["service", "lldp"], - "static_routes": ["protocols", "static"], - "firewall_rules": ["firewall"], - "firewall_global": ["firewall"], - "firewall_interfaces": ["firewall"], - "ospfv2": ["protocols", "ospf"], - "ospfv3": ["protocols", "ospfv3"], -} - - -def _get_subset(client, resource): - path = _RESOURCE_PATHS.get(resource, []) - try: - result = client.retrieve_show_config(path) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def main(): - argument_spec = dict( - gather_subset=dict(type="list", elements="str", default=["min"]), - gather_network_resources=dict(type="list", elements="str"), - available_network_resources=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - - client = VyOSRestClient(module) - facts = {} - - if module.params["available_network_resources"]: - facts["available_network_resources"] = _NETWORK_RESOURCES - - gather_subset = module.params["gather_subset"] - get_all = "all" in gather_subset - - # Always gather system info for "min" or "all" - if get_all or "min" in gather_subset or "default" in gather_subset: - try: - info = client.info() - facts["version"] = info.get("data", {}).get("version", "") - facts["hostname"] = info.get("data", {}).get("hostname", "") - except VyOSRestError: - pass - - # Config subset - if get_all or "config" in gather_subset: - try: - result = client.retrieve_show_config([]) - facts["config"] = result.get("data") or {} - except VyOSRestError: - facts["config"] = {} - - # Network resources - requested = module.params.get("gather_network_resources") or [] - if "all" in requested: - requested = _NETWORK_RESOURCES - - for resource in requested: - if resource.startswith("!"): - continue - if resource in _NETWORK_RESOURCES: - facts["network_resources"] = facts.get("network_resources", {}) - facts["network_resources"][resource] = _get_subset(client, resource) - - module.exit_json(changed=False, ansible_facts=facts) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_facts.py.preview b/plugins/modules/vyos_facts.py.preview new file mode 100644 index 0000000..66e684b --- /dev/null +++ b/plugins/modules/vyos_facts.py.preview @@ -0,0 +1,161 @@ +#!/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_facts +short_description: Get facts about VyOS devices via REST API. +description: + - Collects facts from VyOS devices using the HTTPS REST API. + - Returns system info and optionally per-resource configuration facts. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + gather_subset: + description: + - Restrict facts to a subset. Use C(all), C(default), C(config), or C(min). + type: list + elements: str + default: ["min"] + gather_network_resources: + description: + - Which network resource facts to gather. + type: list + elements: str + available_network_resources: + description: + - When true, return a list of available resource names. + type: bool + default: false + hostname: + description: + - Device IP or FQDN. + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +ansible_facts: + description: Dictionary of facts keyed by resource name. + returned: always + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_NETWORK_RESOURCES = [ + "interfaces", + "l3_interfaces", + "lag_interfaces", + "lldp_global", + "lldp_interfaces", + "static_routes", + "firewall_rules", + "firewall_global", + "firewall_interfaces", + "ospfv2", + "ospfv3", +] + +_RESOURCE_PATHS = { + "interfaces": ["interfaces"], + "l3_interfaces": ["interfaces"], + "lag_interfaces": ["interfaces", "bonding"], + "lldp_global": ["service", "lldp"], + "lldp_interfaces": ["service", "lldp"], + "static_routes": ["protocols", "static"], + "firewall_rules": ["firewall"], + "firewall_global": ["firewall"], + "firewall_interfaces": ["firewall"], + "ospfv2": ["protocols", "ospf"], + "ospfv3": ["protocols", "ospfv3"], +} + + +def _get_subset(client, resource): + path = _RESOURCE_PATHS.get(resource, []) + try: + result = client.retrieve_show_config(path) + return result.get("data") or {} + except VyOSRestError: + return {} + + +def main(): + argument_spec = dict( + gather_subset=dict(type="list", elements="str", default=["min"]), + gather_network_resources=dict(type="list", elements="str"), + available_network_resources=dict(type="bool", default=False), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + client = VyOSRestClient(module) + facts = {} + + if module.params["available_network_resources"]: + facts["available_network_resources"] = _NETWORK_RESOURCES + + gather_subset = module.params["gather_subset"] + get_all = "all" in gather_subset + + # Always gather system info for "min" or "all" + if get_all or "min" in gather_subset or "default" in gather_subset: + try: + info = client.info() + facts["version"] = info.get("data", {}).get("version", "") + facts["hostname"] = info.get("data", {}).get("hostname", "") + except VyOSRestError: + pass + + # Config subset + if get_all or "config" in gather_subset: + try: + result = client.retrieve_show_config([]) + facts["config"] = result.get("data") or {} + except VyOSRestError: + facts["config"] = {} + + # Network resources + requested = module.params.get("gather_network_resources") or [] + if "all" in requested: + requested = _NETWORK_RESOURCES + + for resource in requested: + if resource.startswith("!"): + continue + if resource in _NETWORK_RESOURCES: + facts["network_resources"] = facts.get("network_resources", {}) + facts["network_resources"][resource] = _get_subset(client, resource) + + module.exit_json(changed=False, ansible_facts=facts) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py deleted file mode 100644 index a3974fd..0000000 --- a/plugins/modules/vyos_firewall_global.py +++ /dev/null @@ -1,360 +0,0 @@ -#!/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_firewall_global -short_description: Firewall global resource module via REST API. -description: - - Manages global VyOS firewall parameters (groups, ICMP redirect policy, - source validation, ping policy) via the HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: Global firewall configuration. - type: dict - suboptions: - validation: - description: Source-validation policy (strict/loose/disable). - type: str - choices: [strict, loose, disable] - config_trap: - description: SNMP trap on firewall config changes. - type: bool - ping: - description: ICMP echo policy. - type: dict - suboptions: - all: - type: bool - broadcast: - type: bool - route_redirects: - description: ICMP redirect / source-route options per AFI. - type: list - elements: dict - suboptions: - afi: - type: str - choices: [ipv4, ipv6] - required: true - icmp_redirects: - type: dict - suboptions: - send: - type: bool - receive: - type: bool - ip_src_route: - type: bool - group: - description: Firewall object groups. - type: dict - suboptions: - address_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of IP addresses or ranges. - type: list - elements: dict - suboptions: - address: - type: str - afi: - type: str - default: ipv4 - choices: [ipv4, ipv6] - network_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of network prefixes. - type: list - elements: dict - suboptions: - address: - type: str - port_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of port numbers or ranges. - type: list - elements: dict - suboptions: - port: - type: str - state: - description: Desired state. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_FW_BASE = ["firewall"] - - -def _get(client): - try: - r = client.retrieve_show_config(_FW_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - if config.get("validation"): - path = ( - _FW_BASE + ["ip", "src-route"] - if config["validation"] == "disable" - else _FW_BASE + ["ip", "enable-default-log"] - ) - # Use the proper path - p = ( - ["firewall", "global-options", "source-validation"] - if client.retrieve_exists(["firewall", "global-options"]) - else ["firewall", "ip", "src-route"] - ) - client.configure_set(["firewall", "ip", "source-validation"], config["validation"]) - commands.append("set firewall ip source-validation {v}".format(v=config["validation"])) - - if config.get("config_trap") is not None: - val = "enable" if config["config_trap"] else "disable" - client.configure_set(["firewall", "config-trap"], val) - commands.append("set firewall config-trap {v}".format(v=val)) - - ping = config.get("ping") or {} - if ping.get("all") is not None: - action = "enable" if ping["all"] else "disable" - client.configure_set(["firewall", "ip", "ping", "all"], action) - commands.append("set firewall ip ping all {a}".format(a=action)) - if ping.get("broadcast") is not None: - action = "enable" if ping["broadcast"] else "disable" - client.configure_set(["firewall", "ip", "ping", "broadcast"], action) - commands.append("set firewall ip ping broadcast {a}".format(a=action)) - - for rr in config.get("route_redirects") or []: - afi = rr["afi"] - ip_key = "ip" if afi == "ipv4" else "ipv6" - icmp = rr.get("icmp_redirects") or {} - if icmp.get("send") is not None: - p = ["firewall", ip_key, "send-redirects"] - client.configure_set(p, "enable" if icmp["send"] else "disable") - commands.append( - "set {p} {v}".format(p=" ".join(p), v="enable" if icmp["send"] else "disable"), - ) - if icmp.get("receive") is not None: - p = ["firewall", ip_key, "disable-forwarding"] - client.configure_set(p, "enable" if not icmp["receive"] else "disable") - if rr.get("ip_src_route") is not None: - p = ["firewall", ip_key, "source-route"] - client.configure_set(p, "enable" if rr["ip_src_route"] else "disable") - - grp = config.get("group") or {} - for ag in grp.get("address_group") or []: - gpath = ["firewall", "group", "address-group", ag["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if ag.get("description"): - client.configure_set(gpath + ["description"], ag["description"]) - for m in ag.get("members") or []: - client.configure_set(gpath + ["address"], m["address"]) - commands.append("set {p} address {a}".format(p=" ".join(gpath), a=m["address"])) - - for ng in grp.get("network_group") or []: - gpath = ["firewall", "group", "network-group", ng["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if ng.get("description"): - client.configure_set(gpath + ["description"], ng["description"]) - for m in ng.get("members") or []: - client.configure_set(gpath + ["network"], m["address"]) - - for pg in grp.get("port_group") or []: - gpath = ["firewall", "group", "port-group", pg["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if pg.get("description"): - client.configure_set(gpath + ["description"], pg["description"]) - for m in pg.get("members") or []: - client.configure_set(gpath + ["port"], str(m["port"])) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - validation=dict(type="str", choices=["strict", "loose", "disable"]), - config_trap=dict(type="bool"), - ping=dict( - type="dict", - options=dict(all=dict(type="bool"), broadcast=dict(type="bool")), - ), - route_redirects=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - icmp_redirects=dict( - type="dict", - options=dict(send=dict(type="bool"), receive=dict(type="bool")), - ), - ip_src_route=dict(type="bool"), - ), - ), - group=dict( - type="dict", - options=dict( - address_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - afi=dict(type="str", default="ipv4"), - members=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str")), - ), - ), - ), - network_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - members=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str")), - ), - ), - ), - port_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - members=dict( - type="list", - elements="dict", - options=dict(port=dict(type="str")), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_FW_BASE) - commands.append("delete firewall") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_FW_BASE) - commands.append("delete firewall") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_global.py.preview b/plugins/modules/vyos_firewall_global.py.preview new file mode 100644 index 0000000..a3974fd --- /dev/null +++ b/plugins/modules/vyos_firewall_global.py.preview @@ -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_firewall_global +short_description: Firewall global resource module via REST API. +description: + - Manages global VyOS firewall parameters (groups, ICMP redirect policy, + source validation, ping policy) via the HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: Global firewall configuration. + type: dict + suboptions: + validation: + description: Source-validation policy (strict/loose/disable). + type: str + choices: [strict, loose, disable] + config_trap: + description: SNMP trap on firewall config changes. + type: bool + ping: + description: ICMP echo policy. + type: dict + suboptions: + all: + type: bool + broadcast: + type: bool + route_redirects: + description: ICMP redirect / source-route options per AFI. + type: list + elements: dict + suboptions: + afi: + type: str + choices: [ipv4, ipv6] + required: true + icmp_redirects: + type: dict + suboptions: + send: + type: bool + receive: + type: bool + ip_src_route: + type: bool + group: + description: Firewall object groups. + type: dict + suboptions: + address_group: + type: list + elements: dict + suboptions: + name: + type: str + required: true + description: + type: str + members: + description: List of IP addresses or ranges. + type: list + elements: dict + suboptions: + address: + type: str + afi: + type: str + default: ipv4 + choices: [ipv4, ipv6] + network_group: + type: list + elements: dict + suboptions: + name: + type: str + required: true + description: + type: str + members: + description: List of network prefixes. + type: list + elements: dict + suboptions: + address: + type: str + port_group: + type: list + elements: dict + suboptions: + name: + type: str + required: true + description: + type: str + members: + description: List of port numbers or ranges. + type: list + elements: dict + suboptions: + port: + type: str + state: + description: Desired state. + type: str + choices: [merged, replaced, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: dict +after: + returned: when changed + type: dict +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_FW_BASE = ["firewall"] + + +def _get(client): + try: + r = client.retrieve_show_config(_FW_BASE) + return r.get("data") or {} + except VyOSRestError: + return {} + + +def _apply(client, config, commands): + if config.get("validation"): + path = ( + _FW_BASE + ["ip", "src-route"] + if config["validation"] == "disable" + else _FW_BASE + ["ip", "enable-default-log"] + ) + # Use the proper path + p = ( + ["firewall", "global-options", "source-validation"] + if client.retrieve_exists(["firewall", "global-options"]) + else ["firewall", "ip", "src-route"] + ) + client.configure_set(["firewall", "ip", "source-validation"], config["validation"]) + commands.append("set firewall ip source-validation {v}".format(v=config["validation"])) + + if config.get("config_trap") is not None: + val = "enable" if config["config_trap"] else "disable" + client.configure_set(["firewall", "config-trap"], val) + commands.append("set firewall config-trap {v}".format(v=val)) + + ping = config.get("ping") or {} + if ping.get("all") is not None: + action = "enable" if ping["all"] else "disable" + client.configure_set(["firewall", "ip", "ping", "all"], action) + commands.append("set firewall ip ping all {a}".format(a=action)) + if ping.get("broadcast") is not None: + action = "enable" if ping["broadcast"] else "disable" + client.configure_set(["firewall", "ip", "ping", "broadcast"], action) + commands.append("set firewall ip ping broadcast {a}".format(a=action)) + + for rr in config.get("route_redirects") or []: + afi = rr["afi"] + ip_key = "ip" if afi == "ipv4" else "ipv6" + icmp = rr.get("icmp_redirects") or {} + if icmp.get("send") is not None: + p = ["firewall", ip_key, "send-redirects"] + client.configure_set(p, "enable" if icmp["send"] else "disable") + commands.append( + "set {p} {v}".format(p=" ".join(p), v="enable" if icmp["send"] else "disable"), + ) + if icmp.get("receive") is not None: + p = ["firewall", ip_key, "disable-forwarding"] + client.configure_set(p, "enable" if not icmp["receive"] else "disable") + if rr.get("ip_src_route") is not None: + p = ["firewall", ip_key, "source-route"] + client.configure_set(p, "enable" if rr["ip_src_route"] else "disable") + + grp = config.get("group") or {} + for ag in grp.get("address_group") or []: + gpath = ["firewall", "group", "address-group", ag["name"]] + client.configure_set(gpath) + commands.append("set {p}".format(p=" ".join(gpath))) + if ag.get("description"): + client.configure_set(gpath + ["description"], ag["description"]) + for m in ag.get("members") or []: + client.configure_set(gpath + ["address"], m["address"]) + commands.append("set {p} address {a}".format(p=" ".join(gpath), a=m["address"])) + + for ng in grp.get("network_group") or []: + gpath = ["firewall", "group", "network-group", ng["name"]] + client.configure_set(gpath) + commands.append("set {p}".format(p=" ".join(gpath))) + if ng.get("description"): + client.configure_set(gpath + ["description"], ng["description"]) + for m in ng.get("members") or []: + client.configure_set(gpath + ["network"], m["address"]) + + for pg in grp.get("port_group") or []: + gpath = ["firewall", "group", "port-group", pg["name"]] + client.configure_set(gpath) + commands.append("set {p}".format(p=" ".join(gpath))) + if pg.get("description"): + client.configure_set(gpath + ["description"], pg["description"]) + for m in pg.get("members") or []: + client.configure_set(gpath + ["port"], str(m["port"])) + + +def main(): + argument_spec = dict( + config=dict( + type="dict", + options=dict( + validation=dict(type="str", choices=["strict", "loose", "disable"]), + config_trap=dict(type="bool"), + ping=dict( + type="dict", + options=dict(all=dict(type="bool"), broadcast=dict(type="bool")), + ), + route_redirects=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), + icmp_redirects=dict( + type="dict", + options=dict(send=dict(type="bool"), receive=dict(type="bool")), + ), + ip_src_route=dict(type="bool"), + ), + ), + group=dict( + type="dict", + options=dict( + address_group=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + description=dict(type="str"), + afi=dict(type="str", default="ipv4"), + members=dict( + type="list", + elements="dict", + options=dict(address=dict(type="str")), + ), + ), + ), + network_group=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + description=dict(type="str"), + members=dict( + type="list", + elements="dict", + options=dict(address=dict(type="str")), + ), + ), + ), + port_group=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + description=dict(type="str"), + members=dict( + type="list", + elements="dict", + options=dict(port=dict(type="str")), + ), + ), + ), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], + supports_check_mode=True, + ) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + if before: + client.configure_delete(_FW_BASE) + commands.append("delete firewall") + changed = True + elif state in ("merged", "replaced"): + if state == "replaced" and before: + client.configure_delete(_FW_BASE) + commands.append("delete firewall") + _apply(client, config, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_firewall_interfaces.py b/plugins/modules/vyos_firewall_interfaces.py deleted file mode 100644 index d9085a6..0000000 --- a/plugins/modules/vyos_firewall_interfaces.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/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_firewall_interfaces -short_description: Firewall interfaces resource module via REST API. -description: - - Attaches or detaches named firewall rule sets to/from interface - directions (in/out/local) using the VyOS HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of interface firewall attachment configurations. - type: list - elements: dict - suboptions: - name: - description: Interface name. - type: str - required: true - access_rules: - description: Firewall rule sets per AFI. - type: list - elements: dict - suboptions: - afi: - description: Address family. - type: str - choices: [ipv4, ipv6] - required: true - rules: - description: Rule sets and directions. - type: list - elements: dict - suboptions: - name: - description: Rule set name. - type: str - direction: - description: Traffic direction. - type: str - choices: [in, local, out] - required: true - state: - description: Desired state. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _fw_direction_key(afi, direction): - if afi == "ipv4": - return {"in": "in", "out": "out", "local": "local"}[direction] - return {"in": "in", "out": "out", "local": "local"}[direction] - - -def _apply(client, iface_cfg, commands): - name = iface_cfg["name"] - itype = _itype(name) - ibase = ["interfaces", itype, name, "firewall"] - - for ar in iface_cfg.get("access_rules") or []: - afi = ar["afi"] - fw_key = "firewall" if afi == "ipv4" else "ipv6" - for rule in ar.get("rules") or []: - direction = rule["direction"] - rs_name = rule.get("name") - if rs_name: - path = ibase + [direction, fw_key if afi == "ipv6" else "name"] - client.configure_set(path, rs_name) - commands.append( - "set interfaces {t} {n} firewall {d} {k} '{rs}'".format( - t=itype, - n=name, - d=direction, - k="ipv6-name" if afi == "ipv6" else "name", - rs=rs_name, - ), - ) - - -def _delete_iface_fw(client, name, commands): - itype = _itype(name) - path = ["interfaces", itype, name, "firewall"] - try: - client.configure_delete(path) - commands.append("delete interfaces {t} {n} firewall".format(t=itype, n=name)) - except VyOSRestError: - pass - - -def _get(client): - try: - result = client.retrieve_show_config(["interfaces"]) - raw = result.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - fw = idata.get("firewall") if isinstance(idata, dict) else None - if fw: - out.append({"name": iname, "firewall": fw}) - return out - except VyOSRestError: - return [] - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - access_rules=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - rules=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str"), - direction=dict( - type="str", - required=True, - choices=["in", "local", "out"], - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_iface_fw(client, iface["name"], commands) - changed = True - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - for cfg in config: - _delete_iface_fw(client, cfg["name"], commands) - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_interfaces.py.preview b/plugins/modules/vyos_firewall_interfaces.py.preview new file mode 100644 index 0000000..d9085a6 --- /dev/null +++ b/plugins/modules/vyos_firewall_interfaces.py.preview @@ -0,0 +1,244 @@ +#!/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_firewall_interfaces +short_description: Firewall interfaces resource module via REST API. +description: + - Attaches or detaches named firewall rule sets to/from interface + directions (in/out/local) using the VyOS HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: List of interface firewall attachment configurations. + type: list + elements: dict + suboptions: + name: + description: Interface name. + type: str + required: true + access_rules: + description: Firewall rule sets per AFI. + type: list + elements: dict + suboptions: + afi: + description: Address family. + type: str + choices: [ipv4, ipv6] + required: true + rules: + description: Rule sets and directions. + type: list + elements: dict + suboptions: + name: + description: Rule set name. + type: str + direction: + description: Traffic direction. + type: str + choices: [in, local, out] + required: true + state: + description: Desired state. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: list +after: + returned: when changed + type: list +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_IFACE_TYPES = { + "eth": "ethernet", + "bond": "bonding", + "vti": "vti", + "vxlan": "vxlan", + "lo": "loopback", + "dummy": "dummy", + "br": "bridge", + "wg": "wireguard", + "tun": "tunnel", +} + + +def _itype(name): + for p, t in _IFACE_TYPES.items(): + if name.startswith(p): + return t + return "ethernet" + + +def _fw_direction_key(afi, direction): + if afi == "ipv4": + return {"in": "in", "out": "out", "local": "local"}[direction] + return {"in": "in", "out": "out", "local": "local"}[direction] + + +def _apply(client, iface_cfg, commands): + name = iface_cfg["name"] + itype = _itype(name) + ibase = ["interfaces", itype, name, "firewall"] + + for ar in iface_cfg.get("access_rules") or []: + afi = ar["afi"] + fw_key = "firewall" if afi == "ipv4" else "ipv6" + for rule in ar.get("rules") or []: + direction = rule["direction"] + rs_name = rule.get("name") + if rs_name: + path = ibase + [direction, fw_key if afi == "ipv6" else "name"] + client.configure_set(path, rs_name) + commands.append( + "set interfaces {t} {n} firewall {d} {k} '{rs}'".format( + t=itype, + n=name, + d=direction, + k="ipv6-name" if afi == "ipv6" else "name", + rs=rs_name, + ), + ) + + +def _delete_iface_fw(client, name, commands): + itype = _itype(name) + path = ["interfaces", itype, name, "firewall"] + try: + client.configure_delete(path) + commands.append("delete interfaces {t} {n} firewall".format(t=itype, n=name)) + except VyOSRestError: + pass + + +def _get(client): + try: + result = client.retrieve_show_config(["interfaces"]) + raw = result.get("data") or {} + out = [] + for itype, itype_data in raw.items(): + if not isinstance(itype_data, dict): + continue + for iname, idata in itype_data.items(): + fw = idata.get("firewall") if isinstance(idata, dict) else None + if fw: + out.append({"name": iname, "firewall": fw}) + return out + except VyOSRestError: + return [] + + +def main(): + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + access_rules=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), + rules=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str"), + direction=dict( + type="str", + required=True, + choices=["in", "local", "out"], + ), + ), + ), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + targets = {i["name"] for i in config} if config else {i["name"] for i in before} + for iface in before: + if iface["name"] in targets: + _delete_iface_fw(client, iface["name"], commands) + changed = True + elif state in ("merged", "replaced", "overridden"): + if state in ("replaced", "overridden"): + for cfg in config: + _delete_iface_fw(client, cfg["name"], commands) + for cfg in config: + _apply(client, cfg, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py deleted file mode 100644 index f535f3a..0000000 --- a/plugins/modules/vyos_firewall_rules.py +++ /dev/null @@ -1,492 +0,0 @@ -#!/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_firewall_rules -short_description: Manage firewall rules on VyOS via the REST API. -description: - - Manages VyOS firewall rule sets and individual rules using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_firewall_rules) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of firewall rule set configurations. - type: list - elements: dict - suboptions: - afi: - description: Address family. - type: str - choices: [ipv4, ipv6] - required: true - rule_sets: - description: List of named rule sets. - type: list - elements: dict - suboptions: - name: - description: Rule set name. - type: str - required: true - default_action: - description: Default action for unmatched traffic. - type: str - choices: [drop, reject, accept] - default: drop - description: - description: Rule set description. - type: str - rules: - description: List of firewall rules. - type: list - elements: dict - suboptions: - number: - description: Rule number (1-999999). - type: int - required: true - action: - description: Rule action. - type: str - choices: [drop, reject, accept, inspect] - description: - description: Rule description. - type: str - protocol: - description: Protocol (e.g. C(tcp), C(udp), C(icmp), C(all)). - type: str - source: - description: Source match criteria. - type: dict - suboptions: - address: - description: Source IP address or network. - type: str - port: - description: Source port or port range. - type: str - group: - description: Address or network group. - type: dict - suboptions: - address_group: - type: str - network_group: - type: str - destination: - description: Destination match criteria. - type: dict - suboptions: - address: - description: Destination IP address or network. - type: str - port: - description: Destination port or port range. - type: str - group: - description: Address or network group. - type: dict - suboptions: - address_group: - type: str - network_group: - type: str - state: - description: Connection state matching. - type: dict - suboptions: - established: - type: bool - new: - type: bool - related: - type: bool - invalid: - type: bool - enabled: - description: Whether the rule is active. - type: bool - default: true - state: - description: - - C(merged): Add/update listed rule sets and rules. - - C(replaced): Replace listed rule sets entirely. - - C(overridden): Replace the entire firewall rule config. - - C(deleted): Remove listed (or all) rule sets. - - C(gathered): Read firewall rule config from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_firewall_rules -examples: | - - name: Create an IPv4 firewall rule set - vyos.rest.vyos_firewall_rules: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - afi: ipv4 - rule_sets: - - name: OUTSIDE-IN - default_action: drop - rules: - - number: 10 - action: accept - protocol: tcp - destination: - port: "80,443" - state: - established: true - related: true - state: merged - - - name: Delete all firewall rules - vyos.rest.vyos_firewall_rules: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: Firewall rule config before the module ran. - returned: always - type: list -after: - description: Firewall rule config after the module ran. - returned: when changed - type: list -gathered: - description: Firewall config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_FW_PATH = { - "ipv4": ["firewall", "name"], - "ipv6": ["firewall", "ipv6-name"], -} - - -def _get_fw_rules(client): - try: - result = client.retrieve_show_config(["firewall"]) - data = result.get("data") or {} - out = [] - for afi, key in [("ipv4", "name"), ("ipv6", "ipv6-name")]: - sets_data = data.get(key, {}) - if not isinstance(sets_data, dict): - continue - rule_sets = [] - for rs_name, rs_data in sets_data.items(): - if not isinstance(rs_data, dict): - continue - rs = { - "name": rs_name, - "default_action": rs_data.get("default-action", "drop"), - } - if "description" in rs_data: - rs["description"] = rs_data["description"] - rules = [] - for rnum, rdata in (rs_data.get("rule") or {}).items(): - if not isinstance(rdata, dict): - continue - rule = {"number": int(rnum)} - if "action" in rdata: - rule["action"] = rdata["action"] - if "description" in rdata: - rule["description"] = rdata["description"] - if "protocol" in rdata: - rule["protocol"] = rdata["protocol"] - rule["enabled"] = "disable" not in rdata - rules.append(rule) - if rules: - rs["rules"] = sorted(rules, key=lambda r: r["number"]) - rule_sets.append(rs) - if rule_sets: - out.append({"afi": afi, "rule_sets": rule_sets}) - return out - except VyOSRestError: - return [] - - -def _apply_rule_set(client, afi, rs, commands): - base = _FW_PATH[afi] + [rs["name"]] - client.configure_set(base) - commands.append("set {p}".format(p=" ".join(base))) - - if rs.get("default_action"): - client.configure_set(base + ["default-action"], rs["default_action"]) - commands.append( - "set {p} default-action {a}".format( - p=" ".join(base), - a=rs["default_action"], - ), - ) - if rs.get("description"): - client.configure_set(base + ["description"], rs["description"]) - commands.append( - "set {p} description '{d}'".format( - p=" ".join(base), - d=rs["description"], - ), - ) - - for rule in rs.get("rules") or []: - rbase = base + ["rule", str(rule["number"])] - client.configure_set(rbase) - commands.append("set {p}".format(p=" ".join(rbase))) - - if rule.get("action"): - client.configure_set(rbase + ["action"], rule["action"]) - commands.append( - "set {p} action {a}".format(p=" ".join(rbase), a=rule["action"]), - ) - if rule.get("description"): - client.configure_set(rbase + ["description"], rule["description"]) - if rule.get("protocol"): - client.configure_set(rbase + ["protocol"], rule["protocol"]) - commands.append( - "set {p} protocol {pr}".format( - p=" ".join(rbase), - pr=rule["protocol"], - ), - ) - for direction in ("source", "destination"): - match = rule.get(direction) - if not match: - continue - mbase = rbase + [direction] - if match.get("address"): - client.configure_set(mbase + ["address"], match["address"]) - commands.append( - "set {p} address '{a}'".format( - p=" ".join(mbase), - a=match["address"], - ), - ) - if match.get("port"): - client.configure_set(mbase + ["port"], match["port"]) - commands.append( - "set {p} port {pt}".format( - p=" ".join(mbase), - pt=match["port"], - ), - ) - - state_match = rule.get("state") - if state_match: - sbase = rbase + ["state"] - for st_name in ("established", "new", "related", "invalid"): - if state_match.get(st_name): - client.configure_set(sbase + [st_name], "enable") - commands.append( - "set {p} {s} enable".format( - p=" ".join(sbase), - s=st_name, - ), - ) - - if "enabled" in rule and not rule["enabled"]: - client.configure_set(rbase + ["disable"]) - commands.append("set {p} disable".format(p=" ".join(rbase))) - - -def main(): - rule_spec = dict( - number=dict(type="int", required=True), - action=dict(type="str", choices=["drop", "reject", "accept", "inspect"]), - description=dict(type="str"), - protocol=dict(type="str"), - source=dict( - type="dict", - options=dict( - address=dict(type="str"), - port=dict(type="str"), - group=dict( - type="dict", - options=dict( - address_group=dict(type="str"), - network_group=dict(type="str"), - ), - ), - ), - ), - destination=dict( - type="dict", - options=dict( - address=dict(type="str"), - port=dict(type="str"), - group=dict( - type="dict", - options=dict( - address_group=dict(type="str"), - network_group=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="dict", - options=dict( - established=dict(type="bool"), - new=dict(type="bool"), - related=dict(type="bool"), - invalid=dict(type="bool"), - ), - ), - enabled=dict(type="bool", default=True), - ) - - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - rule_sets=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - default_action=dict( - type="str", - choices=["drop", "reject", "accept"], - default="drop", - ), - description=dict(type="str"), - rules=dict(type="list", elements="dict", options=rule_spec), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_fw_rules(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state in ("deleted", "overridden") and not config: - for afi_key in ("name", "ipv6-name"): - try: - client.configure_delete(["firewall", afi_key]) - commands.append( - "delete firewall {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - changed = True - - elif state == "deleted" and config: - for entry in config: - afi = entry["afi"] - base_path = _FW_PATH[afi] - for rs in entry.get("rule_sets") or []: - try: - client.configure_delete(base_path + [rs["name"]]) - commands.append( - "delete {p} {n}".format( - p=" ".join(base_path), - n=rs["name"], - ), - ) - changed = True - except VyOSRestError: - pass - - elif state in ("merged", "replaced", "overridden"): - if state == "replaced": - for entry in config: - afi = entry["afi"] - base_path = _FW_PATH[afi] - for rs in entry.get("rule_sets") or []: - try: - client.configure_delete(base_path + [rs["name"]]) - commands.append( - "delete {p} {n}".format( - p=" ".join(base_path), - n=rs["name"], - ), - ) - except VyOSRestError: - pass - for entry in config: - afi = entry["afi"] - for rs in entry.get("rule_sets") or []: - _apply_rule_set(client, afi, rs, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_fw_rules(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_rules.py.preview b/plugins/modules/vyos_firewall_rules.py.preview new file mode 100644 index 0000000..f535f3a --- /dev/null +++ b/plugins/modules/vyos_firewall_rules.py.preview @@ -0,0 +1,492 @@ +#!/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_firewall_rules +short_description: Manage firewall rules on VyOS via the REST API. +description: + - Manages VyOS firewall rule sets and individual rules using the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_firewall_rules) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: List of firewall rule set configurations. + type: list + elements: dict + suboptions: + afi: + description: Address family. + type: str + choices: [ipv4, ipv6] + required: true + rule_sets: + description: List of named rule sets. + type: list + elements: dict + suboptions: + name: + description: Rule set name. + type: str + required: true + default_action: + description: Default action for unmatched traffic. + type: str + choices: [drop, reject, accept] + default: drop + description: + description: Rule set description. + type: str + rules: + description: List of firewall rules. + type: list + elements: dict + suboptions: + number: + description: Rule number (1-999999). + type: int + required: true + action: + description: Rule action. + type: str + choices: [drop, reject, accept, inspect] + description: + description: Rule description. + type: str + protocol: + description: Protocol (e.g. C(tcp), C(udp), C(icmp), C(all)). + type: str + source: + description: Source match criteria. + type: dict + suboptions: + address: + description: Source IP address or network. + type: str + port: + description: Source port or port range. + type: str + group: + description: Address or network group. + type: dict + suboptions: + address_group: + type: str + network_group: + type: str + destination: + description: Destination match criteria. + type: dict + suboptions: + address: + description: Destination IP address or network. + type: str + port: + description: Destination port or port range. + type: str + group: + description: Address or network group. + type: dict + suboptions: + address_group: + type: str + network_group: + type: str + state: + description: Connection state matching. + type: dict + suboptions: + established: + type: bool + new: + type: bool + related: + type: bool + invalid: + type: bool + enabled: + description: Whether the rule is active. + type: bool + default: true + state: + description: + - C(merged): Add/update listed rule sets and rules. + - C(replaced): Replace listed rule sets entirely. + - C(overridden): Replace the entire firewall rule config. + - C(deleted): Remove listed (or all) rule sets. + - C(gathered): Read firewall rule config from device. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.vyos.vyos_firewall_rules +examples: | + - name: Create an IPv4 firewall rule set + vyos.rest.vyos_firewall_rules: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - afi: ipv4 + rule_sets: + - name: OUTSIDE-IN + default_action: drop + rules: + - number: 10 + action: accept + protocol: tcp + destination: + port: "80,443" + state: + established: true + related: true + state: merged + + - name: Delete all firewall rules + vyos.rest.vyos_firewall_rules: + hostname: 192.168.1.1 + api_key: MY-KEY + state: deleted +""" + +RETURN = r""" +before: + description: Firewall rule config before the module ran. + returned: always + type: list +after: + description: Firewall rule config after the module ran. + returned: when changed + type: list +gathered: + description: Firewall config read from device (state=gathered). + returned: when state is gathered + type: list +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_FW_PATH = { + "ipv4": ["firewall", "name"], + "ipv6": ["firewall", "ipv6-name"], +} + + +def _get_fw_rules(client): + try: + result = client.retrieve_show_config(["firewall"]) + data = result.get("data") or {} + out = [] + for afi, key in [("ipv4", "name"), ("ipv6", "ipv6-name")]: + sets_data = data.get(key, {}) + if not isinstance(sets_data, dict): + continue + rule_sets = [] + for rs_name, rs_data in sets_data.items(): + if not isinstance(rs_data, dict): + continue + rs = { + "name": rs_name, + "default_action": rs_data.get("default-action", "drop"), + } + if "description" in rs_data: + rs["description"] = rs_data["description"] + rules = [] + for rnum, rdata in (rs_data.get("rule") or {}).items(): + if not isinstance(rdata, dict): + continue + rule = {"number": int(rnum)} + if "action" in rdata: + rule["action"] = rdata["action"] + if "description" in rdata: + rule["description"] = rdata["description"] + if "protocol" in rdata: + rule["protocol"] = rdata["protocol"] + rule["enabled"] = "disable" not in rdata + rules.append(rule) + if rules: + rs["rules"] = sorted(rules, key=lambda r: r["number"]) + rule_sets.append(rs) + if rule_sets: + out.append({"afi": afi, "rule_sets": rule_sets}) + return out + except VyOSRestError: + return [] + + +def _apply_rule_set(client, afi, rs, commands): + base = _FW_PATH[afi] + [rs["name"]] + client.configure_set(base) + commands.append("set {p}".format(p=" ".join(base))) + + if rs.get("default_action"): + client.configure_set(base + ["default-action"], rs["default_action"]) + commands.append( + "set {p} default-action {a}".format( + p=" ".join(base), + a=rs["default_action"], + ), + ) + if rs.get("description"): + client.configure_set(base + ["description"], rs["description"]) + commands.append( + "set {p} description '{d}'".format( + p=" ".join(base), + d=rs["description"], + ), + ) + + for rule in rs.get("rules") or []: + rbase = base + ["rule", str(rule["number"])] + client.configure_set(rbase) + commands.append("set {p}".format(p=" ".join(rbase))) + + if rule.get("action"): + client.configure_set(rbase + ["action"], rule["action"]) + commands.append( + "set {p} action {a}".format(p=" ".join(rbase), a=rule["action"]), + ) + if rule.get("description"): + client.configure_set(rbase + ["description"], rule["description"]) + if rule.get("protocol"): + client.configure_set(rbase + ["protocol"], rule["protocol"]) + commands.append( + "set {p} protocol {pr}".format( + p=" ".join(rbase), + pr=rule["protocol"], + ), + ) + for direction in ("source", "destination"): + match = rule.get(direction) + if not match: + continue + mbase = rbase + [direction] + if match.get("address"): + client.configure_set(mbase + ["address"], match["address"]) + commands.append( + "set {p} address '{a}'".format( + p=" ".join(mbase), + a=match["address"], + ), + ) + if match.get("port"): + client.configure_set(mbase + ["port"], match["port"]) + commands.append( + "set {p} port {pt}".format( + p=" ".join(mbase), + pt=match["port"], + ), + ) + + state_match = rule.get("state") + if state_match: + sbase = rbase + ["state"] + for st_name in ("established", "new", "related", "invalid"): + if state_match.get(st_name): + client.configure_set(sbase + [st_name], "enable") + commands.append( + "set {p} {s} enable".format( + p=" ".join(sbase), + s=st_name, + ), + ) + + if "enabled" in rule and not rule["enabled"]: + client.configure_set(rbase + ["disable"]) + commands.append("set {p} disable".format(p=" ".join(rbase))) + + +def main(): + rule_spec = dict( + number=dict(type="int", required=True), + action=dict(type="str", choices=["drop", "reject", "accept", "inspect"]), + description=dict(type="str"), + protocol=dict(type="str"), + source=dict( + type="dict", + options=dict( + address=dict(type="str"), + port=dict(type="str"), + group=dict( + type="dict", + options=dict( + address_group=dict(type="str"), + network_group=dict(type="str"), + ), + ), + ), + ), + destination=dict( + type="dict", + options=dict( + address=dict(type="str"), + port=dict(type="str"), + group=dict( + type="dict", + options=dict( + address_group=dict(type="str"), + network_group=dict(type="str"), + ), + ), + ), + ), + state=dict( + type="dict", + options=dict( + established=dict(type="bool"), + new=dict(type="bool"), + related=dict(type="bool"), + invalid=dict(type="bool"), + ), + ), + enabled=dict(type="bool", default=True), + ) + + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), + rule_sets=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + default_action=dict( + type="str", + choices=["drop", "reject", "accept"], + default="drop", + ), + description=dict(type="str"), + rules=dict(type="list", elements="dict", options=rule_spec), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + + before = _get_fw_rules(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state in ("deleted", "overridden") and not config: + for afi_key in ("name", "ipv6-name"): + try: + client.configure_delete(["firewall", afi_key]) + commands.append( + "delete firewall {k}".format(k=afi_key), + ) + except VyOSRestError: + pass + changed = True + + elif state == "deleted" and config: + for entry in config: + afi = entry["afi"] + base_path = _FW_PATH[afi] + for rs in entry.get("rule_sets") or []: + try: + client.configure_delete(base_path + [rs["name"]]) + commands.append( + "delete {p} {n}".format( + p=" ".join(base_path), + n=rs["name"], + ), + ) + changed = True + except VyOSRestError: + pass + + elif state in ("merged", "replaced", "overridden"): + if state == "replaced": + for entry in config: + afi = entry["afi"] + base_path = _FW_PATH[afi] + for rs in entry.get("rule_sets") or []: + try: + client.configure_delete(base_path + [rs["name"]]) + commands.append( + "delete {p} {n}".format( + p=" ".join(base_path), + n=rs["name"], + ), + ) + except VyOSRestError: + pass + for entry in config: + afi = entry["afi"] + for rs in entry.get("rule_sets") or []: + _apply_rule_set(client, afi, rs, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_fw_rules(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_generate.py b/plugins/modules/vyos_generate.py deleted file mode 100644 index 448575a..0000000 --- a/plugins/modules/vyos_generate.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/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_generate -short_description: Execute generate commands on a VyOS device via REST API. -description: - - Sends a C(generate) operational command to a VyOS device via the HTTPS - REST API (C(/generate) endpoint). - - Commonly used to generate WireGuard keypairs, TLS certificates, etc. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Generate command tokens. - - E.g. C(["wireguard", "default-keypair"]). - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Generate WireGuard default keypair - vyos.rest.vyos_generate: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["wireguard", "default-keypair"] - - - name: Generate a new SSH host key - vyos.rest.vyos_generate: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["ssh-server", "host-key", "rsa"] -""" - -RETURN = r""" -output: - description: Text output produced by the generate command (may be empty). - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - try: - result = client.generate(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_generate.py.preview b/plugins/modules/vyos_generate.py.preview new file mode 100644 index 0000000..448575a --- /dev/null +++ b/plugins/modules/vyos_generate.py.preview @@ -0,0 +1,102 @@ +#!/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_generate +short_description: Execute generate commands on a VyOS device via REST API. +description: + - Sends a C(generate) operational command to a VyOS device via the HTTPS + REST API (C(/generate) endpoint). + - Commonly used to generate WireGuard keypairs, TLS certificates, etc. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + path: + description: + - Generate command tokens. + - E.g. C(["wireguard", "default-keypair"]). + type: list + elements: str + required: true + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +examples: | + - name: Generate WireGuard default keypair + vyos.rest.vyos_generate: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["wireguard", "default-keypair"] + + - name: Generate a new SSH host key + vyos.rest.vyos_generate: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["ssh-server", "host-key", "rsa"] +""" + +RETURN = r""" +output: + description: Text output produced by the generate command (may be empty). + returned: success + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + path=dict(type="list", elements="str", required=True), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + client = VyOSRestClient(module) + try: + result = client.generate(module.params["path"]) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=True, output=result.get("data", "")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_image.py b/plugins/modules/vyos_image.py deleted file mode 100644 index be12705..0000000 --- a/plugins/modules/vyos_image.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/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_image -short_description: Manage VyOS system images via the REST API. -description: - - Adds or removes VyOS system images using the HTTPS REST API - (C(/image) endpoint). - - Use I(state=present) to download and install an image from a URL. - - Use I(state=absent) to delete an installed image by name. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - state: - description: - - C(present): Download and install the image from I(url). - - C(absent): Delete the image specified by I(name). - type: str - choices: [present, absent] - required: true - url: - description: - - HTTP(S) URL of the VyOS C(.iso) file to install. - - Required when I(state=present). - type: str - name: - description: - - Version name of the image to remove (e.g. C(1.4-rolling-202401010000)). - - Required when I(state=absent). - type: str - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: > - Request timeout in seconds. Image downloads can take several minutes; - increase this value accordingly. - type: int - default: 300 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Install the latest rolling release - vyos.rest.vyos_image: - hostname: 192.168.1.1 - api_key: MY-KEY - state: present - url: "https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso" - timeout: 600 - - - name: Remove an old image - vyos.rest.vyos_image: - hostname: 192.168.1.1 - api_key: MY-KEY - state: absent - name: "1.4-rolling-202312010000" -""" - -RETURN = r""" -output: - description: Text output from the image add/delete operation. - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - state=dict(type="str", required=True, choices=["present", "absent"]), - url=dict(type="str"), - name=dict(type="str"), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - # Override default timeout to 300s for image downloads - argument_spec["timeout"]["default"] = 300 - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[ - ("state", "present", ["url"]), - ("state", "absent", ["name"]), - ], - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - - try: - if state == "present": - result = client.image_add(module.params["url"]) - else: - result = client.image_delete(module.params["name"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_image.py.preview b/plugins/modules/vyos_image.py.preview new file mode 100644 index 0000000..be12705 --- /dev/null +++ b/plugins/modules/vyos_image.py.preview @@ -0,0 +1,131 @@ +#!/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_image +short_description: Manage VyOS system images via the REST API. +description: + - Adds or removes VyOS system images using the HTTPS REST API + (C(/image) endpoint). + - Use I(state=present) to download and install an image from a URL. + - Use I(state=absent) to delete an installed image by name. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + state: + description: + - C(present): Download and install the image from I(url). + - C(absent): Delete the image specified by I(name). + type: str + choices: [present, absent] + required: true + url: + description: + - HTTP(S) URL of the VyOS C(.iso) file to install. + - Required when I(state=present). + type: str + name: + description: + - Version name of the image to remove (e.g. C(1.4-rolling-202401010000)). + - Required when I(state=absent). + type: str + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: > + Request timeout in seconds. Image downloads can take several minutes; + increase this value accordingly. + type: int + default: 300 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +examples: | + - name: Install the latest rolling release + vyos.rest.vyos_image: + hostname: 192.168.1.1 + api_key: MY-KEY + state: present + url: "https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso" + timeout: 600 + + - name: Remove an old image + vyos.rest.vyos_image: + hostname: 192.168.1.1 + api_key: MY-KEY + state: absent + name: "1.4-rolling-202312010000" +""" + +RETURN = r""" +output: + description: Text output from the image add/delete operation. + returned: success + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + state=dict(type="str", required=True, choices=["present", "absent"]), + url=dict(type="str"), + name=dict(type="str"), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + # Override default timeout to 300s for image downloads + argument_spec["timeout"]["default"] = 300 + + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[ + ("state", "present", ["url"]), + ("state", "absent", ["name"]), + ], + supports_check_mode=False, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + + try: + if state == "present": + result = client.image_add(module.params["url"]) + else: + result = client.image_delete(module.params["name"]) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=True, output=result.get("data", "")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_interfaces.py b/plugins/modules/vyos_interfaces.py deleted file mode 100644 index 9edc1da..0000000 --- a/plugins/modules/vyos_interfaces.py +++ /dev/null @@ -1,401 +0,0 @@ -#!/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 attributes on a VyOS device via the REST API. -description: - - Manages interface base attributes (description, MTU, enabled state, VIFs, - duplex, speed) on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_interfaces) but uses HTTP API. - - Supports Ethernet, Bonding, VXLAN, Loopback, and Virtual Tunnel 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. C(eth0), C(bond0), C(vti1), C(vxlan2)). - type: str - required: true - description: - description: Interface description. - type: str - enabled: - description: > - Administrative state. C(true) = up, C(false) = admin-down. - type: bool - default: true - mtu: - description: MTU in bytes. Applicable for Ethernet, Bonding, VXLAN, VTI. - type: int - duplex: - description: Duplex mode. Only for Ethernet interfaces. - type: str - choices: [auto, full, half] - speed: - description: Link speed. Only for Ethernet interfaces. - type: str - vifs: - description: List of VLAN sub-interface configurations. - type: list - elements: dict - suboptions: - vlan_id: - description: 802.1Q VLAN ID. - type: int - required: true - description: - description: VIF description. - type: str - enabled: - description: Administrative state of the VIF. - type: bool - default: true - state: - description: - - C(merged): Merge the provided config with existing interface config. - - C(replaced): Replace per-interface config with provided values. - - C(overridden): Override all interface config with provided values. - - C(deleted): Delete interface config for listed interfaces (or all). - - C(gathered): Read current interface config from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_interfaces - - module: vyos.rest.vyos_l3_interfaces -examples: | - - name: Set description and MTU on eth1 - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - description: "WAN uplink" - mtu: 1500 - enabled: true - state: merged - - - name: Disable eth2 - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth2 - enabled: false - state: merged - - - name: Create a VLAN sub-interface - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - vifs: - - vlan_id: 100 - description: "VLAN 100" - state: merged -""" - -RETURN = r""" -before: - description: Interface configuration before the module ran. - returned: always - type: list -after: - description: Interface configuration after the module ran. - returned: when changed - type: list -gathered: - description: Interface config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: REST API set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "peth": "pseudo-ethernet", - "vtun": "openvpn", - "wg": "wireguard", - "br": "bridge", - "macsec": "macsec", - "tun": "tunnel", -} - - -def _iface_type(name): - """Infer the VyOS interface type from its name prefix.""" - for prefix, itype in _IFACE_TYPES.items(): - if name.startswith(prefix): - return itype - return "ethernet" - - -def _iface_base_path(name): - return ["interfaces", _iface_type(name), name] - - -def _get_interfaces(client): - try: - result = client.retrieve_show_config(["interfaces"]) - ifaces_raw = result.get("data") or {} - interfaces = [] - for itype, itype_data in ifaces_raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - entry = {"name": iname} - if "description" in idata: - entry["description"] = idata["description"] - if "mtu" in idata: - entry["mtu"] = int(idata["mtu"]) - entry["enabled"] = "disable" not in idata - if "duplex" in idata: - entry["duplex"] = idata["duplex"] - if "speed" in idata: - entry["speed"] = idata["speed"] - if "vif" in idata: - vifs = [] - for vid, vdata in idata["vif"].items(): - vif_entry = {"vlan_id": int(vid)} - if isinstance(vdata, dict): - if "description" in vdata: - vif_entry["description"] = vdata["description"] - vif_entry["enabled"] = "disable" not in vdata - vifs.append(vif_entry) - entry["vifs"] = vifs - interfaces.append(entry) - return interfaces - except VyOSRestError: - return [] - - -def _apply_interface(client, iface_cfg, commands): - name = iface_cfg["name"] - base = _iface_base_path(name) - - if iface_cfg.get("description") is not None: - client.configure_set(base + ["description"], iface_cfg["description"]) - commands.append( - "set interfaces {t} {n} description '{d}'".format( - t=_iface_type(name), - n=name, - d=iface_cfg["description"], - ), - ) - if iface_cfg.get("mtu") is not None: - client.configure_set(base + ["mtu"], str(iface_cfg["mtu"])) - commands.append( - "set interfaces {t} {n} mtu {m}".format( - t=_iface_type(name), - n=name, - m=iface_cfg["mtu"], - ), - ) - if "enabled" in iface_cfg: - if not iface_cfg["enabled"]: - client.configure_set(base + ["disable"]) - commands.append( - "set interfaces {t} {n} disable".format(t=_iface_type(name), n=name), - ) - else: - try: - client.configure_delete(base + ["disable"]) - commands.append( - "delete interfaces {t} {n} disable".format( - t=_iface_type(name), - n=name, - ), - ) - except VyOSRestError: - pass - if iface_cfg.get("duplex"): - client.configure_set(base + ["duplex"], iface_cfg["duplex"]) - commands.append( - "set interfaces {t} {n} duplex {d}".format( - t=_iface_type(name), - n=name, - d=iface_cfg["duplex"], - ), - ) - if iface_cfg.get("speed"): - client.configure_set(base + ["speed"], iface_cfg["speed"]) - commands.append( - "set interfaces {t} {n} speed {s}".format( - t=_iface_type(name), - n=name, - s=iface_cfg["speed"], - ), - ) - for vif in iface_cfg.get("vifs") or []: - vid = str(vif["vlan_id"]) - vif_base = base + ["vif", vid] - client.configure_set(vif_base) - commands.append( - "set interfaces {t} {n} vif {v}".format( - t=_iface_type(name), - n=name, - v=vid, - ), - ) - if vif.get("description"): - client.configure_set(vif_base + ["description"], vif["description"]) - commands.append( - "set interfaces {t} {n} vif {v} description '{d}'".format( - t=_iface_type(name), - n=name, - v=vid, - d=vif["description"], - ), - ) - if "enabled" in vif and not vif["enabled"]: - client.configure_set(vif_base + ["disable"]) - commands.append( - "set interfaces {t} {n} vif {v} disable".format( - t=_iface_type(name), - n=name, - v=vid, - ), - ) - - -def main(): - 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"), - vifs=dict( - type="list", - elements="dict", - options=dict( - vlan_id=dict(type="int", required=True), - description=dict(type="str"), - enabled=dict(type="bool", default=True), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_interfaces(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - base = _iface_base_path(iface["name"]) - # Only delete mutable attributes, not the interface itself - for attr in ["description", "mtu", "duplex", "speed"]: - if attr in iface: - try: - client.configure_delete(base + [attr]) - commands.append( - "delete interfaces {t} {n} {a}".format( - t=_iface_type(iface["name"]), - n=iface["name"], - a=attr, - ), - ) - except VyOSRestError: - pass - changed = True - - elif state in ("merged", "replaced", "overridden"): - for iface_cfg in config: - _apply_interface(client, iface_cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_interfaces(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_interfaces.py.preview b/plugins/modules/vyos_interfaces.py.preview new file mode 100644 index 0000000..9edc1da --- /dev/null +++ b/plugins/modules/vyos_interfaces.py.preview @@ -0,0 +1,401 @@ +#!/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 attributes on a VyOS device via the REST API. +description: + - Manages interface base attributes (description, MTU, enabled state, VIFs, + duplex, speed) on VyOS devices using the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_interfaces) but uses HTTP API. + - Supports Ethernet, Bonding, VXLAN, Loopback, and Virtual Tunnel 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. C(eth0), C(bond0), C(vti1), C(vxlan2)). + type: str + required: true + description: + description: Interface description. + type: str + enabled: + description: > + Administrative state. C(true) = up, C(false) = admin-down. + type: bool + default: true + mtu: + description: MTU in bytes. Applicable for Ethernet, Bonding, VXLAN, VTI. + type: int + duplex: + description: Duplex mode. Only for Ethernet interfaces. + type: str + choices: [auto, full, half] + speed: + description: Link speed. Only for Ethernet interfaces. + type: str + vifs: + description: List of VLAN sub-interface configurations. + type: list + elements: dict + suboptions: + vlan_id: + description: 802.1Q VLAN ID. + type: int + required: true + description: + description: VIF description. + type: str + enabled: + description: Administrative state of the VIF. + type: bool + default: true + state: + description: + - C(merged): Merge the provided config with existing interface config. + - C(replaced): Replace per-interface config with provided values. + - C(overridden): Override all interface config with provided values. + - C(deleted): Delete interface config for listed interfaces (or all). + - C(gathered): Read current interface config from device. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.vyos.vyos_interfaces + - module: vyos.rest.vyos_l3_interfaces +examples: | + - name: Set description and MTU on eth1 + vyos.rest.vyos_interfaces: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - name: eth1 + description: "WAN uplink" + mtu: 1500 + enabled: true + state: merged + + - name: Disable eth2 + vyos.rest.vyos_interfaces: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - name: eth2 + enabled: false + state: merged + + - name: Create a VLAN sub-interface + vyos.rest.vyos_interfaces: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - name: eth1 + vifs: + - vlan_id: 100 + description: "VLAN 100" + state: merged +""" + +RETURN = r""" +before: + description: Interface configuration before the module ran. + returned: always + type: list +after: + description: Interface configuration after the module ran. + returned: when changed + type: list +gathered: + description: Interface config read from device (state=gathered). + returned: when state is gathered + type: list +commands: + description: REST API set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_IFACE_TYPES = { + "eth": "ethernet", + "bond": "bonding", + "vti": "vti", + "vxlan": "vxlan", + "lo": "loopback", + "dummy": "dummy", + "peth": "pseudo-ethernet", + "vtun": "openvpn", + "wg": "wireguard", + "br": "bridge", + "macsec": "macsec", + "tun": "tunnel", +} + + +def _iface_type(name): + """Infer the VyOS interface type from its name prefix.""" + for prefix, itype in _IFACE_TYPES.items(): + if name.startswith(prefix): + return itype + return "ethernet" + + +def _iface_base_path(name): + return ["interfaces", _iface_type(name), name] + + +def _get_interfaces(client): + try: + result = client.retrieve_show_config(["interfaces"]) + ifaces_raw = result.get("data") or {} + interfaces = [] + for itype, itype_data in ifaces_raw.items(): + if not isinstance(itype_data, dict): + continue + for iname, idata in itype_data.items(): + if not isinstance(idata, dict): + continue + entry = {"name": iname} + if "description" in idata: + entry["description"] = idata["description"] + if "mtu" in idata: + entry["mtu"] = int(idata["mtu"]) + entry["enabled"] = "disable" not in idata + if "duplex" in idata: + entry["duplex"] = idata["duplex"] + if "speed" in idata: + entry["speed"] = idata["speed"] + if "vif" in idata: + vifs = [] + for vid, vdata in idata["vif"].items(): + vif_entry = {"vlan_id": int(vid)} + if isinstance(vdata, dict): + if "description" in vdata: + vif_entry["description"] = vdata["description"] + vif_entry["enabled"] = "disable" not in vdata + vifs.append(vif_entry) + entry["vifs"] = vifs + interfaces.append(entry) + return interfaces + except VyOSRestError: + return [] + + +def _apply_interface(client, iface_cfg, commands): + name = iface_cfg["name"] + base = _iface_base_path(name) + + if iface_cfg.get("description") is not None: + client.configure_set(base + ["description"], iface_cfg["description"]) + commands.append( + "set interfaces {t} {n} description '{d}'".format( + t=_iface_type(name), + n=name, + d=iface_cfg["description"], + ), + ) + if iface_cfg.get("mtu") is not None: + client.configure_set(base + ["mtu"], str(iface_cfg["mtu"])) + commands.append( + "set interfaces {t} {n} mtu {m}".format( + t=_iface_type(name), + n=name, + m=iface_cfg["mtu"], + ), + ) + if "enabled" in iface_cfg: + if not iface_cfg["enabled"]: + client.configure_set(base + ["disable"]) + commands.append( + "set interfaces {t} {n} disable".format(t=_iface_type(name), n=name), + ) + else: + try: + client.configure_delete(base + ["disable"]) + commands.append( + "delete interfaces {t} {n} disable".format( + t=_iface_type(name), + n=name, + ), + ) + except VyOSRestError: + pass + if iface_cfg.get("duplex"): + client.configure_set(base + ["duplex"], iface_cfg["duplex"]) + commands.append( + "set interfaces {t} {n} duplex {d}".format( + t=_iface_type(name), + n=name, + d=iface_cfg["duplex"], + ), + ) + if iface_cfg.get("speed"): + client.configure_set(base + ["speed"], iface_cfg["speed"]) + commands.append( + "set interfaces {t} {n} speed {s}".format( + t=_iface_type(name), + n=name, + s=iface_cfg["speed"], + ), + ) + for vif in iface_cfg.get("vifs") or []: + vid = str(vif["vlan_id"]) + vif_base = base + ["vif", vid] + client.configure_set(vif_base) + commands.append( + "set interfaces {t} {n} vif {v}".format( + t=_iface_type(name), + n=name, + v=vid, + ), + ) + if vif.get("description"): + client.configure_set(vif_base + ["description"], vif["description"]) + commands.append( + "set interfaces {t} {n} vif {v} description '{d}'".format( + t=_iface_type(name), + n=name, + v=vid, + d=vif["description"], + ), + ) + if "enabled" in vif and not vif["enabled"]: + client.configure_set(vif_base + ["disable"]) + commands.append( + "set interfaces {t} {n} vif {v} disable".format( + t=_iface_type(name), + n=name, + v=vid, + ), + ) + + +def main(): + 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"), + vifs=dict( + type="list", + elements="dict", + options=dict( + vlan_id=dict(type="int", required=True), + description=dict(type="str"), + enabled=dict(type="bool", default=True), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + + before = _get_interfaces(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + targets = {i["name"] for i in config} if config else {i["name"] for i in before} + for iface in before: + if iface["name"] in targets: + base = _iface_base_path(iface["name"]) + # Only delete mutable attributes, not the interface itself + for attr in ["description", "mtu", "duplex", "speed"]: + if attr in iface: + try: + client.configure_delete(base + [attr]) + commands.append( + "delete interfaces {t} {n} {a}".format( + t=_iface_type(iface["name"]), + n=iface["name"], + a=attr, + ), + ) + except VyOSRestError: + pass + changed = True + + elif state in ("merged", "replaced", "overridden"): + for iface_cfg in config: + _apply_interface(client, iface_cfg, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_interfaces(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_l3_interfaces.py b/plugins/modules/vyos_l3_interfaces.py deleted file mode 100644 index f6d4e6c..0000000 --- a/plugins/modules/vyos_l3_interfaces.py +++ /dev/null @@ -1,381 +0,0 @@ -#!/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 attributes on VyOS via the REST API. -description: - - Manages IPv4 and IPv6 addresses on VyOS interfaces via the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_l3_interfaces) but uses the HTTP API. -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 interface name (e.g. C(eth0), C(eth1)). - type: str - required: true - ipv4: - description: List of IPv4 address assignments. - type: list - elements: dict - suboptions: - address: - description: IPv4 address in CIDR notation or C(dhcp). - type: str - required: true - ipv6: - description: List of IPv6 address assignments. - type: list - elements: dict - suboptions: - address: - description: IPv6 address in CIDR notation or C(dhcpv6) or C(autoconf). - type: str - required: true - vifs: - description: VLAN sub-interface L3 settings. - type: list - elements: dict - suboptions: - vlan_id: - description: 802.1Q VLAN ID. - type: int - required: true - ipv4: - description: IPv4 addresses for this VIF. - type: list - elements: dict - suboptions: - address: - type: str - required: true - ipv6: - description: IPv6 addresses for this VIF. - type: list - elements: dict - suboptions: - address: - type: str - required: true - state: - description: - - C(merged): Add addresses to the interface (preserve existing). - - C(replaced): Replace all addresses on each listed interface. - - C(overridden): Replace all addresses on all interfaces. - - C(deleted): Remove all addresses from listed (or all) interfaces. - - C(gathered): Read L3 config from device without changes. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_l3_interfaces - - module: vyos.rest.vyos_interfaces -examples: | - - name: Assign addresses to eth1 and eth2 - vyos.rest.vyos_l3_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - ipv4: - - address: 10.0.1.1/24 - ipv6: - - address: "2001:db8::1/64" - - name: eth2 - ipv4: - - address: dhcp - state: merged - - - name: Remove all addresses from eth1 - vyos.rest.vyos_l3_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - state: deleted -""" - -RETURN = r""" -before: - description: L3 interface config before the module ran. - returned: always - type: list -after: - description: L3 interface config after the module ran. - returned: when changed - type: list -gathered: - description: L3 config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", -} - - -def _iface_type(name): - for prefix, t in _IFACE_TYPES.items(): - if name.startswith(prefix): - return t - return "ethernet" - - -def _base(name): - return ["interfaces", _iface_type(name), name] - - -def _get_l3_interfaces(client): - try: - result = client.retrieve_show_config(["interfaces"]) - raw = result.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - entry = {"name": iname, "ipv4": [], "ipv6": []} - for addr in _listify(idata.get("address")): - if ":" in addr: - entry["ipv6"].append({"address": addr}) - elif addr == "dhcp": - entry["ipv4"].append({"address": addr}) - else: - entry["ipv4"].append({"address": addr}) - # VIFs - if "vif" in idata: - vifs = [] - for vid, vdata in idata["vif"].items(): - vif_e = {"vlan_id": int(vid), "ipv4": [], "ipv6": []} - if isinstance(vdata, dict): - for addr in _listify(vdata.get("address")): - if ":" in addr: - vif_e["ipv6"].append({"address": addr}) - else: - vif_e["ipv4"].append({"address": addr}) - vifs.append(vif_e) - entry["vifs"] = vifs - if entry["ipv4"] or entry["ipv6"] or entry.get("vifs"): - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _listify(val): - """Return val as a list regardless of whether it's a str or list.""" - if val is None: - return [] - if isinstance(val, list): - return val - return [val] - - -def _set_addresses(client, name, ipv4, ipv6, commands): - base = _base(name) - itype = _iface_type(name) - for a in ipv4 or []: - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} address '{a}'".format( - t=itype, - n=name, - a=a["address"], - ), - ) - for a in ipv6 or []: - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} address '{a}'".format( - t=itype, - n=name, - a=a["address"], - ), - ) - - -def _delete_addresses(client, name, commands): - base = _base(name) - itype = _iface_type(name) - try: - client.configure_delete(base + ["address"]) - commands.append( - "delete interfaces {t} {n} address".format(t=itype, n=name), - ) - except VyOSRestError: - pass - - -def main(): - 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", required=True)), - ), - ipv6=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str", required=True)), - ), - 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", required=True)), - ), - ipv6=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str", required=True)), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_l3_interfaces(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_addresses(client, iface["name"], commands) - changed = True - - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - # First remove existing addresses on targeted interfaces - targets = {i["name"] for i in config} - if state == "overridden": - targets = {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_addresses(client, iface["name"], commands) - - for iface_cfg in config: - name = iface_cfg["name"] - _set_addresses( - client, - name, - iface_cfg.get("ipv4"), - iface_cfg.get("ipv6"), - commands, - ) - for vif in iface_cfg.get("vifs") or []: - vid = str(vif["vlan_id"]) - base = _base(name) + ["vif", vid] - itype = _iface_type(name) - for a in (vif.get("ipv4") or []) + (vif.get("ipv6") or []): - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} vif {v} address '{a}'".format( - t=itype, - n=name, - v=vid, - a=a["address"], - ), - ) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_l3_interfaces(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_l3_interfaces.py.preview b/plugins/modules/vyos_l3_interfaces.py.preview new file mode 100644 index 0000000..f6d4e6c --- /dev/null +++ b/plugins/modules/vyos_l3_interfaces.py.preview @@ -0,0 +1,381 @@ +#!/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 attributes on VyOS via the REST API. +description: + - Manages IPv4 and IPv6 addresses on VyOS interfaces via the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_l3_interfaces) but uses the HTTP API. +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 interface name (e.g. C(eth0), C(eth1)). + type: str + required: true + ipv4: + description: List of IPv4 address assignments. + type: list + elements: dict + suboptions: + address: + description: IPv4 address in CIDR notation or C(dhcp). + type: str + required: true + ipv6: + description: List of IPv6 address assignments. + type: list + elements: dict + suboptions: + address: + description: IPv6 address in CIDR notation or C(dhcpv6) or C(autoconf). + type: str + required: true + vifs: + description: VLAN sub-interface L3 settings. + type: list + elements: dict + suboptions: + vlan_id: + description: 802.1Q VLAN ID. + type: int + required: true + ipv4: + description: IPv4 addresses for this VIF. + type: list + elements: dict + suboptions: + address: + type: str + required: true + ipv6: + description: IPv6 addresses for this VIF. + type: list + elements: dict + suboptions: + address: + type: str + required: true + state: + description: + - C(merged): Add addresses to the interface (preserve existing). + - C(replaced): Replace all addresses on each listed interface. + - C(overridden): Replace all addresses on all interfaces. + - C(deleted): Remove all addresses from listed (or all) interfaces. + - C(gathered): Read L3 config from device without changes. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.vyos.vyos_l3_interfaces + - module: vyos.rest.vyos_interfaces +examples: | + - name: Assign addresses to eth1 and eth2 + vyos.rest.vyos_l3_interfaces: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - name: eth1 + ipv4: + - address: 10.0.1.1/24 + ipv6: + - address: "2001:db8::1/64" + - name: eth2 + ipv4: + - address: dhcp + state: merged + + - name: Remove all addresses from eth1 + vyos.rest.vyos_l3_interfaces: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - name: eth1 + state: deleted +""" + +RETURN = r""" +before: + description: L3 interface config before the module ran. + returned: always + type: list +after: + description: L3 interface config after the module ran. + returned: when changed + type: list +gathered: + description: L3 config read from device (state=gathered). + returned: when state is gathered + type: list +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_IFACE_TYPES = { + "eth": "ethernet", + "bond": "bonding", + "vti": "vti", + "vxlan": "vxlan", + "lo": "loopback", + "dummy": "dummy", + "br": "bridge", + "wg": "wireguard", + "tun": "tunnel", +} + + +def _iface_type(name): + for prefix, t in _IFACE_TYPES.items(): + if name.startswith(prefix): + return t + return "ethernet" + + +def _base(name): + return ["interfaces", _iface_type(name), name] + + +def _get_l3_interfaces(client): + try: + result = client.retrieve_show_config(["interfaces"]) + raw = result.get("data") or {} + out = [] + for itype, itype_data in raw.items(): + if not isinstance(itype_data, dict): + continue + for iname, idata in itype_data.items(): + if not isinstance(idata, dict): + continue + entry = {"name": iname, "ipv4": [], "ipv6": []} + for addr in _listify(idata.get("address")): + if ":" in addr: + entry["ipv6"].append({"address": addr}) + elif addr == "dhcp": + entry["ipv4"].append({"address": addr}) + else: + entry["ipv4"].append({"address": addr}) + # VIFs + if "vif" in idata: + vifs = [] + for vid, vdata in idata["vif"].items(): + vif_e = {"vlan_id": int(vid), "ipv4": [], "ipv6": []} + if isinstance(vdata, dict): + for addr in _listify(vdata.get("address")): + if ":" in addr: + vif_e["ipv6"].append({"address": addr}) + else: + vif_e["ipv4"].append({"address": addr}) + vifs.append(vif_e) + entry["vifs"] = vifs + if entry["ipv4"] or entry["ipv6"] or entry.get("vifs"): + out.append(entry) + return out + except VyOSRestError: + return [] + + +def _listify(val): + """Return val as a list regardless of whether it's a str or list.""" + if val is None: + return [] + if isinstance(val, list): + return val + return [val] + + +def _set_addresses(client, name, ipv4, ipv6, commands): + base = _base(name) + itype = _iface_type(name) + for a in ipv4 or []: + client.configure_set(base + ["address"], a["address"]) + commands.append( + "set interfaces {t} {n} address '{a}'".format( + t=itype, + n=name, + a=a["address"], + ), + ) + for a in ipv6 or []: + client.configure_set(base + ["address"], a["address"]) + commands.append( + "set interfaces {t} {n} address '{a}'".format( + t=itype, + n=name, + a=a["address"], + ), + ) + + +def _delete_addresses(client, name, commands): + base = _base(name) + itype = _iface_type(name) + try: + client.configure_delete(base + ["address"]) + commands.append( + "delete interfaces {t} {n} address".format(t=itype, n=name), + ) + except VyOSRestError: + pass + + +def main(): + 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", required=True)), + ), + ipv6=dict( + type="list", + elements="dict", + options=dict(address=dict(type="str", required=True)), + ), + 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", required=True)), + ), + ipv6=dict( + type="list", + elements="dict", + options=dict(address=dict(type="str", required=True)), + ), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + + before = _get_l3_interfaces(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + targets = {i["name"] for i in config} if config else {i["name"] for i in before} + for iface in before: + if iface["name"] in targets: + _delete_addresses(client, iface["name"], commands) + changed = True + + elif state in ("merged", "replaced", "overridden"): + if state in ("replaced", "overridden"): + # First remove existing addresses on targeted interfaces + targets = {i["name"] for i in config} + if state == "overridden": + targets = {i["name"] for i in before} + for iface in before: + if iface["name"] in targets: + _delete_addresses(client, iface["name"], commands) + + for iface_cfg in config: + name = iface_cfg["name"] + _set_addresses( + client, + name, + iface_cfg.get("ipv4"), + iface_cfg.get("ipv6"), + commands, + ) + for vif in iface_cfg.get("vifs") or []: + vid = str(vif["vlan_id"]) + base = _base(name) + ["vif", vid] + itype = _iface_type(name) + for a in (vif.get("ipv4") or []) + (vif.get("ipv6") or []): + client.configure_set(base + ["address"], a["address"]) + commands.append( + "set interfaces {t} {n} vif {v} address '{a}'".format( + t=itype, + n=name, + v=vid, + a=a["address"], + ), + ) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_l3_interfaces(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_lag_interfaces.py b/plugins/modules/vyos_lag_interfaces.py deleted file mode 100644 index 15fe1f3..0000000 --- a/plugins/modules/vyos_lag_interfaces.py +++ /dev/null @@ -1,228 +0,0 @@ -#!/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_lag_interfaces -short_description: LAG/bonding interfaces resource module via REST API. -description: - - Manages Link Aggregation Group (LAG/bond) interfaces on VyOS via REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of LAG interface configurations. - type: list - elements: dict - suboptions: - name: - description: Bond interface name (e.g. bond0). - type: str - required: true - mode: - description: Bonding mode. - type: str - choices: [802.3ad, active-backup, broadcast, round-robin, - transmit-load-balance, adaptive-load-balance, xor-hash] - members: - description: Member interfaces. - type: list - elements: dict - suboptions: - member: - description: Interface name. - type: str - primary: - description: Primary interface name. - type: str - hash_policy: - description: Transmit hash policy. - type: str - choices: [layer2, layer2+3, layer3+4] - arp_monitor: - description: ARP monitoring settings. - type: dict - suboptions: - interval: - description: Monitoring interval in ms. - type: int - target: - description: Target IP addresses. - type: list - elements: str - state: - description: Desired state. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_MODE_MAP = { - "802.3ad": "802.3ad", - "active-backup": "active-backup", - "broadcast": "broadcast", - "round-robin": "round-robin", - "transmit-load-balance": "transmit-load-balance", - "adaptive-load-balance": "adaptive-load-balance", - "xor-hash": "xor-hash", -} - - -def _get(client): - try: - r = client.retrieve_show_config(["interfaces", "bonding"]) - data = r.get("data") or {} - out = [] - for bname, bdata in data.items(): - entry = {"name": bname} - if isinstance(bdata, dict): - if "mode" in bdata: - entry["mode"] = bdata["mode"] - if "primary" in bdata: - entry["primary"] = bdata["primary"] - if "hash-policy" in bdata: - entry["hash_policy"] = bdata["hash-policy"] - members = [] - for m in ( - bdata.get("member", {}).keys() if isinstance(bdata.get("member"), dict) else [] - ): - members.append({"member": m}) - if members: - entry["members"] = members - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _apply(client, cfg, commands): - name = cfg["name"] - base = ["interfaces", "bonding", name] - client.configure_set(base) - commands.append("set interfaces bonding {n}".format(n=name)) - - if cfg.get("mode"): - client.configure_set(base + ["mode"], cfg["mode"]) - commands.append("set interfaces bonding {n} mode {m}".format(n=name, m=cfg["mode"])) - if cfg.get("primary"): - client.configure_set(base + ["primary"], cfg["primary"]) - if cfg.get("hash_policy"): - client.configure_set(base + ["hash-policy"], cfg["hash_policy"]) - for m in cfg.get("members") or []: - client.configure_set(["interfaces", "ethernet", m["member"], "bond-group"], name) - commands.append("set interfaces ethernet {m} bond-group {n}".format(m=m["member"], n=name)) - arp = cfg.get("arp_monitor") or {} - if arp.get("interval"): - client.configure_set(base + ["arp-monitor", "interval"], str(arp["interval"])) - for t in arp.get("target") or []: - client.configure_set(base + ["arp-monitor", "target"], t) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - mode=dict(type="str", choices=list(_MODE_MAP.keys())), - 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"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for b in before: - if b["name"] in targets: - client.configure_delete(["interfaces", "bonding", b["name"]]) - commands.append("delete interfaces bonding {n}".format(n=b["name"])) - changed = True - elif state in ("merged", "replaced", "overridden"): - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_lag_interfaces.py.preview b/plugins/modules/vyos_lag_interfaces.py.preview new file mode 100644 index 0000000..15fe1f3 --- /dev/null +++ b/plugins/modules/vyos_lag_interfaces.py.preview @@ -0,0 +1,228 @@ +#!/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_lag_interfaces +short_description: LAG/bonding interfaces resource module via REST API. +description: + - Manages Link Aggregation Group (LAG/bond) interfaces on VyOS via REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: List of LAG interface configurations. + type: list + elements: dict + suboptions: + name: + description: Bond interface name (e.g. bond0). + type: str + required: true + mode: + description: Bonding mode. + type: str + choices: [802.3ad, active-backup, broadcast, round-robin, + transmit-load-balance, adaptive-load-balance, xor-hash] + members: + description: Member interfaces. + type: list + elements: dict + suboptions: + member: + description: Interface name. + type: str + primary: + description: Primary interface name. + type: str + hash_policy: + description: Transmit hash policy. + type: str + choices: [layer2, layer2+3, layer3+4] + arp_monitor: + description: ARP monitoring settings. + type: dict + suboptions: + interval: + description: Monitoring interval in ms. + type: int + target: + description: Target IP addresses. + type: list + elements: str + state: + description: Desired state. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: list +after: + returned: when changed + type: list +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_MODE_MAP = { + "802.3ad": "802.3ad", + "active-backup": "active-backup", + "broadcast": "broadcast", + "round-robin": "round-robin", + "transmit-load-balance": "transmit-load-balance", + "adaptive-load-balance": "adaptive-load-balance", + "xor-hash": "xor-hash", +} + + +def _get(client): + try: + r = client.retrieve_show_config(["interfaces", "bonding"]) + data = r.get("data") or {} + out = [] + for bname, bdata in data.items(): + entry = {"name": bname} + if isinstance(bdata, dict): + if "mode" in bdata: + entry["mode"] = bdata["mode"] + if "primary" in bdata: + entry["primary"] = bdata["primary"] + if "hash-policy" in bdata: + entry["hash_policy"] = bdata["hash-policy"] + members = [] + for m in ( + bdata.get("member", {}).keys() if isinstance(bdata.get("member"), dict) else [] + ): + members.append({"member": m}) + if members: + entry["members"] = members + out.append(entry) + return out + except VyOSRestError: + return [] + + +def _apply(client, cfg, commands): + name = cfg["name"] + base = ["interfaces", "bonding", name] + client.configure_set(base) + commands.append("set interfaces bonding {n}".format(n=name)) + + if cfg.get("mode"): + client.configure_set(base + ["mode"], cfg["mode"]) + commands.append("set interfaces bonding {n} mode {m}".format(n=name, m=cfg["mode"])) + if cfg.get("primary"): + client.configure_set(base + ["primary"], cfg["primary"]) + if cfg.get("hash_policy"): + client.configure_set(base + ["hash-policy"], cfg["hash_policy"]) + for m in cfg.get("members") or []: + client.configure_set(["interfaces", "ethernet", m["member"], "bond-group"], name) + commands.append("set interfaces ethernet {m} bond-group {n}".format(m=m["member"], n=name)) + arp = cfg.get("arp_monitor") or {} + if arp.get("interval"): + client.configure_set(base + ["arp-monitor", "interval"], str(arp["interval"])) + for t in arp.get("target") or []: + client.configure_set(base + ["arp-monitor", "target"], t) + + +def main(): + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + mode=dict(type="str", choices=list(_MODE_MAP.keys())), + 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"), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + targets = {i["name"] for i in config} if config else {i["name"] for i in before} + for b in before: + if b["name"] in targets: + client.configure_delete(["interfaces", "bonding", b["name"]]) + commands.append("delete interfaces bonding {n}".format(n=b["name"])) + changed = True + elif state in ("merged", "replaced", "overridden"): + for cfg in config: + _apply(client, cfg, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_lldp_interfaces.py b/plugins/modules/vyos_lldp_interfaces.py deleted file mode 100644 index 4f012aa..0000000 --- a/plugins/modules/vyos_lldp_interfaces.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/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_lldp_interfaces -short_description: LLDP interfaces resource module via REST API. -description: - - Manages per-interface LLDP settings (enable/disable, location) on VyOS - via the HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of LLDP interface configurations. - type: list - elements: dict - suboptions: - name: - description: Interface name. - type: str - required: true - enable: - description: Enable LLDP on this interface (default true). - type: bool - default: true - location: - description: LLDP-MED location data. - type: dict - suboptions: - coordinate_based: - type: dict - suboptions: - altitude: - type: int - datum: - type: str - choices: [WGS84, NAD83, MLLW] - latitude: - type: str - required: true - longitude: - type: str - required: true - elin: - description: Emergency ELIN number (10-25 digits). - type: str - state: - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _get(client): - try: - r = client.retrieve_show_config(["service", "lldp", "interface"]) - data = r.get("data") or {} - out = [] - for iname, idata in data.items(): - entry = {"name": iname} - if isinstance(idata, dict): - entry["enable"] = "disable" not in idata - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _apply(client, cfg, commands): - name = cfg["name"] - base = ["service", "lldp", "interface", name] - client.configure_set(base) - commands.append("set service lldp interface {n}".format(n=name)) - - if "enable" in cfg and not cfg["enable"]: - client.configure_set(base + ["disable"]) - commands.append("set service lldp interface {n} disable".format(n=name)) - - loc = cfg.get("location") or {} - coord = loc.get("coordinate_based") or {} - if coord: - lbase = base + ["location", "coordinate-based"] - if coord.get("latitude"): - client.configure_set(lbase + ["latitude"], coord["latitude"]) - if coord.get("longitude"): - client.configure_set(lbase + ["longitude"], coord["longitude"]) - if coord.get("altitude"): - client.configure_set(lbase + ["altitude"], str(coord["altitude"])) - if coord.get("datum"): - client.configure_set(lbase + ["datum"], coord["datum"]) - commands.append( - "set service lldp interface {n} location coordinate-based ...".format(n=name), - ) - if loc.get("elin"): - client.configure_set(base + ["location", "elin"], loc["elin"]) - commands.append( - "set service lldp interface {n} location elin {e}".format(n=name, e=loc["elin"]), - ) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - enable=dict(type="bool", default=True), - location=dict( - type="dict", - options=dict( - coordinate_based=dict( - type="dict", - options=dict( - altitude=dict(type="int"), - datum=dict(type="str", choices=["WGS84", "NAD83", "MLLW"]), - latitude=dict(type="str", required=True), - longitude=dict(type="str", required=True), - ), - ), - elin=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - for cfg in config if config else before: - name = cfg["name"] if isinstance(cfg, dict) else cfg - try: - client.configure_delete(["service", "lldp", "interface", name]) - commands.append("delete service lldp interface {n}".format(n=name)) - changed = True - except VyOSRestError: - pass - elif state in ("merged", "replaced", "overridden"): - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_lldp_interfaces.py.preview b/plugins/modules/vyos_lldp_interfaces.py.preview new file mode 100644 index 0000000..4f012aa --- /dev/null +++ b/plugins/modules/vyos_lldp_interfaces.py.preview @@ -0,0 +1,227 @@ +#!/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_lldp_interfaces +short_description: LLDP interfaces resource module via REST API. +description: + - Manages per-interface LLDP settings (enable/disable, location) on VyOS + via the HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: List of LLDP interface configurations. + type: list + elements: dict + suboptions: + name: + description: Interface name. + type: str + required: true + enable: + description: Enable LLDP on this interface (default true). + type: bool + default: true + location: + description: LLDP-MED location data. + type: dict + suboptions: + coordinate_based: + type: dict + suboptions: + altitude: + type: int + datum: + type: str + choices: [WGS84, NAD83, MLLW] + latitude: + type: str + required: true + longitude: + type: str + required: true + elin: + description: Emergency ELIN number (10-25 digits). + type: str + state: + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: list +after: + returned: when changed + type: list +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_IFACE_TYPES = { + "eth": "ethernet", + "bond": "bonding", + "lo": "loopback", + "dummy": "dummy", + "br": "bridge", +} + + +def _itype(name): + for p, t in _IFACE_TYPES.items(): + if name.startswith(p): + return t + return "ethernet" + + +def _get(client): + try: + r = client.retrieve_show_config(["service", "lldp", "interface"]) + data = r.get("data") or {} + out = [] + for iname, idata in data.items(): + entry = {"name": iname} + if isinstance(idata, dict): + entry["enable"] = "disable" not in idata + out.append(entry) + return out + except VyOSRestError: + return [] + + +def _apply(client, cfg, commands): + name = cfg["name"] + base = ["service", "lldp", "interface", name] + client.configure_set(base) + commands.append("set service lldp interface {n}".format(n=name)) + + if "enable" in cfg and not cfg["enable"]: + client.configure_set(base + ["disable"]) + commands.append("set service lldp interface {n} disable".format(n=name)) + + loc = cfg.get("location") or {} + coord = loc.get("coordinate_based") or {} + if coord: + lbase = base + ["location", "coordinate-based"] + if coord.get("latitude"): + client.configure_set(lbase + ["latitude"], coord["latitude"]) + if coord.get("longitude"): + client.configure_set(lbase + ["longitude"], coord["longitude"]) + if coord.get("altitude"): + client.configure_set(lbase + ["altitude"], str(coord["altitude"])) + if coord.get("datum"): + client.configure_set(lbase + ["datum"], coord["datum"]) + commands.append( + "set service lldp interface {n} location coordinate-based ...".format(n=name), + ) + if loc.get("elin"): + client.configure_set(base + ["location", "elin"], loc["elin"]) + commands.append( + "set service lldp interface {n} location elin {e}".format(n=name, e=loc["elin"]), + ) + + +def main(): + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + enable=dict(type="bool", default=True), + location=dict( + type="dict", + options=dict( + coordinate_based=dict( + type="dict", + options=dict( + altitude=dict(type="int"), + datum=dict(type="str", choices=["WGS84", "NAD83", "MLLW"]), + latitude=dict(type="str", required=True), + longitude=dict(type="str", required=True), + ), + ), + elin=dict(type="str"), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + for cfg in config if config else before: + name = cfg["name"] if isinstance(cfg, dict) else cfg + try: + client.configure_delete(["service", "lldp", "interface", name]) + commands.append("delete service lldp interface {n}".format(n=name)) + changed = True + except VyOSRestError: + pass + elif state in ("merged", "replaced", "overridden"): + for cfg in config: + _apply(client, cfg, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py deleted file mode 100644 index 37876d4..0000000 --- a/plugins/modules/vyos_ospf_interfaces.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/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: OSPF Interfaces Resource Module via REST API. -description: - - Manages per-interface OSPF parameters (cost, hello/dead timers, authentication, - passive mode, etc.) for both OSPFv2 and OSPFv3 via the VyOS HTTPS REST API. -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 - address_family: - description: OSPF settings per address family. - type: list - elements: dict - suboptions: - afi: - description: Address family (ipv4=OSPFv2, ipv6=OSPFv3). - type: str - choices: [ipv4, ipv6] - required: true - authentication: - type: dict - suboptions: - plaintext_password: - type: str - md5_key: - type: dict - suboptions: - key_id: - type: int - key: - type: str - bandwidth: - type: int - cost: - type: int - dead_interval: - type: int - hello_interval: - type: int - mtu_ignore: - type: bool - network: - type: str - priority: - type: int - retransmit_interval: - type: int - transmit_delay: - type: int - ifmtu: - type: int - instance: - type: str - passive: - type: bool - state: - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", - "vxlan": "vxlan", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _ospf_iface_base(name, afi): - itype = _itype(name) - if afi == "ipv4": - return ["interfaces", itype, name, "ip", "ospf"] - return ["interfaces", itype, name, "ipv6", "ospfv3"] - - -def _apply(client, cfg, commands): - name = cfg["name"] - for af in cfg.get("address_family") or []: - afi = af["afi"] - base = _ospf_iface_base(name, afi) - - int_map = { - "cost": "cost", - "bandwidth": "bandwidth", - "dead_interval": "dead-interval", - "hello_interval": "hello-interval", - "priority": "priority", - "retransmit_interval": "retransmit-interval", - "transmit_delay": "transmit-delay", - "ifmtu": "ifmtu", - } - for param, key in int_map.items(): - if af.get(param) is not None: - client.configure_set(base + [key], str(af[param])) - commands.append("set {b} {k} {v}".format(b=" ".join(base), k=key, v=af[param])) - - if af.get("mtu_ignore"): - client.configure_set(base + ["mtu-ignore"]) - commands.append("set {b} mtu-ignore".format(b=" ".join(base))) - if af.get("passive"): - client.configure_set(base + ["passive"]) - commands.append("set {b} passive".format(b=" ".join(base))) - if af.get("network"): - client.configure_set(base + ["network"], af["network"]) - if af.get("instance"): - client.configure_set(base + ["instance-id"], af["instance"]) - - auth = af.get("authentication") or {} - if auth.get("plaintext_password"): - client.configure_set( - base + ["authentication", "plaintext-password"], - auth["plaintext_password"], - ) - commands.append( - "set {b} authentication plaintext-password ...".format(b=" ".join(base)), - ) - md5 = auth.get("md5_key") or {} - if md5.get("key_id") and md5.get("key"): - client.configure_set( - base + ["authentication", "md5", "key-id", str(md5["key_id"]), "md5-key"], - md5["key"], - ) - - -def _get(client): - try: - r = client.retrieve_show_config(["interfaces"]) - raw = r.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - afs = [] - ip_ospf = (idata.get("ip") or {}).get("ospf") - if ip_ospf: - afs.append({"afi": "ipv4", "ospf_data": ip_ospf}) - ip6_ospfv3 = (idata.get("ipv6") or {}).get("ospfv3") - if ip6_ospfv3: - afs.append({"afi": "ipv6", "ospfv3_data": ip6_ospfv3}) - if afs: - out.append({"name": iname, "address_family": afs}) - return out - except VyOSRestError: - return [] - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str"), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - authentication=dict( - type="dict", - options=dict( - plaintext_password=dict(type="str", no_log=True), - md5_key=dict( - type="dict", - 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"), - mtu_ignore=dict(type="bool"), - network=dict(type="str"), - priority=dict(type="int"), - retransmit_interval=dict(type="int"), - transmit_delay=dict(type="int"), - ifmtu=dict(type="int"), - instance=dict(type="str"), - passive=dict(type="bool"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospf_interfaces.py.preview b/plugins/modules/vyos_ospf_interfaces.py.preview new file mode 100644 index 0000000..37876d4 --- /dev/null +++ b/plugins/modules/vyos_ospf_interfaces.py.preview @@ -0,0 +1,290 @@ +#!/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: OSPF Interfaces Resource Module via REST API. +description: + - Manages per-interface OSPF parameters (cost, hello/dead timers, authentication, + passive mode, etc.) for both OSPFv2 and OSPFv3 via the VyOS HTTPS REST API. +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 + address_family: + description: OSPF settings per address family. + type: list + elements: dict + suboptions: + afi: + description: Address family (ipv4=OSPFv2, ipv6=OSPFv3). + type: str + choices: [ipv4, ipv6] + required: true + authentication: + type: dict + suboptions: + plaintext_password: + type: str + md5_key: + type: dict + suboptions: + key_id: + type: int + key: + type: str + bandwidth: + type: int + cost: + type: int + dead_interval: + type: int + hello_interval: + type: int + mtu_ignore: + type: bool + network: + type: str + priority: + type: int + retransmit_interval: + type: int + transmit_delay: + type: int + ifmtu: + type: int + instance: + type: str + passive: + type: bool + state: + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: list +after: + returned: when changed + type: list +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_IFACE_TYPES = { + "eth": "ethernet", + "bond": "bonding", + "lo": "loopback", + "dummy": "dummy", + "br": "bridge", + "wg": "wireguard", + "tun": "tunnel", + "vxlan": "vxlan", +} + + +def _itype(name): + for p, t in _IFACE_TYPES.items(): + if name.startswith(p): + return t + return "ethernet" + + +def _ospf_iface_base(name, afi): + itype = _itype(name) + if afi == "ipv4": + return ["interfaces", itype, name, "ip", "ospf"] + return ["interfaces", itype, name, "ipv6", "ospfv3"] + + +def _apply(client, cfg, commands): + name = cfg["name"] + for af in cfg.get("address_family") or []: + afi = af["afi"] + base = _ospf_iface_base(name, afi) + + int_map = { + "cost": "cost", + "bandwidth": "bandwidth", + "dead_interval": "dead-interval", + "hello_interval": "hello-interval", + "priority": "priority", + "retransmit_interval": "retransmit-interval", + "transmit_delay": "transmit-delay", + "ifmtu": "ifmtu", + } + for param, key in int_map.items(): + if af.get(param) is not None: + client.configure_set(base + [key], str(af[param])) + commands.append("set {b} {k} {v}".format(b=" ".join(base), k=key, v=af[param])) + + if af.get("mtu_ignore"): + client.configure_set(base + ["mtu-ignore"]) + commands.append("set {b} mtu-ignore".format(b=" ".join(base))) + if af.get("passive"): + client.configure_set(base + ["passive"]) + commands.append("set {b} passive".format(b=" ".join(base))) + if af.get("network"): + client.configure_set(base + ["network"], af["network"]) + if af.get("instance"): + client.configure_set(base + ["instance-id"], af["instance"]) + + auth = af.get("authentication") or {} + if auth.get("plaintext_password"): + client.configure_set( + base + ["authentication", "plaintext-password"], + auth["plaintext_password"], + ) + commands.append( + "set {b} authentication plaintext-password ...".format(b=" ".join(base)), + ) + md5 = auth.get("md5_key") or {} + if md5.get("key_id") and md5.get("key"): + client.configure_set( + base + ["authentication", "md5", "key-id", str(md5["key_id"]), "md5-key"], + md5["key"], + ) + + +def _get(client): + try: + r = client.retrieve_show_config(["interfaces"]) + raw = r.get("data") or {} + out = [] + for itype, itype_data in raw.items(): + if not isinstance(itype_data, dict): + continue + for iname, idata in itype_data.items(): + if not isinstance(idata, dict): + continue + afs = [] + ip_ospf = (idata.get("ip") or {}).get("ospf") + if ip_ospf: + afs.append({"afi": "ipv4", "ospf_data": ip_ospf}) + ip6_ospfv3 = (idata.get("ipv6") or {}).get("ospfv3") + if ip6_ospfv3: + afs.append({"afi": "ipv6", "ospfv3_data": ip6_ospfv3}) + if afs: + out.append({"name": iname, "address_family": afs}) + return out + except VyOSRestError: + return [] + + +def main(): + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str"), + address_family=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), + authentication=dict( + type="dict", + options=dict( + plaintext_password=dict(type="str", no_log=True), + md5_key=dict( + type="dict", + 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"), + mtu_ignore=dict(type="bool"), + network=dict(type="str"), + priority=dict(type="int"), + retransmit_interval=dict(type="int"), + transmit_delay=dict(type="int"), + ifmtu=dict(type="int"), + instance=dict(type="str"), + passive=dict(type="bool"), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + for cfg in config: + _apply(client, cfg, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_ospfv2.py b/plugins/modules/vyos_ospfv2.py deleted file mode 100644 index 063d0dc..0000000 --- a/plugins/modules/vyos_ospfv2.py +++ /dev/null @@ -1,487 +0,0 @@ -#!/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: OSPFv2 resource module via REST API. -description: - - Manages OSPFv2 (OSPF for IPv4) configuration on VyOS via HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: OSPFv2 configuration. - type: dict - suboptions: - areas: - type: list - elements: dict - suboptions: - area_id: - type: str - area_type: - type: dict - suboptions: - normal: - type: bool - nssa: - type: dict - suboptions: - set: - type: bool - default_cost: - type: int - no_summary: - type: bool - translate: - type: str - choices: [always, candidate, never] - stub: - type: dict - suboptions: - set: - type: bool - default_cost: - type: int - no_summary: - type: bool - authentication: - type: str - choices: [plaintext-password, md5] - network: - type: list - elements: dict - suboptions: - address: - type: str - range: - type: list - elements: dict - suboptions: - address: - type: str - cost: - type: int - not_advertise: - type: bool - substitute: - type: str - shortcut: - type: str - choices: [default, disable, enable] - auto_cost: - type: dict - suboptions: - reference_bandwidth: - type: int - default_information: - type: dict - suboptions: - originate: - type: dict - suboptions: - always: - type: bool - metric: - type: int - metric_type: - type: int - route_map: - type: str - log_adjacency_changes: - type: str - choices: [detail] - max_metric: - type: dict - suboptions: - router_lsa: - type: dict - suboptions: - administrative: - type: bool - on_shutdown: - type: int - on_startup: - type: int - mpls_te: - type: dict - suboptions: - enabled: - type: bool - router_address: - type: str - neighbor: - type: list - elements: dict - suboptions: - neighbor_id: - type: str - poll_interval: - type: int - priority: - type: int - parameters: - type: dict - suboptions: - router_id: - type: str - opaque_lsa: - type: bool - rfc1583_compatibility: - type: bool - abr_type: - type: str - choices: [cisco, ibm, shortcut, standard] - passive_interface: - type: list - elements: str - redistribute: - type: list - elements: dict - suboptions: - route_type: - type: str - choices: [bgp, connected, kernel, rip, static] - metric: - type: int - metric_type: - type: int - route_map: - type: str - state: - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BASE = ["protocols", "ospf"] - - -def _get(client): - try: - r = client.retrieve_show_config(_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - params = config.get("parameters") or {} - if params.get("router_id"): - client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) - commands.append("set protocols ospf parameters router-id {r}".format(r=params["router_id"])) - if params.get("opaque_lsa"): - client.configure_set(_BASE + ["parameters", "opaque-lsa"]) - if params.get("rfc1583_compatibility"): - client.configure_set(_BASE + ["parameters", "rfc1583-compatibility"]) - if params.get("abr_type"): - client.configure_set(_BASE + ["parameters", "abr-type"], params["abr_type"]) - - ac = config.get("auto_cost") or {} - if ac.get("reference_bandwidth"): - client.configure_set( - _BASE + ["auto-cost", "reference-bandwidth"], - str(ac["reference_bandwidth"]), - ) - commands.append( - "set protocols ospf auto-cost reference-bandwidth {b}".format( - b=ac["reference_bandwidth"], - ), - ) - - if config.get("log_adjacency_changes"): - client.configure_set(_BASE + ["log-adjacency-changes", config["log_adjacency_changes"]]) - - for nbr in config.get("neighbor") or []: - nb = _BASE + ["neighbor", nbr["neighbor_id"]] - client.configure_set(nb) - if nbr.get("poll_interval"): - client.configure_set(nb + ["poll-interval"], str(nbr["poll_interval"])) - if nbr.get("priority"): - client.configure_set(nb + ["priority"], str(nbr["priority"])) - - for pi in config.get("passive_interface") or []: - client.configure_set(_BASE + ["passive-interface"], pi) - commands.append("set protocols ospf passive-interface {i}".format(i=pi)) - - for redist in config.get("redistribute") or []: - rb = _BASE + ["redistribute", redist["route_type"]] - client.configure_set(rb) - commands.append("set protocols ospf redistribute {r}".format(r=redist["route_type"])) - if redist.get("metric"): - client.configure_set(rb + ["metric"], str(redist["metric"])) - if redist.get("metric_type"): - client.configure_set(rb + ["metric-type"], str(redist["metric_type"])) - if redist.get("route_map"): - client.configure_set(rb + ["route-map"], redist["route_map"]) - - di = (config.get("default_information") or {}).get("originate") or {} - if di: - dib = _BASE + ["default-information", "originate"] - client.configure_set(dib) - commands.append("set protocols ospf default-information originate") - if di.get("always"): - client.configure_set(dib + ["always"]) - if di.get("metric"): - client.configure_set(dib + ["metric"], str(di["metric"])) - if di.get("metric_type"): - client.configure_set(dib + ["metric-type"], str(di["metric_type"])) - if di.get("route_map"): - client.configure_set(dib + ["route-map"], di["route_map"]) - - for area in config.get("areas") or []: - aid = area["area_id"] - ab = _BASE + ["area", aid] - client.configure_set(ab) - commands.append("set protocols ospf area {a}".format(a=aid)) - - at = area.get("area_type") or {} - if at.get("normal"): - pass # default - nssa = at.get("nssa") or {} - if nssa.get("set"): - client.configure_set(ab + ["area-type", "nssa"]) - if nssa.get("default_cost"): - client.configure_set( - ab + ["area-type", "nssa", "default-cost"], - str(nssa["default_cost"]), - ) - if nssa.get("no_summary"): - client.configure_set(ab + ["area-type", "nssa", "no-summary"]) - stub = at.get("stub") or {} - if stub.get("set"): - client.configure_set(ab + ["area-type", "stub"]) - if stub.get("default_cost"): - client.configure_set( - ab + ["area-type", "stub", "default-cost"], - str(stub["default_cost"]), - ) - - if area.get("authentication"): - client.configure_set(ab + ["authentication"], area["authentication"]) - - for net in area.get("network") or []: - client.configure_set(ab + ["network"], net["address"]) - commands.append( - "set protocols ospf area {a} network {n}".format( - a=aid, - n=net["address"], - ), - ) - - for rng in area.get("range") or []: - rb2 = ab + ["range", rng["address"]] - client.configure_set(rb2) - if rng.get("cost"): - client.configure_set(rb2 + ["cost"], str(rng["cost"])) - if rng.get("not_advertise"): - client.configure_set(rb2 + ["not-advertise"]) - if rng.get("substitute"): - client.configure_set(rb2 + ["substitute"], rng["substitute"]) - - if area.get("shortcut"): - client.configure_set(ab + ["shortcut"], area["shortcut"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - areas=dict( - type="list", - elements="dict", - options=dict( - area_id=dict(type="str"), - 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")), - ), - range=dict( - type="list", - elements="dict", - options=dict( - address=dict(type="str"), - 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"), - ), - ), - ), - ), - log_adjacency_changes=dict(type="str", choices=["detail"]), - max_metric=dict(type="dict"), - mpls_te=dict( - type="dict", - options=dict( - enabled=dict(type="bool"), - router_address=dict(type="str"), - ), - ), - neighbor=dict( - type="list", - elements="dict", - options=dict( - neighbor_id=dict(type="str"), - poll_interval=dict(type="int"), - priority=dict(type="int"), - ), - ), - parameters=dict( - type="dict", - options=dict( - router_id=dict(type="str"), - opaque_lsa=dict(type="bool"), - rfc1583_compatibility=dict(type="bool"), - abr_type=dict(type="str", choices=["cisco", "ibm", "shortcut", "standard"]), - ), - ), - 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( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BASE) - commands.append("delete protocols ospf") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BASE) - commands.append("delete protocols ospf") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospfv2.py.preview b/plugins/modules/vyos_ospfv2.py.preview new file mode 100644 index 0000000..063d0dc --- /dev/null +++ b/plugins/modules/vyos_ospfv2.py.preview @@ -0,0 +1,487 @@ +#!/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: OSPFv2 resource module via REST API. +description: + - Manages OSPFv2 (OSPF for IPv4) configuration on VyOS via HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: OSPFv2 configuration. + type: dict + suboptions: + areas: + type: list + elements: dict + suboptions: + area_id: + type: str + area_type: + type: dict + suboptions: + normal: + type: bool + nssa: + type: dict + suboptions: + set: + type: bool + default_cost: + type: int + no_summary: + type: bool + translate: + type: str + choices: [always, candidate, never] + stub: + type: dict + suboptions: + set: + type: bool + default_cost: + type: int + no_summary: + type: bool + authentication: + type: str + choices: [plaintext-password, md5] + network: + type: list + elements: dict + suboptions: + address: + type: str + range: + type: list + elements: dict + suboptions: + address: + type: str + cost: + type: int + not_advertise: + type: bool + substitute: + type: str + shortcut: + type: str + choices: [default, disable, enable] + auto_cost: + type: dict + suboptions: + reference_bandwidth: + type: int + default_information: + type: dict + suboptions: + originate: + type: dict + suboptions: + always: + type: bool + metric: + type: int + metric_type: + type: int + route_map: + type: str + log_adjacency_changes: + type: str + choices: [detail] + max_metric: + type: dict + suboptions: + router_lsa: + type: dict + suboptions: + administrative: + type: bool + on_shutdown: + type: int + on_startup: + type: int + mpls_te: + type: dict + suboptions: + enabled: + type: bool + router_address: + type: str + neighbor: + type: list + elements: dict + suboptions: + neighbor_id: + type: str + poll_interval: + type: int + priority: + type: int + parameters: + type: dict + suboptions: + router_id: + type: str + opaque_lsa: + type: bool + rfc1583_compatibility: + type: bool + abr_type: + type: str + choices: [cisco, ibm, shortcut, standard] + passive_interface: + type: list + elements: str + redistribute: + type: list + elements: dict + suboptions: + route_type: + type: str + choices: [bgp, connected, kernel, rip, static] + metric: + type: int + metric_type: + type: int + route_map: + type: str + state: + type: str + choices: [merged, replaced, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: dict +after: + returned: when changed + type: dict +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_BASE = ["protocols", "ospf"] + + +def _get(client): + try: + r = client.retrieve_show_config(_BASE) + return r.get("data") or {} + except VyOSRestError: + return {} + + +def _apply(client, config, commands): + params = config.get("parameters") or {} + if params.get("router_id"): + client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) + commands.append("set protocols ospf parameters router-id {r}".format(r=params["router_id"])) + if params.get("opaque_lsa"): + client.configure_set(_BASE + ["parameters", "opaque-lsa"]) + if params.get("rfc1583_compatibility"): + client.configure_set(_BASE + ["parameters", "rfc1583-compatibility"]) + if params.get("abr_type"): + client.configure_set(_BASE + ["parameters", "abr-type"], params["abr_type"]) + + ac = config.get("auto_cost") or {} + if ac.get("reference_bandwidth"): + client.configure_set( + _BASE + ["auto-cost", "reference-bandwidth"], + str(ac["reference_bandwidth"]), + ) + commands.append( + "set protocols ospf auto-cost reference-bandwidth {b}".format( + b=ac["reference_bandwidth"], + ), + ) + + if config.get("log_adjacency_changes"): + client.configure_set(_BASE + ["log-adjacency-changes", config["log_adjacency_changes"]]) + + for nbr in config.get("neighbor") or []: + nb = _BASE + ["neighbor", nbr["neighbor_id"]] + client.configure_set(nb) + if nbr.get("poll_interval"): + client.configure_set(nb + ["poll-interval"], str(nbr["poll_interval"])) + if nbr.get("priority"): + client.configure_set(nb + ["priority"], str(nbr["priority"])) + + for pi in config.get("passive_interface") or []: + client.configure_set(_BASE + ["passive-interface"], pi) + commands.append("set protocols ospf passive-interface {i}".format(i=pi)) + + for redist in config.get("redistribute") or []: + rb = _BASE + ["redistribute", redist["route_type"]] + client.configure_set(rb) + commands.append("set protocols ospf redistribute {r}".format(r=redist["route_type"])) + if redist.get("metric"): + client.configure_set(rb + ["metric"], str(redist["metric"])) + if redist.get("metric_type"): + client.configure_set(rb + ["metric-type"], str(redist["metric_type"])) + if redist.get("route_map"): + client.configure_set(rb + ["route-map"], redist["route_map"]) + + di = (config.get("default_information") or {}).get("originate") or {} + if di: + dib = _BASE + ["default-information", "originate"] + client.configure_set(dib) + commands.append("set protocols ospf default-information originate") + if di.get("always"): + client.configure_set(dib + ["always"]) + if di.get("metric"): + client.configure_set(dib + ["metric"], str(di["metric"])) + if di.get("metric_type"): + client.configure_set(dib + ["metric-type"], str(di["metric_type"])) + if di.get("route_map"): + client.configure_set(dib + ["route-map"], di["route_map"]) + + for area in config.get("areas") or []: + aid = area["area_id"] + ab = _BASE + ["area", aid] + client.configure_set(ab) + commands.append("set protocols ospf area {a}".format(a=aid)) + + at = area.get("area_type") or {} + if at.get("normal"): + pass # default + nssa = at.get("nssa") or {} + if nssa.get("set"): + client.configure_set(ab + ["area-type", "nssa"]) + if nssa.get("default_cost"): + client.configure_set( + ab + ["area-type", "nssa", "default-cost"], + str(nssa["default_cost"]), + ) + if nssa.get("no_summary"): + client.configure_set(ab + ["area-type", "nssa", "no-summary"]) + stub = at.get("stub") or {} + if stub.get("set"): + client.configure_set(ab + ["area-type", "stub"]) + if stub.get("default_cost"): + client.configure_set( + ab + ["area-type", "stub", "default-cost"], + str(stub["default_cost"]), + ) + + if area.get("authentication"): + client.configure_set(ab + ["authentication"], area["authentication"]) + + for net in area.get("network") or []: + client.configure_set(ab + ["network"], net["address"]) + commands.append( + "set protocols ospf area {a} network {n}".format( + a=aid, + n=net["address"], + ), + ) + + for rng in area.get("range") or []: + rb2 = ab + ["range", rng["address"]] + client.configure_set(rb2) + if rng.get("cost"): + client.configure_set(rb2 + ["cost"], str(rng["cost"])) + if rng.get("not_advertise"): + client.configure_set(rb2 + ["not-advertise"]) + if rng.get("substitute"): + client.configure_set(rb2 + ["substitute"], rng["substitute"]) + + if area.get("shortcut"): + client.configure_set(ab + ["shortcut"], area["shortcut"]) + + +def main(): + argument_spec = dict( + config=dict( + type="dict", + options=dict( + areas=dict( + type="list", + elements="dict", + options=dict( + area_id=dict(type="str"), + 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")), + ), + range=dict( + type="list", + elements="dict", + options=dict( + address=dict(type="str"), + 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"), + ), + ), + ), + ), + log_adjacency_changes=dict(type="str", choices=["detail"]), + max_metric=dict(type="dict"), + mpls_te=dict( + type="dict", + options=dict( + enabled=dict(type="bool"), + router_address=dict(type="str"), + ), + ), + neighbor=dict( + type="list", + elements="dict", + options=dict( + neighbor_id=dict(type="str"), + poll_interval=dict(type="int"), + priority=dict(type="int"), + ), + ), + parameters=dict( + type="dict", + options=dict( + router_id=dict(type="str"), + opaque_lsa=dict(type="bool"), + rfc1583_compatibility=dict(type="bool"), + abr_type=dict(type="str", choices=["cisco", "ibm", "shortcut", "standard"]), + ), + ), + 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( + type="str", + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], + supports_check_mode=True, + ) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + if before: + client.configure_delete(_BASE) + commands.append("delete protocols ospf") + changed = True + elif state in ("merged", "replaced"): + if state == "replaced" and before: + client.configure_delete(_BASE) + commands.append("delete protocols ospf") + _apply(client, config, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_ospfv3.py b/plugins/modules/vyos_ospfv3.py deleted file mode 100644 index 0de49d7..0000000 --- a/plugins/modules/vyos_ospfv3.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/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: OSPFv3 resource module via REST API. -description: - - Manages OSPFv3 (OSPF for IPv6) configuration on VyOS via HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: OSPFv3 configuration. - type: dict - suboptions: - areas: - type: list - elements: dict - suboptions: - area_id: - type: str - export_list: - type: str - import_list: - type: str - interface: - description: Interfaces in this area. - type: list - elements: dict - suboptions: - name: - type: str - range: - type: list - elements: dict - suboptions: - address: - type: str - advertise: - type: bool - not_advertise: - type: bool - parameters: - type: dict - suboptions: - router_id: - type: str - redistribute: - type: list - elements: dict - suboptions: - route_type: - type: str - choices: [bgp, connected, kernel, ripng, static] - route_map: - type: str - state: - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BASE = ["protocols", "ospfv3"] - - -def _get(client): - try: - r = client.retrieve_show_config(_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - params = config.get("parameters") or {} - if params.get("router_id"): - client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) - commands.append( - "set protocols ospfv3 parameters router-id {r}".format(r=params["router_id"]), - ) - - for redist in config.get("redistribute") or []: - rb = _BASE + ["redistribute", redist["route_type"]] - client.configure_set(rb) - commands.append("set protocols ospfv3 redistribute {r}".format(r=redist["route_type"])) - if redist.get("route_map"): - client.configure_set(rb + ["route-map"], redist["route_map"]) - - for area in config.get("areas") or []: - aid = area["area_id"] - ab = _BASE + ["area", aid] - client.configure_set(ab) - commands.append("set protocols ospfv3 area '{a}'".format(a=aid)) - if area.get("export_list"): - client.configure_set(ab + ["export-list"], area["export_list"]) - commands.append( - "set protocols ospfv3 area {a} export-list {e}".format( - a=aid, - e=area["export_list"], - ), - ) - if area.get("import_list"): - client.configure_set(ab + ["import-list"], area["import_list"]) - for iface in area.get("interface") or []: - client.configure_set(ab + ["interface"], iface["name"]) - commands.append( - "set protocols ospfv3 area {a} interface {i}".format( - a=aid, - i=iface["name"], - ), - ) - for rng in area.get("range") or []: - rb2 = ab + ["range", rng["address"]] - client.configure_set(rb2) - commands.append( - "set protocols ospfv3 area {a} range {r}".format( - a=aid, - r=rng["address"], - ), - ) - if rng.get("advertise"): - client.configure_set(rb2 + ["advertise"]) - if rng.get("not_advertise"): - client.configure_set(rb2 + ["not-advertise"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - areas=dict( - type="list", - elements="dict", - options=dict( - area_id=dict(type="str"), - export_list=dict(type="str"), - import_list=dict(type="str"), - interface=dict( - type="list", - elements="dict", - options=dict(name=dict(type="str")), - ), - range=dict( - type="list", - elements="dict", - options=dict( - address=dict(type="str"), - 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( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BASE) - commands.append("delete protocols ospfv3") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BASE) - commands.append("delete protocols ospfv3") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospfv3.py.preview b/plugins/modules/vyos_ospfv3.py.preview new file mode 100644 index 0000000..0de49d7 --- /dev/null +++ b/plugins/modules/vyos_ospfv3.py.preview @@ -0,0 +1,257 @@ +#!/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: OSPFv3 resource module via REST API. +description: + - Manages OSPFv3 (OSPF for IPv6) configuration on VyOS via HTTPS REST API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: OSPFv3 configuration. + type: dict + suboptions: + areas: + type: list + elements: dict + suboptions: + area_id: + type: str + export_list: + type: str + import_list: + type: str + interface: + description: Interfaces in this area. + type: list + elements: dict + suboptions: + name: + type: str + range: + type: list + elements: dict + suboptions: + address: + type: str + advertise: + type: bool + not_advertise: + type: bool + parameters: + type: dict + suboptions: + router_id: + type: str + redistribute: + type: list + elements: dict + suboptions: + route_type: + type: str + choices: [bgp, connected, kernel, ripng, static] + route_map: + type: str + state: + type: str + choices: [merged, replaced, deleted, gathered] + default: merged + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +before: + returned: always + type: dict +after: + returned: when changed + type: dict +commands: + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_BASE = ["protocols", "ospfv3"] + + +def _get(client): + try: + r = client.retrieve_show_config(_BASE) + return r.get("data") or {} + except VyOSRestError: + return {} + + +def _apply(client, config, commands): + params = config.get("parameters") or {} + if params.get("router_id"): + client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) + commands.append( + "set protocols ospfv3 parameters router-id {r}".format(r=params["router_id"]), + ) + + for redist in config.get("redistribute") or []: + rb = _BASE + ["redistribute", redist["route_type"]] + client.configure_set(rb) + commands.append("set protocols ospfv3 redistribute {r}".format(r=redist["route_type"])) + if redist.get("route_map"): + client.configure_set(rb + ["route-map"], redist["route_map"]) + + for area in config.get("areas") or []: + aid = area["area_id"] + ab = _BASE + ["area", aid] + client.configure_set(ab) + commands.append("set protocols ospfv3 area '{a}'".format(a=aid)) + if area.get("export_list"): + client.configure_set(ab + ["export-list"], area["export_list"]) + commands.append( + "set protocols ospfv3 area {a} export-list {e}".format( + a=aid, + e=area["export_list"], + ), + ) + if area.get("import_list"): + client.configure_set(ab + ["import-list"], area["import_list"]) + for iface in area.get("interface") or []: + client.configure_set(ab + ["interface"], iface["name"]) + commands.append( + "set protocols ospfv3 area {a} interface {i}".format( + a=aid, + i=iface["name"], + ), + ) + for rng in area.get("range") or []: + rb2 = ab + ["range", rng["address"]] + client.configure_set(rb2) + commands.append( + "set protocols ospfv3 area {a} range {r}".format( + a=aid, + r=rng["address"], + ), + ) + if rng.get("advertise"): + client.configure_set(rb2 + ["advertise"]) + if rng.get("not_advertise"): + client.configure_set(rb2 + ["not-advertise"]) + + +def main(): + argument_spec = dict( + config=dict( + type="dict", + options=dict( + areas=dict( + type="list", + elements="dict", + options=dict( + area_id=dict(type="str"), + export_list=dict(type="str"), + import_list=dict(type="str"), + interface=dict( + type="list", + elements="dict", + options=dict(name=dict(type="str")), + ), + range=dict( + type="list", + elements="dict", + options=dict( + address=dict(type="str"), + 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( + type="str", + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], + supports_check_mode=True, + ) + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") + commands = [] + changed = False + before = _get(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state == "deleted": + if before: + client.configure_delete(_BASE) + commands.append("delete protocols ospfv3") + changed = True + elif state in ("merged", "replaced"): + if state == "replaced" and before: + client.configure_delete(_BASE) + commands.append("delete protocols ospfv3") + _apply(client, config, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_ping.py b/plugins/modules/vyos_ping.py deleted file mode 100644 index 45368d1..0000000 --- a/plugins/modules/vyos_ping.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/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_ping -short_description: Test reachability using ping via VyOS REST API. -description: - - Sends an ICMP ping via the VyOS HTTPS REST API (/show endpoint) and - checks reachability. Mirrors C(vyos.vyos.vyos_ping). -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - dest: - description: Destination IP address or hostname. - type: str - required: true - count: - description: Number of packets to send. - type: int - default: 5 - source: - description: Source interface or IP address. - type: str - ttl: - description: Time-to-live value. - type: int - size: - description: Packet size in bytes. - type: int - interval: - description: Interval between pings in seconds. - type: int - state: - description: C(present) = expect success; C(absent) = expect failure. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: Ping command tokens sent to the API. - returned: always - type: list -packet_loss: - description: Packet loss percentage string. - returned: always - type: str -packets_rx: - description: Packets received. - returned: always - type: int -packets_tx: - description: Packets transmitted. - returned: always - type: int -rtt: - description: RTT statistics dict (min/avg/max/mdev). - returned: when available - type: dict -""" - -import re - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _build_ping_path(params): - path = ["ping", params["dest"]] - if params.get("count"): - path += ["count", str(params["count"])] - if params.get("source"): - path += ["interface", params["source"]] - if params.get("ttl"): - path += ["ttl", str(params["ttl"])] - if params.get("size"): - path += ["size", str(params["size"])] - return path - - -def _parse_ping_output(output): - stats = {"packet_loss": "100%", "packets_rx": 0, "packets_tx": 0} - tx_match = re.search(r"(\d+) packets transmitted", output) - rx_match = re.search(r"(\d+) received", output) - loss_match = re.search(r"(\d+(?:\.\d+)?%)\s+packet loss", output) - if tx_match: - stats["packets_tx"] = int(tx_match.group(1)) - if rx_match: - stats["packets_rx"] = int(rx_match.group(1)) - if loss_match: - stats["packet_loss"] = loss_match.group(1) - rtt_match = re.search( - r"rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)", - output, - ) - if rtt_match: - stats["rtt"] = { - "min": rtt_match.group(1), - "avg": rtt_match.group(2), - "max": rtt_match.group(3), - "mdev": rtt_match.group(4), - } - return stats - - -def main(): - argument_spec = dict( - dest=dict(type="str", required=True), - count=dict(type="int", default=5), - source=dict(type="str"), - ttl=dict(type="int"), - size=dict(type="int"), - interval=dict(type="int"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - - if module.check_mode: - module.exit_json(changed=False, commands=[], packet_loss="0%", packets_rx=0, packets_tx=0) - - client = VyOSRestClient(module) - path = _build_ping_path(module.params) - - try: - result = client.show(path) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - output = result.get("data", "") - stats = _parse_ping_output(output) - reachable = stats["packets_rx"] > 0 - - if module.params["state"] == "present" and not reachable: - module.fail_json( - msg="Ping to {dest} failed — no packets received.".format(dest=module.params["dest"]), - **stats, - ) - elif module.params["state"] == "absent" and reachable: - module.fail_json( - msg="Expected {dest} to be unreachable but ping succeeded.".format( - dest=module.params["dest"], - ), - **stats, - ) - - module.exit_json(changed=False, commands=path, **stats) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ping.py.preview b/plugins/modules/vyos_ping.py.preview new file mode 100644 index 0000000..45368d1 --- /dev/null +++ b/plugins/modules/vyos_ping.py.preview @@ -0,0 +1,180 @@ +#!/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_ping +short_description: Test reachability using ping via VyOS REST API. +description: + - Sends an ICMP ping via the VyOS HTTPS REST API (/show endpoint) and + checks reachability. Mirrors C(vyos.vyos.vyos_ping). +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + dest: + description: Destination IP address or hostname. + type: str + required: true + count: + description: Number of packets to send. + type: int + default: 5 + source: + description: Source interface or IP address. + type: str + ttl: + description: Time-to-live value. + type: int + size: + description: Packet size in bytes. + type: int + interval: + description: Interval between pings in seconds. + type: int + state: + description: C(present) = expect success; C(absent) = expect failure. + type: str + choices: [present, absent] + default: present + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +commands: + description: Ping command tokens sent to the API. + returned: always + type: list +packet_loss: + description: Packet loss percentage string. + returned: always + type: str +packets_rx: + description: Packets received. + returned: always + type: int +packets_tx: + description: Packets transmitted. + returned: always + type: int +rtt: + description: RTT statistics dict (min/avg/max/mdev). + returned: when available + type: dict +""" + +import re + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def _build_ping_path(params): + path = ["ping", params["dest"]] + if params.get("count"): + path += ["count", str(params["count"])] + if params.get("source"): + path += ["interface", params["source"]] + if params.get("ttl"): + path += ["ttl", str(params["ttl"])] + if params.get("size"): + path += ["size", str(params["size"])] + return path + + +def _parse_ping_output(output): + stats = {"packet_loss": "100%", "packets_rx": 0, "packets_tx": 0} + tx_match = re.search(r"(\d+) packets transmitted", output) + rx_match = re.search(r"(\d+) received", output) + loss_match = re.search(r"(\d+(?:\.\d+)?%)\s+packet loss", output) + if tx_match: + stats["packets_tx"] = int(tx_match.group(1)) + if rx_match: + stats["packets_rx"] = int(rx_match.group(1)) + if loss_match: + stats["packet_loss"] = loss_match.group(1) + rtt_match = re.search( + r"rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)", + output, + ) + if rtt_match: + stats["rtt"] = { + "min": rtt_match.group(1), + "avg": rtt_match.group(2), + "max": rtt_match.group(3), + "mdev": rtt_match.group(4), + } + return stats + + +def main(): + argument_spec = dict( + dest=dict(type="str", required=True), + count=dict(type="int", default=5), + source=dict(type="str"), + ttl=dict(type="int"), + size=dict(type="int"), + interval=dict(type="int"), + state=dict(type="str", default="present", choices=["present", "absent"]), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + if module.check_mode: + module.exit_json(changed=False, commands=[], packet_loss="0%", packets_rx=0, packets_tx=0) + + client = VyOSRestClient(module) + path = _build_ping_path(module.params) + + try: + result = client.show(path) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + output = result.get("data", "") + stats = _parse_ping_output(output) + reachable = stats["packets_rx"] > 0 + + if module.params["state"] == "present" and not reachable: + module.fail_json( + msg="Ping to {dest} failed — no packets received.".format(dest=module.params["dest"]), + **stats, + ) + elif module.params["state"] == "absent" and reachable: + module.fail_json( + msg="Expected {dest} to be unreachable but ping succeeded.".format( + dest=module.params["dest"], + ), + **stats, + ) + + module.exit_json(changed=False, commands=path, **stats) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_prefix_lists.py b/plugins/modules/vyos_prefix_lists.py deleted file mode 100644 index 5334e7a..0000000 --- a/plugins/modules/vyos_prefix_lists.py +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: vyos_prefix_lists -short_description: Manage prefix-list configuration on VyOS devices using REST API -description: - - Manages IPv4 and IPv6 prefix lists on VyOS 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: List of prefix-list configurations grouped by address family. - type: list - elements: dict - suboptions: - afi: - description: Address family identifier. - type: str - choices: [ipv4, ipv6] - required: true - prefix_lists: - description: Named prefix lists. - type: list - elements: dict - suboptions: - name: - description: Prefix list name. - type: str - required: true - description: - description: Prefix list description. - type: str - entries: - description: Prefix list rules. - type: list - elements: dict - suboptions: - sequence: - description: Rule sequence number. - type: int - required: true - description: - description: Rule description. - type: str - action: - description: Permit or deny. - type: str - choices: [permit, deny] - ge: - description: Minimum prefix length. - type: int - le: - description: Maximum prefix length. - type: int - prefix: - description: Network prefix to match. - type: str - - state: - description: - - Desired state of the prefix-list configuration. - - C(merged) adds or updates entries without removing existing ones. - - C(replaced) replaces each named prefix list mentioned in config. - - C(overridden) replaces all prefix lists for the given AFIs. - - C(deleted) removes prefix lists. Without config removes all. - - 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 prefix list configuration - 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 - entries: - - sequence: 5 - action: permit - prefix: 2001:db8:8000::/35 - le: 37 - state: merged - -- name: Delete all prefix lists - vyos.rest.vyos_prefix_lists: - state: deleted - -- name: Gather current prefix list configuration - vyos.rest.vyos_prefix_lists: - state: gathered -""" - -RETURN = r""" -before: - description: Prefix list configuration before this module ran. - returned: always - type: list - -after: - description: Prefix list 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 prefix list 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 - - -# AFI → API key mapping -_PL_KEY = {"ipv4": "prefix-list", "ipv6": "prefix-list6"} - - -# ------------------------------------------------------------ -# Parsing: API response → argspec list -# ------------------------------------------------------------ - - -def get_running_config(vyos): - """ - Fetch current prefix list config and return in argspec list format: - [{"afi": "ipv4", "prefix_lists": [...]}, {"afi": "ipv6", ...}] - """ - raw = vyos.get_config(["policy"]) - if not raw or not isinstance(raw, dict): - return [] - - result = [] - for afi, api_key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: - pl_data = raw.get(api_key) or {} - if not isinstance(pl_data, dict): - continue - - pls = [] - for pl_name, pl_info in sorted(pl_data.items()): - entry = {"name": pl_name} - pl_info = pl_info or {} - - if pl_info.get("description"): - entry["description"] = pl_info["description"] - - rules = [] - for seq, rdata in sorted( - (pl_info.get("rule") or {}).items(), - key=lambda x: int(x[0]), - ): - rdata = rdata or {} - rule = {"sequence": int(seq)} - if rdata.get("action"): - rule["action"] = rdata["action"] - if rdata.get("prefix"): - rule["prefix"] = rdata["prefix"] - if rdata.get("ge") is not None: - rule["ge"] = int(rdata["ge"]) - if rdata.get("le") is not None: - rule["le"] = int(rdata["le"]) - if rdata.get("description"): - rule["description"] = rdata["description"] - rules.append(rule) - - if rules: - entry["entries"] = rules - pls.append(entry) - - if pls: - result.append({"afi": afi, "prefix_lists": pls}) - - return result - - -# ------------------------------------------------------------ -# Internal normalization: argspec list → nested dicts for diffing -# ------------------------------------------------------------ - - -def _normalize(config): - """ - Convert argspec list to nested dict keyed by afi → pl_name → rule_seq. - { - "ipv4": { - "AnsibleIPv4PrefixList": { - "description": "...", - "rules": {2: {...}, 3: {...}} - } - }, - "ipv6": {...} - } - """ - result = {"ipv4": {}, "ipv6": {}} - for entry in config or []: - afi = entry.get("afi") - if afi not in result: - continue - for pl in entry.get("prefix_lists") or []: - name = pl["name"] - rules = {} - for r in pl.get("entries") or []: - seq = r["sequence"] - rules[seq] = {k: v for k, v in r.items() if k != "sequence" and v is not None} - result[afi][name] = { - "description": pl.get("description"), - "rules": rules, - } - return result - - -# ------------------------------------------------------------ -# Command builders -# ------------------------------------------------------------ - - -def _rule_cmds(base, seq, want_rule, have_rule, state): - """Build commands for a single rule entry.""" - cmds = [] - rbase = base + ["rule", str(seq)] - - if seq not in (have_rule or {}): - # New rule - for field, api_key in [ - ("action", "action"), - ("prefix", "prefix"), - ("description", "description"), - ]: - if want_rule.get(field): - cmds.append(("set", rbase + [api_key, want_rule[field]])) - if want_rule.get("ge") is not None: - cmds.append(("set", rbase + ["ge", str(want_rule["ge"])])) - if want_rule.get("le") is not None: - cmds.append(("set", rbase + ["le", str(want_rule["le"])])) - else: - # Existing rule — update changed fields - h = have_rule[seq] - for field, api_key in [ - ("action", "action"), - ("prefix", "prefix"), - ("description", "description"), - ]: - if want_rule.get(field) and want_rule[field] != h.get(field): - cmds.append(("set", rbase + [api_key, want_rule[field]])) - for field in ("ge", "le"): - if want_rule.get(field) is not None and want_rule[field] != h.get(field): - cmds.append(("set", rbase + [field, str(want_rule[field])])) - - return cmds - - -def build_commands(config, have_raw, state): - """ - Build command tuples to move from have → want. - """ - cmds = [] - - if state == "deleted": - if not config: - # Delete all prefix lists for both AFIs - for afi, api_key in _PL_KEY.items(): - # Only delete if something exists - if any(e.get("afi") == afi for e in have_raw): - cmds.append(("delete", ["policy", api_key])) - else: - # Delete only specified prefix lists - for entry in config: - afi = entry["afi"] - api_key = _PL_KEY[afi] - for pl in entry.get("prefix_lists") or []: - cmds.append(("delete", ["policy", api_key, pl["name"]])) - return cmds - - want = _normalize(config) - have = _normalize(have_raw) - - for afi, api_key in _PL_KEY.items(): - want_afi = want.get(afi, {}) - have_afi = have.get(afi, {}) - - if state == "overridden": - # Delete PLs present on device but not in want - for pl_name in set(have_afi) - set(want_afi): - cmds.append(("delete", ["policy", api_key, pl_name])) - - for pl_name, want_pl in want_afi.items(): - have_pl = have_afi.get(pl_name, {}) - base = ["policy", api_key, pl_name] - - if state == "replaced" and pl_name in have_afi: - # Full replacement — delete existing then re-set - cmds.append(("delete", ["policy", api_key, pl_name])) - have_pl = {} - - # Description - if want_pl.get("description") and want_pl["description"] != have_pl.get("description"): - cmds.append(("set", base + ["description", want_pl["description"]])) - - # Rules - want_rules = want_pl.get("rules", {}) - have_rules = have_pl.get("rules", {}) - - if state == "replaced": - # have_rules is now empty after delete above - have_rules = {} - - for seq, want_rule in want_rules.items(): - cmds += _rule_cmds(base, seq, want_rule, have_rules, state) - - # merged: leave extra have rules alone - # replaced: they were deleted with the parent node above - - return cmds - - -# ------------------------------------------------------------ -# Argument spec -# ------------------------------------------------------------ - -ARGUMENT_SPEC = dict( - config=dict(type="list", elements="dict"), - state=dict( - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), -) - - -# ------------------------------------------------------------ -# Main -# ------------------------------------------------------------ - - -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_prefix_lists.py.preview b/plugins/modules/vyos_prefix_lists.py.preview new file mode 100644 index 0000000..5334e7a --- /dev/null +++ b/plugins/modules/vyos_prefix_lists.py.preview @@ -0,0 +1,412 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: vyos_prefix_lists +short_description: Manage prefix-list configuration on VyOS devices using REST API +description: + - Manages IPv4 and IPv6 prefix lists on VyOS 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: List of prefix-list configurations grouped by address family. + type: list + elements: dict + suboptions: + afi: + description: Address family identifier. + type: str + choices: [ipv4, ipv6] + required: true + prefix_lists: + description: Named prefix lists. + type: list + elements: dict + suboptions: + name: + description: Prefix list name. + type: str + required: true + description: + description: Prefix list description. + type: str + entries: + description: Prefix list rules. + type: list + elements: dict + suboptions: + sequence: + description: Rule sequence number. + type: int + required: true + description: + description: Rule description. + type: str + action: + description: Permit or deny. + type: str + choices: [permit, deny] + ge: + description: Minimum prefix length. + type: int + le: + description: Maximum prefix length. + type: int + prefix: + description: Network prefix to match. + type: str + + state: + description: + - Desired state of the prefix-list configuration. + - C(merged) adds or updates entries without removing existing ones. + - C(replaced) replaces each named prefix list mentioned in config. + - C(overridden) replaces all prefix lists for the given AFIs. + - C(deleted) removes prefix lists. Without config removes all. + - 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 prefix list configuration + 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 + entries: + - sequence: 5 + action: permit + prefix: 2001:db8:8000::/35 + le: 37 + state: merged + +- name: Delete all prefix lists + vyos.rest.vyos_prefix_lists: + state: deleted + +- name: Gather current prefix list configuration + vyos.rest.vyos_prefix_lists: + state: gathered +""" + +RETURN = r""" +before: + description: Prefix list configuration before this module ran. + returned: always + type: list + +after: + description: Prefix list 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 prefix list 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 + + +# AFI → API key mapping +_PL_KEY = {"ipv4": "prefix-list", "ipv6": "prefix-list6"} + + +# ------------------------------------------------------------ +# Parsing: API response → argspec list +# ------------------------------------------------------------ + + +def get_running_config(vyos): + """ + Fetch current prefix list config and return in argspec list format: + [{"afi": "ipv4", "prefix_lists": [...]}, {"afi": "ipv6", ...}] + """ + raw = vyos.get_config(["policy"]) + if not raw or not isinstance(raw, dict): + return [] + + result = [] + for afi, api_key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: + pl_data = raw.get(api_key) or {} + if not isinstance(pl_data, dict): + continue + + pls = [] + for pl_name, pl_info in sorted(pl_data.items()): + entry = {"name": pl_name} + pl_info = pl_info or {} + + if pl_info.get("description"): + entry["description"] = pl_info["description"] + + rules = [] + for seq, rdata in sorted( + (pl_info.get("rule") or {}).items(), + key=lambda x: int(x[0]), + ): + rdata = rdata or {} + rule = {"sequence": int(seq)} + if rdata.get("action"): + rule["action"] = rdata["action"] + if rdata.get("prefix"): + rule["prefix"] = rdata["prefix"] + if rdata.get("ge") is not None: + rule["ge"] = int(rdata["ge"]) + if rdata.get("le") is not None: + rule["le"] = int(rdata["le"]) + if rdata.get("description"): + rule["description"] = rdata["description"] + rules.append(rule) + + if rules: + entry["entries"] = rules + pls.append(entry) + + if pls: + result.append({"afi": afi, "prefix_lists": pls}) + + return result + + +# ------------------------------------------------------------ +# Internal normalization: argspec list → nested dicts for diffing +# ------------------------------------------------------------ + + +def _normalize(config): + """ + Convert argspec list to nested dict keyed by afi → pl_name → rule_seq. + { + "ipv4": { + "AnsibleIPv4PrefixList": { + "description": "...", + "rules": {2: {...}, 3: {...}} + } + }, + "ipv6": {...} + } + """ + result = {"ipv4": {}, "ipv6": {}} + for entry in config or []: + afi = entry.get("afi") + if afi not in result: + continue + for pl in entry.get("prefix_lists") or []: + name = pl["name"] + rules = {} + for r in pl.get("entries") or []: + seq = r["sequence"] + rules[seq] = {k: v for k, v in r.items() if k != "sequence" and v is not None} + result[afi][name] = { + "description": pl.get("description"), + "rules": rules, + } + return result + + +# ------------------------------------------------------------ +# Command builders +# ------------------------------------------------------------ + + +def _rule_cmds(base, seq, want_rule, have_rule, state): + """Build commands for a single rule entry.""" + cmds = [] + rbase = base + ["rule", str(seq)] + + if seq not in (have_rule or {}): + # New rule + for field, api_key in [ + ("action", "action"), + ("prefix", "prefix"), + ("description", "description"), + ]: + if want_rule.get(field): + cmds.append(("set", rbase + [api_key, want_rule[field]])) + if want_rule.get("ge") is not None: + cmds.append(("set", rbase + ["ge", str(want_rule["ge"])])) + if want_rule.get("le") is not None: + cmds.append(("set", rbase + ["le", str(want_rule["le"])])) + else: + # Existing rule — update changed fields + h = have_rule[seq] + for field, api_key in [ + ("action", "action"), + ("prefix", "prefix"), + ("description", "description"), + ]: + if want_rule.get(field) and want_rule[field] != h.get(field): + cmds.append(("set", rbase + [api_key, want_rule[field]])) + for field in ("ge", "le"): + if want_rule.get(field) is not None and want_rule[field] != h.get(field): + cmds.append(("set", rbase + [field, str(want_rule[field])])) + + return cmds + + +def build_commands(config, have_raw, state): + """ + Build command tuples to move from have → want. + """ + cmds = [] + + if state == "deleted": + if not config: + # Delete all prefix lists for both AFIs + for afi, api_key in _PL_KEY.items(): + # Only delete if something exists + if any(e.get("afi") == afi for e in have_raw): + cmds.append(("delete", ["policy", api_key])) + else: + # Delete only specified prefix lists + for entry in config: + afi = entry["afi"] + api_key = _PL_KEY[afi] + for pl in entry.get("prefix_lists") or []: + cmds.append(("delete", ["policy", api_key, pl["name"]])) + return cmds + + want = _normalize(config) + have = _normalize(have_raw) + + for afi, api_key in _PL_KEY.items(): + want_afi = want.get(afi, {}) + have_afi = have.get(afi, {}) + + if state == "overridden": + # Delete PLs present on device but not in want + for pl_name in set(have_afi) - set(want_afi): + cmds.append(("delete", ["policy", api_key, pl_name])) + + for pl_name, want_pl in want_afi.items(): + have_pl = have_afi.get(pl_name, {}) + base = ["policy", api_key, pl_name] + + if state == "replaced" and pl_name in have_afi: + # Full replacement — delete existing then re-set + cmds.append(("delete", ["policy", api_key, pl_name])) + have_pl = {} + + # Description + if want_pl.get("description") and want_pl["description"] != have_pl.get("description"): + cmds.append(("set", base + ["description", want_pl["description"]])) + + # Rules + want_rules = want_pl.get("rules", {}) + have_rules = have_pl.get("rules", {}) + + if state == "replaced": + # have_rules is now empty after delete above + have_rules = {} + + for seq, want_rule in want_rules.items(): + cmds += _rule_cmds(base, seq, want_rule, have_rules, state) + + # merged: leave extra have rules alone + # replaced: they were deleted with the parent node above + + return cmds + + +# ------------------------------------------------------------ +# Argument spec +# ------------------------------------------------------------ + +ARGUMENT_SPEC = dict( + config=dict(type="list", elements="dict"), + state=dict( + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), +) + + +# ------------------------------------------------------------ +# Main +# ------------------------------------------------------------ + + +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_reset.py b/plugins/modules/vyos_reset.py deleted file mode 100644 index ca3e343..0000000 --- a/plugins/modules/vyos_reset.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/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_reset -short_description: Execute reset commands on a VyOS device via REST API. -description: - - Sends a C(reset) operational command to a VyOS device via the HTTPS REST - API (C(/reset) endpoint). - - Useful for resetting BGP sessions, VPN tunnels, ARP caches, etc. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Reset command path tokens. - - E.g. C(["ip", "bgp", "192.0.2.1"]) to reset a BGP peer. - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Reset a BGP peer - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["ip", "bgp", "192.0.2.11"] - - - name: Reset an IPsec VPN peer - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["vpn", "ipsec-peer", "203.0.113.5"] - - - name: Clear ARP cache - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["arp", "cache"] -""" - -RETURN = r""" -output: - description: Text output from the reset command (usually empty on success). - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - try: - result = client.reset(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_reset.py.preview b/plugins/modules/vyos_reset.py.preview new file mode 100644 index 0000000..ca3e343 --- /dev/null +++ b/plugins/modules/vyos_reset.py.preview @@ -0,0 +1,108 @@ +#!/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_reset +short_description: Execute reset commands on a VyOS device via REST API. +description: + - Sends a C(reset) operational command to a VyOS device via the HTTPS REST + API (C(/reset) endpoint). + - Useful for resetting BGP sessions, VPN tunnels, ARP caches, etc. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + path: + description: + - Reset command path tokens. + - E.g. C(["ip", "bgp", "192.0.2.1"]) to reset a BGP peer. + type: list + elements: str + required: true + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +examples: | + - name: Reset a BGP peer + vyos.rest.vyos_reset: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["ip", "bgp", "192.0.2.11"] + + - name: Reset an IPsec VPN peer + vyos.rest.vyos_reset: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["vpn", "ipsec-peer", "203.0.113.5"] + + - name: Clear ARP cache + vyos.rest.vyos_reset: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["arp", "cache"] +""" + +RETURN = r""" +output: + description: Text output from the reset command (usually empty on success). + returned: success + type: str +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + path=dict(type="list", elements="str", required=True), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + client = VyOSRestClient(module) + try: + result = client.reset(module.params["path"]) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=True, output=result.get("data", "")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_show.py b/plugins/modules/vyos_show.py deleted file mode 100644 index fbe6c1b..0000000 --- a/plugins/modules/vyos_show.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/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_show -short_description: Execute op-mode show commands on a VyOS device via REST API. -description: - - Sends a C(show) operational-mode command to a VyOS device via the - HTTPS REST API (C(/show) endpoint) and returns the text output. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Operational-mode command tokens following C(show). - - E.g. C(["interfaces"]) maps to C(show interfaces). - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -notes: - - This module never modifies device state. -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.rest.vyos_configure - - module: vyos.rest.vyos_retrieve -examples: | - - name: Show interfaces - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["interfaces"] - register: iface_output - - - name: Show version - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["version"] - register: ver_output - - - name: Show installed images - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["system", "image"] - register: images -""" - -RETURN = r""" -output: - description: Raw text output from the show command. - returned: success - type: str - sample: | - Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down - Interface IP Address S/L Description - eth0 10.0.0.1/24 u/u WAN -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - try: - result = client.show(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=False, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_show.py.preview b/plugins/modules/vyos_show.py.preview new file mode 100644 index 0000000..fbe6c1b --- /dev/null +++ b/plugins/modules/vyos_show.py.preview @@ -0,0 +1,119 @@ +#!/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_show +short_description: Execute op-mode show commands on a VyOS device via REST API. +description: + - Sends a C(show) operational-mode command to a VyOS device via the + HTTPS REST API (C(/show) endpoint) and returns the text output. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + path: + description: + - Operational-mode command tokens following C(show). + - E.g. C(["interfaces"]) maps to C(show interfaces). + type: list + elements: str + required: true + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +notes: + - This module never modifies device state. +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.rest.vyos_configure + - module: vyos.rest.vyos_retrieve +examples: | + - name: Show interfaces + vyos.rest.vyos_show: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["interfaces"] + register: iface_output + + - name: Show version + vyos.rest.vyos_show: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["version"] + register: ver_output + + - name: Show installed images + vyos.rest.vyos_show: + hostname: 192.168.1.1 + api_key: MY-KEY + path: ["system", "image"] + register: images +""" + +RETURN = r""" +output: + description: Raw text output from the show command. + returned: success + type: str + sample: | + Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down + Interface IP Address S/L Description + eth0 10.0.0.1/24 u/u WAN +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + path=dict(type="list", elements="str", required=True), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + try: + result = client.show(module.params["path"]) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=False, output=result.get("data", "")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_static_routes.py b/plugins/modules/vyos_static_routes.py deleted file mode 100644 index f203603..0000000 --- a/plugins/modules/vyos_static_routes.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/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_static_routes -short_description: Manage static routes on VyOS via the REST API. -description: - - Manages IPv4 and IPv6 static routes on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_static_routes) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of static route address-family configurations. - type: list - elements: dict - suboptions: - address_families: - description: List of address family route groups. - type: list - elements: dict - suboptions: - afi: - description: Address family indicator. - type: str - choices: [ipv4, ipv6] - required: true - routes: - description: List of static route entries. - type: list - elements: dict - suboptions: - dest: - description: Destination prefix in CIDR notation. - type: str - required: true - blackhole_config: - description: Blackhole route configuration. - type: dict - suboptions: - distance: - description: Administrative distance (1-255). - type: int - type: - description: Blackhole type. - type: str - next_hops: - description: List of next-hop addresses. - type: list - elements: dict - suboptions: - forward_router_address: - description: Next-hop IP address. - type: str - required: true - admin_distance: - description: Administrative distance for this next-hop. - type: int - enabled: - description: Whether this next-hop is enabled. - type: bool - default: true - interface: - description: Outgoing interface name. - type: str - state: - description: - - C(merged): Add routes (preserve existing). - - C(replaced): Replace routes for listed destinations. - - C(overridden): Replace the entire static route table. - - C(deleted): Remove listed (or all) static routes. - - C(gathered): Read static routes from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_static_routes -examples: | - - name: Add IPv4 static routes - vyos.rest.vyos_static_routes: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - address_families: - - afi: ipv4 - routes: - - dest: 192.0.2.0/24 - next_hops: - - forward_router_address: 10.0.0.1 - - dest: 203.0.113.0/24 - blackhole_config: - distance: 200 - state: merged - - - name: Delete all static routes - vyos.rest.vyos_static_routes: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: Static route config before the module ran. - returned: always - type: list -after: - description: Static route config after the module ran. - returned: when changed - type: list -gathered: - description: Static routes read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_ROUTE_BASE = { - "ipv4": ["protocols", "static", "route"], - "ipv6": ["protocols", "static", "route6"], -} - - -def _get_static_routes(client): - try: - result = client.retrieve_show_config(["protocols", "static"]) - data = result.get("data") or {} - out = [] - for afi, route_key in [("ipv4", "route"), ("ipv6", "route6")]: - routes_data = data.get(route_key, {}) - if not isinstance(routes_data, dict): - continue - routes = [] - for dest, rdata in routes_data.items(): - route_entry = {"dest": dest} - if isinstance(rdata, dict): - if "blackhole" in rdata: - bh = rdata["blackhole"] - bc = {} - if isinstance(bh, dict) and "distance" in bh: - bc["distance"] = int(bh["distance"]) - route_entry["blackhole_config"] = bc - next_hops = [] - for nh_key in ("next-hop", "next_hop"): - nh_data = rdata.get(nh_key, {}) - if isinstance(nh_data, dict): - for nh_addr, nh_opts in nh_data.items(): - nh = {"forward_router_address": nh_addr} - if isinstance(nh_opts, dict): - if "distance" in nh_opts: - nh["admin_distance"] = int(nh_opts["distance"]) - nh["enabled"] = "disable" not in nh_opts - next_hops.append(nh) - if next_hops: - route_entry["next_hops"] = next_hops - routes.append(route_entry) - if routes: - out.append({"address_families": [{"afi": afi, "routes": routes}]}) - return out - except VyOSRestError: - return [] - - -def _apply_route(client, afi, route, commands): - base = _ROUTE_BASE[afi] - dest = route["dest"] - - if route.get("blackhole_config") is not None: - bh_path = base + [dest, "blackhole"] - client.configure_set(bh_path) - commands.append("set {p}".format(p=" ".join(bh_path))) - dist = route["blackhole_config"].get("distance") - if dist: - client.configure_set(bh_path + ["distance"], str(dist)) - commands.append( - "set {p} distance {d}".format(p=" ".join(bh_path), d=dist), - ) - - for nh in route.get("next_hops") or []: - nh_addr = nh["forward_router_address"] - nh_path = base + [dest, "next-hop", nh_addr] - client.configure_set(nh_path) - commands.append("set {p}".format(p=" ".join(nh_path))) - if nh.get("admin_distance"): - client.configure_set(nh_path + ["distance"], str(nh["admin_distance"])) - commands.append( - "set {p} distance {d}".format( - p=" ".join(nh_path), - d=nh["admin_distance"], - ), - ) - if "enabled" in nh and not nh["enabled"]: - client.configure_set(nh_path + ["disable"]) - commands.append("set {p} disable".format(p=" ".join(nh_path))) - if nh.get("interface"): - client.configure_set(nh_path + ["interface"], nh["interface"]) - commands.append( - "set {p} interface {i}".format( - p=" ".join(nh_path), - i=nh["interface"], - ), - ) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - address_families=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - routes=dict( - type="list", - elements="dict", - options=dict( - dest=dict(type="str", required=True), - blackhole_config=dict( - type="dict", - options=dict( - distance=dict(type="int"), - type=dict(type="str"), - ), - ), - next_hops=dict( - type="list", - elements="dict", - options=dict( - forward_router_address=dict( - type="str", - required=True, - ), - admin_distance=dict(type="int"), - enabled=dict(type="bool", default=True), - interface=dict(type="str"), - ), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_static_routes(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state in ("overridden", "deleted") and not config: - # Delete all static routes - for afi_key in ("route", "route6"): - try: - client.configure_delete(["protocols", "static", afi_key]) - commands.append( - "delete protocols static {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - changed = True - - elif state == "deleted" and config: - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - base = _ROUTE_BASE[afi] - for route in af.get("routes") or []: - try: - client.configure_delete(base + [route["dest"]]) - commands.append( - "delete {p} {d}".format( - p=" ".join(base), - d=route["dest"], - ), - ) - except VyOSRestError: - pass - changed = True - - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - # Remove existing routes for affected destinations - dests_by_afi = {} - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - dests_by_afi.setdefault(afi, set()) - for route in af.get("routes") or []: - dests_by_afi[afi].add(route["dest"]) - if state == "overridden": - for afi_key in ("route", "route6"): - try: - client.configure_delete( - ["protocols", "static", afi_key], - ) - commands.append( - "delete protocols static {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - else: - for afi, dests in dests_by_afi.items(): - base = _ROUTE_BASE[afi] - for dest in dests: - try: - client.configure_delete(base + [dest]) - commands.append( - "delete {p} {d}".format( - p=" ".join(base), - d=dest, - ), - ) - except VyOSRestError: - pass - - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - for route in af.get("routes") or []: - _apply_route(client, afi, route, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_static_routes(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_static_routes.py.preview b/plugins/modules/vyos_static_routes.py.preview new file mode 100644 index 0000000..f203603 --- /dev/null +++ b/plugins/modules/vyos_static_routes.py.preview @@ -0,0 +1,397 @@ +#!/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_static_routes +short_description: Manage static routes on VyOS via the REST API. +description: + - Manages IPv4 and IPv6 static routes on VyOS devices using the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_static_routes) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + config: + description: List of static route address-family configurations. + type: list + elements: dict + suboptions: + address_families: + description: List of address family route groups. + type: list + elements: dict + suboptions: + afi: + description: Address family indicator. + type: str + choices: [ipv4, ipv6] + required: true + routes: + description: List of static route entries. + type: list + elements: dict + suboptions: + dest: + description: Destination prefix in CIDR notation. + type: str + required: true + blackhole_config: + description: Blackhole route configuration. + type: dict + suboptions: + distance: + description: Administrative distance (1-255). + type: int + type: + description: Blackhole type. + type: str + next_hops: + description: List of next-hop addresses. + type: list + elements: dict + suboptions: + forward_router_address: + description: Next-hop IP address. + type: str + required: true + admin_distance: + description: Administrative distance for this next-hop. + type: int + enabled: + description: Whether this next-hop is enabled. + type: bool + default: true + interface: + description: Outgoing interface name. + type: str + state: + description: + - C(merged): Add routes (preserve existing). + - C(replaced): Replace routes for listed destinations. + - C(overridden): Replace the entire static route table. + - C(deleted): Remove listed (or all) static routes. + - C(gathered): Read static routes from device. + type: str + choices: [merged, replaced, overridden, deleted, gathered] + default: merged + hostname: + description: IP address or FQDN of the VyOS device. + type: str + required: true + port: + description: HTTPS port for the REST API. + type: int + default: 443 + api_key: + description: API key configured on the device. + type: str + required: true + no_log: true + timeout: + description: Request timeout in seconds. + type: int + default: 30 + verify_ssl: + description: Validate the device's TLS certificate. + type: bool + default: false +requirements: + - VyOS 1.3+ +seealso: + - module: vyos.vyos.vyos_static_routes +examples: | + - name: Add IPv4 static routes + vyos.rest.vyos_static_routes: + hostname: 192.168.1.1 + api_key: MY-KEY + config: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.0/24 + next_hops: + - forward_router_address: 10.0.0.1 + - dest: 203.0.113.0/24 + blackhole_config: + distance: 200 + state: merged + + - name: Delete all static routes + vyos.rest.vyos_static_routes: + hostname: 192.168.1.1 + api_key: MY-KEY + state: deleted +""" + +RETURN = r""" +before: + description: Static route config before the module ran. + returned: always + type: list +after: + description: Static route config after the module ran. + returned: when changed + type: list +gathered: + description: Static routes read from device (state=gathered). + returned: when state is gathered + type: list +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_ROUTE_BASE = { + "ipv4": ["protocols", "static", "route"], + "ipv6": ["protocols", "static", "route6"], +} + + +def _get_static_routes(client): + try: + result = client.retrieve_show_config(["protocols", "static"]) + data = result.get("data") or {} + out = [] + for afi, route_key in [("ipv4", "route"), ("ipv6", "route6")]: + routes_data = data.get(route_key, {}) + if not isinstance(routes_data, dict): + continue + routes = [] + for dest, rdata in routes_data.items(): + route_entry = {"dest": dest} + if isinstance(rdata, dict): + if "blackhole" in rdata: + bh = rdata["blackhole"] + bc = {} + if isinstance(bh, dict) and "distance" in bh: + bc["distance"] = int(bh["distance"]) + route_entry["blackhole_config"] = bc + next_hops = [] + for nh_key in ("next-hop", "next_hop"): + nh_data = rdata.get(nh_key, {}) + if isinstance(nh_data, dict): + for nh_addr, nh_opts in nh_data.items(): + nh = {"forward_router_address": nh_addr} + if isinstance(nh_opts, dict): + if "distance" in nh_opts: + nh["admin_distance"] = int(nh_opts["distance"]) + nh["enabled"] = "disable" not in nh_opts + next_hops.append(nh) + if next_hops: + route_entry["next_hops"] = next_hops + routes.append(route_entry) + if routes: + out.append({"address_families": [{"afi": afi, "routes": routes}]}) + return out + except VyOSRestError: + return [] + + +def _apply_route(client, afi, route, commands): + base = _ROUTE_BASE[afi] + dest = route["dest"] + + if route.get("blackhole_config") is not None: + bh_path = base + [dest, "blackhole"] + client.configure_set(bh_path) + commands.append("set {p}".format(p=" ".join(bh_path))) + dist = route["blackhole_config"].get("distance") + if dist: + client.configure_set(bh_path + ["distance"], str(dist)) + commands.append( + "set {p} distance {d}".format(p=" ".join(bh_path), d=dist), + ) + + for nh in route.get("next_hops") or []: + nh_addr = nh["forward_router_address"] + nh_path = base + [dest, "next-hop", nh_addr] + client.configure_set(nh_path) + commands.append("set {p}".format(p=" ".join(nh_path))) + if nh.get("admin_distance"): + client.configure_set(nh_path + ["distance"], str(nh["admin_distance"])) + commands.append( + "set {p} distance {d}".format( + p=" ".join(nh_path), + d=nh["admin_distance"], + ), + ) + if "enabled" in nh and not nh["enabled"]: + client.configure_set(nh_path + ["disable"]) + commands.append("set {p} disable".format(p=" ".join(nh_path))) + if nh.get("interface"): + client.configure_set(nh_path + ["interface"], nh["interface"]) + commands.append( + "set {p} interface {i}".format( + p=" ".join(nh_path), + i=nh["interface"], + ), + ) + + +def main(): + argument_spec = dict( + config=dict( + type="list", + elements="dict", + options=dict( + address_families=dict( + type="list", + elements="dict", + options=dict( + afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), + routes=dict( + type="list", + elements="dict", + options=dict( + dest=dict(type="str", required=True), + blackhole_config=dict( + type="dict", + options=dict( + distance=dict(type="int"), + type=dict(type="str"), + ), + ), + next_hops=dict( + type="list", + elements="dict", + options=dict( + forward_router_address=dict( + type="str", + required=True, + ), + admin_distance=dict(type="int"), + enabled=dict(type="bool", default=True), + interface=dict(type="str"), + ), + ), + ), + ), + ), + ), + ), + ), + state=dict( + type="str", + default="merged", + choices=["merged", "replaced", "overridden", "deleted", "gathered"], + ), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = VyOSRestClient(module) + state = module.params["state"] + config = module.params.get("config") or [] + commands = [] + changed = False + + before = _get_static_routes(client) + + if state == "gathered": + module.exit_json(changed=False, gathered=before, before=before, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, before=before, commands=["(check mode)"]) + + try: + if state in ("overridden", "deleted") and not config: + # Delete all static routes + for afi_key in ("route", "route6"): + try: + client.configure_delete(["protocols", "static", afi_key]) + commands.append( + "delete protocols static {k}".format(k=afi_key), + ) + except VyOSRestError: + pass + changed = True + + elif state == "deleted" and config: + for entry in config: + for af in entry.get("address_families") or []: + afi = af["afi"] + base = _ROUTE_BASE[afi] + for route in af.get("routes") or []: + try: + client.configure_delete(base + [route["dest"]]) + commands.append( + "delete {p} {d}".format( + p=" ".join(base), + d=route["dest"], + ), + ) + except VyOSRestError: + pass + changed = True + + elif state in ("merged", "replaced", "overridden"): + if state in ("replaced", "overridden"): + # Remove existing routes for affected destinations + dests_by_afi = {} + for entry in config: + for af in entry.get("address_families") or []: + afi = af["afi"] + dests_by_afi.setdefault(afi, set()) + for route in af.get("routes") or []: + dests_by_afi[afi].add(route["dest"]) + if state == "overridden": + for afi_key in ("route", "route6"): + try: + client.configure_delete( + ["protocols", "static", afi_key], + ) + commands.append( + "delete protocols static {k}".format(k=afi_key), + ) + except VyOSRestError: + pass + else: + for afi, dests in dests_by_afi.items(): + base = _ROUTE_BASE[afi] + for dest in dests: + try: + client.configure_delete(base + [dest]) + commands.append( + "delete {p} {d}".format( + p=" ".join(base), + d=dest, + ), + ) + except VyOSRestError: + pass + + for entry in config: + for af in entry.get("address_families") or []: + afi = af["afi"] + for route in af.get("routes") or []: + _apply_route(client, afi, route, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + after = _get_static_routes(client) if changed else before + module.exit_json(changed=changed, before=before, after=after, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_system.py b/plugins/modules/vyos_system.py deleted file mode 100644 index d1d696e..0000000 --- a/plugins/modules/vyos_system.py +++ /dev/null @@ -1,147 +0,0 @@ -#!/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_system -short_description: Manage system settings on VyOS via the REST API. -description: - - Manages system-level settings — hostname, domain name, name servers, - domain search list — using the VyOS HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_system) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - host_name: - description: System hostname. - type: str - domain_name: - description: System domain name. - type: str - name_server: - description: List of DNS name servers. Mutually exclusive with domain_search. - type: list - elements: str - aliases: [name_servers] - domain_search: - description: List of domain search suffixes. Mutually exclusive with name_server. - type: list - elements: str - state: - description: C(present) to apply, C(absent) to remove. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - host_name=dict(type="str"), - domain_name=dict(type="str"), - name_server=dict(type="list", elements="str", aliases=["name_servers"]), - domain_search=dict(type="list", elements="str"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["name_server", "domain_search"]], - supports_check_mode=True, - ) - - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - state = module.params["state"] - commands = [] - changed = False - - try: - if state == "present": - if module.params.get("host_name"): - client.configure_set(["system", "host-name"], module.params["host_name"]) - commands.append("set system host-name '{h}'".format(h=module.params["host_name"])) - changed = True - if module.params.get("domain_name"): - client.configure_set(["system", "domain-name"], module.params["domain_name"]) - commands.append( - "set system domain-name '{d}'".format(d=module.params["domain_name"]), - ) - changed = True - for ns in module.params.get("name_server") or []: - client.configure_set(["system", "name-server"], ns) - commands.append("set system name-server {ns}".format(ns=ns)) - changed = True - for ds in module.params.get("domain_search") or []: - client.configure_set(["system", "domain-search", "domain"], ds) - commands.append("set system domain-search domain {ds}".format(ds=ds)) - changed = True - else: - if module.params.get("host_name"): - try: - client.configure_delete(["system", "host-name"]) - commands.append("delete system host-name") - changed = True - except VyOSRestError: - pass - if module.params.get("domain_name"): - try: - client.configure_delete(["system", "domain-name"]) - commands.append("delete system domain-name") - changed = True - except VyOSRestError: - pass - if module.params.get("name_server"): - try: - client.configure_delete(["system", "name-server"]) - commands.append("delete system name-server") - changed = True - except VyOSRestError: - pass - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_system.py.preview b/plugins/modules/vyos_system.py.preview new file mode 100644 index 0000000..d1d696e --- /dev/null +++ b/plugins/modules/vyos_system.py.preview @@ -0,0 +1,147 @@ +#!/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_system +short_description: Manage system settings on VyOS via the REST API. +description: + - Manages system-level settings — hostname, domain name, name servers, + domain search list — using the VyOS HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_system) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + host_name: + description: System hostname. + type: str + domain_name: + description: System domain name. + type: str + name_server: + description: List of DNS name servers. Mutually exclusive with domain_search. + type: list + elements: str + aliases: [name_servers] + domain_search: + description: List of domain search suffixes. Mutually exclusive with name_server. + type: list + elements: str + state: + description: C(present) to apply, C(absent) to remove. + type: str + choices: [present, absent] + default: present + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def main(): + argument_spec = dict( + host_name=dict(type="str"), + domain_name=dict(type="str"), + name_server=dict(type="list", elements="str", aliases=["name_servers"]), + domain_search=dict(type="list", elements="str"), + state=dict(type="str", default="present", choices=["present", "absent"]), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["name_server", "domain_search"]], + supports_check_mode=True, + ) + + if module.check_mode: + module.exit_json(changed=True, commands=["(check mode)"]) + + client = VyOSRestClient(module) + state = module.params["state"] + commands = [] + changed = False + + try: + if state == "present": + if module.params.get("host_name"): + client.configure_set(["system", "host-name"], module.params["host_name"]) + commands.append("set system host-name '{h}'".format(h=module.params["host_name"])) + changed = True + if module.params.get("domain_name"): + client.configure_set(["system", "domain-name"], module.params["domain_name"]) + commands.append( + "set system domain-name '{d}'".format(d=module.params["domain_name"]), + ) + changed = True + for ns in module.params.get("name_server") or []: + client.configure_set(["system", "name-server"], ns) + commands.append("set system name-server {ns}".format(ns=ns)) + changed = True + for ds in module.params.get("domain_search") or []: + client.configure_set(["system", "domain-search", "domain"], ds) + commands.append("set system domain-search domain {ds}".format(ds=ds)) + changed = True + else: + if module.params.get("host_name"): + try: + client.configure_delete(["system", "host-name"]) + commands.append("delete system host-name") + changed = True + except VyOSRestError: + pass + if module.params.get("domain_name"): + try: + client.configure_delete(["system", "domain-name"]) + commands.append("delete system domain-name") + changed = True + except VyOSRestError: + pass + if module.params.get("name_server"): + try: + client.configure_delete(["system", "name-server"]) + commands.append("delete system name-server") + changed = True + except VyOSRestError: + pass + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=changed, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_user.py b/plugins/modules/vyos_user.py deleted file mode 100644 index 41e014d..0000000 --- a/plugins/modules/vyos_user.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/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_user -short_description: Manage local users on VyOS via the REST API. -description: - - Manages local user accounts on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_user) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - aggregate: - description: List of user definitions. Mutually exclusive with I(name). - type: list - elements: dict - aliases: [users, collection] - suboptions: - name: - description: Username. - type: str - required: true - full_name: - description: Full name. - type: str - configured_password: - description: Plaintext password (will be hashed on device). - type: str - no_log: true - encrypted_password: - description: Pre-hashed password string. - type: str - no_log: true - update_password: - description: When to update the password. - type: str - choices: [always, on_create] - level: - description: User privilege level. - type: str - choices: [admin, operator] - public_keys: - description: SSH public keys for authentication. - type: list - elements: dict - suboptions: - name: - description: Key identifier (e.g. user@host). - type: str - required: true - key: - description: Base64-encoded public key. - type: str - required: true - type: - description: Key type. - type: str - required: true - choices: [ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, - ecdsa-sha2-nistp384, ssh-ed25519, ecdsa-sha2-nistp521] - state: - description: Present or absent. - type: str - choices: [present, absent] - name: - description: Single username. Mutually exclusive with I(aggregate). - type: str - full_name: - description: Full name for the single user. - type: str - configured_password: - description: Plaintext password. - type: str - no_log: true - encrypted_password: - description: Pre-hashed password. - type: str - no_log: true - update_password: - type: str - choices: [always, on_create] - level: - type: str - choices: [admin, operator] - public_keys: - type: list - elements: dict - suboptions: - name: - type: str - required: true - key: - type: str - required: true - type: - type: str - required: true - state: - description: Whether to create (present) or remove (absent) the user. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_KEY_TYPES = [ - "ssh-dss", - "ssh-rsa", - "ecdsa-sha2-nistp256", - "ecdsa-sha2-nistp384", - "ssh-ed25519", - "ecdsa-sha2-nistp521", -] - - -def _apply_user(client, user_def, commands, update_password="always"): - name = user_def["name"] - base = ["system", "login", "user", name] - client.configure_set(base) - commands.append("set system login user {n}".format(n=name)) - - if user_def.get("full_name"): - client.configure_set(base + ["full-name"], user_def["full_name"]) - if user_def.get("level"): - client.configure_set(base + ["level"], user_def["level"]) - if user_def.get("configured_password") and update_password in ("always",): - client.configure_set( - base + ["authentication", "plaintext-password"], - user_def["configured_password"], - ) - commands.append( - "set system login user {n} authentication plaintext-password".format(n=name), - ) - if user_def.get("encrypted_password"): - client.configure_set( - base + ["authentication", "encrypted-password"], - user_def["encrypted_password"], - ) - for pk in user_def.get("public_keys") or []: - kb = base + ["authentication", "public-keys", pk["name"]] - client.configure_set(kb + ["key"], pk["key"]) - client.configure_set(kb + ["type"], pk["type"]) - commands.append( - "set system login user {n} authentication public-keys {k}".format( - n=name, - k=pk["name"], - ), - ) - - -def _delete_user(client, name, commands): - try: - client.configure_delete(["system", "login", "user", name]) - commands.append("delete system login user {n}".format(n=name)) - except VyOSRestError: - pass - - -def main(): - pk_spec = dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - key=dict(type="str", required=True), - type=dict(type="str", required=True, choices=_KEY_TYPES), - ), - ) - user_spec = dict( - name=dict(type="str", required=True), - full_name=dict(type="str"), - configured_password=dict(type="str", no_log=True), - encrypted_password=dict(type="str", no_log=True), - update_password=dict(type="str", choices=["always", "on_create"]), - level=dict(type="str", choices=["admin", "operator"]), - public_keys=pk_spec, - state=dict(type="str", choices=["present", "absent"]), - ) - argument_spec = dict( - aggregate=dict( - type="list", - elements="dict", - aliases=["users", "collection"], - options=user_spec, - ), - name=dict(type="str"), - full_name=dict(type="str"), - configured_password=dict(type="str", no_log=True), - encrypted_password=dict(type="str", no_log=True), - update_password=dict(type="str", choices=["always", "on_create"]), - level=dict(type="str", choices=["admin", "operator"]), - public_keys=pk_spec, - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["aggregate", "name"]], - supports_check_mode=True, - ) - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - commands = [] - changed = False - - # Build unified user list - users = module.params.get("aggregate") or [] - if module.params.get("name"): - users = [ - { - "name": module.params["name"], - "full_name": module.params.get("full_name"), - "configured_password": module.params.get("configured_password"), - "encrypted_password": module.params.get("encrypted_password"), - "update_password": module.params.get("update_password", "always"), - "level": module.params.get("level"), - "public_keys": module.params.get("public_keys"), - "state": module.params.get("state", "present"), - }, - ] - - try: - for u in users: - u_state = u.get("state") or module.params.get("state", "present") - if u_state == "absent": - _delete_user(client, u["name"], commands) - else: - _apply_user( - client, - u, - commands, - update_password=u.get("update_password") or "always", - ) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_user.py.preview b/plugins/modules/vyos_user.py.preview new file mode 100644 index 0000000..41e014d --- /dev/null +++ b/plugins/modules/vyos_user.py.preview @@ -0,0 +1,282 @@ +#!/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_user +short_description: Manage local users on VyOS via the REST API. +description: + - Manages local user accounts on VyOS devices using the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_user) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + aggregate: + description: List of user definitions. Mutually exclusive with I(name). + type: list + elements: dict + aliases: [users, collection] + suboptions: + name: + description: Username. + type: str + required: true + full_name: + description: Full name. + type: str + configured_password: + description: Plaintext password (will be hashed on device). + type: str + no_log: true + encrypted_password: + description: Pre-hashed password string. + type: str + no_log: true + update_password: + description: When to update the password. + type: str + choices: [always, on_create] + level: + description: User privilege level. + type: str + choices: [admin, operator] + public_keys: + description: SSH public keys for authentication. + type: list + elements: dict + suboptions: + name: + description: Key identifier (e.g. user@host). + type: str + required: true + key: + description: Base64-encoded public key. + type: str + required: true + type: + description: Key type. + type: str + required: true + choices: [ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, + ecdsa-sha2-nistp384, ssh-ed25519, ecdsa-sha2-nistp521] + state: + description: Present or absent. + type: str + choices: [present, absent] + name: + description: Single username. Mutually exclusive with I(aggregate). + type: str + full_name: + description: Full name for the single user. + type: str + configured_password: + description: Plaintext password. + type: str + no_log: true + encrypted_password: + description: Pre-hashed password. + type: str + no_log: true + update_password: + type: str + choices: [always, on_create] + level: + type: str + choices: [admin, operator] + public_keys: + type: list + elements: dict + suboptions: + name: + type: str + required: true + key: + type: str + required: true + type: + type: str + required: true + state: + description: Whether to create (present) or remove (absent) the user. + type: str + choices: [present, absent] + default: present + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +_KEY_TYPES = [ + "ssh-dss", + "ssh-rsa", + "ecdsa-sha2-nistp256", + "ecdsa-sha2-nistp384", + "ssh-ed25519", + "ecdsa-sha2-nistp521", +] + + +def _apply_user(client, user_def, commands, update_password="always"): + name = user_def["name"] + base = ["system", "login", "user", name] + client.configure_set(base) + commands.append("set system login user {n}".format(n=name)) + + if user_def.get("full_name"): + client.configure_set(base + ["full-name"], user_def["full_name"]) + if user_def.get("level"): + client.configure_set(base + ["level"], user_def["level"]) + if user_def.get("configured_password") and update_password in ("always",): + client.configure_set( + base + ["authentication", "plaintext-password"], + user_def["configured_password"], + ) + commands.append( + "set system login user {n} authentication plaintext-password".format(n=name), + ) + if user_def.get("encrypted_password"): + client.configure_set( + base + ["authentication", "encrypted-password"], + user_def["encrypted_password"], + ) + for pk in user_def.get("public_keys") or []: + kb = base + ["authentication", "public-keys", pk["name"]] + client.configure_set(kb + ["key"], pk["key"]) + client.configure_set(kb + ["type"], pk["type"]) + commands.append( + "set system login user {n} authentication public-keys {k}".format( + n=name, + k=pk["name"], + ), + ) + + +def _delete_user(client, name, commands): + try: + client.configure_delete(["system", "login", "user", name]) + commands.append("delete system login user {n}".format(n=name)) + except VyOSRestError: + pass + + +def main(): + pk_spec = dict( + type="list", + elements="dict", + options=dict( + name=dict(type="str", required=True), + key=dict(type="str", required=True), + type=dict(type="str", required=True, choices=_KEY_TYPES), + ), + ) + user_spec = dict( + name=dict(type="str", required=True), + full_name=dict(type="str"), + configured_password=dict(type="str", no_log=True), + encrypted_password=dict(type="str", no_log=True), + update_password=dict(type="str", choices=["always", "on_create"]), + level=dict(type="str", choices=["admin", "operator"]), + public_keys=pk_spec, + state=dict(type="str", choices=["present", "absent"]), + ) + argument_spec = dict( + aggregate=dict( + type="list", + elements="dict", + aliases=["users", "collection"], + options=user_spec, + ), + name=dict(type="str"), + full_name=dict(type="str"), + configured_password=dict(type="str", no_log=True), + encrypted_password=dict(type="str", no_log=True), + update_password=dict(type="str", choices=["always", "on_create"]), + level=dict(type="str", choices=["admin", "operator"]), + public_keys=pk_spec, + state=dict(type="str", default="present", choices=["present", "absent"]), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["aggregate", "name"]], + supports_check_mode=True, + ) + if module.check_mode: + module.exit_json(changed=True, commands=["(check mode)"]) + + client = VyOSRestClient(module) + commands = [] + changed = False + + # Build unified user list + users = module.params.get("aggregate") or [] + if module.params.get("name"): + users = [ + { + "name": module.params["name"], + "full_name": module.params.get("full_name"), + "configured_password": module.params.get("configured_password"), + "encrypted_password": module.params.get("encrypted_password"), + "update_password": module.params.get("update_password", "always"), + "level": module.params.get("level"), + "public_keys": module.params.get("public_keys"), + "state": module.params.get("state", "present"), + }, + ] + + try: + for u in users: + u_state = u.get("state") or module.params.get("state", "present") + if u_state == "absent": + _delete_user(client, u["name"], commands) + else: + _apply_user( + client, + u, + commands, + update_password=u.get("update_password") or "always", + ) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=changed, commands=commands) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_vlan.py b/plugins/modules/vyos_vlan.py deleted file mode 100644 index 18eca92..0000000 --- a/plugins/modules/vyos_vlan.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/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_vlan -short_description: Manage VLANs on VyOS network devices via REST API. -description: - - Manages 802.1Q VLAN sub-interfaces on VyOS Ethernet interfaces using - the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_vlan) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - vlan_id: - description: VLAN ID (0-4094). - type: int - name: - description: VLAN name (used as description on the VIF). - type: str - address: - description: IP address for the VLAN interface. - type: str - interfaces: - description: List of physical interfaces to configure the VLAN on. - type: list - elements: str - aggregate: - description: List of VLAN definitions. - type: list - elements: dict - suboptions: - vlan_id: - type: int - required: true - name: - type: str - address: - type: str - interfaces: - type: list - elements: str - required: true - state: - type: str - choices: [present, absent] - state: - description: C(present) to configure, C(absent) to remove. - type: str - choices: [present, absent] - default: present - purge: - description: Remove VLANs not defined in aggregate. - type: bool - default: false - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _apply_vlan(client, vlan, commands): - vid = str(vlan["vlan_id"]) - for iface in vlan.get("interfaces") or []: - # Detect interface type - if iface.startswith("bond"): - itype = "bonding" - else: - itype = "ethernet" - vif_base = ["interfaces", itype, iface, "vif", vid] - client.configure_set(vif_base) - commands.append("set interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) - if vlan.get("name"): - client.configure_set(vif_base + ["description"], vlan["name"]) - commands.append( - "set interfaces {t} {i} vif {v} description '{n}'".format( - t=itype, - i=iface, - v=vid, - n=vlan["name"], - ), - ) - if vlan.get("address"): - client.configure_set(vif_base + ["address"], vlan["address"]) - commands.append( - "set interfaces {t} {i} vif {v} address {a}".format( - t=itype, - i=iface, - v=vid, - a=vlan["address"], - ), - ) - - -def _delete_vlan(client, vlan, commands): - vid = str(vlan["vlan_id"]) - for iface in vlan.get("interfaces") or []: - itype = "bonding" if iface.startswith("bond") else "ethernet" - try: - client.configure_delete(["interfaces", itype, iface, "vif", vid]) - commands.append("delete interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) - except VyOSRestError: - pass - - -def main(): - vlan_spec = dict( - vlan_id=dict(type="int", required=True), - name=dict(type="str"), - address=dict(type="str"), - interfaces=dict(type="list", elements="str"), - state=dict(type="str", choices=["present", "absent"]), - ) - argument_spec = dict( - vlan_id=dict(type="int"), - name=dict(type="str"), - address=dict(type="str"), - interfaces=dict(type="list", elements="str"), - aggregate=dict(type="list", elements="dict", options=vlan_spec), - state=dict(type="str", default="present", choices=["present", "absent"]), - purge=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["aggregate", "vlan_id"]], - supports_check_mode=True, - ) - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - commands = [] - changed = False - - vlans = module.params.get("aggregate") or [] - if module.params.get("vlan_id"): - vlans = [ - { - "vlan_id": module.params["vlan_id"], - "name": module.params.get("name"), - "address": module.params.get("address"), - "interfaces": module.params.get("interfaces") or [], - "state": module.params.get("state", "present"), - }, - ] - - try: - for vlan in vlans: - v_state = vlan.get("state") or module.params.get("state", "present") - if v_state == "absent": - _delete_vlan(client, vlan, commands) - else: - _apply_vlan(client, vlan, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_vlan.py.preview b/plugins/modules/vyos_vlan.py.preview new file mode 100644 index 0000000..18eca92 --- /dev/null +++ b/plugins/modules/vyos_vlan.py.preview @@ -0,0 +1,197 @@ +#!/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_vlan +short_description: Manage VLANs on VyOS network devices via REST API. +description: + - Manages 802.1Q VLAN sub-interfaces on VyOS Ethernet interfaces using + the HTTPS REST API. + - Mirrors C(vyos.vyos.vyos_vlan) but uses the HTTP API. +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + vlan_id: + description: VLAN ID (0-4094). + type: int + name: + description: VLAN name (used as description on the VIF). + type: str + address: + description: IP address for the VLAN interface. + type: str + interfaces: + description: List of physical interfaces to configure the VLAN on. + type: list + elements: str + aggregate: + description: List of VLAN definitions. + type: list + elements: dict + suboptions: + vlan_id: + type: int + required: true + name: + type: str + address: + type: str + interfaces: + type: list + elements: str + required: true + state: + type: str + choices: [present, absent] + state: + description: C(present) to configure, C(absent) to remove. + type: str + choices: [present, absent] + default: present + purge: + description: Remove VLANs not defined in aggregate. + type: bool + default: false + hostname: + type: str + required: true + port: + type: int + default: 443 + api_key: + type: str + required: true + no_log: true + timeout: + type: int + default: 30 + verify_ssl: + type: bool + default: false +""" + +RETURN = r""" +commands: + description: set/delete commands issued. + returned: always + type: list +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( + VYOS_REST_CONNECTION_ARGSPEC, + VyOSRestClient, + VyOSRestError, +) + + +def _apply_vlan(client, vlan, commands): + vid = str(vlan["vlan_id"]) + for iface in vlan.get("interfaces") or []: + # Detect interface type + if iface.startswith("bond"): + itype = "bonding" + else: + itype = "ethernet" + vif_base = ["interfaces", itype, iface, "vif", vid] + client.configure_set(vif_base) + commands.append("set interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) + if vlan.get("name"): + client.configure_set(vif_base + ["description"], vlan["name"]) + commands.append( + "set interfaces {t} {i} vif {v} description '{n}'".format( + t=itype, + i=iface, + v=vid, + n=vlan["name"], + ), + ) + if vlan.get("address"): + client.configure_set(vif_base + ["address"], vlan["address"]) + commands.append( + "set interfaces {t} {i} vif {v} address {a}".format( + t=itype, + i=iface, + v=vid, + a=vlan["address"], + ), + ) + + +def _delete_vlan(client, vlan, commands): + vid = str(vlan["vlan_id"]) + for iface in vlan.get("interfaces") or []: + itype = "bonding" if iface.startswith("bond") else "ethernet" + try: + client.configure_delete(["interfaces", itype, iface, "vif", vid]) + commands.append("delete interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) + except VyOSRestError: + pass + + +def main(): + vlan_spec = dict( + vlan_id=dict(type="int", required=True), + name=dict(type="str"), + address=dict(type="str"), + interfaces=dict(type="list", elements="str"), + state=dict(type="str", choices=["present", "absent"]), + ) + argument_spec = dict( + vlan_id=dict(type="int"), + name=dict(type="str"), + address=dict(type="str"), + interfaces=dict(type="list", elements="str"), + aggregate=dict(type="list", elements="dict", options=vlan_spec), + state=dict(type="str", default="present", choices=["present", "absent"]), + purge=dict(type="bool", default=False), + ) + argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["aggregate", "vlan_id"]], + supports_check_mode=True, + ) + if module.check_mode: + module.exit_json(changed=True, commands=["(check mode)"]) + + client = VyOSRestClient(module) + commands = [] + changed = False + + vlans = module.params.get("aggregate") or [] + if module.params.get("vlan_id"): + vlans = [ + { + "vlan_id": module.params["vlan_id"], + "name": module.params.get("name"), + "address": module.params.get("address"), + "interfaces": module.params.get("interfaces") or [], + "state": module.params.get("state", "present"), + }, + ] + + try: + for vlan in vlans: + v_state = vlan.get("state") or module.params.get("state", "present") + if v_state == "absent": + _delete_vlan(client, vlan, commands) + else: + _apply_vlan(client, vlan, commands) + changed = True + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + + module.exit_json(changed=changed, commands=commands) + + +if __name__ == "__main__": + main() -- cgit v1.2.3 From 6a161ed8f263e152ebfd227e669a51773de5a028 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Sun, 31 May 2026 07:25:03 +1000 Subject: updated tests --- README.md | 25 - docs/vyos.rest.vyos_bgp_address_family_module.rst | 780 ------------- docs/vyos.rest.vyos_bgp_global_module.rst | 573 ---------- docs/vyos.rest.vyos_config_file_module.rst | 234 ---- docs/vyos.rest.vyos_configure_module.rst | 290 ----- docs/vyos.rest.vyos_facts_module.rst | 185 --- docs/vyos.rest.vyos_firewall_global_module.rst | 752 ------------ docs/vyos.rest.vyos_firewall_interfaces_module.rst | 345 ------ docs/vyos.rest.vyos_firewall_rules_module.rst | 822 ------------- docs/vyos.rest.vyos_generate_module.rst | 191 ---- docs/vyos.rest.vyos_image_module.rst | 227 ---- docs/vyos.rest.vyos_interfaces_module.rst | 452 -------- docs/vyos.rest.vyos_l3_interfaces_module.rst | 470 -------- docs/vyos.rest.vyos_lag_interfaces_module.rst | 393 ------- docs/vyos.rest.vyos_lldp_interfaces_module.rst | 389 ------- docs/vyos.rest.vyos_ospf_interfaces_module.rst | 570 ---------- docs/vyos.rest.vyos_ospfv2_module.rst | 1202 -------------------- docs/vyos.rest.vyos_ospfv3_module.rst | 490 -------- docs/vyos.rest.vyos_ping_module.rst | 332 ------ docs/vyos.rest.vyos_prefix_lists_module.rst | 454 -------- docs/vyos.rest.vyos_reset_module.rst | 191 ---- docs/vyos.rest.vyos_show_module.rst | 210 ---- docs/vyos.rest.vyos_static_routes_module.rst | 505 -------- docs/vyos.rest.vyos_system_module.rst | 244 ---- docs/vyos.rest.vyos_user_module.rst | 564 --------- docs/vyos.rest.vyos_vlan_module.rst | 360 ------ plugins/httpapi/vyos.py | 8 +- plugins/module_utils/vyos.py | 113 +- plugins/module_utils/vyos_rest.py | 48 +- plugins/modules/vyos_ntp_global.py | 13 +- .../targets/vyos_banner/defaults/main.yaml | 3 + .../targets/vyos_banner/tasks/httpapi.yaml | 21 + .../targets/vyos_banner/tasks/main.yaml | 5 + .../tests/httpapi/_populate_config.yaml | 11 + .../vyos_banner/tests/httpapi/_remove_config.yaml | 5 + .../targets/vyos_banner/tests/httpapi/deleted.yaml | 28 + .../vyos_banner/tests/httpapi/gathered.yaml | 20 + .../targets/vyos_banner/tests/httpapi/merged.yaml | 32 + .../integration/targets/vyos_banner/vars/main.yaml | 2 + .../targets/vyos_hostname/defaults/main.yaml | 3 + .../targets/vyos_hostname/tasks/httpapi.yaml | 21 + .../targets/vyos_hostname/tasks/main.yaml | 5 + .../tests/httpapi/_populate_config.yaml | 6 + .../tests/httpapi/_remove_config.yaml | 7 + .../vyos_hostname/tests/httpapi/gathered.yaml | 19 + .../vyos_hostname/tests/httpapi/merged.yaml | 30 + .../vyos_hostname/tests/httpapi/replaced.yaml | 30 + .../targets/vyos_hostname/vars/main.yaml | 2 + .../targets/vyos_lldp_global/defaults/main.yaml | 3 + .../targets/vyos_lldp_global/tasks/httpapi.yaml | 21 + .../targets/vyos_lldp_global/tasks/main.yaml | 5 + .../tests/httpapi/_populate_config.yaml | 13 + .../tests/httpapi/_remove_config.yaml | 5 + .../vyos_lldp_global/tests/httpapi/deleted.yaml | 28 + .../vyos_lldp_global/tests/httpapi/gathered.yaml | 20 + .../vyos_lldp_global/tests/httpapi/merged.yaml | 36 + .../vyos_lldp_global/tests/httpapi/replaced.yaml | 35 + .../targets/vyos_lldp_global/vars/main.yaml | 2 + .../targets/vyos_logging_global/defaults/main.yaml | 3 + .../targets/vyos_logging_global/tasks/httpapi.yaml | 21 + .../targets/vyos_logging_global/tasks/main.yaml | 136 +-- .../tests/httpapi/_populate_config.yaml | 21 + .../tests/httpapi/_remove_config.yaml | 5 + .../vyos_logging_global/tests/httpapi/deleted.yaml | 28 + .../tests/httpapi/gathered.yaml | 20 + .../vyos_logging_global/tests/httpapi/merged.yaml | 44 + .../tests/httpapi/replaced.yaml | 33 + .../targets/vyos_logging_global/vars/main.yaml | 2 + .../targets/vyos_prefix_lists.old/aliases | 1 + .../targets/vyos_prefix_lists.old/tasks/main.yaml | 144 +++ .../targets/vyos_prefix_lists/defaults/main.yaml | 3 + .../targets/vyos_prefix_lists/tasks/httpapi.yaml | 21 + .../targets/vyos_prefix_lists/tasks/main.yaml | 147 +-- .../tests/httpapi/_populate_config.yaml | 28 + .../tests/httpapi/_remove_config.yaml | 5 + .../vyos_prefix_lists/tests/httpapi/deleted.yaml | 28 + .../vyos_prefix_lists/tests/httpapi/gathered.yaml | 20 + .../vyos_prefix_lists/tests/httpapi/merged.yaml | 42 + .../vyos_prefix_lists/tests/httpapi/replaced.yaml | 37 + .../targets/vyos_prefix_lists/vars/main.yaml | 2 + .../targets/vyos_route_maps/defaults/main.yaml | 3 + .../targets/vyos_route_maps/tasks/httpapi.yaml | 21 + .../targets/vyos_route_maps/tasks/main.yaml | 136 +-- .../tests/httpapi/_populate_config.yaml | 17 + .../tests/httpapi/_remove_config.yaml | 5 + .../vyos_route_maps/tests/httpapi/deleted.yaml | 28 + .../vyos_route_maps/tests/httpapi/gathered.yaml | 20 + .../vyos_route_maps/tests/httpapi/merged.yaml | 40 + .../vyos_route_maps/tests/httpapi/replaced.yaml | 36 + .../targets/vyos_route_maps/vars/main.yaml | 2 + .../targets/vyos_snmp_server/defaults/main.yaml | 3 + .../targets/vyos_snmp_server/tasks/httpapi.yaml | 21 + .../targets/vyos_snmp_server/tasks/main.yaml | 5 + .../tests/httpapi/_populate_config.yaml | 15 + .../tests/httpapi/_remove_config.yaml | 5 + .../vyos_snmp_server/tests/httpapi/deleted.yaml | 28 + .../vyos_snmp_server/tests/httpapi/gathered.yaml | 20 + .../vyos_snmp_server/tests/httpapi/merged.yaml | 38 + .../vyos_snmp_server/tests/httpapi/replaced.yaml | 34 + .../targets/vyos_snmp_server/vars/main.yaml | 2 + 100 files changed, 1374 insertions(+), 11746 deletions(-) delete mode 100644 docs/vyos.rest.vyos_bgp_address_family_module.rst delete mode 100644 docs/vyos.rest.vyos_bgp_global_module.rst delete mode 100644 docs/vyos.rest.vyos_config_file_module.rst delete mode 100644 docs/vyos.rest.vyos_configure_module.rst delete mode 100644 docs/vyos.rest.vyos_facts_module.rst delete mode 100644 docs/vyos.rest.vyos_firewall_global_module.rst delete mode 100644 docs/vyos.rest.vyos_firewall_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_firewall_rules_module.rst delete mode 100644 docs/vyos.rest.vyos_generate_module.rst delete mode 100644 docs/vyos.rest.vyos_image_module.rst delete mode 100644 docs/vyos.rest.vyos_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_l3_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_lag_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_lldp_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_ospf_interfaces_module.rst delete mode 100644 docs/vyos.rest.vyos_ospfv2_module.rst delete mode 100644 docs/vyos.rest.vyos_ospfv3_module.rst delete mode 100644 docs/vyos.rest.vyos_ping_module.rst delete mode 100644 docs/vyos.rest.vyos_prefix_lists_module.rst delete mode 100644 docs/vyos.rest.vyos_reset_module.rst delete mode 100644 docs/vyos.rest.vyos_show_module.rst delete mode 100644 docs/vyos.rest.vyos_static_routes_module.rst delete mode 100644 docs/vyos.rest.vyos_system_module.rst delete mode 100644 docs/vyos.rest.vyos_user_module.rst delete mode 100644 docs/vyos.rest.vyos_vlan_module.rst create mode 100644 tests/integration/targets/vyos_banner/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_banner/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_banner/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_banner/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_banner/vars/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_hostname/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_hostname/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_hostname/vars/main.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_lldp_global/vars/main.yaml create mode 100644 tests/integration/targets/vyos_logging_global/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_logging_global/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_logging_global/vars/main.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists.old/aliases create mode 100644 tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/vars/main.yaml create mode 100644 tests/integration/targets/vyos_route_maps/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_route_maps/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_route_maps/vars/main.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/defaults/main.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tasks/httpapi.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/vars/main.yaml (limited to 'plugins/modules') diff --git a/README.md b/README.md index ccc953b..2430ebb 100644 --- a/README.md +++ b/README.md @@ -12,37 +12,12 @@ Name | Description 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_bgp_address_family](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_bgp_address_family_module.rst)|BGP Address Family resource module via REST API. -[vyos.rest.vyos_bgp_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_bgp_global_module.rst)|Manage BGP global configuration on VyOS via the REST API. -[vyos.rest.vyos_config_file](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_config_file_module.rst)|Save or load VyOS configuration files via the REST API. -[vyos.rest.vyos_configure](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_configure_module.rst)|Manage VyOS device configuration via the REST API. -[vyos.rest.vyos_facts](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_facts_module.rst)|Get facts about VyOS devices via REST API. -[vyos.rest.vyos_firewall_global](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_firewall_global_module.rst)|Firewall global resource module via REST API. -[vyos.rest.vyos_firewall_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_firewall_interfaces_module.rst)|Firewall interfaces resource module via REST API. -[vyos.rest.vyos_firewall_rules](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_firewall_rules_module.rst)|Manage firewall rules on VyOS via the REST API. -[vyos.rest.vyos_generate](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_generate_module.rst)|Execute generate commands on 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_image](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_image_module.rst)|Manage VyOS system images 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 attributes on a VyOS device via the 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 attributes on VyOS via the REST API. -[vyos.rest.vyos_lag_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lag_interfaces_module.rst)|LAG/bonding interfaces resource module 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. -[vyos.rest.vyos_lldp_interfaces](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_lldp_interfaces_module.rst)|LLDP interfaces resource module 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)|OSPF Interfaces Resource Module via REST API. -[vyos.rest.vyos_ospfv2](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv2_module.rst)|OSPFv2 resource module via REST API. -[vyos.rest.vyos_ospfv3](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ospfv3_module.rst)|OSPFv3 resource module via REST API. -[vyos.rest.vyos_ping](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_ping_module.rst)|Test reachability using ping via VyOS 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_reset](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_reset_module.rst)|Execute reset commands on a VyOS device via 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_show](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_show_module.rst)|Execute op-mode show commands on a VyOS device via 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 via the REST API. -[vyos.rest.vyos_system](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_system_module.rst)|Manage system settings on VyOS via the REST API. -[vyos.rest.vyos_user](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_user_module.rst)|Manage local users on VyOS via the REST API. -[vyos.rest.vyos_vlan](https://github.com/vyos/vyos.rest/blob/main/docs/vyos.rest.vyos_vlan_module.rst)|Manage VLANs on VyOS network devices via REST API. diff --git a/docs/vyos.rest.vyos_bgp_address_family_module.rst b/docs/vyos.rest.vyos_bgp_address_family_module.rst deleted file mode 100644 index 8718293..0000000 --- a/docs/vyos.rest.vyos_bgp_address_family_module.rst +++ /dev/null @@ -1,780 +0,0 @@ -.. _vyos.rest.vyos_bgp_address_family_module: - - -********************************* -vyos.rest.vyos_bgp_address_family -********************************* - -**BGP Address Family resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages BGP address-family configuration (networks, redistribute, aggregate-address, per-neighbor AFI settings) via the VyOS HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- dictionary -
-
- -
BGP address-family configuration.
-
-
- address_family - -
- list - / elements=dictionary -
-
- -
Global address-family parameters.
-
-
- afi - -
- string -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family (ipv4 or ipv6).
-
-
- aggregate_address - -
- list - / elements=dictionary -
-
- -
List of BGP aggregate networks.
-
-
- as_set - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- prefix - -
- string -
-
- -
-
- summary_only - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- networks - -
- list - / elements=dictionary -
-
- -
Networks to originate.
-
-
- backdoor - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- path_limit - -
- integer -
-
- -
-
- prefix - -
- string -
-
- -
-
- route_map - -
- string -
-
- -
-
- redistribute - -
- list - / elements=dictionary -
-
- -
Protocols to redistribute.
-
-
- metric - -
- integer -
-
- -
-
- protocol - -
- string -
-
-
    Choices: -
  • connected
  • -
  • kernel
  • -
  • ospf
  • -
  • ospfv3
  • -
  • rip
  • -
  • ripng
  • -
  • static
  • -
-
-
-
- route_map - -
- string -
-
- -
-
- table - -
- string -
-
- -
-
- as_number - -
- integer - / required -
-
- -
Local AS number.
-
-
- neighbors - -
- list - / elements=dictionary -
-
- -
Per-neighbor address-family settings.
-
-
- address_family - -
- list - / elements=dictionary -
-
- -
-
- afi - -
- string -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
-
- allowas_in - -
- integer -
-
- -
-
- as_override - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- nexthop_self - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- remove_private_as - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- route_map - -
- dictionary -
-
- -
Route-map settings.
-
-
- export_map - -
- string -
-
- -
-
- import_map - -
- string -
-
- -
-
- soft_reconfiguration - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- neighbor_address - -
- string -
-
- -
Neighbor IP.
-
-
- hostname - -
- string - / required -
-
- -
Device IP or FQDN.
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • deleted
  • -
  • gathered
  • -
-
-
Desired state.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- dictionary -
-
when changed -
Config after module ran.
-
-
-
- before - -
- dictionary -
-
always -
Config before module ran.
-
-
-
- commands - -
- list -
-
always -
Commands issued.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_bgp_global_module.rst b/docs/vyos.rest.vyos_bgp_global_module.rst deleted file mode 100644 index 4c91b4a..0000000 --- a/docs/vyos.rest.vyos_bgp_global_module.rst +++ /dev/null @@ -1,573 +0,0 @@ -.. _vyos.rest.vyos_bgp_global_module: - - -************************* -vyos.rest.vyos_bgp_global -************************* - -**Manage BGP global configuration on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages BGP global parameters on VyOS devices using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_bgp_global`` but uses the HTTP API. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- config - -
- dictionary -
-
- -
BGP global configuration.
-
-
- as_number - -
- integer - / required -
-
- -
Local BGP AS number.
-
-
- neighbors - -
- list - / elements=dictionary -
-
- -
List of BGP neighbor configurations.
-
-
- description - -
- string -
-
- -
Neighbor description.
-
-
- ebgp_multihop - -
- integer -
-
- -
Maximum hops for eBGP sessions.
-
-
- neighbor - -
- string - / required -
-
- -
Neighbor IP address.
-
-
- password - -
- string -
-
- -
MD5 authentication password.
-
-
- remote_as - -
- integer -
-
- -
Neighbor's AS number.
-
-
- shutdown - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
Whether to administratively shut down the neighbor.
-
-
- update_source - -
- string -
-
- -
Local interface or address for BGP session.
-
-
- networks - -
- list - / elements=dictionary -
-
- -
List of networks to originate.
-
-
- prefix - -
- string - / required -
-
- -
Network prefix to advertise.
-
-
- route_map - -
- string -
-
- -
Route map to apply.
-
-
- redistribute - -
- list - / elements=dictionary -
-
- -
List of protocols to redistribute into BGP.
-
-
- metric - -
- integer -
-
- -
Metric for redistributed routes.
-
-
- protocol - -
- string - / required -
-
-
    Choices: -
  • connected
  • -
  • kernel
  • -
  • ospf
  • -
  • ospfv3
  • -
  • rip
  • -
  • ripng
  • -
  • static
  • -
-
-
Protocol to redistribute.
-
-
- route_map - -
- string -
-
- -
Route map to apply.
-
-
- router_id - -
- string -
-
- -
BGP router ID (IPv4 address).
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • deleted
  • -
  • gathered
  • -
-
-
{'merged': 'Merge BGP config with existing.'}
-
{'replaced': 'Replace entire BGP config.'}
-
{'deleted': 'Remove BGP configuration.'}
-
{'gathered': 'Read BGP config from device.'}
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - -See Also --------- - -.. seealso:: - - :ref:`vyos.vyos.vyos_bgp_global_module` - The official documentation on the **vyos.vyos.vyos_bgp_global** module. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- dictionary -
-
when changed -
BGP config after the module ran.
-
-
-
- before - -
- dictionary -
-
always -
BGP config before the module ran.
-
-
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-
- gathered - -
- dictionary -
-
when state is gathered -
BGP config read from device (state=gathered).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_config_file_module.rst b/docs/vyos.rest.vyos_config_file_module.rst deleted file mode 100644 index a1f0dba..0000000 --- a/docs/vyos.rest.vyos_config_file_module.rst +++ /dev/null @@ -1,234 +0,0 @@ -.. _vyos.rest.vyos_config_file_module: - - -************************** -vyos.rest.vyos_config_file -************************** - -**Save or load VyOS configuration files via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages VyOS configuration persistence via the HTTPS REST API (``/config-file`` endpoint). -- Use *op=save* to write the running configuration to a file. -- Use *op=load* to load a configuration from a file and apply it. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- file - -
- string -
-
- -
Path on the VyOS device for save/load target.
-
If omitted for save, the device uses its default boot config path.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- op - -
- string -
-
-
    Choices: -
  • save ←
  • -
  • load
  • -
-
-
save - Save the running configuration to file or the default /config/config.boot.
-
load - Load and commit the configuration from file.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - - -Examples --------- - -.. code-block:: yaml - - - name: Save running config (default location) - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - - - name: Save running config to a custom file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - file: /config/backups/config.boot.bak - - - name: Load a configuration from file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: load - file: /config/backups/config.boot.bak - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- output - -
- string -
-
success -
Text output from the config-file operation.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_configure_module.rst b/docs/vyos.rest.vyos_configure_module.rst deleted file mode 100644 index e896fcc..0000000 --- a/docs/vyos.rest.vyos_configure_module.rst +++ /dev/null @@ -1,290 +0,0 @@ -.. _vyos.rest.vyos_configure_module: - - -************************ -vyos.rest.vyos_configure -************************ - -**Manage VyOS device configuration via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Sends one or more ``set`` or ``delete`` configuration commands to a VyOS device via its HTTPS REST API (``/configure`` endpoint). -- Each command in *commands* is executed and committed atomically. -- Supports Ansible check-mode (``check_mode=yes``) - no changes are applied. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- commands - -
- list - / elements=raw - / required -
-
- -
List of VyOS configuration commands.
-
Each item may be either a plain string in VyOS CLI syntax (e.g. set interfaces ethernet eth0 description 'WAN') or a dict with keys op, path, and optionally value.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- save - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Whether to save the configuration to disk after a successful commit.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - -Notes ------ - -.. note:: - - The VyOS HTTP API must be enabled on the device before using this module. - - Enable with ``set service https api keys id key `` and ``set service https api rest``, then ``commit && save``. - - Tested against VyOS 1.3 and 1.4. - - -See Also --------- - -.. seealso:: - - :ref:`vyos.rest.vyos_retrieve_module` - The official documentation on the **vyos.rest.vyos_retrieve** module. - :ref:`vyos.rest.vyos_show_module` - The official documentation on the **vyos.rest.vyos_show** module. - - -Examples --------- - -.. code-block:: yaml - - - name: Set hostname via REST API - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set system host-name vyos-router" - - - name: Configure an interface description and address - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set interfaces ethernet eth1 description 'UPLINK'" - - "set interfaces ethernet eth1 address 10.0.0.1/30" - save: true - - - name: Delete a static route using dict syntax - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - op: delete - path: ["protocols", "static", "route", "192.0.2.0/24"] - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- changed - -
- boolean -
-
always -
Whether the device configuration was modified.
-
-
-
- commands_sent - -
- list - / elements=dictionary -
-
always -
The list of commands dispatched to the API.
-
-
Sample:
-
[{'op': 'set', 'path': ['system', 'host-name'], 'value': 'vyos-router'}]
-
-
- saved - -
- boolean -
-
always -
Whether the configuration was saved to disk.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_facts_module.rst b/docs/vyos.rest.vyos_facts_module.rst deleted file mode 100644 index 23c385f..0000000 --- a/docs/vyos.rest.vyos_facts_module.rst +++ /dev/null @@ -1,185 +0,0 @@ -.. _vyos.rest.vyos_facts_module: - - -******************** -vyos.rest.vyos_facts -******************** - -**Get facts about VyOS devices via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Collects facts from VyOS devices using the HTTPS REST API. -- Returns system info and optionally per-resource configuration facts. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- available_network_resources - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
When true, return a list of available resource names.
-
-
- gather_network_resources - -
- list - / elements=string -
-
- -
Which network resource facts to gather.
-
-
- gather_subset - -
- list - / elements=string -
-
- Default:
["min"]
-
-
Restrict facts to a subset. Use all, default, config, or min.
-
-
- hostname - -
- string - / required -
-
- -
Device IP or FQDN.
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_firewall_global_module.rst b/docs/vyos.rest.vyos_firewall_global_module.rst deleted file mode 100644 index 581e912..0000000 --- a/docs/vyos.rest.vyos_firewall_global_module.rst +++ /dev/null @@ -1,752 +0,0 @@ -.. _vyos.rest.vyos_firewall_global_module: - - -****************************** -vyos.rest.vyos_firewall_global -****************************** - -**Firewall global resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages global VyOS firewall parameters (groups, ICMP redirect policy, source validation, ping policy) via the HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- dictionary -
-
- -
Global firewall configuration.
-
-
- config_trap - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
SNMP trap on firewall config changes.
-
-
- group - -
- dictionary -
-
- -
Firewall object groups.
-
-
- address_group - -
- list - / elements=dictionary -
-
- -
-
- afi - -
- string -
-
-
    Choices: -
  • ipv4 ←
  • -
  • ipv6
  • -
-
-
-
- description - -
- string -
-
- -
-
- members - -
- list - / elements=dictionary -
-
- -
List of IP addresses or ranges.
-
-
- address - -
- string -
-
- -
-
- name - -
- string - / required -
-
- -
-
- network_group - -
- list - / elements=dictionary -
-
- -
-
- description - -
- string -
-
- -
-
- members - -
- list - / elements=dictionary -
-
- -
List of network prefixes.
-
-
- address - -
- string -
-
- -
-
- name - -
- string - / required -
-
- -
-
- port_group - -
- list - / elements=dictionary -
-
- -
-
- description - -
- string -
-
- -
-
- members - -
- list - / elements=dictionary -
-
- -
List of port numbers or ranges.
-
-
- port - -
- string -
-
- -
-
- name - -
- string - / required -
-
- -
-
- ping - -
- dictionary -
-
- -
ICMP echo policy.
-
-
- all - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- broadcast - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- route_redirects - -
- list - / elements=dictionary -
-
- -
ICMP redirect / source-route options per AFI.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
-
- icmp_redirects - -
- dictionary -
-
- -
-
- receive - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- send - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- ip_src_route - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- validation - -
- string -
-
-
    Choices: -
  • strict
  • -
  • loose
  • -
  • disable
  • -
-
-
Source-validation policy (strict/loose/disable).
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • deleted
  • -
  • gathered
  • -
-
-
Desired state.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- dictionary -
-
when changed -
-
-
- before - -
- dictionary -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_firewall_interfaces_module.rst b/docs/vyos.rest.vyos_firewall_interfaces_module.rst deleted file mode 100644 index e1efc1d..0000000 --- a/docs/vyos.rest.vyos_firewall_interfaces_module.rst +++ /dev/null @@ -1,345 +0,0 @@ -.. _vyos.rest.vyos_firewall_interfaces_module: - - -********************************** -vyos.rest.vyos_firewall_interfaces -********************************** - -**Firewall interfaces resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Attaches or detaches named firewall rule sets to/from interface directions (in/out/local) using the VyOS HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- list - / elements=dictionary -
-
- -
List of interface firewall attachment configurations.
-
-
- access_rules - -
- list - / elements=dictionary -
-
- -
Firewall rule sets per AFI.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family.
-
-
- rules - -
- list - / elements=dictionary -
-
- -
Rule sets and directions.
-
-
- direction - -
- string - / required -
-
-
    Choices: -
  • in
  • -
  • local
  • -
  • out
  • -
-
-
Traffic direction.
-
-
- name - -
- string -
-
- -
Rule set name.
-
-
- name - -
- string - / required -
-
- -
Interface name.
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
Desired state.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
-
-
- before - -
- list -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_firewall_rules_module.rst b/docs/vyos.rest.vyos_firewall_rules_module.rst deleted file mode 100644 index b95891a..0000000 --- a/docs/vyos.rest.vyos_firewall_rules_module.rst +++ /dev/null @@ -1,822 +0,0 @@ -.. _vyos.rest.vyos_firewall_rules_module: - - -***************************** -vyos.rest.vyos_firewall_rules -***************************** - -**Manage firewall rules on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages VyOS firewall rule sets and individual rules using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_firewall_rules`` but uses the HTTP API. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- config - -
- list - / elements=dictionary -
-
- -
List of firewall rule set configurations.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family.
-
-
- rule_sets - -
- list - / elements=dictionary -
-
- -
List of named rule sets.
-
-
- default_action - -
- string -
-
-
    Choices: -
  • drop ←
  • -
  • reject
  • -
  • accept
  • -
-
-
Default action for unmatched traffic.
-
-
- description - -
- string -
-
- -
Rule set description.
-
-
- name - -
- string - / required -
-
- -
Rule set name.
-
-
- rules - -
- list - / elements=dictionary -
-
- -
List of firewall rules.
-
-
- action - -
- string -
-
-
    Choices: -
  • drop
  • -
  • reject
  • -
  • accept
  • -
  • inspect
  • -
-
-
Rule action.
-
-
- description - -
- string -
-
- -
Rule description.
-
-
- destination - -
- dictionary -
-
- -
Destination match criteria.
-
-
- address - -
- string -
-
- -
Destination IP address or network.
-
-
- group - -
- dictionary -
-
- -
Address or network group.
-
-
- address_group - -
- string -
-
- -
-
- network_group - -
- string -
-
- -
-
- port - -
- string -
-
- -
Destination port or port range.
-
-
- enabled - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes ←
  • -
-
-
Whether the rule is active.
-
-
- number - -
- integer - / required -
-
- -
Rule number (1-999999).
-
-
- protocol - -
- string -
-
- -
Protocol (e.g. tcp, udp, icmp, all).
-
-
- source - -
- dictionary -
-
- -
Source match criteria.
-
-
- address - -
- string -
-
- -
Source IP address or network.
-
-
- group - -
- dictionary -
-
- -
Address or network group.
-
-
- address_group - -
- string -
-
- -
-
- network_group - -
- string -
-
- -
-
- port - -
- string -
-
- -
Source port or port range.
-
-
- state - -
- dictionary -
-
- -
Connection state matching.
-
-
- established - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- invalid - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- new - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- related - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
{'merged': 'Add/update listed rule sets and rules.'}
-
{'replaced': 'Replace listed rule sets entirely.'}
-
{'overridden': 'Replace the entire firewall rule config.'}
-
{'deleted': 'Remove listed (or all) rule sets.'}
-
{'gathered': 'Read firewall rule config from device.'}
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - -See Also --------- - -.. seealso:: - - :ref:`vyos.vyos.vyos_firewall_rules_module` - The official documentation on the **vyos.vyos.vyos_firewall_rules** module. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
Firewall rule config after the module ran.
-
-
-
- before - -
- list -
-
always -
Firewall rule config before the module ran.
-
-
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-
- gathered - -
- list -
-
when state is gathered -
Firewall config read from device (state=gathered).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_generate_module.rst b/docs/vyos.rest.vyos_generate_module.rst deleted file mode 100644 index f972ee7..0000000 --- a/docs/vyos.rest.vyos_generate_module.rst +++ /dev/null @@ -1,191 +0,0 @@ -.. _vyos.rest.vyos_generate_module: - - -*********************** -vyos.rest.vyos_generate -*********************** - -**Execute generate commands on a VyOS device via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Sends a ``generate`` operational command to a VyOS device via the HTTPS REST API (``/generate`` endpoint). -- Commonly used to generate WireGuard keypairs, TLS certificates, etc. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- path - -
- list - / elements=string - / required -
-
- -
Generate command tokens.
-
E.g. ["wireguard", "default-keypair"].
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- output - -
- string -
-
success -
Text output produced by the generate command (may be empty).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_image_module.rst b/docs/vyos.rest.vyos_image_module.rst deleted file mode 100644 index 3dd396d..0000000 --- a/docs/vyos.rest.vyos_image_module.rst +++ /dev/null @@ -1,227 +0,0 @@ -.. _vyos.rest.vyos_image_module: - - -******************** -vyos.rest.vyos_image -******************** - -**Manage VyOS system images via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Adds or removes VyOS system images using the HTTPS REST API (``/image`` endpoint). -- Use *state=present* to download and install an image from a URL. -- Use *state=absent* to delete an installed image by name. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- name - -
- string -
-
- -
Version name of the image to remove (e.g. 1.4-rolling-202401010000).
-
Required when state=absent.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string - / required -
-
-
    Choices: -
  • present
  • -
  • absent
  • -
-
-
{'present': 'Download and install the image from url.'}
-
{'absent': 'Delete the image specified by name.'}
-
-
- timeout - -
- integer -
-
- Default:
300
-
-
Request timeout in seconds. Image downloads can take several minutes; increase this value accordingly.
-
-
- url - -
- string -
-
- -
HTTP(S) URL of the VyOS .iso file to install.
-
Required when state=present.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- output - -
- string -
-
success -
Text output from the image add/delete operation.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_interfaces_module.rst b/docs/vyos.rest.vyos_interfaces_module.rst deleted file mode 100644 index fbaca26..0000000 --- a/docs/vyos.rest.vyos_interfaces_module.rst +++ /dev/null @@ -1,452 +0,0 @@ -.. _vyos.rest.vyos_interfaces_module: - - -************************* -vyos.rest.vyos_interfaces -************************* - -**Manage interface attributes on a VyOS device via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages interface base attributes (description, MTU, enabled state, VIFs, duplex, speed) on VyOS devices using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_interfaces`` but uses HTTP API. -- Supports Ethernet, Bonding, VXLAN, Loopback, and Virtual Tunnel interfaces. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- config - -
- list - / elements=dictionary -
-
- -
List of interface configurations.
-
-
- description - -
- string -
-
- -
Interface description.
-
-
- duplex - -
- string -
-
-
    Choices: -
  • auto
  • -
  • full
  • -
  • half
  • -
-
-
Duplex mode. Only for Ethernet interfaces.
-
-
- enabled - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes ←
  • -
-
-
Administrative state. true = up, false = admin-down.
-
-
- mtu - -
- integer -
-
- -
MTU in bytes. Applicable for Ethernet, Bonding, VXLAN, VTI.
-
-
- name - -
- string - / required -
-
- -
Full interface name (e.g. eth0, bond0, vti1, vxlan2).
-
-
- speed - -
- string -
-
- -
Link speed. Only for Ethernet interfaces.
-
-
- vifs - -
- list - / elements=dictionary -
-
- -
List of VLAN sub-interface configurations.
-
-
- description - -
- string -
-
- -
VIF description.
-
-
- enabled - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes ←
  • -
-
-
Administrative state of the VIF.
-
-
- vlan_id - -
- integer - / required -
-
- -
802.1Q VLAN ID.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
{'merged': 'Merge the provided config with existing interface config.'}
-
{'replaced': 'Replace per-interface config with provided values.'}
-
{'overridden': 'Override all interface config with provided values.'}
-
{'deleted': 'Delete interface config for listed interfaces (or all).'}
-
{'gathered': 'Read current interface config from device.'}
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - -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. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
Interface configuration after the module ran.
-
-
-
- before - -
- list -
-
always -
Interface configuration before the module ran.
-
-
-
- commands - -
- list -
-
always -
REST API set/delete commands issued.
-
-
-
- gathered - -
- list -
-
when state is gathered -
Interface config read from device (state=gathered).
-
-
-

- - -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 deleted file mode 100644 index ffc50ca..0000000 --- a/docs/vyos.rest.vyos_l3_interfaces_module.rst +++ /dev/null @@ -1,470 +0,0 @@ -.. _vyos.rest.vyos_l3_interfaces_module: - - -**************************** -vyos.rest.vyos_l3_interfaces -**************************** - -**Manage L3 interface attributes on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages IPv4 and IPv6 addresses on VyOS interfaces via the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_l3_interfaces`` but uses the HTTP API. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- config - -
- list - / elements=dictionary -
-
- -
List of L3 interface configurations.
-
-
- ipv4 - -
- list - / elements=dictionary -
-
- -
List of IPv4 address assignments.
-
-
- address - -
- string - / required -
-
- -
IPv4 address in CIDR notation or dhcp.
-
-
- ipv6 - -
- list - / elements=dictionary -
-
- -
List of IPv6 address assignments.
-
-
- address - -
- string - / required -
-
- -
IPv6 address in CIDR notation or dhcpv6 or autoconf.
-
-
- name - -
- string - / required -
-
- -
Full interface name (e.g. eth0, eth1).
-
-
- vifs - -
- list - / elements=dictionary -
-
- -
VLAN sub-interface L3 settings.
-
-
- ipv4 - -
- list - / elements=dictionary -
-
- -
IPv4 addresses for this VIF.
-
-
- address - -
- string - / required -
-
- -
-
- ipv6 - -
- list - / elements=dictionary -
-
- -
IPv6 addresses for this VIF.
-
-
- address - -
- string - / required -
-
- -
-
- vlan_id - -
- integer - / required -
-
- -
802.1Q VLAN ID.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
{'merged': 'Add addresses to the interface (preserve existing).'}
-
{'replaced': 'Replace all addresses on each listed interface.'}
-
{'overridden': 'Replace all addresses on all interfaces.'}
-
{'deleted': 'Remove all addresses from listed (or all) interfaces.'}
-
{'gathered': 'Read L3 config from device without changes.'}
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - -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. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
L3 interface config after the module ran.
-
-
-
- before - -
- list -
-
always -
L3 interface config before the module ran.
-
-
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-
- gathered - -
- list -
-
when state is gathered -
L3 config read from device (state=gathered).
-
-
-

- - -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 deleted file mode 100644 index 39edaa1..0000000 --- a/docs/vyos.rest.vyos_lag_interfaces_module.rst +++ /dev/null @@ -1,393 +0,0 @@ -.. _vyos.rest.vyos_lag_interfaces_module: - - -***************************** -vyos.rest.vyos_lag_interfaces -***************************** - -**LAG/bonding interfaces resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages Link Aggregation Group (LAG/bond) interfaces on VyOS via REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- list - / elements=dictionary -
-
- -
List of LAG interface configurations.
-
-
- arp_monitor - -
- dictionary -
-
- -
ARP monitoring settings.
-
-
- interval - -
- integer -
-
- -
Monitoring interval in ms.
-
-
- target - -
- list - / elements=string -
-
- -
Target IP addresses.
-
-
- hash_policy - -
- string -
-
-
    Choices: -
  • layer2
  • -
  • layer2+3
  • -
  • layer3+4
  • -
-
-
Transmit hash policy.
-
-
- members - -
- list - / elements=dictionary -
-
- -
Member interfaces.
-
-
- member - -
- string -
-
- -
Interface name.
-
-
- mode - -
- string -
-
-
    Choices: -
  • 802.3ad
  • -
  • active-backup
  • -
  • broadcast
  • -
  • round-robin
  • -
  • transmit-load-balance
  • -
  • adaptive-load-balance
  • -
  • xor-hash
  • -
-
-
Bonding mode.
-
-
- name - -
- string - / required -
-
- -
Bond interface name (e.g. bond0).
-
-
- primary - -
- string -
-
- -
Primary interface name.
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
Desired state.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
-
-
- before - -
- list -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -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 deleted file mode 100644 index 59d25d1..0000000 --- a/docs/vyos.rest.vyos_lldp_interfaces_module.rst +++ /dev/null @@ -1,389 +0,0 @@ -.. _vyos.rest.vyos_lldp_interfaces_module: - - -****************************** -vyos.rest.vyos_lldp_interfaces -****************************** - -**LLDP interfaces resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages per-interface LLDP settings (enable/disable, location) on VyOS via the HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- list - / elements=dictionary -
-
- -
List of LLDP interface configurations.
-
-
- enable - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes ←
  • -
-
-
Enable LLDP on this interface (default true).
-
-
- location - -
- dictionary -
-
- -
LLDP-MED location data.
-
-
- coordinate_based - -
- dictionary -
-
- -
-
- altitude - -
- integer -
-
- -
-
- datum - -
- string -
-
-
    Choices: -
  • WGS84
  • -
  • NAD83
  • -
  • MLLW
  • -
-
-
-
- latitude - -
- string - / required -
-
- -
-
- longitude - -
- string - / required -
-
- -
-
- elin - -
- string -
-
- -
Emergency ELIN number (10-25 digits).
-
-
- name - -
- string - / required -
-
- -
Interface name.
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
-
-
- before - -
- list -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -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 deleted file mode 100644 index 7447c4e..0000000 --- a/docs/vyos.rest.vyos_ospf_interfaces_module.rst +++ /dev/null @@ -1,570 +0,0 @@ -.. _vyos.rest.vyos_ospf_interfaces_module: - - -****************************** -vyos.rest.vyos_ospf_interfaces -****************************** - -**OSPF Interfaces Resource Module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages per-interface OSPF parameters (cost, hello/dead timers, authentication, passive mode, etc.) for both OSPFv2 and OSPFv3 via the VyOS HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- list - / elements=dictionary -
-
- -
List of OSPF interface configurations.
-
-
- address_family - -
- list - / elements=dictionary -
-
- -
OSPF settings per address family.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family (ipv4=OSPFv2, ipv6=OSPFv3).
-
-
- authentication - -
- dictionary -
-
- -
-
- md5_key - -
- dictionary -
-
- -
-
- key - -
- string -
-
- -
-
- key_id - -
- integer -
-
- -
-
- plaintext_password - -
- string -
-
- -
-
- bandwidth - -
- integer -
-
- -
-
- cost - -
- integer -
-
- -
-
- dead_interval - -
- integer -
-
- -
-
- hello_interval - -
- integer -
-
- -
-
- ifmtu - -
- integer -
-
- -
-
- instance - -
- string -
-
- -
-
- mtu_ignore - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- network - -
- string -
-
- -
-
- passive - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- priority - -
- integer -
-
- -
-
- retransmit_interval - -
- integer -
-
- -
-
- transmit_delay - -
- integer -
-
- -
-
- name - -
- string -
-
- -
Interface name.
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
-
-
- before - -
- list -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_ospfv2_module.rst b/docs/vyos.rest.vyos_ospfv2_module.rst deleted file mode 100644 index f487ac7..0000000 --- a/docs/vyos.rest.vyos_ospfv2_module.rst +++ /dev/null @@ -1,1202 +0,0 @@ -.. _vyos.rest.vyos_ospfv2_module: - - -********************* -vyos.rest.vyos_ospfv2 -********************* - -**OSPFv2 resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages OSPFv2 (OSPF for IPv4) configuration on VyOS via HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- dictionary -
-
- -
OSPFv2 configuration.
-
-
- areas - -
- list - / elements=dictionary -
-
- -
-
- area_id - -
- string -
-
- -
-
- area_type - -
- dictionary -
-
- -
-
- normal - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- nssa - -
- dictionary -
-
- -
-
- default_cost - -
- integer -
-
- -
-
- no_summary - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- set - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- translate - -
- string -
-
-
    Choices: -
  • always
  • -
  • candidate
  • -
  • never
  • -
-
-
-
- stub - -
- dictionary -
-
- -
-
- default_cost - -
- integer -
-
- -
-
- no_summary - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- set - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- authentication - -
- string -
-
-
    Choices: -
  • plaintext-password
  • -
  • md5
  • -
-
-
-
- network - -
- list - / elements=dictionary -
-
- -
-
- address - -
- string -
-
- -
-
- range - -
- list - / elements=dictionary -
-
- -
-
- address - -
- string -
-
- -
-
- cost - -
- integer -
-
- -
-
- not_advertise - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- substitute - -
- string -
-
- -
-
- shortcut - -
- string -
-
-
    Choices: -
  • default
  • -
  • disable
  • -
  • enable
  • -
-
-
-
- auto_cost - -
- dictionary -
-
- -
-
- reference_bandwidth - -
- integer -
-
- -
-
- default_information - -
- dictionary -
-
- -
-
- originate - -
- dictionary -
-
- -
-
- always - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- metric - -
- integer -
-
- -
-
- metric_type - -
- integer -
-
- -
-
- route_map - -
- string -
-
- -
-
- log_adjacency_changes - -
- string -
-
-
    Choices: -
  • detail
  • -
-
-
-
- max_metric - -
- dictionary -
-
- -
-
- router_lsa - -
- dictionary -
-
- -
-
- administrative - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- on_shutdown - -
- integer -
-
- -
-
- on_startup - -
- integer -
-
- -
-
- mpls_te - -
- dictionary -
-
- -
-
- enabled - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- router_address - -
- string -
-
- -
-
- neighbor - -
- list - / elements=dictionary -
-
- -
-
- neighbor_id - -
- string -
-
- -
-
- poll_interval - -
- integer -
-
- -
-
- priority - -
- integer -
-
- -
-
- parameters - -
- dictionary -
-
- -
-
- abr_type - -
- string -
-
-
    Choices: -
  • cisco
  • -
  • ibm
  • -
  • shortcut
  • -
  • standard
  • -
-
-
-
- opaque_lsa - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- rfc1583_compatibility - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- router_id - -
- string -
-
- -
-
- passive_interface - -
- list - / elements=string -
-
- -
-
- redistribute - -
- list - / elements=dictionary -
-
- -
-
- metric - -
- integer -
-
- -
-
- metric_type - -
- integer -
-
- -
-
- route_map - -
- string -
-
- -
-
- route_type - -
- string -
-
-
    Choices: -
  • bgp
  • -
  • connected
  • -
  • kernel
  • -
  • rip
  • -
  • static
  • -
-
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • deleted
  • -
  • gathered
  • -
-
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- dictionary -
-
when changed -
-
-
- before - -
- dictionary -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_ospfv3_module.rst b/docs/vyos.rest.vyos_ospfv3_module.rst deleted file mode 100644 index 4b78c59..0000000 --- a/docs/vyos.rest.vyos_ospfv3_module.rst +++ /dev/null @@ -1,490 +0,0 @@ -.. _vyos.rest.vyos_ospfv3_module: - - -********************* -vyos.rest.vyos_ospfv3 -********************* - -**OSPFv3 resource module via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages OSPFv3 (OSPF for IPv6) configuration on VyOS via HTTPS REST API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- config - -
- dictionary -
-
- -
OSPFv3 configuration.
-
-
- areas - -
- list - / elements=dictionary -
-
- -
-
- area_id - -
- string -
-
- -
-
- export_list - -
- string -
-
- -
-
- import_list - -
- string -
-
- -
-
- interface - -
- list - / elements=dictionary -
-
- -
Interfaces in this area.
-
-
- name - -
- string -
-
- -
-
- range - -
- list - / elements=dictionary -
-
- -
-
- address - -
- string -
-
- -
-
- advertise - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- not_advertise - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes
  • -
-
-
-
- parameters - -
- dictionary -
-
- -
-
- router_id - -
- string -
-
- -
-
- redistribute - -
- list - / elements=dictionary -
-
- -
-
- route_map - -
- string -
-
- -
-
- route_type - -
- string -
-
-
    Choices: -
  • bgp
  • -
  • connected
  • -
  • kernel
  • -
  • ripng
  • -
  • static
  • -
-
-
-
- hostname - -
- string - / required -
-
- -
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • deleted
  • -
  • gathered
  • -
-
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- dictionary -
-
when changed -
-
-
- before - -
- dictionary -
-
always -
-
-
- commands - -
- list -
-
always -
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_ping_module.rst b/docs/vyos.rest.vyos_ping_module.rst deleted file mode 100644 index 315a395..0000000 --- a/docs/vyos.rest.vyos_ping_module.rst +++ /dev/null @@ -1,332 +0,0 @@ -.. _vyos.rest.vyos_ping_module: - - -******************* -vyos.rest.vyos_ping -******************* - -**Test reachability using ping via VyOS REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Sends an ICMP ping via the VyOS HTTPS REST API (/show endpoint) and checks reachability. Mirrors ``vyos.vyos.vyos_ping``. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- count - -
- integer -
-
- Default:
5
-
-
Number of packets to send.
-
-
- dest - -
- string - / required -
-
- -
Destination IP address or hostname.
-
-
- hostname - -
- string - / required -
-
- -
-
- interval - -
- integer -
-
- -
Interval between pings in seconds.
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- size - -
- integer -
-
- -
Packet size in bytes.
-
-
- source - -
- string -
-
- -
Source interface or IP address.
-
-
- state - -
- string -
-
-
    Choices: -
  • present ←
  • -
  • absent
  • -
-
-
present = expect success; absent = expect failure.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- ttl - -
- integer -
-
- -
Time-to-live value.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- commands - -
- list -
-
always -
Ping command tokens sent to the API.
-
-
-
- packet_loss - -
- string -
-
always -
Packet loss percentage string.
-
-
-
- packets_rx - -
- integer -
-
always -
Packets received.
-
-
-
- packets_tx - -
- integer -
-
always -
Packets transmitted.
-
-
-
- rtt - -
- dictionary -
-
when available -
RTT statistics dict (min/avg/max/mdev).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_prefix_lists_module.rst b/docs/vyos.rest.vyos_prefix_lists_module.rst deleted file mode 100644 index 53321c9..0000000 --- a/docs/vyos.rest.vyos_prefix_lists_module.rst +++ /dev/null @@ -1,454 +0,0 @@ -.. _vyos.rest.vyos_prefix_lists_module: - - -*************************** -vyos.rest.vyos_prefix_lists -*************************** - -**Manage prefix-list configuration on VyOS devices using REST API** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages IPv4 and IPv6 prefix lists on VyOS via the REST API. -- Uses REST API (``connection=httpapi``) instead of CLI. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- config - -
- list - / elements=dictionary -
-
- -
List of prefix-list configurations grouped by address family.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family identifier.
-
-
- prefix_lists - -
- list - / elements=dictionary -
-
- -
Named prefix lists.
-
-
- description - -
- string -
-
- -
Prefix list description.
-
-
- entries - -
- list - / elements=dictionary -
-
- -
Prefix list rules.
-
-
- action - -
- string -
-
-
    Choices: -
  • permit
  • -
  • deny
  • -
-
-
Permit or deny.
-
-
- description - -
- string -
-
- -
Rule description.
-
-
- ge - -
- integer -
-
- -
Minimum prefix length.
-
-
- le - -
- integer -
-
- -
Maximum prefix length.
-
-
- prefix - -
- string -
-
- -
Network prefix to match.
-
-
- sequence - -
- integer - / required -
-
- -
Rule sequence number.
-
-
- name - -
- string - / required -
-
- -
Prefix list name.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
Desired state of the prefix-list configuration.
-
merged adds or updates entries without removing existing ones.
-
replaced replaces each named prefix list mentioned in config.
-
overridden replaces all prefix lists for the given AFIs.
-
deleted removes prefix lists. Without config removes all.
-
gathered returns current configuration as structured data.
-
-
- - -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 prefix list configuration - 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 - entries: - - sequence: 5 - action: permit - prefix: 2001:db8:8000::/35 - le: 37 - state: merged - - - name: Delete all prefix lists - vyos.rest.vyos_prefix_lists: - state: deleted - - - name: Gather current prefix list configuration - vyos.rest.vyos_prefix_lists: - state: gathered - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
Prefix list configuration after this module ran.
-
-
-
- before - -
- list -
-
always -
Prefix list configuration before this module ran.
-
-
-
- commands - -
- list -
-
always -
List of API command tuples sent to the device.
-
-
-
- gathered - -
- list -
-
when state is gathered -
Current prefix list configuration as structured data.
-
-
-
- response - -
- dictionary -
-
when changes are applied -
Raw API response.
-
-
-
- saved - -
- boolean -
-
when changes are applied -
Whether the config was saved after changes.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_reset_module.rst b/docs/vyos.rest.vyos_reset_module.rst deleted file mode 100644 index 46d4dee..0000000 --- a/docs/vyos.rest.vyos_reset_module.rst +++ /dev/null @@ -1,191 +0,0 @@ -.. _vyos.rest.vyos_reset_module: - - -******************** -vyos.rest.vyos_reset -******************** - -**Execute reset commands on a VyOS device via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Sends a ``reset`` operational command to a VyOS device via the HTTPS REST API (``/reset`` endpoint). -- Useful for resetting BGP sessions, VPN tunnels, ARP caches, etc. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- path - -
- list - / elements=string - / required -
-
- -
Reset command path tokens.
-
E.g. ["ip", "bgp", "192.0.2.1"] to reset a BGP peer.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- output - -
- string -
-
success -
Text output from the reset command (usually empty on success).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_show_module.rst b/docs/vyos.rest.vyos_show_module.rst deleted file mode 100644 index 29d7ee1..0000000 --- a/docs/vyos.rest.vyos_show_module.rst +++ /dev/null @@ -1,210 +0,0 @@ -.. _vyos.rest.vyos_show_module: - - -******************* -vyos.rest.vyos_show -******************* - -**Execute op-mode show commands on a VyOS device via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Sends a ``show`` operational-mode command to a VyOS device via the HTTPS REST API (``/show`` endpoint) and returns the text output. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- path - -
- list - / elements=string - / required -
-
- -
Operational-mode command tokens following show.
-
E.g. ["interfaces"] maps to show interfaces.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - -Notes ------ - -.. note:: - - This module never modifies device state. - - -See Also --------- - -.. seealso:: - - :ref:`vyos.rest.vyos_configure_module` - The official documentation on the **vyos.rest.vyos_configure** module. - :ref:`vyos.rest.vyos_retrieve_module` - The official documentation on the **vyos.rest.vyos_retrieve** module. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- output - -
- string -
-
success -
Raw text output from the show command.
-
-
Sample:
-
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down - Interface IP Address S/L Description - eth0 10.0.0.1/24 u/u WAN
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_static_routes_module.rst b/docs/vyos.rest.vyos_static_routes_module.rst deleted file mode 100644 index f5cf5ee..0000000 --- a/docs/vyos.rest.vyos_static_routes_module.rst +++ /dev/null @@ -1,505 +0,0 @@ -.. _vyos.rest.vyos_static_routes_module: - - -**************************** -vyos.rest.vyos_static_routes -**************************** - -**Manage static routes on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages IPv4 and IPv6 static routes on VyOS devices using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_static_routes`` but uses the HTTP API. - - - -Requirements ------------- -The below requirements are needed on the host that executes this module. - -- VyOS 1.3+ - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
API key configured on the device.
-
-
- config - -
- list - / elements=dictionary -
-
- -
List of static route address-family configurations.
-
-
- address_families - -
- list - / elements=dictionary -
-
- -
List of address family route groups.
-
-
- afi - -
- string - / required -
-
-
    Choices: -
  • ipv4
  • -
  • ipv6
  • -
-
-
Address family indicator.
-
-
- routes - -
- list - / elements=dictionary -
-
- -
List of static route entries.
-
-
- blackhole_config - -
- dictionary -
-
- -
Blackhole route configuration.
-
-
- distance - -
- integer -
-
- -
Administrative distance (1-255).
-
-
- type - -
- string -
-
- -
Blackhole type.
-
-
- dest - -
- string - / required -
-
- -
Destination prefix in CIDR notation.
-
-
- next_hops - -
- list - / elements=dictionary -
-
- -
List of next-hop addresses.
-
-
- admin_distance - -
- integer -
-
- -
Administrative distance for this next-hop.
-
-
- enabled - -
- boolean -
-
-
    Choices: -
  • no
  • -
  • yes ←
  • -
-
-
Whether this next-hop is enabled.
-
-
- forward_router_address - -
- string - / required -
-
- -
Next-hop IP address.
-
-
- interface - -
- string -
-
- -
Outgoing interface name.
-
-
- hostname - -
- string - / required -
-
- -
IP address or FQDN of the VyOS device.
-
-
- port - -
- integer -
-
- Default:
443
-
-
HTTPS port for the REST API.
-
-
- state - -
- string -
-
-
    Choices: -
  • merged ←
  • -
  • replaced
  • -
  • overridden
  • -
  • deleted
  • -
  • gathered
  • -
-
-
{'merged': 'Add routes (preserve existing).'}
-
{'replaced': 'Replace routes for listed destinations.'}
-
{'overridden': 'Replace the entire static route table.'}
-
{'deleted': 'Remove listed (or all) static routes.'}
-
{'gathered': 'Read static routes from device.'}
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
Request timeout in seconds.
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Validate the device's TLS certificate.
-
-
- - - -See Also --------- - -.. seealso:: - - :ref:`vyos.vyos.vyos_static_routes_module` - The official documentation on the **vyos.vyos.vyos_static_routes** module. - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyReturnedDescription
-
- after - -
- list -
-
when changed -
Static route config after the module ran.
-
-
-
- before - -
- list -
-
always -
Static route config before the module ran.
-
-
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-
- gathered - -
- list -
-
when state is gathered -
Static routes read from device (state=gathered).
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_system_module.rst b/docs/vyos.rest.vyos_system_module.rst deleted file mode 100644 index 7b70e41..0000000 --- a/docs/vyos.rest.vyos_system_module.rst +++ /dev/null @@ -1,244 +0,0 @@ -.. _vyos.rest.vyos_system_module: - - -********************* -vyos.rest.vyos_system -********************* - -**Manage system settings on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages system-level settings — hostname, domain name, name servers, domain search list — using the VyOS HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_system`` but uses the HTTP API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- api_key - -
- string - / required -
-
- -
-
- domain_name - -
- string -
-
- -
System domain name.
-
-
- domain_search - -
- list - / elements=string -
-
- -
List of domain search suffixes. Mutually exclusive with name_server.
-
-
- host_name - -
- string -
-
- -
System hostname.
-
-
- hostname - -
- string - / required -
-
- -
-
- name_server - -
- list - / elements=string -
-
- -
List of DNS name servers. Mutually exclusive with domain_search.
-

aliases: name_servers
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- state - -
- string -
-
-
    Choices: -
  • present ←
  • -
  • absent
  • -
-
-
present to apply, absent to remove.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_user_module.rst b/docs/vyos.rest.vyos_user_module.rst deleted file mode 100644 index 119edf0..0000000 --- a/docs/vyos.rest.vyos_user_module.rst +++ /dev/null @@ -1,564 +0,0 @@ -.. _vyos.rest.vyos_user_module: - - -******************* -vyos.rest.vyos_user -******************* - -**Manage local users on VyOS via the REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages local user accounts on VyOS devices using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_user`` but uses the HTTP API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- aggregate - -
- list - / elements=dictionary -
-
- -
List of user definitions. Mutually exclusive with name.
-

aliases: users, collection
-
-
- configured_password - -
- string -
-
- -
Plaintext password (will be hashed on device).
-
-
- encrypted_password - -
- string -
-
- -
Pre-hashed password string.
-
-
- full_name - -
- string -
-
- -
Full name.
-
-
- level - -
- string -
-
-
    Choices: -
  • admin
  • -
  • operator
  • -
-
-
User privilege level.
-
-
- name - -
- string - / required -
-
- -
Username.
-
-
- public_keys - -
- list - / elements=dictionary -
-
- -
SSH public keys for authentication.
-
-
- key - -
- string - / required -
-
- -
Base64-encoded public key.
-
-
- name - -
- string - / required -
-
- -
Key identifier (e.g. user@host).
-
-
- type - -
- string - / required -
-
-
    Choices: -
  • ssh-dss
  • -
  • ssh-rsa
  • -
  • ecdsa-sha2-nistp256
  • -
  • ecdsa-sha2-nistp384
  • -
  • ssh-ed25519
  • -
  • ecdsa-sha2-nistp521
  • -
-
-
Key type.
-
-
- state - -
- string -
-
-
    Choices: -
  • present
  • -
  • absent
  • -
-
-
Present or absent.
-
-
- update_password - -
- string -
-
-
    Choices: -
  • always
  • -
  • on_create
  • -
-
-
When to update the password.
-
-
- api_key - -
- string - / required -
-
- -
-
- configured_password - -
- string -
-
- -
Plaintext password.
-
-
- encrypted_password - -
- string -
-
- -
Pre-hashed password.
-
-
- full_name - -
- string -
-
- -
Full name for the single user.
-
-
- hostname - -
- string - / required -
-
- -
-
- level - -
- string -
-
-
    Choices: -
  • admin
  • -
  • operator
  • -
-
-
-
- name - -
- string -
-
- -
Single username. Mutually exclusive with aggregate.
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- public_keys - -
- list - / elements=dictionary -
-
- -
-
- key - -
- string - / required -
-
- -
-
- name - -
- string - / required -
-
- -
-
- type - -
- string - / required -
-
- -
-
- state - -
- string -
-
-
    Choices: -
  • present ←
  • -
  • absent
  • -
-
-
Whether to create (present) or remove (absent) the user.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- update_password - -
- string -
-
-
    Choices: -
  • always
  • -
  • on_create
  • -
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/docs/vyos.rest.vyos_vlan_module.rst b/docs/vyos.rest.vyos_vlan_module.rst deleted file mode 100644 index db45fd8..0000000 --- a/docs/vyos.rest.vyos_vlan_module.rst +++ /dev/null @@ -1,360 +0,0 @@ -.. _vyos.rest.vyos_vlan_module: - - -******************* -vyos.rest.vyos_vlan -******************* - -**Manage VLANs on VyOS network devices via REST API.** - - -Version added: 1.0.0 - -.. contents:: - :local: - :depth: 1 - - -Synopsis --------- -- Manages 802.1Q VLAN sub-interfaces on VyOS Ethernet interfaces using the HTTPS REST API. -- Mirrors ``vyos.vyos.vyos_vlan`` but uses the HTTP API. - - - - -Parameters ----------- - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterChoices/DefaultsComments
-
- address - -
- string -
-
- -
IP address for the VLAN interface.
-
-
- aggregate - -
- list - / elements=dictionary -
-
- -
List of VLAN definitions.
-
-
- address - -
- string -
-
- -
-
- interfaces - -
- list - / elements=string - / required -
-
- -
-
- name - -
- string -
-
- -
-
- state - -
- string -
-
-
    Choices: -
  • present
  • -
  • absent
  • -
-
-
-
- vlan_id - -
- integer - / required -
-
- -
-
- api_key - -
- string - / required -
-
- -
-
- hostname - -
- string - / required -
-
- -
-
- interfaces - -
- list - / elements=string -
-
- -
List of physical interfaces to configure the VLAN on.
-
-
- name - -
- string -
-
- -
VLAN name (used as description on the VIF).
-
-
- port - -
- integer -
-
- Default:
443
-
-
-
- purge - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
Remove VLANs not defined in aggregate.
-
-
- state - -
- string -
-
-
    Choices: -
  • present ←
  • -
  • absent
  • -
-
-
present to configure, absent to remove.
-
-
- timeout - -
- integer -
-
- Default:
30
-
-
-
- verify_ssl - -
- boolean -
-
-
    Choices: -
  • no ←
  • -
  • yes
  • -
-
-
-
- vlan_id - -
- integer -
-
- -
VLAN ID (0-4094).
-
-
- - - - - - - -Return Values -------------- -Common return values are documented `here `_, the following are the fields unique to this module: - -.. raw:: html - - - - - - - - - - - - -
KeyReturnedDescription
-
- commands - -
- list -
-
always -
set/delete commands issued.
-
-
-

- - -Status ------- - - -Authors -~~~~~~~ - -- VyOS Community (@vyos) diff --git a/plugins/httpapi/vyos.py b/plugins/httpapi/vyos.py index 1719254..6dd6042 100644 --- a/plugins/httpapi/vyos.py +++ b/plugins/httpapi/vyos.py @@ -105,11 +105,15 @@ class HttpApi(HttpApiBase): Raises: ConnectionError: on HTTP error or VyOS success=false response. """ + try: api_key = self._get_api_key() - body = json.dumps(payload) - form_data = urlencode({"data": body, "key": api_key}) + if "_raw_list" in payload: + body = json.dumps(payload["_raw_list"]) + else: + body = json.dumps(payload) + form_data = urlencode({"data": body, "key": api_key}) response, response_data = self.connection.send( endpoint, data=form_data, diff --git a/plugins/module_utils/vyos.py b/plugins/module_utils/vyos.py index 87fc37b..82670e7 100644 --- a/plugins/module_utils/vyos.py +++ b/plugins/module_utils/vyos.py @@ -62,58 +62,75 @@ class VyOSModule: # Write # ------------------------------------------------------------------ - def apply_commands(self, commands): - """Execute a list of ``(op, path)`` command tuples against the device. - - Args: - commands (list): Each item is a 2-tuple ``(op, path)`` where: - - *op* is ``"set"`` or ``"delete"`` - - *path* is a list of config path tokens - - Optionally a 3-tuple ``(op, path, value)`` for leaf nodes - that carry a value. - - Returns: - list: Results from each command (dicts with ``op`` and ``path``). - - Raises: - VyOSRestError: If a command fails and cannot be recovered. - - Notes: - **Version-adaptive set:** if a ``set`` is rejected by the device, - the method retries once with the last path segment removed. - This handles schema simplifications between VyOS releases - (e.g. ``allow-client address `` -> ``allow-client `` - in VyOS 1.5+). - - **Idempotent delete:** ``delete`` failures are silently ignored - (the path is already absent). - """ - results = [] + # def apply_commands(self, commands): + # """Execute a list of ``(op, path)`` command tuples against the device. + + # Args: + # commands (list): Each item is a 2-tuple ``(op, path)`` where: + # - *op* is ``"set"`` or ``"delete"`` + # - *path* is a list of config path tokens + + # Optionally a 3-tuple ``(op, path, value)`` for leaf nodes + # that carry a value. + + # Returns: + # list: Results from each command (dicts with ``op`` and ``path``). + + # Raises: + # VyOSRestError: If a command fails and cannot be recovered. + + # Notes: + # **Version-adaptive set:** if a ``set`` is rejected by the device, + # the method retries once with the last path segment removed. + # This handles schema simplifications between VyOS releases + # (e.g. ``allow-client address `` -> ``allow-client `` + # in VyOS 1.5+). + + # **Idempotent delete:** ``delete`` failures are silently ignored + # (the path is already absent). + # """ + # results = [] + + # for cmd in commands: + # # Support two command formats: + # # Tuple: ("set", ["path", "tokens"]) or ("set", ["path"], "value") + # # Dict: {"op": "set", "path": ["path", "tokens"]} + # if isinstance(cmd, dict): + # op = cmd["op"] + # path = list(cmd["path"]) + # value = cmd.get("value") + # else: + # op = cmd[0] + # path = list(cmd[1]) + # value = cmd[2] if len(cmd) > 2 else None + + # if op == "set": + # results.append(self._apply_set(path, value)) + # elif op == "delete": + # results.append(self._apply_delete(path)) + # else: + # self._module.fail_json( + # msg="Unknown command op '{op}' in apply_commands".format(op=op), + # ) + + # return results + def apply_commands(self, commands): + if not commands: + return [] + payload = [] for cmd in commands: - # Support two command formats: - # Tuple: ("set", ["path", "tokens"]) or ("set", ["path"], "value") - # Dict: {"op": "set", "path": ["path", "tokens"]} if isinstance(cmd, dict): - op = cmd["op"] - path = list(cmd["path"]) - value = cmd.get("value") - else: - op = cmd[0] - path = list(cmd[1]) - value = cmd[2] if len(cmd) > 2 else None - - if op == "set": - results.append(self._apply_set(path, value)) - elif op == "delete": - results.append(self._apply_delete(path)) + op, path = cmd["op"], list(cmd["path"]) else: - self._module.fail_json( - msg="Unknown command op '{op}' in apply_commands".format(op=op), - ) - - return results + op, path = cmd[0], list(cmd[1]) + payload.append({"op": op, "path": path}) + try: + return self._client.configure_batch(payload) + except VyOSRestError as exc: + self._module.fail_json( + msg="apply_commands failed: {e}".format(e=str(exc)), + ) def _apply_set(self, path, value=None): """Set a config path, retrying with a shortened path on failure.""" diff --git a/plugins/module_utils/vyos_rest.py b/plugins/module_utils/vyos_rest.py index 95b3aac..aaf2b23 100644 --- a/plugins/module_utils/vyos_rest.py +++ b/plugins/module_utils/vyos_rest.py @@ -1,36 +1,5 @@ """ VyOS REST API client utility — supports both connection modes. - -**httpapi mode (recommended)**:: - - # inventory - vyos15x ansible_host=192.168.122.194 - ansible_network_os=vyos.rest.vyos - ansible_connection=ansible.netcommon.httpapi - ansible_httpapi_use_ssl=true - ansible_httpapi_port=443 - ansible_httpapi_validate_certs=false - ansible_httpapi_api_key=vyos123 - - # playbook — no connection params needed on any task - - vyos.rest.vyos_banner: - banner: pre-login - text: "Authorised use only." - state: present - -**local mode (alternative, no inventory network_os required)**:: - - - vyos.rest.vyos_banner: - hostname: 192.168.122.194 - api_key: vyos123 - verify_ssl: false - banner: pre-login - text: "Authorised use only." - state: present - -Both modes call the same VyOS endpoints. The httpapi mode routes calls -through the persistent connection daemon (better for large inventories); -the local mode opens a new HTTPS connection per task. """ from __future__ import absolute_import, division, print_function @@ -45,7 +14,7 @@ import os try: from urllib.parse import urlencode except ImportError: - from urllib import urlencode # Python 2 + from urllib import urlencode try: import urllib3 @@ -218,6 +187,21 @@ class VyOSRestClient: def image_delete(self, name): return self._post("/image", {"op": "delete", "name": name}) + def configure_batch(self, commands): + """Send all commands as a single atomic commit.""" + if self._mode == "httpapi": + try: + return self._conn.send_request( + endpoint="/configure", + _raw_list=commands, + ) + except ConnectionError as exc: + raise VyOSRestError(str(exc)) + except Exception as exc: + raise VyOSRestError( + "configure_batch failed: {e}".format(e=str(exc)), + ) + def config_file_save(self, path=None): payload = {"op": "save"} if path: diff --git a/plugins/modules/vyos_ntp_global.py b/plugins/modules/vyos_ntp_global.py index 185dc02..88073e1 100644 --- a/plugins/modules/vyos_ntp_global.py +++ b/plugins/modules/vyos_ntp_global.py @@ -218,13 +218,12 @@ def build_commands(desired, existing, state): cmds = [] if state == "overridden": - if existing["allow_clients"]: - cmds.append(("delete", ["service", "ntp", "allow-client"])) - if existing["listen_addresses"]: - cmds.append(("delete", ["service", "ntp", "listen-address"])) - if existing["servers"]: - cmds.append(("delete", ["service", "ntp", "server"])) - state = "merged" + state = "replaced" + + if state == "deleted": + if existing["servers"] or existing["allow_clients"] or existing["listen_addresses"]: + cmds.append(("delete", ["service", "ntp"])) + return cmds cmds += diff_list( "allow-client", diff --git a/tests/integration/targets/vyos_banner/defaults/main.yaml b/tests/integration/targets/vyos_banner/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_banner/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_banner/tasks/httpapi.yaml b/tests/integration/targets/vyos_banner/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_banner/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_banner/tasks/main.yaml b/tests/integration/targets/vyos_banner/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_banner/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_banner/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml new file mode 100644 index 0000000..2557b70 --- /dev/null +++ b/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml @@ -0,0 +1,11 @@ +--- +- name: Populate banner config for testing + vyos.rest.vyos_banner: + config: + banners: + - banner: pre-login + text: "Ansible test pre-login banner" + - banner: post-login + text: "Ansible test post-login banner" + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..cc56d27 --- /dev/null +++ b/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing banner config + vyos.rest.vyos_banner: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..5d0db34 --- /dev/null +++ b/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_banner deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all banner configuration + register: result + vyos.rest.vyos_banner: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete banner configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_banner: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..a51ff48 --- /dev/null +++ b/tests/integration/targets/vyos_banner/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_banner gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather banner configuration + register: result + vyos.rest.vyos_banner: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered is defined + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml new file mode 100644 index 0000000..027702a --- /dev/null +++ b/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml @@ -0,0 +1,32 @@ +--- +- debug: + msg: START vyos_banner merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge banner configuration + register: result + vyos.rest.vyos_banner: &id001 + config: + banners: + - banner: pre-login + text: "Ansible test pre-login banner" + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge banner configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_banner: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_banner/vars/main.yaml b/tests/integration/targets/vyos_banner/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_banner/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_hostname/defaults/main.yaml b/tests/integration/targets/vyos_hostname/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_hostname/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_hostname/tasks/httpapi.yaml b/tests/integration/targets/vyos_hostname/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_hostname/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_hostname/tasks/main.yaml b/tests/integration/targets/vyos_hostname/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/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_hostname/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/_populate_config.yaml new file mode 100644 index 0000000..c6d2583 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/httpapi/_populate_config.yaml @@ -0,0 +1,6 @@ +--- +- name: Populate hostname for testing + vyos.rest.vyos_hostname: + config: + hostname: ansible-test-host + state: merged diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..a6f7aeb --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/httpapi/_remove_config.yaml @@ -0,0 +1,7 @@ +--- +- name: Reset hostname to default + vyos.rest.vyos_hostname: + config: + hostname: vyos + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..dfc8896 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/httpapi/gathered.yaml @@ -0,0 +1,19 @@ +--- +- debug: + msg: START vyos_hostname gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Gather hostname configuration + register: result + vyos.rest.vyos_hostname: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered.hostname == "ansible-test-host" + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/merged.yaml new file mode 100644 index 0000000..d815d53 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/httpapi/merged.yaml @@ -0,0 +1,30 @@ +--- +- debug: + msg: START vyos_hostname merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge hostname configuration + register: result + vyos.rest.vyos_hostname: &id001 + config: + hostname: ansible-test-host + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge hostname configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_hostname: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_hostname/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..b4e318d --- /dev/null +++ b/tests/integration/targets/vyos_hostname/tests/httpapi/replaced.yaml @@ -0,0 +1,30 @@ +--- +- debug: + msg: START vyos_hostname replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _populate_config.yaml + +- block: + - name: Replace hostname configuration + register: result + vyos.rest.vyos_hostname: &id001 + config: + hostname: ansible-replaced-host + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace hostname configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_hostname: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_hostname/vars/main.yaml b/tests/integration/targets/vyos_hostname/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_hostname/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_lldp_global/defaults/main.yaml b/tests/integration/targets/vyos_lldp_global/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_lldp_global/tasks/httpapi.yaml b/tests/integration/targets/vyos_lldp_global/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/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_global/tasks/main.yaml b/tests/integration/targets/vyos_lldp_global/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/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_global/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml new file mode 100644 index 0000000..3bf4fb0 --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml @@ -0,0 +1,13 @@ +--- +- name: Populate lldp_global config for testing + vyos.rest.vyos_lldp_global: + config: + enable: true + addresses: + - 192.0.2.17 + snmp: enable + legacy_protocols: + - cdp + - sonmp + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..9e3b01d --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing lldp_global config + vyos.rest.vyos_lldp_global: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..d084d4a --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_lldp_global deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all lldp_global configuration + register: result + vyos.rest.vyos_lldp_global: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete lldp_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_lldp_global: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..20ac2b2 --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_lldp_global gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather lldp_global configuration + register: result + vyos.rest.vyos_lldp_global: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered is defined + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/merged.yaml new file mode 100644 index 0000000..a4c9bec --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/merged.yaml @@ -0,0 +1,36 @@ +--- +- debug: + msg: START vyos_lldp_global merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge lldp_global configuration + register: result + vyos.rest.vyos_lldp_global: &id001 + config: + enable: true + addresses: + - 192.0.2.17 + snmp: enable + legacy_protocols: + - cdp + - sonmp + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge lldp_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_lldp_global: *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_global/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..d5523eb --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/replaced.yaml @@ -0,0 +1,35 @@ +--- +- debug: + msg: START vyos_lldp_global replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace lldp_global configuration + register: result + vyos.rest.vyos_lldp_global: &id001 + config: + enable: true + addresses: + - 192.0.2.99 + legacy_protocols: + - cdp + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace lldp_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_lldp_global: *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_global/vars/main.yaml b/tests/integration/targets/vyos_lldp_global/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_lldp_global/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_logging_global/defaults/main.yaml b/tests/integration/targets/vyos_logging_global/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_logging_global/tasks/httpapi.yaml b/tests/integration/targets/vyos_logging_global/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/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_logging_global/tasks/main.yaml b/tests/integration/targets/vyos_logging_global/tasks/main.yaml index f5a1f31..b1f6193 100644 --- a/tests/integration/targets/vyos_logging_global/tasks/main.yaml +++ b/tests/integration/targets/vyos_logging_global/tasks/main.yaml @@ -1,133 +1,5 @@ --- -# tests/integration/targets/vyos_logging_global/tasks/main.yaml - -- name: TEARDOWN — delete all syslog config before tests - vyos.rest.vyos_logging_global: - state: deleted - -# ------------------------------------------------------------------ -# MERGED -# ------------------------------------------------------------------ - -- name: MERGED — set initial logging config - register: result - vyos.rest.vyos_logging_global: - config: - console: - facilities: - - facility: local7 - severity: err - hosts: - - hostname: 172.16.0.1 - port: 514 - facilities: - - facility: local7 - severity: all - global_params: - facilities: - - facility: cron - severity: debug - preserve_fqdn: true - state: merged - -- name: ASSERT — merged changed - assert: - that: - - result.changed == true - -- name: MERGED — idempotency check - register: result - vyos.rest.vyos_logging_global: - config: - console: - facilities: - - facility: local7 - severity: err - hosts: - - hostname: 172.16.0.1 - port: 514 - facilities: - - facility: local7 - severity: all - global_params: - facilities: - - facility: cron - severity: debug - preserve_fqdn: true - state: merged - -- name: ASSERT — merged idempotent - assert: - that: - - result.changed == false - - result.commands | length == 0 - -# ------------------------------------------------------------------ -# GATHERED -# ------------------------------------------------------------------ - -- name: GATHERED — read current config - register: result - vyos.rest.vyos_logging_global: - state: gathered - -- name: ASSERT — gathered returns expected data - assert: - that: - - result.gathered is defined - -# ------------------------------------------------------------------ -# REPLACED -# ------------------------------------------------------------------ - -- name: REPLACED — replace with minimal config - register: result - vyos.rest.vyos_logging_global: - config: - console: - facilities: - - facility: local6 - state: replaced - -- name: ASSERT — replaced changed - assert: - that: - - result.changed == true - -- name: REPLACED — idempotency check - register: result - vyos.rest.vyos_logging_global: - config: - console: - facilities: - - facility: local6 - state: replaced - -- name: ASSERT — replaced idempotent - assert: - that: - - result.changed == false - -# ------------------------------------------------------------------ -# DELETED -# ------------------------------------------------------------------ - -- name: DELETED — remove all syslog config - register: result - vyos.rest.vyos_logging_global: - state: deleted - -- name: ASSERT — deleted changed - assert: - that: - - result.changed == true - -- name: DELETED — idempotency check - register: result - vyos.rest.vyos_logging_global: - state: deleted - -- name: ASSERT — deleted idempotent - assert: - that: - - result.changed == false +- name: Run httpapi tests + ansible.builtin.include_tasks: httpapi.yaml + tags: + - httpapi 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 new file mode 100644 index 0000000..c081853 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/_populate_config.yaml @@ -0,0 +1,21 @@ +--- +- name: Populate logging_global config for testing + vyos.rest.vyos_logging_global: + config: + console: + facilities: + - facility: local7 + severity: err + hosts: + - hostname: 172.16.0.1 + port: 514 + facilities: + - facility: local7 + severity: all + global_params: + facilities: + - facility: cron + severity: debug + preserve_fqdn: true + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..14975a5 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing logging_global config + vyos.rest.vyos_logging_global: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..8c62627 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_logging_global deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all logging_global configuration + register: result + vyos.rest.vyos_logging_global: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete logging_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_logging_global: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..667c088 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_logging_global gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather logging_global configuration + register: result + vyos.rest.vyos_logging_global: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered is defined + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/merged.yaml new file mode 100644 index 0000000..daa1c2a --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/merged.yaml @@ -0,0 +1,44 @@ +--- +- debug: + msg: START vyos_logging_global merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge logging_global configuration + register: result + vyos.rest.vyos_logging_global: &id001 + config: + console: + facilities: + - facility: local7 + severity: err + hosts: + - hostname: 172.16.0.1 + port: 514 + facilities: + - facility: local7 + severity: all + global_params: + facilities: + - facility: cron + severity: debug + preserve_fqdn: true + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge logging_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_logging_global: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_logging_global/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_logging_global/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..a1c24c0 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/tests/httpapi/replaced.yaml @@ -0,0 +1,33 @@ +--- +- debug: + msg: START vyos_logging_global replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace logging_global configuration + register: result + vyos.rest.vyos_logging_global: &id001 + config: + console: + facilities: + - facility: local6 + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace logging_global configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_logging_global: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_logging_global/vars/main.yaml b/tests/integration/targets/vyos_logging_global/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_logging_global/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_prefix_lists.old/aliases b/tests/integration/targets/vyos_prefix_lists.old/aliases new file mode 100644 index 0000000..cc0afef --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists.old/aliases @@ -0,0 +1 @@ +network/vyos diff --git a/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml b/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml new file mode 100644 index 0000000..e5c62cd --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists.old/tasks/main.yaml @@ -0,0 +1,144 @@ +--- +# tests/integration/targets/vyos_prefix_lists/tasks/main.yaml + +- name: TEARDOWN — delete all prefix lists before tests + vyos.rest.vyos_prefix_lists: + state: deleted + +# ------------------------------------------------------------------ +# MERGED +# ------------------------------------------------------------------ + +- name: MERGED — create prefix lists + register: result + 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 + +- name: ASSERT — merged changed + assert: + that: + - result.changed == true + +- name: MERGED — idempotency check + register: result + 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 + state: merged + +- name: ASSERT — merged idempotent + assert: + that: + - result.changed == false + - result.commands | length == 0 + +# ------------------------------------------------------------------ +# GATHERED +# ------------------------------------------------------------------ + +- name: GATHERED — read current prefix lists + register: result + vyos.rest.vyos_prefix_lists: + state: gathered + +- name: ASSERT — gathered has ipv4 entry + assert: + that: + - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 + +# ------------------------------------------------------------------ +# REPLACED +# ------------------------------------------------------------------ + +- name: REPLACED — replace AnsibleIPv4PrefixList + register: result + vyos.rest.vyos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AnsibleIPv4PrefixList + entries: + - sequence: 10 + action: permit + prefix: 10.0.0.0/8 + state: replaced + +- name: ASSERT — replaced changed + assert: + that: + - result.changed == true + +- name: REPLACED — idempotency check + register: result + vyos.rest.vyos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AnsibleIPv4PrefixList + entries: + - sequence: 10 + action: permit + prefix: 10.0.0.0/8 + state: replaced + +- name: ASSERT — replaced idempotent + assert: + that: + - result.changed == false + +# ------------------------------------------------------------------ +# DELETED +# ------------------------------------------------------------------ + +- name: DELETED — remove specific prefix list + register: result + vyos.rest.vyos_prefix_lists: + config: + - afi: ipv4 + prefix_lists: + - name: AnsibleIPv4PrefixList + state: deleted + +- name: ASSERT — deleted changed + assert: + that: + - result.changed == true + +- name: DELETED — remove all remaining + vyos.rest.vyos_prefix_lists: + state: deleted diff --git a/tests/integration/targets/vyos_prefix_lists/defaults/main.yaml b/tests/integration/targets/vyos_prefix_lists/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml b/tests/integration/targets/vyos_prefix_lists/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/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_prefix_lists/tasks/main.yaml b/tests/integration/targets/vyos_prefix_lists/tasks/main.yaml index e5c62cd..b1f6193 100644 --- a/tests/integration/targets/vyos_prefix_lists/tasks/main.yaml +++ b/tests/integration/targets/vyos_prefix_lists/tasks/main.yaml @@ -1,144 +1,5 @@ --- -# tests/integration/targets/vyos_prefix_lists/tasks/main.yaml - -- name: TEARDOWN — delete all prefix lists before tests - vyos.rest.vyos_prefix_lists: - state: deleted - -# ------------------------------------------------------------------ -# MERGED -# ------------------------------------------------------------------ - -- name: MERGED — create prefix lists - register: result - 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 - -- name: ASSERT — merged changed - assert: - that: - - result.changed == true - -- name: MERGED — idempotency check - register: result - 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 - state: merged - -- name: ASSERT — merged idempotent - assert: - that: - - result.changed == false - - result.commands | length == 0 - -# ------------------------------------------------------------------ -# GATHERED -# ------------------------------------------------------------------ - -- name: GATHERED — read current prefix lists - register: result - vyos.rest.vyos_prefix_lists: - state: gathered - -- name: ASSERT — gathered has ipv4 entry - assert: - that: - - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 - -# ------------------------------------------------------------------ -# REPLACED -# ------------------------------------------------------------------ - -- name: REPLACED — replace AnsibleIPv4PrefixList - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - entries: - - sequence: 10 - action: permit - prefix: 10.0.0.0/8 - state: replaced - -- name: ASSERT — replaced changed - assert: - that: - - result.changed == true - -- name: REPLACED — idempotency check - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - entries: - - sequence: 10 - action: permit - prefix: 10.0.0.0/8 - state: replaced - -- name: ASSERT — replaced idempotent - assert: - that: - - result.changed == false - -# ------------------------------------------------------------------ -# DELETED -# ------------------------------------------------------------------ - -- name: DELETED — remove specific prefix list - register: result - vyos.rest.vyos_prefix_lists: - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - state: deleted - -- name: ASSERT — deleted changed - assert: - that: - - result.changed == true - -- name: DELETED — remove all remaining - vyos.rest.vyos_prefix_lists: - state: deleted +- name: Run httpapi tests + ansible.builtin.include_tasks: httpapi.yaml + tags: + - httpapi 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 new file mode 100644 index 0000000..9d73f0b --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_populate_config.yaml @@ -0,0 +1,28 @@ +--- +- 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 new file mode 100644 index 0000000..043fb3e --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing prefix_lists config + vyos.rest.vyos_prefix_lists: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..6cfb063 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_prefix_lists deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..083cb48 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_prefix_lists gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml new file mode 100644 index 0000000..a70f826 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_prefix_lists merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + 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 + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *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/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..bd8c5c1 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_prefix_lists replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + config: + - afi: ipv4 + prefix_lists: + - name: AnsibleIPv4PrefixList + entries: + - sequence: 10 + action: permit + prefix: 10.0.0.0/8 + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *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_prefix_lists/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_route_maps/defaults/main.yaml b/tests/integration/targets/vyos_route_maps/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_route_maps/tasks/httpapi.yaml b/tests/integration/targets/vyos_route_maps/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/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_route_maps/tasks/main.yaml b/tests/integration/targets/vyos_route_maps/tasks/main.yaml index e8171dd..b1f6193 100644 --- a/tests/integration/targets/vyos_route_maps/tasks/main.yaml +++ b/tests/integration/targets/vyos_route_maps/tasks/main.yaml @@ -1,133 +1,5 @@ --- -# tests/integration/targets/vyos_route_maps/tasks/main.yaml - -- name: TEARDOWN — delete all route maps before tests - vyos.rest.vyos_route_maps: - state: deleted - -# ------------------------------------------------------------------ -# MERGED -# ------------------------------------------------------------------ - -- name: MERGED — create route map - register: result - 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" - as_path_exclude: "111" - aggregator: - as: 100 - state: merged - -- name: ASSERT — merged changed - assert: - that: - - result.changed == true - -- name: MERGED — idempotency check - register: result - 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" - as_path_exclude: "111" - aggregator: - as: 100 - state: merged - -- name: ASSERT — merged idempotent - assert: - that: - - result.changed == false - - result.commands | length == 0 - -# ------------------------------------------------------------------ -# GATHERED -# ------------------------------------------------------------------ - -- name: GATHERED — read current route maps - register: result - vyos.rest.vyos_route_maps: - state: gathered - -- name: ASSERT — gathered has expected route map - assert: - that: - - result.gathered | selectattr('route_map','eq','RM-TEST-EXPORT-POLICY') | list | length == 1 - -# ------------------------------------------------------------------ -# REPLACED -# ------------------------------------------------------------------ - -- name: REPLACED — replace route map with updated set - register: result - vyos.rest.vyos_route_maps: - config: - - route_map: RM-TEST-EXPORT-POLICY - entries: - - sequence: 10 - action: permit - set: - metric: "10" - state: replaced - -- name: ASSERT — replaced changed - assert: - that: - - result.changed == true - -- name: REPLACED — idempotency check - register: result - vyos.rest.vyos_route_maps: - config: - - route_map: RM-TEST-EXPORT-POLICY - entries: - - sequence: 10 - action: permit - set: - metric: "10" - state: replaced - -- name: ASSERT — replaced idempotent - assert: - that: - - result.changed == false - -# ------------------------------------------------------------------ -# DELETED -# ------------------------------------------------------------------ - -- name: DELETED — remove specific route map - register: result - vyos.rest.vyos_route_maps: - config: - - route_map: RM-TEST-EXPORT-POLICY - state: deleted - -- name: ASSERT — deleted changed - assert: - that: - - result.changed == true - -- name: DELETED — idempotency check - register: result - vyos.rest.vyos_route_maps: - state: deleted - -- name: ASSERT — deleted idempotent - assert: - that: - - result.changed == false +- name: Run httpapi tests + ansible.builtin.include_tasks: httpapi.yaml + tags: + - httpapi diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/_populate_config.yaml new file mode 100644 index 0000000..adcaf55 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/_populate_config.yaml @@ -0,0 +1,17 @@ +--- +- name: Populate route_maps config for testing + 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" + as_path_exclude: "111" + aggregator: + as: 100 + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..de02f54 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing route_maps config + vyos.rest.vyos_route_maps: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..ec80b23 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_route_maps deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all route_maps configuration + register: result + vyos.rest.vyos_route_maps: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete route_maps configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_route_maps: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..494a23e --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_route_maps gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather route_maps configuration + register: result + vyos.rest.vyos_route_maps: + state: gathered + + - assert: + that: + - result.changed == false + - result.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_route_maps/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/merged.yaml new file mode 100644 index 0000000..c8c9474 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/merged.yaml @@ -0,0 +1,40 @@ +--- +- debug: + msg: START vyos_route_maps merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge 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: "5" + as_path_exclude: "111" + aggregator: + as: 100 + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge route_maps configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_route_maps: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_route_maps/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_route_maps/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..dece9a8 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/tests/httpapi/replaced.yaml @@ -0,0 +1,36 @@ +--- +- debug: + msg: START vyos_route_maps replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace route_maps configuration + register: result + vyos.rest.vyos_route_maps: &id001 + config: + - route_map: RM-TEST-EXPORT-POLICY + entries: + - sequence: 10 + action: permit + set: + metric: "10" + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace route_maps configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_route_maps: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_route_maps/vars/main.yaml b/tests/integration/targets/vyos_route_maps/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_route_maps/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here diff --git a/tests/integration/targets/vyos_snmp_server/defaults/main.yaml b/tests/integration/targets/vyos_snmp_server/defaults/main.yaml new file mode 100644 index 0000000..164afea --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: "[^_].*" +test_items: [] diff --git a/tests/integration/targets/vyos_snmp_server/tasks/httpapi.yaml b/tests/integration/targets/vyos_snmp_server/tasks/httpapi.yaml new file mode 100644 index 0000000..4147e6d --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/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_snmp_server/tasks/main.yaml b/tests/integration/targets/vyos_snmp_server/tasks/main.yaml new file mode 100644 index 0000000..b1f6193 --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/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_snmp_server/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml new file mode 100644 index 0000000..c847765 --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml @@ -0,0 +1,15 @@ +--- +- name: Populate snmp_server config for testing + vyos.rest.vyos_snmp_server: + config: + communities: + - name: switches + authorization_type: rw + - name: bridges + clients: + - 1.1.1.1 + contact: admin@example.com + listen_addresses: + - address: 20.1.1.1 + state: merged + ignore_errors: true diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml new file mode 100644 index 0000000..c77493a --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml @@ -0,0 +1,5 @@ +--- +- name: Remove pre-existing snmp_server config + vyos.rest.vyos_snmp_server: + state: deleted + ignore_errors: true diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml new file mode 100644 index 0000000..90ee0ae --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_snmp_server deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all snmp_server configuration + register: result + vyos.rest.vyos_snmp_server: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete snmp_server configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_snmp_server: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml new file mode 100644 index 0000000..740446f --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_snmp_server gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather snmp_server configuration + register: result + vyos.rest.vyos_snmp_server: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered is defined + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml new file mode 100644 index 0000000..475b5fe --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml @@ -0,0 +1,38 @@ +--- +- debug: + msg: START vyos_snmp_server merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge snmp_server configuration + register: result + vyos.rest.vyos_snmp_server: &id001 + config: + communities: + - name: switches + authorization_type: rw + - name: bridges + clients: + - 1.1.1.1 + contact: admin@example.com + listen_addresses: + - address: 20.1.1.1 + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge snmp_server configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_snmp_server: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml new file mode 100644 index 0000000..cd9bef2 --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: START vyos_snmp_server replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace snmp_server configuration + register: result + vyos.rest.vyos_snmp_server: &id001 + config: + communities: + - name: routers + authorization_type: ro + location: "RDU, NC" + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace snmp_server configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_snmp_server: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + - result.commands == [] + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_snmp_server/vars/main.yaml b/tests/integration/targets/vyos_snmp_server/vars/main.yaml new file mode 100644 index 0000000..4303881 --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/vars/main.yaml @@ -0,0 +1,2 @@ +--- +# only common vars here -- cgit v1.2.3 From 967a8c581df46f3c01d642ed08ec0d7ec43d79f6 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Mon, 1 Jun 2026 05:42:31 +1000 Subject: removing previews --- plugins/modules/vyos_bgp_address_family.py.preview | 354 --------------- plugins/modules/vyos_bgp_global.py.preview | 365 --------------- plugins/modules/vyos_config_file.py.preview | 130 ------ plugins/modules/vyos_configure.py.preview | 259 ----------- plugins/modules/vyos_facts.py.preview | 161 ------- plugins/modules/vyos_firewall_global.py.preview | 360 --------------- .../modules/vyos_firewall_interfaces.py.preview | 244 ---------- plugins/modules/vyos_firewall_rules.py.preview | 492 --------------------- plugins/modules/vyos_generate.py.preview | 102 ----- plugins/modules/vyos_image.py.preview | 131 ------ plugins/modules/vyos_interfaces.py.preview | 401 ----------------- plugins/modules/vyos_l3_interfaces.py.preview | 381 ---------------- plugins/modules/vyos_lag_interfaces.py.preview | 228 ---------- plugins/modules/vyos_lldp_interfaces.py.preview | 227 ---------- plugins/modules/vyos_ospf_interfaces.py.preview | 290 ------------ plugins/modules/vyos_ospfv2.py.preview | 487 -------------------- plugins/modules/vyos_ospfv3.py.preview | 257 ----------- plugins/modules/vyos_ping.py.preview | 180 -------- plugins/modules/vyos_prefix_lists.py.preview | 412 ----------------- plugins/modules/vyos_reset.py.preview | 108 ----- plugins/modules/vyos_show.py.preview | 119 ----- plugins/modules/vyos_static_routes.py.preview | 397 ----------------- plugins/modules/vyos_system.py.preview | 147 ------ plugins/modules/vyos_user.py.preview | 282 ------------ plugins/modules/vyos_vlan.py.preview | 197 --------- 25 files changed, 6711 deletions(-) delete mode 100644 plugins/modules/vyos_bgp_address_family.py.preview delete mode 100644 plugins/modules/vyos_bgp_global.py.preview delete mode 100644 plugins/modules/vyos_config_file.py.preview delete mode 100644 plugins/modules/vyos_configure.py.preview delete mode 100644 plugins/modules/vyos_facts.py.preview delete mode 100644 plugins/modules/vyos_firewall_global.py.preview delete mode 100644 plugins/modules/vyos_firewall_interfaces.py.preview delete mode 100644 plugins/modules/vyos_firewall_rules.py.preview delete mode 100644 plugins/modules/vyos_generate.py.preview delete mode 100644 plugins/modules/vyos_image.py.preview delete mode 100644 plugins/modules/vyos_interfaces.py.preview delete mode 100644 plugins/modules/vyos_l3_interfaces.py.preview delete mode 100644 plugins/modules/vyos_lag_interfaces.py.preview delete mode 100644 plugins/modules/vyos_lldp_interfaces.py.preview delete mode 100644 plugins/modules/vyos_ospf_interfaces.py.preview delete mode 100644 plugins/modules/vyos_ospfv2.py.preview delete mode 100644 plugins/modules/vyos_ospfv3.py.preview delete mode 100644 plugins/modules/vyos_ping.py.preview delete mode 100644 plugins/modules/vyos_prefix_lists.py.preview delete mode 100644 plugins/modules/vyos_reset.py.preview delete mode 100644 plugins/modules/vyos_show.py.preview delete mode 100644 plugins/modules/vyos_static_routes.py.preview delete mode 100644 plugins/modules/vyos_system.py.preview delete mode 100644 plugins/modules/vyos_user.py.preview delete mode 100644 plugins/modules/vyos_vlan.py.preview (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_bgp_address_family.py.preview b/plugins/modules/vyos_bgp_address_family.py.preview deleted file mode 100644 index ff80053..0000000 --- a/plugins/modules/vyos_bgp_address_family.py.preview +++ /dev/null @@ -1,354 +0,0 @@ -#!/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_bgp_address_family -short_description: BGP Address Family resource module via REST API. -description: - - Manages BGP address-family configuration (networks, redistribute, - aggregate-address, per-neighbor AFI settings) via the VyOS HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: BGP address-family configuration. - type: dict - suboptions: - as_number: - description: Local AS number. - type: int - required: true - address_family: - description: Global address-family parameters. - type: list - elements: dict - suboptions: - afi: - description: Address family (ipv4 or ipv6). - type: str - choices: [ipv4, ipv6] - aggregate_address: - description: List of BGP aggregate networks. - type: list - elements: dict - suboptions: - prefix: - type: str - as_set: - type: bool - summary_only: - type: bool - networks: - description: Networks to originate. - type: list - elements: dict - suboptions: - prefix: - type: str - path_limit: - type: int - backdoor: - type: bool - route_map: - type: str - redistribute: - description: Protocols to redistribute. - type: list - elements: dict - suboptions: - protocol: - type: str - choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] - table: - type: str - route_map: - type: str - metric: - type: int - neighbors: - description: Per-neighbor address-family settings. - type: list - elements: dict - suboptions: - neighbor_address: - description: Neighbor IP. - type: str - address_family: - type: list - elements: dict - suboptions: - afi: - type: str - choices: [ipv4, ipv6] - allowas_in: - type: int - as_override: - type: bool - route_map: - description: Route-map settings. - type: dict - suboptions: - import_map: - type: str - export_map: - type: str - soft_reconfiguration: - type: bool - nexthop_self: - type: bool - remove_private_as: - type: bool - state: - description: Desired state. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - description: Device IP or FQDN. - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - description: Config before module ran. - returned: always - type: dict -after: - description: Config after module ran. - returned: when changed - type: dict -commands: - description: Commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _get_bgp(client): - try: - result = client.retrieve_show_config(["protocols", "bgp"]) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def _afi_unicast_key(afi): - return "ipv4-unicast" if afi == "ipv4" else "ipv6-unicast" - - -def _apply(client, config, commands): - asn = str(config["as_number"]) - base = ["protocols", "bgp", asn] - - for af in config.get("address_family") or []: - afi = af["afi"] - af_base = base + ["address-family", _afi_unicast_key(afi)] - - for agg in af.get("aggregate_address") or []: - p = agg["prefix"] - agg_base = af_base + ["aggregate-address", p] - client.configure_set(agg_base) - commands.append("set {b}".format(b=" ".join(agg_base))) - if agg.get("as_set"): - client.configure_set(agg_base + ["as-set"]) - if agg.get("summary_only"): - client.configure_set(agg_base + ["summary-only"]) - - for net in af.get("networks") or []: - net_base = af_base + ["network", net["prefix"]] - client.configure_set(net_base) - commands.append("set {b}".format(b=" ".join(net_base))) - if net.get("route_map"): - client.configure_set(net_base + ["route-map"], net["route_map"]) - if net.get("backdoor"): - client.configure_set(net_base + ["backdoor"]) - - for redist in af.get("redistribute") or []: - r_base = af_base + ["redistribute", redist["protocol"]] - client.configure_set(r_base) - commands.append("set {b}".format(b=" ".join(r_base))) - if redist.get("metric"): - client.configure_set(r_base + ["metric"], str(redist["metric"])) - if redist.get("route_map"): - client.configure_set(r_base + ["route-map"], redist["route_map"]) - - for nbr in config.get("neighbors") or []: - n_base = base + ["neighbor", nbr["neighbor_address"]] - for af in nbr.get("address_family") or []: - afi = af["afi"] - n_af_base = n_base + ["address-family", _afi_unicast_key(afi)] - client.configure_set(n_af_base) - commands.append("set {b}".format(b=" ".join(n_af_base))) - if af.get("allowas_in"): - client.configure_set(n_af_base + ["allowas-in", "number"], str(af["allowas_in"])) - if af.get("as_override"): - client.configure_set(n_af_base + ["as-override"]) - if af.get("nexthop_self"): - client.configure_set(n_af_base + ["nexthop-self"]) - if af.get("remove_private_as"): - client.configure_set(n_af_base + ["remove-private-as"]) - if af.get("soft_reconfiguration"): - client.configure_set(n_af_base + ["soft-reconfiguration", "inbound"]) - rm = af.get("route_map") or {} - if rm.get("import_map"): - client.configure_set(n_af_base + ["route-map", "import"], rm["import_map"]) - if rm.get("export_map"): - client.configure_set(n_af_base + ["route-map", "export"], rm["export_map"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - as_number=dict(type="int", required=True), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", choices=["ipv4", "ipv6"]), - aggregate_address=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str"), - as_set=dict(type="bool"), - summary_only=dict(type="bool"), - ), - ), - networks=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str"), - path_limit=dict(type="int"), - backdoor=dict(type="bool"), - route_map=dict(type="str"), - ), - ), - redistribute=dict( - type="list", - elements="dict", - options=dict( - protocol=dict( - type="str", - choices=[ - "connected", - "kernel", - "ospf", - "ospfv3", - "rip", - "ripng", - "static", - ], - ), - table=dict(type="str"), - route_map=dict(type="str"), - metric=dict(type="int"), - ), - ), - ), - ), - neighbors=dict( - type="list", - elements="dict", - options=dict( - neighbor_address=dict(type="str"), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", choices=["ipv4", "ipv6"]), - allowas_in=dict(type="int"), - as_override=dict(type="bool"), - nexthop_self=dict(type="bool"), - remove_private_as=dict(type="bool"), - soft_reconfiguration=dict(type="bool"), - route_map=dict( - type="dict", - options=dict( - import_map=dict(type="str"), - export_map=dict(type="str"), - ), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get_bgp(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(["protocols", "bgp"]) - commands.append("delete protocols bgp") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(["protocols", "bgp"]) - commands.append("delete protocols bgp") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_bgp(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_bgp_global.py.preview b/plugins/modules/vyos_bgp_global.py.preview deleted file mode 100644 index 6a08f4f..0000000 --- a/plugins/modules/vyos_bgp_global.py.preview +++ /dev/null @@ -1,365 +0,0 @@ -#!/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_bgp_global -short_description: Manage BGP global configuration on VyOS via the REST API. -description: - - Manages BGP global parameters on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_bgp_global) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: BGP global configuration. - type: dict - suboptions: - as_number: - description: Local BGP AS number. - type: int - required: true - router_id: - description: BGP router ID (IPv4 address). - type: str - neighbors: - description: List of BGP neighbor configurations. - type: list - elements: dict - suboptions: - neighbor: - description: Neighbor IP address. - type: str - required: true - remote_as: - description: Neighbor's AS number. - type: int - description: - description: Neighbor description. - type: str - password: - description: MD5 authentication password. - type: str - no_log: true - update_source: - description: Local interface or address for BGP session. - type: str - ebgp_multihop: - description: Maximum hops for eBGP sessions. - type: int - shutdown: - description: Whether to administratively shut down the neighbor. - type: bool - networks: - description: List of networks to originate. - type: list - elements: dict - suboptions: - prefix: - description: Network prefix to advertise. - type: str - required: true - route_map: - description: Route map to apply. - type: str - redistribute: - description: List of protocols to redistribute into BGP. - type: list - elements: dict - suboptions: - protocol: - description: Protocol to redistribute. - type: str - choices: [connected, kernel, ospf, ospfv3, rip, ripng, static] - required: true - metric: - description: Metric for redistributed routes. - type: int - route_map: - description: Route map to apply. - type: str - state: - description: - - C(merged): Merge BGP config with existing. - - C(replaced): Replace entire BGP config. - - C(deleted): Remove BGP configuration. - - C(gathered): Read BGP config from device. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_bgp_global -examples: | - - name: Configure BGP - vyos.rest.vyos_bgp_global: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - as_number: 65001 - router_id: 192.168.1.1 - neighbors: - - neighbor: 192.168.2.1 - remote_as: 65002 - description: "Peer AS65002" - networks: - - prefix: 10.0.0.0/8 - redistribute: - - protocol: connected - state: merged - - - name: Remove BGP configuration - vyos.rest.vyos_bgp_global: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: BGP config before the module ran. - returned: always - type: dict -after: - description: BGP config after the module ran. - returned: when changed - type: dict -gathered: - description: BGP config read from device (state=gathered). - returned: when state is gathered - type: dict -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BGP_BASE = ["protocols", "bgp"] - - -def _get_bgp(client): - try: - result = client.retrieve_show_config(_BGP_BASE) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def _apply_bgp(client, config, commands): - asn = str(config["as_number"]) - base = _BGP_BASE + [asn] - client.configure_set(base) - commands.append("set protocols bgp {a}".format(a=asn)) - - if config.get("router_id"): - client.configure_set(base + ["parameters", "router-id"], config["router_id"]) - commands.append( - "set protocols bgp {a} parameters router-id {r}".format( - a=asn, - r=config["router_id"], - ), - ) - - for nbr in config.get("neighbors") or []: - nbase = base + ["neighbor", nbr["neighbor"]] - client.configure_set(nbase) - commands.append("set protocols bgp {a} neighbor {n}".format(a=asn, n=nbr["neighbor"])) - if nbr.get("remote_as"): - client.configure_set(nbase + ["remote-as"], str(nbr["remote_as"])) - commands.append( - "set protocols bgp {a} neighbor {n} remote-as {r}".format( - a=asn, - n=nbr["neighbor"], - r=nbr["remote_as"], - ), - ) - if nbr.get("description"): - client.configure_set(nbase + ["description"], nbr["description"]) - if nbr.get("password"): - client.configure_set(nbase + ["password"], nbr["password"]) - if nbr.get("update_source"): - client.configure_set(nbase + ["update-source"], nbr["update_source"]) - if nbr.get("ebgp_multihop"): - client.configure_set(nbase + ["ebgp-multihop"], str(nbr["ebgp_multihop"])) - if nbr.get("shutdown"): - client.configure_set(nbase + ["shutdown"]) - commands.append( - "set protocols bgp {a} neighbor {n} shutdown".format( - a=asn, - n=nbr["neighbor"], - ), - ) - - for net in config.get("networks") or []: - nbase = base + ["address-family", "ipv4-unicast", "network", net["prefix"]] - client.configure_set(nbase) - commands.append( - "set protocols bgp {a} address-family ipv4-unicast network {p}".format( - a=asn, - p=net["prefix"], - ), - ) - if net.get("route_map"): - client.configure_set(nbase + ["route-map"], net["route_map"]) - - for redist in config.get("redistribute") or []: - rbase = base + [ - "address-family", - "ipv4-unicast", - "redistribute", - redist["protocol"], - ] - client.configure_set(rbase) - commands.append( - "set protocols bgp {a} address-family ipv4-unicast redistribute {p}".format( - a=asn, - p=redist["protocol"], - ), - ) - if redist.get("metric"): - client.configure_set(rbase + ["metric"], str(redist["metric"])) - if redist.get("route_map"): - client.configure_set(rbase + ["route-map"], redist["route_map"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - as_number=dict(type="int", required=True), - router_id=dict(type="str"), - neighbors=dict( - type="list", - elements="dict", - options=dict( - neighbor=dict(type="str", required=True), - remote_as=dict(type="int"), - description=dict(type="str"), - password=dict(type="str", no_log=True), - update_source=dict(type="str"), - ebgp_multihop=dict(type="int"), - shutdown=dict(type="bool"), - ), - ), - networks=dict( - type="list", - elements="dict", - options=dict( - prefix=dict(type="str", required=True), - route_map=dict(type="str"), - ), - ), - redistribute=dict( - type="list", - elements="dict", - options=dict( - protocol=dict( - type="str", - required=True, - choices=[ - "connected", - "kernel", - "ospf", - "ospfv3", - "rip", - "ripng", - "static", - ], - ), - metric=dict(type="int"), - route_map=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[ - ("state", "merged", ["config"]), - ("state", "replaced", ["config"]), - ], - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - - before = _get_bgp(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BGP_BASE) - commands.append("delete protocols bgp") - changed = True - - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BGP_BASE) - commands.append("delete protocols bgp") - _apply_bgp(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_bgp(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_config_file.py.preview b/plugins/modules/vyos_config_file.py.preview deleted file mode 100644 index 9d5e48a..0000000 --- a/plugins/modules/vyos_config_file.py.preview +++ /dev/null @@ -1,130 +0,0 @@ -#!/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_config_file -short_description: Save or load VyOS configuration files via the REST API. -description: - - Manages VyOS configuration persistence via the HTTPS REST API - (C(/config-file) endpoint). - - Use I(op=save) to write the running configuration to a file. - - Use I(op=load) to load a configuration from a file and apply it. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - op: - description: - - C(save) - Save the running configuration to I(file) or the default C(/config/config.boot). - - C(load) - Load and commit the configuration from I(file). - type: str - choices: [save, load] - default: save - file: - description: - - Path on the VyOS device for save/load target. - - If omitted for C(save), the device uses its default boot config path. - type: str - hostname: - description: - - IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: - - HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: - - API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: - - Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: - - Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+""" - -RETURN = r""" -output: - description: Text output from the config-file operation. - returned: success - type: str -""" - -EXAMPLES = r""" -- name: Save running config (default location) - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - -- name: Save running config to a custom file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: save - file: /config/backups/config.boot.bak - -- name: Load a configuration from file - vyos.rest.vyos_config_file: - hostname: 192.168.1.1 - api_key: MY-KEY - op: load - file: /config/backups/config.boot.bak -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - op=dict(type="str", default="save", choices=["save", "load"]), - file=dict(type="str"), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("op", "load", ["file"])], - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - op = module.params["op"] - file_path = module.params.get("file") - - try: - if op == "save": - result = client.config_file_save(file_path) - else: - result = client.config_file_load(file_path) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_configure.py.preview b/plugins/modules/vyos_configure.py.preview deleted file mode 100644 index 355a78c..0000000 --- a/plugins/modules/vyos_configure.py.preview +++ /dev/null @@ -1,259 +0,0 @@ -#!/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_configure -short_description: Manage VyOS device configuration via the REST API. -description: - - Sends one or more C(set) or C(delete) configuration commands to a VyOS - device via its HTTPS REST API (C(/configure) endpoint). - - Each command in I(commands) is executed and committed atomically. - - Supports Ansible check-mode (C(check_mode=yes)) - no changes are applied. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - commands: - description: - - List of VyOS configuration commands. - - Each item may be either a plain string in VyOS CLI syntax - (e.g. C(set interfaces ethernet eth0 description 'WAN')) - or a dict with keys C(op), C(path), and optionally C(value). - type: list - elements: raw - required: true - save: - description: - - Whether to save the configuration to disk after a successful commit. - type: bool - default: false - hostname: - description: - - IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: - - HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: - - API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: - - Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: - - Validate the device's TLS certificate. - type: bool - default: false -notes: - - The VyOS HTTP API must be enabled on the device before using this module. - - Enable with C(set service https api keys id key ) and - C(set service https api rest), then C(commit && save). - - Tested against VyOS 1.3 and 1.4. -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.rest.vyos_retrieve - - module: vyos.rest.vyos_show""" - -RETURN = r""" -commands_sent: - description: The list of commands dispatched to the API. - returned: always - type: list - elements: dict - sample: - - op: set - path: ["system", "host-name"] - value: vyos-router -changed: - description: Whether the device configuration was modified. - returned: always - type: bool -saved: - description: Whether the configuration was saved to disk. - returned: always - type: bool -""" - -import re - - -EXAMPLES = r""" -- name: Set hostname via REST API - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set system host-name vyos-router" - -- name: Configure an interface description and address - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - "set interfaces ethernet eth1 description 'UPLINK'" - - "set interfaces ethernet eth1 address 10.0.0.1/30" - save: true - -- name: Delete a static route using dict syntax - vyos.rest.vyos_configure: - hostname: 192.168.1.1 - api_key: MY-KEY - commands: - - op: delete - path: ["protocols", "static", "route", "192.0.2.0/24"] -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _parse_cli_command(cmd_str): - """Parse a VyOS CLI-syntax string into an API payload dict. - - Supports: - set [] - delete - comment - - Args: - cmd_str (str): e.g. "set interfaces ethernet eth0 description 'WAN'" - - Returns: - dict: e.g. {"op": "set", "path": [...], "value": "WAN"} - """ - cmd_str = cmd_str.strip() - op_match = re.match(r"^(set|delete|comment)\s+(.+)$", cmd_str, re.IGNORECASE) - if not op_match: - raise ValueError( - "Cannot parse command '{cmd}'. Must start with set/delete/comment.".format( - cmd=cmd_str, - ), - ) - op = op_match.group(1).lower() - rest = op_match.group(2).strip() - - # Extract a quoted or bare trailing value for 'set' - value = None - if op == "set": - # Look for a trailing quoted value: '...' or "..." - quoted = re.search(r"""(['"])(.*?)\1\s*$""", rest) - if quoted: - value = quoted.group(2) - rest = rest[: quoted.start()].strip() - else: - # Bare final token may be the value if the second-to-last token - # is a known leaf keyword – use a simple heuristic: if the - # last segment has no sub-tokens we treat it as value. - tokens = rest.split() - # We cannot know the schema here, so we pass the full path and - # no value; the device will decide. - path = tokens - return {"op": op, "path": path} - - path = rest.split() - payload = {"op": op, "path": path} - if value is not None: - payload["value"] = value - return payload - - -def _normalise_commands(raw_commands): - """Normalise the mixed list of str/dict commands into API dicts.""" - result = [] - for item in raw_commands: - if isinstance(item, dict): - if "op" not in item or "path" not in item: - raise ValueError( - "Command dict must have 'op' and 'path' keys: {item}".format( - item=item, - ), - ) - result.append(item) - elif isinstance(item, str): - result.append(_parse_cli_command(item)) - else: - raise ValueError( - "Command must be a string or dict, got: {t}".format(t=type(item)), - ) - return result - - -def main(): - argument_spec = dict( - commands=dict(type="list", elements="raw", required=True), - save=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - try: - commands = _normalise_commands(module.params["commands"]) - except ValueError as exc: - module.fail_json(msg=str(exc)) - - if module.check_mode: - module.exit_json( - changed=True, - commands_sent=commands, - saved=False, - msg="Check mode: no changes applied.", - ) - - client = VyOSRestClient(module) - changed = False - saved = False - - try: - for cmd in commands: - op = cmd["op"].lower() - path = cmd["path"] - value = cmd.get("value") - if op == "set": - client.configure_set(path, value) - elif op == "delete": - client.configure_delete(path) - elif op == "comment": - client.configure_comment(path, value or "") - else: - module.fail_json( - msg="Unsupported op '{op}'".format(op=op), - ) - changed = True - - if module.params["save"]: - client.config_file_save() - saved = True - - except VyOSRestError as exc: - module.fail_json(msg=str(exc), commands_sent=commands) - - module.exit_json(changed=changed, commands_sent=commands, saved=saved) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_facts.py.preview b/plugins/modules/vyos_facts.py.preview deleted file mode 100644 index 66e684b..0000000 --- a/plugins/modules/vyos_facts.py.preview +++ /dev/null @@ -1,161 +0,0 @@ -#!/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_facts -short_description: Get facts about VyOS devices via REST API. -description: - - Collects facts from VyOS devices using the HTTPS REST API. - - Returns system info and optionally per-resource configuration facts. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - gather_subset: - description: - - Restrict facts to a subset. Use C(all), C(default), C(config), or C(min). - type: list - elements: str - default: ["min"] - gather_network_resources: - description: - - Which network resource facts to gather. - type: list - elements: str - available_network_resources: - description: - - When true, return a list of available resource names. - type: bool - default: false - hostname: - description: - - Device IP or FQDN. - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -ansible_facts: - description: Dictionary of facts keyed by resource name. - returned: always - type: dict -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_NETWORK_RESOURCES = [ - "interfaces", - "l3_interfaces", - "lag_interfaces", - "lldp_global", - "lldp_interfaces", - "static_routes", - "firewall_rules", - "firewall_global", - "firewall_interfaces", - "ospfv2", - "ospfv3", -] - -_RESOURCE_PATHS = { - "interfaces": ["interfaces"], - "l3_interfaces": ["interfaces"], - "lag_interfaces": ["interfaces", "bonding"], - "lldp_global": ["service", "lldp"], - "lldp_interfaces": ["service", "lldp"], - "static_routes": ["protocols", "static"], - "firewall_rules": ["firewall"], - "firewall_global": ["firewall"], - "firewall_interfaces": ["firewall"], - "ospfv2": ["protocols", "ospf"], - "ospfv3": ["protocols", "ospfv3"], -} - - -def _get_subset(client, resource): - path = _RESOURCE_PATHS.get(resource, []) - try: - result = client.retrieve_show_config(path) - return result.get("data") or {} - except VyOSRestError: - return {} - - -def main(): - argument_spec = dict( - gather_subset=dict(type="list", elements="str", default=["min"]), - gather_network_resources=dict(type="list", elements="str"), - available_network_resources=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - - client = VyOSRestClient(module) - facts = {} - - if module.params["available_network_resources"]: - facts["available_network_resources"] = _NETWORK_RESOURCES - - gather_subset = module.params["gather_subset"] - get_all = "all" in gather_subset - - # Always gather system info for "min" or "all" - if get_all or "min" in gather_subset or "default" in gather_subset: - try: - info = client.info() - facts["version"] = info.get("data", {}).get("version", "") - facts["hostname"] = info.get("data", {}).get("hostname", "") - except VyOSRestError: - pass - - # Config subset - if get_all or "config" in gather_subset: - try: - result = client.retrieve_show_config([]) - facts["config"] = result.get("data") or {} - except VyOSRestError: - facts["config"] = {} - - # Network resources - requested = module.params.get("gather_network_resources") or [] - if "all" in requested: - requested = _NETWORK_RESOURCES - - for resource in requested: - if resource.startswith("!"): - continue - if resource in _NETWORK_RESOURCES: - facts["network_resources"] = facts.get("network_resources", {}) - facts["network_resources"][resource] = _get_subset(client, resource) - - module.exit_json(changed=False, ansible_facts=facts) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_global.py.preview b/plugins/modules/vyos_firewall_global.py.preview deleted file mode 100644 index a3974fd..0000000 --- a/plugins/modules/vyos_firewall_global.py.preview +++ /dev/null @@ -1,360 +0,0 @@ -#!/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_firewall_global -short_description: Firewall global resource module via REST API. -description: - - Manages global VyOS firewall parameters (groups, ICMP redirect policy, - source validation, ping policy) via the HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: Global firewall configuration. - type: dict - suboptions: - validation: - description: Source-validation policy (strict/loose/disable). - type: str - choices: [strict, loose, disable] - config_trap: - description: SNMP trap on firewall config changes. - type: bool - ping: - description: ICMP echo policy. - type: dict - suboptions: - all: - type: bool - broadcast: - type: bool - route_redirects: - description: ICMP redirect / source-route options per AFI. - type: list - elements: dict - suboptions: - afi: - type: str - choices: [ipv4, ipv6] - required: true - icmp_redirects: - type: dict - suboptions: - send: - type: bool - receive: - type: bool - ip_src_route: - type: bool - group: - description: Firewall object groups. - type: dict - suboptions: - address_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of IP addresses or ranges. - type: list - elements: dict - suboptions: - address: - type: str - afi: - type: str - default: ipv4 - choices: [ipv4, ipv6] - network_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of network prefixes. - type: list - elements: dict - suboptions: - address: - type: str - port_group: - type: list - elements: dict - suboptions: - name: - type: str - required: true - description: - type: str - members: - description: List of port numbers or ranges. - type: list - elements: dict - suboptions: - port: - type: str - state: - description: Desired state. - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_FW_BASE = ["firewall"] - - -def _get(client): - try: - r = client.retrieve_show_config(_FW_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - if config.get("validation"): - path = ( - _FW_BASE + ["ip", "src-route"] - if config["validation"] == "disable" - else _FW_BASE + ["ip", "enable-default-log"] - ) - # Use the proper path - p = ( - ["firewall", "global-options", "source-validation"] - if client.retrieve_exists(["firewall", "global-options"]) - else ["firewall", "ip", "src-route"] - ) - client.configure_set(["firewall", "ip", "source-validation"], config["validation"]) - commands.append("set firewall ip source-validation {v}".format(v=config["validation"])) - - if config.get("config_trap") is not None: - val = "enable" if config["config_trap"] else "disable" - client.configure_set(["firewall", "config-trap"], val) - commands.append("set firewall config-trap {v}".format(v=val)) - - ping = config.get("ping") or {} - if ping.get("all") is not None: - action = "enable" if ping["all"] else "disable" - client.configure_set(["firewall", "ip", "ping", "all"], action) - commands.append("set firewall ip ping all {a}".format(a=action)) - if ping.get("broadcast") is not None: - action = "enable" if ping["broadcast"] else "disable" - client.configure_set(["firewall", "ip", "ping", "broadcast"], action) - commands.append("set firewall ip ping broadcast {a}".format(a=action)) - - for rr in config.get("route_redirects") or []: - afi = rr["afi"] - ip_key = "ip" if afi == "ipv4" else "ipv6" - icmp = rr.get("icmp_redirects") or {} - if icmp.get("send") is not None: - p = ["firewall", ip_key, "send-redirects"] - client.configure_set(p, "enable" if icmp["send"] else "disable") - commands.append( - "set {p} {v}".format(p=" ".join(p), v="enable" if icmp["send"] else "disable"), - ) - if icmp.get("receive") is not None: - p = ["firewall", ip_key, "disable-forwarding"] - client.configure_set(p, "enable" if not icmp["receive"] else "disable") - if rr.get("ip_src_route") is not None: - p = ["firewall", ip_key, "source-route"] - client.configure_set(p, "enable" if rr["ip_src_route"] else "disable") - - grp = config.get("group") or {} - for ag in grp.get("address_group") or []: - gpath = ["firewall", "group", "address-group", ag["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if ag.get("description"): - client.configure_set(gpath + ["description"], ag["description"]) - for m in ag.get("members") or []: - client.configure_set(gpath + ["address"], m["address"]) - commands.append("set {p} address {a}".format(p=" ".join(gpath), a=m["address"])) - - for ng in grp.get("network_group") or []: - gpath = ["firewall", "group", "network-group", ng["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if ng.get("description"): - client.configure_set(gpath + ["description"], ng["description"]) - for m in ng.get("members") or []: - client.configure_set(gpath + ["network"], m["address"]) - - for pg in grp.get("port_group") or []: - gpath = ["firewall", "group", "port-group", pg["name"]] - client.configure_set(gpath) - commands.append("set {p}".format(p=" ".join(gpath))) - if pg.get("description"): - client.configure_set(gpath + ["description"], pg["description"]) - for m in pg.get("members") or []: - client.configure_set(gpath + ["port"], str(m["port"])) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - validation=dict(type="str", choices=["strict", "loose", "disable"]), - config_trap=dict(type="bool"), - ping=dict( - type="dict", - options=dict(all=dict(type="bool"), broadcast=dict(type="bool")), - ), - route_redirects=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - icmp_redirects=dict( - type="dict", - options=dict(send=dict(type="bool"), receive=dict(type="bool")), - ), - ip_src_route=dict(type="bool"), - ), - ), - group=dict( - type="dict", - options=dict( - address_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - afi=dict(type="str", default="ipv4"), - members=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str")), - ), - ), - ), - network_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - members=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str")), - ), - ), - ), - port_group=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - description=dict(type="str"), - members=dict( - type="list", - elements="dict", - options=dict(port=dict(type="str")), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_FW_BASE) - commands.append("delete firewall") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_FW_BASE) - commands.append("delete firewall") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_interfaces.py.preview b/plugins/modules/vyos_firewall_interfaces.py.preview deleted file mode 100644 index d9085a6..0000000 --- a/plugins/modules/vyos_firewall_interfaces.py.preview +++ /dev/null @@ -1,244 +0,0 @@ -#!/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_firewall_interfaces -short_description: Firewall interfaces resource module via REST API. -description: - - Attaches or detaches named firewall rule sets to/from interface - directions (in/out/local) using the VyOS HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of interface firewall attachment configurations. - type: list - elements: dict - suboptions: - name: - description: Interface name. - type: str - required: true - access_rules: - description: Firewall rule sets per AFI. - type: list - elements: dict - suboptions: - afi: - description: Address family. - type: str - choices: [ipv4, ipv6] - required: true - rules: - description: Rule sets and directions. - type: list - elements: dict - suboptions: - name: - description: Rule set name. - type: str - direction: - description: Traffic direction. - type: str - choices: [in, local, out] - required: true - state: - description: Desired state. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _fw_direction_key(afi, direction): - if afi == "ipv4": - return {"in": "in", "out": "out", "local": "local"}[direction] - return {"in": "in", "out": "out", "local": "local"}[direction] - - -def _apply(client, iface_cfg, commands): - name = iface_cfg["name"] - itype = _itype(name) - ibase = ["interfaces", itype, name, "firewall"] - - for ar in iface_cfg.get("access_rules") or []: - afi = ar["afi"] - fw_key = "firewall" if afi == "ipv4" else "ipv6" - for rule in ar.get("rules") or []: - direction = rule["direction"] - rs_name = rule.get("name") - if rs_name: - path = ibase + [direction, fw_key if afi == "ipv6" else "name"] - client.configure_set(path, rs_name) - commands.append( - "set interfaces {t} {n} firewall {d} {k} '{rs}'".format( - t=itype, - n=name, - d=direction, - k="ipv6-name" if afi == "ipv6" else "name", - rs=rs_name, - ), - ) - - -def _delete_iface_fw(client, name, commands): - itype = _itype(name) - path = ["interfaces", itype, name, "firewall"] - try: - client.configure_delete(path) - commands.append("delete interfaces {t} {n} firewall".format(t=itype, n=name)) - except VyOSRestError: - pass - - -def _get(client): - try: - result = client.retrieve_show_config(["interfaces"]) - raw = result.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - fw = idata.get("firewall") if isinstance(idata, dict) else None - if fw: - out.append({"name": iname, "firewall": fw}) - return out - except VyOSRestError: - return [] - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - access_rules=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - rules=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str"), - direction=dict( - type="str", - required=True, - choices=["in", "local", "out"], - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_iface_fw(client, iface["name"], commands) - changed = True - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - for cfg in config: - _delete_iface_fw(client, cfg["name"], commands) - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_firewall_rules.py.preview b/plugins/modules/vyos_firewall_rules.py.preview deleted file mode 100644 index f535f3a..0000000 --- a/plugins/modules/vyos_firewall_rules.py.preview +++ /dev/null @@ -1,492 +0,0 @@ -#!/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_firewall_rules -short_description: Manage firewall rules on VyOS via the REST API. -description: - - Manages VyOS firewall rule sets and individual rules using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_firewall_rules) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of firewall rule set configurations. - type: list - elements: dict - suboptions: - afi: - description: Address family. - type: str - choices: [ipv4, ipv6] - required: true - rule_sets: - description: List of named rule sets. - type: list - elements: dict - suboptions: - name: - description: Rule set name. - type: str - required: true - default_action: - description: Default action for unmatched traffic. - type: str - choices: [drop, reject, accept] - default: drop - description: - description: Rule set description. - type: str - rules: - description: List of firewall rules. - type: list - elements: dict - suboptions: - number: - description: Rule number (1-999999). - type: int - required: true - action: - description: Rule action. - type: str - choices: [drop, reject, accept, inspect] - description: - description: Rule description. - type: str - protocol: - description: Protocol (e.g. C(tcp), C(udp), C(icmp), C(all)). - type: str - source: - description: Source match criteria. - type: dict - suboptions: - address: - description: Source IP address or network. - type: str - port: - description: Source port or port range. - type: str - group: - description: Address or network group. - type: dict - suboptions: - address_group: - type: str - network_group: - type: str - destination: - description: Destination match criteria. - type: dict - suboptions: - address: - description: Destination IP address or network. - type: str - port: - description: Destination port or port range. - type: str - group: - description: Address or network group. - type: dict - suboptions: - address_group: - type: str - network_group: - type: str - state: - description: Connection state matching. - type: dict - suboptions: - established: - type: bool - new: - type: bool - related: - type: bool - invalid: - type: bool - enabled: - description: Whether the rule is active. - type: bool - default: true - state: - description: - - C(merged): Add/update listed rule sets and rules. - - C(replaced): Replace listed rule sets entirely. - - C(overridden): Replace the entire firewall rule config. - - C(deleted): Remove listed (or all) rule sets. - - C(gathered): Read firewall rule config from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_firewall_rules -examples: | - - name: Create an IPv4 firewall rule set - vyos.rest.vyos_firewall_rules: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - afi: ipv4 - rule_sets: - - name: OUTSIDE-IN - default_action: drop - rules: - - number: 10 - action: accept - protocol: tcp - destination: - port: "80,443" - state: - established: true - related: true - state: merged - - - name: Delete all firewall rules - vyos.rest.vyos_firewall_rules: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: Firewall rule config before the module ran. - returned: always - type: list -after: - description: Firewall rule config after the module ran. - returned: when changed - type: list -gathered: - description: Firewall config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_FW_PATH = { - "ipv4": ["firewall", "name"], - "ipv6": ["firewall", "ipv6-name"], -} - - -def _get_fw_rules(client): - try: - result = client.retrieve_show_config(["firewall"]) - data = result.get("data") or {} - out = [] - for afi, key in [("ipv4", "name"), ("ipv6", "ipv6-name")]: - sets_data = data.get(key, {}) - if not isinstance(sets_data, dict): - continue - rule_sets = [] - for rs_name, rs_data in sets_data.items(): - if not isinstance(rs_data, dict): - continue - rs = { - "name": rs_name, - "default_action": rs_data.get("default-action", "drop"), - } - if "description" in rs_data: - rs["description"] = rs_data["description"] - rules = [] - for rnum, rdata in (rs_data.get("rule") or {}).items(): - if not isinstance(rdata, dict): - continue - rule = {"number": int(rnum)} - if "action" in rdata: - rule["action"] = rdata["action"] - if "description" in rdata: - rule["description"] = rdata["description"] - if "protocol" in rdata: - rule["protocol"] = rdata["protocol"] - rule["enabled"] = "disable" not in rdata - rules.append(rule) - if rules: - rs["rules"] = sorted(rules, key=lambda r: r["number"]) - rule_sets.append(rs) - if rule_sets: - out.append({"afi": afi, "rule_sets": rule_sets}) - return out - except VyOSRestError: - return [] - - -def _apply_rule_set(client, afi, rs, commands): - base = _FW_PATH[afi] + [rs["name"]] - client.configure_set(base) - commands.append("set {p}".format(p=" ".join(base))) - - if rs.get("default_action"): - client.configure_set(base + ["default-action"], rs["default_action"]) - commands.append( - "set {p} default-action {a}".format( - p=" ".join(base), - a=rs["default_action"], - ), - ) - if rs.get("description"): - client.configure_set(base + ["description"], rs["description"]) - commands.append( - "set {p} description '{d}'".format( - p=" ".join(base), - d=rs["description"], - ), - ) - - for rule in rs.get("rules") or []: - rbase = base + ["rule", str(rule["number"])] - client.configure_set(rbase) - commands.append("set {p}".format(p=" ".join(rbase))) - - if rule.get("action"): - client.configure_set(rbase + ["action"], rule["action"]) - commands.append( - "set {p} action {a}".format(p=" ".join(rbase), a=rule["action"]), - ) - if rule.get("description"): - client.configure_set(rbase + ["description"], rule["description"]) - if rule.get("protocol"): - client.configure_set(rbase + ["protocol"], rule["protocol"]) - commands.append( - "set {p} protocol {pr}".format( - p=" ".join(rbase), - pr=rule["protocol"], - ), - ) - for direction in ("source", "destination"): - match = rule.get(direction) - if not match: - continue - mbase = rbase + [direction] - if match.get("address"): - client.configure_set(mbase + ["address"], match["address"]) - commands.append( - "set {p} address '{a}'".format( - p=" ".join(mbase), - a=match["address"], - ), - ) - if match.get("port"): - client.configure_set(mbase + ["port"], match["port"]) - commands.append( - "set {p} port {pt}".format( - p=" ".join(mbase), - pt=match["port"], - ), - ) - - state_match = rule.get("state") - if state_match: - sbase = rbase + ["state"] - for st_name in ("established", "new", "related", "invalid"): - if state_match.get(st_name): - client.configure_set(sbase + [st_name], "enable") - commands.append( - "set {p} {s} enable".format( - p=" ".join(sbase), - s=st_name, - ), - ) - - if "enabled" in rule and not rule["enabled"]: - client.configure_set(rbase + ["disable"]) - commands.append("set {p} disable".format(p=" ".join(rbase))) - - -def main(): - rule_spec = dict( - number=dict(type="int", required=True), - action=dict(type="str", choices=["drop", "reject", "accept", "inspect"]), - description=dict(type="str"), - protocol=dict(type="str"), - source=dict( - type="dict", - options=dict( - address=dict(type="str"), - port=dict(type="str"), - group=dict( - type="dict", - options=dict( - address_group=dict(type="str"), - network_group=dict(type="str"), - ), - ), - ), - ), - destination=dict( - type="dict", - options=dict( - address=dict(type="str"), - port=dict(type="str"), - group=dict( - type="dict", - options=dict( - address_group=dict(type="str"), - network_group=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="dict", - options=dict( - established=dict(type="bool"), - new=dict(type="bool"), - related=dict(type="bool"), - invalid=dict(type="bool"), - ), - ), - enabled=dict(type="bool", default=True), - ) - - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - rule_sets=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - default_action=dict( - type="str", - choices=["drop", "reject", "accept"], - default="drop", - ), - description=dict(type="str"), - rules=dict(type="list", elements="dict", options=rule_spec), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_fw_rules(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state in ("deleted", "overridden") and not config: - for afi_key in ("name", "ipv6-name"): - try: - client.configure_delete(["firewall", afi_key]) - commands.append( - "delete firewall {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - changed = True - - elif state == "deleted" and config: - for entry in config: - afi = entry["afi"] - base_path = _FW_PATH[afi] - for rs in entry.get("rule_sets") or []: - try: - client.configure_delete(base_path + [rs["name"]]) - commands.append( - "delete {p} {n}".format( - p=" ".join(base_path), - n=rs["name"], - ), - ) - changed = True - except VyOSRestError: - pass - - elif state in ("merged", "replaced", "overridden"): - if state == "replaced": - for entry in config: - afi = entry["afi"] - base_path = _FW_PATH[afi] - for rs in entry.get("rule_sets") or []: - try: - client.configure_delete(base_path + [rs["name"]]) - commands.append( - "delete {p} {n}".format( - p=" ".join(base_path), - n=rs["name"], - ), - ) - except VyOSRestError: - pass - for entry in config: - afi = entry["afi"] - for rs in entry.get("rule_sets") or []: - _apply_rule_set(client, afi, rs, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_fw_rules(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_generate.py.preview b/plugins/modules/vyos_generate.py.preview deleted file mode 100644 index 448575a..0000000 --- a/plugins/modules/vyos_generate.py.preview +++ /dev/null @@ -1,102 +0,0 @@ -#!/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_generate -short_description: Execute generate commands on a VyOS device via REST API. -description: - - Sends a C(generate) operational command to a VyOS device via the HTTPS - REST API (C(/generate) endpoint). - - Commonly used to generate WireGuard keypairs, TLS certificates, etc. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Generate command tokens. - - E.g. C(["wireguard", "default-keypair"]). - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Generate WireGuard default keypair - vyos.rest.vyos_generate: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["wireguard", "default-keypair"] - - - name: Generate a new SSH host key - vyos.rest.vyos_generate: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["ssh-server", "host-key", "rsa"] -""" - -RETURN = r""" -output: - description: Text output produced by the generate command (may be empty). - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - try: - result = client.generate(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_image.py.preview b/plugins/modules/vyos_image.py.preview deleted file mode 100644 index be12705..0000000 --- a/plugins/modules/vyos_image.py.preview +++ /dev/null @@ -1,131 +0,0 @@ -#!/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_image -short_description: Manage VyOS system images via the REST API. -description: - - Adds or removes VyOS system images using the HTTPS REST API - (C(/image) endpoint). - - Use I(state=present) to download and install an image from a URL. - - Use I(state=absent) to delete an installed image by name. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - state: - description: - - C(present): Download and install the image from I(url). - - C(absent): Delete the image specified by I(name). - type: str - choices: [present, absent] - required: true - url: - description: - - HTTP(S) URL of the VyOS C(.iso) file to install. - - Required when I(state=present). - type: str - name: - description: - - Version name of the image to remove (e.g. C(1.4-rolling-202401010000)). - - Required when I(state=absent). - type: str - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: > - Request timeout in seconds. Image downloads can take several minutes; - increase this value accordingly. - type: int - default: 300 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Install the latest rolling release - vyos.rest.vyos_image: - hostname: 192.168.1.1 - api_key: MY-KEY - state: present - url: "https://downloads.vyos.io/rolling/current/amd64/vyos-rolling-latest.iso" - timeout: 600 - - - name: Remove an old image - vyos.rest.vyos_image: - hostname: 192.168.1.1 - api_key: MY-KEY - state: absent - name: "1.4-rolling-202312010000" -""" - -RETURN = r""" -output: - description: Text output from the image add/delete operation. - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - state=dict(type="str", required=True, choices=["present", "absent"]), - url=dict(type="str"), - name=dict(type="str"), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - # Override default timeout to 300s for image downloads - argument_spec["timeout"]["default"] = 300 - - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[ - ("state", "present", ["url"]), - ("state", "absent", ["name"]), - ], - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - - try: - if state == "present": - result = client.image_add(module.params["url"]) - else: - result = client.image_delete(module.params["name"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_interfaces.py.preview b/plugins/modules/vyos_interfaces.py.preview deleted file mode 100644 index 9edc1da..0000000 --- a/plugins/modules/vyos_interfaces.py.preview +++ /dev/null @@ -1,401 +0,0 @@ -#!/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 attributes on a VyOS device via the REST API. -description: - - Manages interface base attributes (description, MTU, enabled state, VIFs, - duplex, speed) on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_interfaces) but uses HTTP API. - - Supports Ethernet, Bonding, VXLAN, Loopback, and Virtual Tunnel 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. C(eth0), C(bond0), C(vti1), C(vxlan2)). - type: str - required: true - description: - description: Interface description. - type: str - enabled: - description: > - Administrative state. C(true) = up, C(false) = admin-down. - type: bool - default: true - mtu: - description: MTU in bytes. Applicable for Ethernet, Bonding, VXLAN, VTI. - type: int - duplex: - description: Duplex mode. Only for Ethernet interfaces. - type: str - choices: [auto, full, half] - speed: - description: Link speed. Only for Ethernet interfaces. - type: str - vifs: - description: List of VLAN sub-interface configurations. - type: list - elements: dict - suboptions: - vlan_id: - description: 802.1Q VLAN ID. - type: int - required: true - description: - description: VIF description. - type: str - enabled: - description: Administrative state of the VIF. - type: bool - default: true - state: - description: - - C(merged): Merge the provided config with existing interface config. - - C(replaced): Replace per-interface config with provided values. - - C(overridden): Override all interface config with provided values. - - C(deleted): Delete interface config for listed interfaces (or all). - - C(gathered): Read current interface config from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_interfaces - - module: vyos.rest.vyos_l3_interfaces -examples: | - - name: Set description and MTU on eth1 - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - description: "WAN uplink" - mtu: 1500 - enabled: true - state: merged - - - name: Disable eth2 - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth2 - enabled: false - state: merged - - - name: Create a VLAN sub-interface - vyos.rest.vyos_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - vifs: - - vlan_id: 100 - description: "VLAN 100" - state: merged -""" - -RETURN = r""" -before: - description: Interface configuration before the module ran. - returned: always - type: list -after: - description: Interface configuration after the module ran. - returned: when changed - type: list -gathered: - description: Interface config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: REST API set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "peth": "pseudo-ethernet", - "vtun": "openvpn", - "wg": "wireguard", - "br": "bridge", - "macsec": "macsec", - "tun": "tunnel", -} - - -def _iface_type(name): - """Infer the VyOS interface type from its name prefix.""" - for prefix, itype in _IFACE_TYPES.items(): - if name.startswith(prefix): - return itype - return "ethernet" - - -def _iface_base_path(name): - return ["interfaces", _iface_type(name), name] - - -def _get_interfaces(client): - try: - result = client.retrieve_show_config(["interfaces"]) - ifaces_raw = result.get("data") or {} - interfaces = [] - for itype, itype_data in ifaces_raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - entry = {"name": iname} - if "description" in idata: - entry["description"] = idata["description"] - if "mtu" in idata: - entry["mtu"] = int(idata["mtu"]) - entry["enabled"] = "disable" not in idata - if "duplex" in idata: - entry["duplex"] = idata["duplex"] - if "speed" in idata: - entry["speed"] = idata["speed"] - if "vif" in idata: - vifs = [] - for vid, vdata in idata["vif"].items(): - vif_entry = {"vlan_id": int(vid)} - if isinstance(vdata, dict): - if "description" in vdata: - vif_entry["description"] = vdata["description"] - vif_entry["enabled"] = "disable" not in vdata - vifs.append(vif_entry) - entry["vifs"] = vifs - interfaces.append(entry) - return interfaces - except VyOSRestError: - return [] - - -def _apply_interface(client, iface_cfg, commands): - name = iface_cfg["name"] - base = _iface_base_path(name) - - if iface_cfg.get("description") is not None: - client.configure_set(base + ["description"], iface_cfg["description"]) - commands.append( - "set interfaces {t} {n} description '{d}'".format( - t=_iface_type(name), - n=name, - d=iface_cfg["description"], - ), - ) - if iface_cfg.get("mtu") is not None: - client.configure_set(base + ["mtu"], str(iface_cfg["mtu"])) - commands.append( - "set interfaces {t} {n} mtu {m}".format( - t=_iface_type(name), - n=name, - m=iface_cfg["mtu"], - ), - ) - if "enabled" in iface_cfg: - if not iface_cfg["enabled"]: - client.configure_set(base + ["disable"]) - commands.append( - "set interfaces {t} {n} disable".format(t=_iface_type(name), n=name), - ) - else: - try: - client.configure_delete(base + ["disable"]) - commands.append( - "delete interfaces {t} {n} disable".format( - t=_iface_type(name), - n=name, - ), - ) - except VyOSRestError: - pass - if iface_cfg.get("duplex"): - client.configure_set(base + ["duplex"], iface_cfg["duplex"]) - commands.append( - "set interfaces {t} {n} duplex {d}".format( - t=_iface_type(name), - n=name, - d=iface_cfg["duplex"], - ), - ) - if iface_cfg.get("speed"): - client.configure_set(base + ["speed"], iface_cfg["speed"]) - commands.append( - "set interfaces {t} {n} speed {s}".format( - t=_iface_type(name), - n=name, - s=iface_cfg["speed"], - ), - ) - for vif in iface_cfg.get("vifs") or []: - vid = str(vif["vlan_id"]) - vif_base = base + ["vif", vid] - client.configure_set(vif_base) - commands.append( - "set interfaces {t} {n} vif {v}".format( - t=_iface_type(name), - n=name, - v=vid, - ), - ) - if vif.get("description"): - client.configure_set(vif_base + ["description"], vif["description"]) - commands.append( - "set interfaces {t} {n} vif {v} description '{d}'".format( - t=_iface_type(name), - n=name, - v=vid, - d=vif["description"], - ), - ) - if "enabled" in vif and not vif["enabled"]: - client.configure_set(vif_base + ["disable"]) - commands.append( - "set interfaces {t} {n} vif {v} disable".format( - t=_iface_type(name), - n=name, - v=vid, - ), - ) - - -def main(): - 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"), - vifs=dict( - type="list", - elements="dict", - options=dict( - vlan_id=dict(type="int", required=True), - description=dict(type="str"), - enabled=dict(type="bool", default=True), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_interfaces(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - base = _iface_base_path(iface["name"]) - # Only delete mutable attributes, not the interface itself - for attr in ["description", "mtu", "duplex", "speed"]: - if attr in iface: - try: - client.configure_delete(base + [attr]) - commands.append( - "delete interfaces {t} {n} {a}".format( - t=_iface_type(iface["name"]), - n=iface["name"], - a=attr, - ), - ) - except VyOSRestError: - pass - changed = True - - elif state in ("merged", "replaced", "overridden"): - for iface_cfg in config: - _apply_interface(client, iface_cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_interfaces(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_l3_interfaces.py.preview b/plugins/modules/vyos_l3_interfaces.py.preview deleted file mode 100644 index f6d4e6c..0000000 --- a/plugins/modules/vyos_l3_interfaces.py.preview +++ /dev/null @@ -1,381 +0,0 @@ -#!/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 attributes on VyOS via the REST API. -description: - - Manages IPv4 and IPv6 addresses on VyOS interfaces via the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_l3_interfaces) but uses the HTTP API. -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 interface name (e.g. C(eth0), C(eth1)). - type: str - required: true - ipv4: - description: List of IPv4 address assignments. - type: list - elements: dict - suboptions: - address: - description: IPv4 address in CIDR notation or C(dhcp). - type: str - required: true - ipv6: - description: List of IPv6 address assignments. - type: list - elements: dict - suboptions: - address: - description: IPv6 address in CIDR notation or C(dhcpv6) or C(autoconf). - type: str - required: true - vifs: - description: VLAN sub-interface L3 settings. - type: list - elements: dict - suboptions: - vlan_id: - description: 802.1Q VLAN ID. - type: int - required: true - ipv4: - description: IPv4 addresses for this VIF. - type: list - elements: dict - suboptions: - address: - type: str - required: true - ipv6: - description: IPv6 addresses for this VIF. - type: list - elements: dict - suboptions: - address: - type: str - required: true - state: - description: - - C(merged): Add addresses to the interface (preserve existing). - - C(replaced): Replace all addresses on each listed interface. - - C(overridden): Replace all addresses on all interfaces. - - C(deleted): Remove all addresses from listed (or all) interfaces. - - C(gathered): Read L3 config from device without changes. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_l3_interfaces - - module: vyos.rest.vyos_interfaces -examples: | - - name: Assign addresses to eth1 and eth2 - vyos.rest.vyos_l3_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - ipv4: - - address: 10.0.1.1/24 - ipv6: - - address: "2001:db8::1/64" - - name: eth2 - ipv4: - - address: dhcp - state: merged - - - name: Remove all addresses from eth1 - vyos.rest.vyos_l3_interfaces: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - name: eth1 - state: deleted -""" - -RETURN = r""" -before: - description: L3 interface config before the module ran. - returned: always - type: list -after: - description: L3 interface config after the module ran. - returned: when changed - type: list -gathered: - description: L3 config read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "vti": "vti", - "vxlan": "vxlan", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", -} - - -def _iface_type(name): - for prefix, t in _IFACE_TYPES.items(): - if name.startswith(prefix): - return t - return "ethernet" - - -def _base(name): - return ["interfaces", _iface_type(name), name] - - -def _get_l3_interfaces(client): - try: - result = client.retrieve_show_config(["interfaces"]) - raw = result.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - entry = {"name": iname, "ipv4": [], "ipv6": []} - for addr in _listify(idata.get("address")): - if ":" in addr: - entry["ipv6"].append({"address": addr}) - elif addr == "dhcp": - entry["ipv4"].append({"address": addr}) - else: - entry["ipv4"].append({"address": addr}) - # VIFs - if "vif" in idata: - vifs = [] - for vid, vdata in idata["vif"].items(): - vif_e = {"vlan_id": int(vid), "ipv4": [], "ipv6": []} - if isinstance(vdata, dict): - for addr in _listify(vdata.get("address")): - if ":" in addr: - vif_e["ipv6"].append({"address": addr}) - else: - vif_e["ipv4"].append({"address": addr}) - vifs.append(vif_e) - entry["vifs"] = vifs - if entry["ipv4"] or entry["ipv6"] or entry.get("vifs"): - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _listify(val): - """Return val as a list regardless of whether it's a str or list.""" - if val is None: - return [] - if isinstance(val, list): - return val - return [val] - - -def _set_addresses(client, name, ipv4, ipv6, commands): - base = _base(name) - itype = _iface_type(name) - for a in ipv4 or []: - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} address '{a}'".format( - t=itype, - n=name, - a=a["address"], - ), - ) - for a in ipv6 or []: - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} address '{a}'".format( - t=itype, - n=name, - a=a["address"], - ), - ) - - -def _delete_addresses(client, name, commands): - base = _base(name) - itype = _iface_type(name) - try: - client.configure_delete(base + ["address"]) - commands.append( - "delete interfaces {t} {n} address".format(t=itype, n=name), - ) - except VyOSRestError: - pass - - -def main(): - 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", required=True)), - ), - ipv6=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str", required=True)), - ), - 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", required=True)), - ), - ipv6=dict( - type="list", - elements="dict", - options=dict(address=dict(type="str", required=True)), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_l3_interfaces(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_addresses(client, iface["name"], commands) - changed = True - - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - # First remove existing addresses on targeted interfaces - targets = {i["name"] for i in config} - if state == "overridden": - targets = {i["name"] for i in before} - for iface in before: - if iface["name"] in targets: - _delete_addresses(client, iface["name"], commands) - - for iface_cfg in config: - name = iface_cfg["name"] - _set_addresses( - client, - name, - iface_cfg.get("ipv4"), - iface_cfg.get("ipv6"), - commands, - ) - for vif in iface_cfg.get("vifs") or []: - vid = str(vif["vlan_id"]) - base = _base(name) + ["vif", vid] - itype = _iface_type(name) - for a in (vif.get("ipv4") or []) + (vif.get("ipv6") or []): - client.configure_set(base + ["address"], a["address"]) - commands.append( - "set interfaces {t} {n} vif {v} address '{a}'".format( - t=itype, - n=name, - v=vid, - a=a["address"], - ), - ) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_l3_interfaces(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_lag_interfaces.py.preview b/plugins/modules/vyos_lag_interfaces.py.preview deleted file mode 100644 index 15fe1f3..0000000 --- a/plugins/modules/vyos_lag_interfaces.py.preview +++ /dev/null @@ -1,228 +0,0 @@ -#!/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_lag_interfaces -short_description: LAG/bonding interfaces resource module via REST API. -description: - - Manages Link Aggregation Group (LAG/bond) interfaces on VyOS via REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of LAG interface configurations. - type: list - elements: dict - suboptions: - name: - description: Bond interface name (e.g. bond0). - type: str - required: true - mode: - description: Bonding mode. - type: str - choices: [802.3ad, active-backup, broadcast, round-robin, - transmit-load-balance, adaptive-load-balance, xor-hash] - members: - description: Member interfaces. - type: list - elements: dict - suboptions: - member: - description: Interface name. - type: str - primary: - description: Primary interface name. - type: str - hash_policy: - description: Transmit hash policy. - type: str - choices: [layer2, layer2+3, layer3+4] - arp_monitor: - description: ARP monitoring settings. - type: dict - suboptions: - interval: - description: Monitoring interval in ms. - type: int - target: - description: Target IP addresses. - type: list - elements: str - state: - description: Desired state. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_MODE_MAP = { - "802.3ad": "802.3ad", - "active-backup": "active-backup", - "broadcast": "broadcast", - "round-robin": "round-robin", - "transmit-load-balance": "transmit-load-balance", - "adaptive-load-balance": "adaptive-load-balance", - "xor-hash": "xor-hash", -} - - -def _get(client): - try: - r = client.retrieve_show_config(["interfaces", "bonding"]) - data = r.get("data") or {} - out = [] - for bname, bdata in data.items(): - entry = {"name": bname} - if isinstance(bdata, dict): - if "mode" in bdata: - entry["mode"] = bdata["mode"] - if "primary" in bdata: - entry["primary"] = bdata["primary"] - if "hash-policy" in bdata: - entry["hash_policy"] = bdata["hash-policy"] - members = [] - for m in ( - bdata.get("member", {}).keys() if isinstance(bdata.get("member"), dict) else [] - ): - members.append({"member": m}) - if members: - entry["members"] = members - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _apply(client, cfg, commands): - name = cfg["name"] - base = ["interfaces", "bonding", name] - client.configure_set(base) - commands.append("set interfaces bonding {n}".format(n=name)) - - if cfg.get("mode"): - client.configure_set(base + ["mode"], cfg["mode"]) - commands.append("set interfaces bonding {n} mode {m}".format(n=name, m=cfg["mode"])) - if cfg.get("primary"): - client.configure_set(base + ["primary"], cfg["primary"]) - if cfg.get("hash_policy"): - client.configure_set(base + ["hash-policy"], cfg["hash_policy"]) - for m in cfg.get("members") or []: - client.configure_set(["interfaces", "ethernet", m["member"], "bond-group"], name) - commands.append("set interfaces ethernet {m} bond-group {n}".format(m=m["member"], n=name)) - arp = cfg.get("arp_monitor") or {} - if arp.get("interval"): - client.configure_set(base + ["arp-monitor", "interval"], str(arp["interval"])) - for t in arp.get("target") or []: - client.configure_set(base + ["arp-monitor", "target"], t) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - mode=dict(type="str", choices=list(_MODE_MAP.keys())), - 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"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - targets = {i["name"] for i in config} if config else {i["name"] for i in before} - for b in before: - if b["name"] in targets: - client.configure_delete(["interfaces", "bonding", b["name"]]) - commands.append("delete interfaces bonding {n}".format(n=b["name"])) - changed = True - elif state in ("merged", "replaced", "overridden"): - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_lldp_interfaces.py.preview b/plugins/modules/vyos_lldp_interfaces.py.preview deleted file mode 100644 index 4f012aa..0000000 --- a/plugins/modules/vyos_lldp_interfaces.py.preview +++ /dev/null @@ -1,227 +0,0 @@ -#!/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_lldp_interfaces -short_description: LLDP interfaces resource module via REST API. -description: - - Manages per-interface LLDP settings (enable/disable, location) on VyOS - via the HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of LLDP interface configurations. - type: list - elements: dict - suboptions: - name: - description: Interface name. - type: str - required: true - enable: - description: Enable LLDP on this interface (default true). - type: bool - default: true - location: - description: LLDP-MED location data. - type: dict - suboptions: - coordinate_based: - type: dict - suboptions: - altitude: - type: int - datum: - type: str - choices: [WGS84, NAD83, MLLW] - latitude: - type: str - required: true - longitude: - type: str - required: true - elin: - description: Emergency ELIN number (10-25 digits). - type: str - state: - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _get(client): - try: - r = client.retrieve_show_config(["service", "lldp", "interface"]) - data = r.get("data") or {} - out = [] - for iname, idata in data.items(): - entry = {"name": iname} - if isinstance(idata, dict): - entry["enable"] = "disable" not in idata - out.append(entry) - return out - except VyOSRestError: - return [] - - -def _apply(client, cfg, commands): - name = cfg["name"] - base = ["service", "lldp", "interface", name] - client.configure_set(base) - commands.append("set service lldp interface {n}".format(n=name)) - - if "enable" in cfg and not cfg["enable"]: - client.configure_set(base + ["disable"]) - commands.append("set service lldp interface {n} disable".format(n=name)) - - loc = cfg.get("location") or {} - coord = loc.get("coordinate_based") or {} - if coord: - lbase = base + ["location", "coordinate-based"] - if coord.get("latitude"): - client.configure_set(lbase + ["latitude"], coord["latitude"]) - if coord.get("longitude"): - client.configure_set(lbase + ["longitude"], coord["longitude"]) - if coord.get("altitude"): - client.configure_set(lbase + ["altitude"], str(coord["altitude"])) - if coord.get("datum"): - client.configure_set(lbase + ["datum"], coord["datum"]) - commands.append( - "set service lldp interface {n} location coordinate-based ...".format(n=name), - ) - if loc.get("elin"): - client.configure_set(base + ["location", "elin"], loc["elin"]) - commands.append( - "set service lldp interface {n} location elin {e}".format(n=name, e=loc["elin"]), - ) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - enable=dict(type="bool", default=True), - location=dict( - type="dict", - options=dict( - coordinate_based=dict( - type="dict", - options=dict( - altitude=dict(type="int"), - datum=dict(type="str", choices=["WGS84", "NAD83", "MLLW"]), - latitude=dict(type="str", required=True), - longitude=dict(type="str", required=True), - ), - ), - elin=dict(type="str"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - for cfg in config if config else before: - name = cfg["name"] if isinstance(cfg, dict) else cfg - try: - client.configure_delete(["service", "lldp", "interface", name]) - commands.append("delete service lldp interface {n}".format(n=name)) - changed = True - except VyOSRestError: - pass - elif state in ("merged", "replaced", "overridden"): - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospf_interfaces.py.preview b/plugins/modules/vyos_ospf_interfaces.py.preview deleted file mode 100644 index 37876d4..0000000 --- a/plugins/modules/vyos_ospf_interfaces.py.preview +++ /dev/null @@ -1,290 +0,0 @@ -#!/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: OSPF Interfaces Resource Module via REST API. -description: - - Manages per-interface OSPF parameters (cost, hello/dead timers, authentication, - passive mode, etc.) for both OSPFv2 and OSPFv3 via the VyOS HTTPS REST API. -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 - address_family: - description: OSPF settings per address family. - type: list - elements: dict - suboptions: - afi: - description: Address family (ipv4=OSPFv2, ipv6=OSPFv3). - type: str - choices: [ipv4, ipv6] - required: true - authentication: - type: dict - suboptions: - plaintext_password: - type: str - md5_key: - type: dict - suboptions: - key_id: - type: int - key: - type: str - bandwidth: - type: int - cost: - type: int - dead_interval: - type: int - hello_interval: - type: int - mtu_ignore: - type: bool - network: - type: str - priority: - type: int - retransmit_interval: - type: int - transmit_delay: - type: int - ifmtu: - type: int - instance: - type: str - passive: - type: bool - state: - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: list -after: - returned: when changed - type: list -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_IFACE_TYPES = { - "eth": "ethernet", - "bond": "bonding", - "lo": "loopback", - "dummy": "dummy", - "br": "bridge", - "wg": "wireguard", - "tun": "tunnel", - "vxlan": "vxlan", -} - - -def _itype(name): - for p, t in _IFACE_TYPES.items(): - if name.startswith(p): - return t - return "ethernet" - - -def _ospf_iface_base(name, afi): - itype = _itype(name) - if afi == "ipv4": - return ["interfaces", itype, name, "ip", "ospf"] - return ["interfaces", itype, name, "ipv6", "ospfv3"] - - -def _apply(client, cfg, commands): - name = cfg["name"] - for af in cfg.get("address_family") or []: - afi = af["afi"] - base = _ospf_iface_base(name, afi) - - int_map = { - "cost": "cost", - "bandwidth": "bandwidth", - "dead_interval": "dead-interval", - "hello_interval": "hello-interval", - "priority": "priority", - "retransmit_interval": "retransmit-interval", - "transmit_delay": "transmit-delay", - "ifmtu": "ifmtu", - } - for param, key in int_map.items(): - if af.get(param) is not None: - client.configure_set(base + [key], str(af[param])) - commands.append("set {b} {k} {v}".format(b=" ".join(base), k=key, v=af[param])) - - if af.get("mtu_ignore"): - client.configure_set(base + ["mtu-ignore"]) - commands.append("set {b} mtu-ignore".format(b=" ".join(base))) - if af.get("passive"): - client.configure_set(base + ["passive"]) - commands.append("set {b} passive".format(b=" ".join(base))) - if af.get("network"): - client.configure_set(base + ["network"], af["network"]) - if af.get("instance"): - client.configure_set(base + ["instance-id"], af["instance"]) - - auth = af.get("authentication") or {} - if auth.get("plaintext_password"): - client.configure_set( - base + ["authentication", "plaintext-password"], - auth["plaintext_password"], - ) - commands.append( - "set {b} authentication plaintext-password ...".format(b=" ".join(base)), - ) - md5 = auth.get("md5_key") or {} - if md5.get("key_id") and md5.get("key"): - client.configure_set( - base + ["authentication", "md5", "key-id", str(md5["key_id"]), "md5-key"], - md5["key"], - ) - - -def _get(client): - try: - r = client.retrieve_show_config(["interfaces"]) - raw = r.get("data") or {} - out = [] - for itype, itype_data in raw.items(): - if not isinstance(itype_data, dict): - continue - for iname, idata in itype_data.items(): - if not isinstance(idata, dict): - continue - afs = [] - ip_ospf = (idata.get("ip") or {}).get("ospf") - if ip_ospf: - afs.append({"afi": "ipv4", "ospf_data": ip_ospf}) - ip6_ospfv3 = (idata.get("ipv6") or {}).get("ospfv3") - if ip6_ospfv3: - afs.append({"afi": "ipv6", "ospfv3_data": ip6_ospfv3}) - if afs: - out.append({"name": iname, "address_family": afs}) - return out - except VyOSRestError: - return [] - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str"), - address_family=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - authentication=dict( - type="dict", - options=dict( - plaintext_password=dict(type="str", no_log=True), - md5_key=dict( - type="dict", - 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"), - mtu_ignore=dict(type="bool"), - network=dict(type="str"), - priority=dict(type="int"), - retransmit_interval=dict(type="int"), - transmit_delay=dict(type="int"), - ifmtu=dict(type="int"), - instance=dict(type="str"), - passive=dict(type="bool"), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - for cfg in config: - _apply(client, cfg, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospfv2.py.preview b/plugins/modules/vyos_ospfv2.py.preview deleted file mode 100644 index 063d0dc..0000000 --- a/plugins/modules/vyos_ospfv2.py.preview +++ /dev/null @@ -1,487 +0,0 @@ -#!/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: OSPFv2 resource module via REST API. -description: - - Manages OSPFv2 (OSPF for IPv4) configuration on VyOS via HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: OSPFv2 configuration. - type: dict - suboptions: - areas: - type: list - elements: dict - suboptions: - area_id: - type: str - area_type: - type: dict - suboptions: - normal: - type: bool - nssa: - type: dict - suboptions: - set: - type: bool - default_cost: - type: int - no_summary: - type: bool - translate: - type: str - choices: [always, candidate, never] - stub: - type: dict - suboptions: - set: - type: bool - default_cost: - type: int - no_summary: - type: bool - authentication: - type: str - choices: [plaintext-password, md5] - network: - type: list - elements: dict - suboptions: - address: - type: str - range: - type: list - elements: dict - suboptions: - address: - type: str - cost: - type: int - not_advertise: - type: bool - substitute: - type: str - shortcut: - type: str - choices: [default, disable, enable] - auto_cost: - type: dict - suboptions: - reference_bandwidth: - type: int - default_information: - type: dict - suboptions: - originate: - type: dict - suboptions: - always: - type: bool - metric: - type: int - metric_type: - type: int - route_map: - type: str - log_adjacency_changes: - type: str - choices: [detail] - max_metric: - type: dict - suboptions: - router_lsa: - type: dict - suboptions: - administrative: - type: bool - on_shutdown: - type: int - on_startup: - type: int - mpls_te: - type: dict - suboptions: - enabled: - type: bool - router_address: - type: str - neighbor: - type: list - elements: dict - suboptions: - neighbor_id: - type: str - poll_interval: - type: int - priority: - type: int - parameters: - type: dict - suboptions: - router_id: - type: str - opaque_lsa: - type: bool - rfc1583_compatibility: - type: bool - abr_type: - type: str - choices: [cisco, ibm, shortcut, standard] - passive_interface: - type: list - elements: str - redistribute: - type: list - elements: dict - suboptions: - route_type: - type: str - choices: [bgp, connected, kernel, rip, static] - metric: - type: int - metric_type: - type: int - route_map: - type: str - state: - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BASE = ["protocols", "ospf"] - - -def _get(client): - try: - r = client.retrieve_show_config(_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - params = config.get("parameters") or {} - if params.get("router_id"): - client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) - commands.append("set protocols ospf parameters router-id {r}".format(r=params["router_id"])) - if params.get("opaque_lsa"): - client.configure_set(_BASE + ["parameters", "opaque-lsa"]) - if params.get("rfc1583_compatibility"): - client.configure_set(_BASE + ["parameters", "rfc1583-compatibility"]) - if params.get("abr_type"): - client.configure_set(_BASE + ["parameters", "abr-type"], params["abr_type"]) - - ac = config.get("auto_cost") or {} - if ac.get("reference_bandwidth"): - client.configure_set( - _BASE + ["auto-cost", "reference-bandwidth"], - str(ac["reference_bandwidth"]), - ) - commands.append( - "set protocols ospf auto-cost reference-bandwidth {b}".format( - b=ac["reference_bandwidth"], - ), - ) - - if config.get("log_adjacency_changes"): - client.configure_set(_BASE + ["log-adjacency-changes", config["log_adjacency_changes"]]) - - for nbr in config.get("neighbor") or []: - nb = _BASE + ["neighbor", nbr["neighbor_id"]] - client.configure_set(nb) - if nbr.get("poll_interval"): - client.configure_set(nb + ["poll-interval"], str(nbr["poll_interval"])) - if nbr.get("priority"): - client.configure_set(nb + ["priority"], str(nbr["priority"])) - - for pi in config.get("passive_interface") or []: - client.configure_set(_BASE + ["passive-interface"], pi) - commands.append("set protocols ospf passive-interface {i}".format(i=pi)) - - for redist in config.get("redistribute") or []: - rb = _BASE + ["redistribute", redist["route_type"]] - client.configure_set(rb) - commands.append("set protocols ospf redistribute {r}".format(r=redist["route_type"])) - if redist.get("metric"): - client.configure_set(rb + ["metric"], str(redist["metric"])) - if redist.get("metric_type"): - client.configure_set(rb + ["metric-type"], str(redist["metric_type"])) - if redist.get("route_map"): - client.configure_set(rb + ["route-map"], redist["route_map"]) - - di = (config.get("default_information") or {}).get("originate") or {} - if di: - dib = _BASE + ["default-information", "originate"] - client.configure_set(dib) - commands.append("set protocols ospf default-information originate") - if di.get("always"): - client.configure_set(dib + ["always"]) - if di.get("metric"): - client.configure_set(dib + ["metric"], str(di["metric"])) - if di.get("metric_type"): - client.configure_set(dib + ["metric-type"], str(di["metric_type"])) - if di.get("route_map"): - client.configure_set(dib + ["route-map"], di["route_map"]) - - for area in config.get("areas") or []: - aid = area["area_id"] - ab = _BASE + ["area", aid] - client.configure_set(ab) - commands.append("set protocols ospf area {a}".format(a=aid)) - - at = area.get("area_type") or {} - if at.get("normal"): - pass # default - nssa = at.get("nssa") or {} - if nssa.get("set"): - client.configure_set(ab + ["area-type", "nssa"]) - if nssa.get("default_cost"): - client.configure_set( - ab + ["area-type", "nssa", "default-cost"], - str(nssa["default_cost"]), - ) - if nssa.get("no_summary"): - client.configure_set(ab + ["area-type", "nssa", "no-summary"]) - stub = at.get("stub") or {} - if stub.get("set"): - client.configure_set(ab + ["area-type", "stub"]) - if stub.get("default_cost"): - client.configure_set( - ab + ["area-type", "stub", "default-cost"], - str(stub["default_cost"]), - ) - - if area.get("authentication"): - client.configure_set(ab + ["authentication"], area["authentication"]) - - for net in area.get("network") or []: - client.configure_set(ab + ["network"], net["address"]) - commands.append( - "set protocols ospf area {a} network {n}".format( - a=aid, - n=net["address"], - ), - ) - - for rng in area.get("range") or []: - rb2 = ab + ["range", rng["address"]] - client.configure_set(rb2) - if rng.get("cost"): - client.configure_set(rb2 + ["cost"], str(rng["cost"])) - if rng.get("not_advertise"): - client.configure_set(rb2 + ["not-advertise"]) - if rng.get("substitute"): - client.configure_set(rb2 + ["substitute"], rng["substitute"]) - - if area.get("shortcut"): - client.configure_set(ab + ["shortcut"], area["shortcut"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - areas=dict( - type="list", - elements="dict", - options=dict( - area_id=dict(type="str"), - 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")), - ), - range=dict( - type="list", - elements="dict", - options=dict( - address=dict(type="str"), - 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"), - ), - ), - ), - ), - log_adjacency_changes=dict(type="str", choices=["detail"]), - max_metric=dict(type="dict"), - mpls_te=dict( - type="dict", - options=dict( - enabled=dict(type="bool"), - router_address=dict(type="str"), - ), - ), - neighbor=dict( - type="list", - elements="dict", - options=dict( - neighbor_id=dict(type="str"), - poll_interval=dict(type="int"), - priority=dict(type="int"), - ), - ), - parameters=dict( - type="dict", - options=dict( - router_id=dict(type="str"), - opaque_lsa=dict(type="bool"), - rfc1583_compatibility=dict(type="bool"), - abr_type=dict(type="str", choices=["cisco", "ibm", "shortcut", "standard"]), - ), - ), - 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( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BASE) - commands.append("delete protocols ospf") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BASE) - commands.append("delete protocols ospf") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ospfv3.py.preview b/plugins/modules/vyos_ospfv3.py.preview deleted file mode 100644 index 0de49d7..0000000 --- a/plugins/modules/vyos_ospfv3.py.preview +++ /dev/null @@ -1,257 +0,0 @@ -#!/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: OSPFv3 resource module via REST API. -description: - - Manages OSPFv3 (OSPF for IPv6) configuration on VyOS via HTTPS REST API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: OSPFv3 configuration. - type: dict - suboptions: - areas: - type: list - elements: dict - suboptions: - area_id: - type: str - export_list: - type: str - import_list: - type: str - interface: - description: Interfaces in this area. - type: list - elements: dict - suboptions: - name: - type: str - range: - type: list - elements: dict - suboptions: - address: - type: str - advertise: - type: bool - not_advertise: - type: bool - parameters: - type: dict - suboptions: - router_id: - type: str - redistribute: - type: list - elements: dict - suboptions: - route_type: - type: str - choices: [bgp, connected, kernel, ripng, static] - route_map: - type: str - state: - type: str - choices: [merged, replaced, deleted, gathered] - default: merged - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -before: - returned: always - type: dict -after: - returned: when changed - type: dict -commands: - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_BASE = ["protocols", "ospfv3"] - - -def _get(client): - try: - r = client.retrieve_show_config(_BASE) - return r.get("data") or {} - except VyOSRestError: - return {} - - -def _apply(client, config, commands): - params = config.get("parameters") or {} - if params.get("router_id"): - client.configure_set(_BASE + ["parameters", "router-id"], params["router_id"]) - commands.append( - "set protocols ospfv3 parameters router-id {r}".format(r=params["router_id"]), - ) - - for redist in config.get("redistribute") or []: - rb = _BASE + ["redistribute", redist["route_type"]] - client.configure_set(rb) - commands.append("set protocols ospfv3 redistribute {r}".format(r=redist["route_type"])) - if redist.get("route_map"): - client.configure_set(rb + ["route-map"], redist["route_map"]) - - for area in config.get("areas") or []: - aid = area["area_id"] - ab = _BASE + ["area", aid] - client.configure_set(ab) - commands.append("set protocols ospfv3 area '{a}'".format(a=aid)) - if area.get("export_list"): - client.configure_set(ab + ["export-list"], area["export_list"]) - commands.append( - "set protocols ospfv3 area {a} export-list {e}".format( - a=aid, - e=area["export_list"], - ), - ) - if area.get("import_list"): - client.configure_set(ab + ["import-list"], area["import_list"]) - for iface in area.get("interface") or []: - client.configure_set(ab + ["interface"], iface["name"]) - commands.append( - "set protocols ospfv3 area {a} interface {i}".format( - a=aid, - i=iface["name"], - ), - ) - for rng in area.get("range") or []: - rb2 = ab + ["range", rng["address"]] - client.configure_set(rb2) - commands.append( - "set protocols ospfv3 area {a} range {r}".format( - a=aid, - r=rng["address"], - ), - ) - if rng.get("advertise"): - client.configure_set(rb2 + ["advertise"]) - if rng.get("not_advertise"): - client.configure_set(rb2 + ["not-advertise"]) - - -def main(): - argument_spec = dict( - config=dict( - type="dict", - options=dict( - areas=dict( - type="list", - elements="dict", - options=dict( - area_id=dict(type="str"), - export_list=dict(type="str"), - import_list=dict(type="str"), - interface=dict( - type="list", - elements="dict", - options=dict(name=dict(type="str")), - ), - range=dict( - type="list", - elements="dict", - options=dict( - address=dict(type="str"), - 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( - type="str", - default="merged", - choices=["merged", "replaced", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - required_if=[("state", "merged", ["config"]), ("state", "replaced", ["config"])], - supports_check_mode=True, - ) - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") - commands = [] - changed = False - before = _get(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state == "deleted": - if before: - client.configure_delete(_BASE) - commands.append("delete protocols ospfv3") - changed = True - elif state in ("merged", "replaced"): - if state == "replaced" and before: - client.configure_delete(_BASE) - commands.append("delete protocols ospfv3") - _apply(client, config, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_ping.py.preview b/plugins/modules/vyos_ping.py.preview deleted file mode 100644 index 45368d1..0000000 --- a/plugins/modules/vyos_ping.py.preview +++ /dev/null @@ -1,180 +0,0 @@ -#!/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_ping -short_description: Test reachability using ping via VyOS REST API. -description: - - Sends an ICMP ping via the VyOS HTTPS REST API (/show endpoint) and - checks reachability. Mirrors C(vyos.vyos.vyos_ping). -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - dest: - description: Destination IP address or hostname. - type: str - required: true - count: - description: Number of packets to send. - type: int - default: 5 - source: - description: Source interface or IP address. - type: str - ttl: - description: Time-to-live value. - type: int - size: - description: Packet size in bytes. - type: int - interval: - description: Interval between pings in seconds. - type: int - state: - description: C(present) = expect success; C(absent) = expect failure. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: Ping command tokens sent to the API. - returned: always - type: list -packet_loss: - description: Packet loss percentage string. - returned: always - type: str -packets_rx: - description: Packets received. - returned: always - type: int -packets_tx: - description: Packets transmitted. - returned: always - type: int -rtt: - description: RTT statistics dict (min/avg/max/mdev). - returned: when available - type: dict -""" - -import re - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _build_ping_path(params): - path = ["ping", params["dest"]] - if params.get("count"): - path += ["count", str(params["count"])] - if params.get("source"): - path += ["interface", params["source"]] - if params.get("ttl"): - path += ["ttl", str(params["ttl"])] - if params.get("size"): - path += ["size", str(params["size"])] - return path - - -def _parse_ping_output(output): - stats = {"packet_loss": "100%", "packets_rx": 0, "packets_tx": 0} - tx_match = re.search(r"(\d+) packets transmitted", output) - rx_match = re.search(r"(\d+) received", output) - loss_match = re.search(r"(\d+(?:\.\d+)?%)\s+packet loss", output) - if tx_match: - stats["packets_tx"] = int(tx_match.group(1)) - if rx_match: - stats["packets_rx"] = int(rx_match.group(1)) - if loss_match: - stats["packet_loss"] = loss_match.group(1) - rtt_match = re.search( - r"rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)", - output, - ) - if rtt_match: - stats["rtt"] = { - "min": rtt_match.group(1), - "avg": rtt_match.group(2), - "max": rtt_match.group(3), - "mdev": rtt_match.group(4), - } - return stats - - -def main(): - argument_spec = dict( - dest=dict(type="str", required=True), - count=dict(type="int", default=5), - source=dict(type="str"), - ttl=dict(type="int"), - size=dict(type="int"), - interval=dict(type="int"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - - if module.check_mode: - module.exit_json(changed=False, commands=[], packet_loss="0%", packets_rx=0, packets_tx=0) - - client = VyOSRestClient(module) - path = _build_ping_path(module.params) - - try: - result = client.show(path) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - output = result.get("data", "") - stats = _parse_ping_output(output) - reachable = stats["packets_rx"] > 0 - - if module.params["state"] == "present" and not reachable: - module.fail_json( - msg="Ping to {dest} failed — no packets received.".format(dest=module.params["dest"]), - **stats, - ) - elif module.params["state"] == "absent" and reachable: - module.fail_json( - msg="Expected {dest} to be unreachable but ping succeeded.".format( - dest=module.params["dest"], - ), - **stats, - ) - - module.exit_json(changed=False, commands=path, **stats) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_prefix_lists.py.preview b/plugins/modules/vyos_prefix_lists.py.preview deleted file mode 100644 index 5334e7a..0000000 --- a/plugins/modules/vyos_prefix_lists.py.preview +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: vyos_prefix_lists -short_description: Manage prefix-list configuration on VyOS devices using REST API -description: - - Manages IPv4 and IPv6 prefix lists on VyOS 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: List of prefix-list configurations grouped by address family. - type: list - elements: dict - suboptions: - afi: - description: Address family identifier. - type: str - choices: [ipv4, ipv6] - required: true - prefix_lists: - description: Named prefix lists. - type: list - elements: dict - suboptions: - name: - description: Prefix list name. - type: str - required: true - description: - description: Prefix list description. - type: str - entries: - description: Prefix list rules. - type: list - elements: dict - suboptions: - sequence: - description: Rule sequence number. - type: int - required: true - description: - description: Rule description. - type: str - action: - description: Permit or deny. - type: str - choices: [permit, deny] - ge: - description: Minimum prefix length. - type: int - le: - description: Maximum prefix length. - type: int - prefix: - description: Network prefix to match. - type: str - - state: - description: - - Desired state of the prefix-list configuration. - - C(merged) adds or updates entries without removing existing ones. - - C(replaced) replaces each named prefix list mentioned in config. - - C(overridden) replaces all prefix lists for the given AFIs. - - C(deleted) removes prefix lists. Without config removes all. - - 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 prefix list configuration - 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 - entries: - - sequence: 5 - action: permit - prefix: 2001:db8:8000::/35 - le: 37 - state: merged - -- name: Delete all prefix lists - vyos.rest.vyos_prefix_lists: - state: deleted - -- name: Gather current prefix list configuration - vyos.rest.vyos_prefix_lists: - state: gathered -""" - -RETURN = r""" -before: - description: Prefix list configuration before this module ran. - returned: always - type: list - -after: - description: Prefix list 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 prefix list 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 - - -# AFI → API key mapping -_PL_KEY = {"ipv4": "prefix-list", "ipv6": "prefix-list6"} - - -# ------------------------------------------------------------ -# Parsing: API response → argspec list -# ------------------------------------------------------------ - - -def get_running_config(vyos): - """ - Fetch current prefix list config and return in argspec list format: - [{"afi": "ipv4", "prefix_lists": [...]}, {"afi": "ipv6", ...}] - """ - raw = vyos.get_config(["policy"]) - if not raw or not isinstance(raw, dict): - return [] - - result = [] - for afi, api_key in [("ipv4", "prefix-list"), ("ipv6", "prefix-list6")]: - pl_data = raw.get(api_key) or {} - if not isinstance(pl_data, dict): - continue - - pls = [] - for pl_name, pl_info in sorted(pl_data.items()): - entry = {"name": pl_name} - pl_info = pl_info or {} - - if pl_info.get("description"): - entry["description"] = pl_info["description"] - - rules = [] - for seq, rdata in sorted( - (pl_info.get("rule") or {}).items(), - key=lambda x: int(x[0]), - ): - rdata = rdata or {} - rule = {"sequence": int(seq)} - if rdata.get("action"): - rule["action"] = rdata["action"] - if rdata.get("prefix"): - rule["prefix"] = rdata["prefix"] - if rdata.get("ge") is not None: - rule["ge"] = int(rdata["ge"]) - if rdata.get("le") is not None: - rule["le"] = int(rdata["le"]) - if rdata.get("description"): - rule["description"] = rdata["description"] - rules.append(rule) - - if rules: - entry["entries"] = rules - pls.append(entry) - - if pls: - result.append({"afi": afi, "prefix_lists": pls}) - - return result - - -# ------------------------------------------------------------ -# Internal normalization: argspec list → nested dicts for diffing -# ------------------------------------------------------------ - - -def _normalize(config): - """ - Convert argspec list to nested dict keyed by afi → pl_name → rule_seq. - { - "ipv4": { - "AnsibleIPv4PrefixList": { - "description": "...", - "rules": {2: {...}, 3: {...}} - } - }, - "ipv6": {...} - } - """ - result = {"ipv4": {}, "ipv6": {}} - for entry in config or []: - afi = entry.get("afi") - if afi not in result: - continue - for pl in entry.get("prefix_lists") or []: - name = pl["name"] - rules = {} - for r in pl.get("entries") or []: - seq = r["sequence"] - rules[seq] = {k: v for k, v in r.items() if k != "sequence" and v is not None} - result[afi][name] = { - "description": pl.get("description"), - "rules": rules, - } - return result - - -# ------------------------------------------------------------ -# Command builders -# ------------------------------------------------------------ - - -def _rule_cmds(base, seq, want_rule, have_rule, state): - """Build commands for a single rule entry.""" - cmds = [] - rbase = base + ["rule", str(seq)] - - if seq not in (have_rule or {}): - # New rule - for field, api_key in [ - ("action", "action"), - ("prefix", "prefix"), - ("description", "description"), - ]: - if want_rule.get(field): - cmds.append(("set", rbase + [api_key, want_rule[field]])) - if want_rule.get("ge") is not None: - cmds.append(("set", rbase + ["ge", str(want_rule["ge"])])) - if want_rule.get("le") is not None: - cmds.append(("set", rbase + ["le", str(want_rule["le"])])) - else: - # Existing rule — update changed fields - h = have_rule[seq] - for field, api_key in [ - ("action", "action"), - ("prefix", "prefix"), - ("description", "description"), - ]: - if want_rule.get(field) and want_rule[field] != h.get(field): - cmds.append(("set", rbase + [api_key, want_rule[field]])) - for field in ("ge", "le"): - if want_rule.get(field) is not None and want_rule[field] != h.get(field): - cmds.append(("set", rbase + [field, str(want_rule[field])])) - - return cmds - - -def build_commands(config, have_raw, state): - """ - Build command tuples to move from have → want. - """ - cmds = [] - - if state == "deleted": - if not config: - # Delete all prefix lists for both AFIs - for afi, api_key in _PL_KEY.items(): - # Only delete if something exists - if any(e.get("afi") == afi for e in have_raw): - cmds.append(("delete", ["policy", api_key])) - else: - # Delete only specified prefix lists - for entry in config: - afi = entry["afi"] - api_key = _PL_KEY[afi] - for pl in entry.get("prefix_lists") or []: - cmds.append(("delete", ["policy", api_key, pl["name"]])) - return cmds - - want = _normalize(config) - have = _normalize(have_raw) - - for afi, api_key in _PL_KEY.items(): - want_afi = want.get(afi, {}) - have_afi = have.get(afi, {}) - - if state == "overridden": - # Delete PLs present on device but not in want - for pl_name in set(have_afi) - set(want_afi): - cmds.append(("delete", ["policy", api_key, pl_name])) - - for pl_name, want_pl in want_afi.items(): - have_pl = have_afi.get(pl_name, {}) - base = ["policy", api_key, pl_name] - - if state == "replaced" and pl_name in have_afi: - # Full replacement — delete existing then re-set - cmds.append(("delete", ["policy", api_key, pl_name])) - have_pl = {} - - # Description - if want_pl.get("description") and want_pl["description"] != have_pl.get("description"): - cmds.append(("set", base + ["description", want_pl["description"]])) - - # Rules - want_rules = want_pl.get("rules", {}) - have_rules = have_pl.get("rules", {}) - - if state == "replaced": - # have_rules is now empty after delete above - have_rules = {} - - for seq, want_rule in want_rules.items(): - cmds += _rule_cmds(base, seq, want_rule, have_rules, state) - - # merged: leave extra have rules alone - # replaced: they were deleted with the parent node above - - return cmds - - -# ------------------------------------------------------------ -# Argument spec -# ------------------------------------------------------------ - -ARGUMENT_SPEC = dict( - config=dict(type="list", elements="dict"), - state=dict( - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), -) - - -# ------------------------------------------------------------ -# Main -# ------------------------------------------------------------ - - -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_reset.py.preview b/plugins/modules/vyos_reset.py.preview deleted file mode 100644 index ca3e343..0000000 --- a/plugins/modules/vyos_reset.py.preview +++ /dev/null @@ -1,108 +0,0 @@ -#!/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_reset -short_description: Execute reset commands on a VyOS device via REST API. -description: - - Sends a C(reset) operational command to a VyOS device via the HTTPS REST - API (C(/reset) endpoint). - - Useful for resetting BGP sessions, VPN tunnels, ARP caches, etc. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Reset command path tokens. - - E.g. C(["ip", "bgp", "192.0.2.1"]) to reset a BGP peer. - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -examples: | - - name: Reset a BGP peer - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["ip", "bgp", "192.0.2.11"] - - - name: Reset an IPsec VPN peer - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["vpn", "ipsec-peer", "203.0.113.5"] - - - name: Clear ARP cache - vyos.rest.vyos_reset: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["arp", "cache"] -""" - -RETURN = r""" -output: - description: Text output from the reset command (usually empty on success). - returned: success - type: str -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=False, - ) - - client = VyOSRestClient(module) - try: - result = client.reset(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=True, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_show.py.preview b/plugins/modules/vyos_show.py.preview deleted file mode 100644 index fbe6c1b..0000000 --- a/plugins/modules/vyos_show.py.preview +++ /dev/null @@ -1,119 +0,0 @@ -#!/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_show -short_description: Execute op-mode show commands on a VyOS device via REST API. -description: - - Sends a C(show) operational-mode command to a VyOS device via the - HTTPS REST API (C(/show) endpoint) and returns the text output. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - path: - description: - - Operational-mode command tokens following C(show). - - E.g. C(["interfaces"]) maps to C(show interfaces). - type: list - elements: str - required: true - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -notes: - - This module never modifies device state. -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.rest.vyos_configure - - module: vyos.rest.vyos_retrieve -examples: | - - name: Show interfaces - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["interfaces"] - register: iface_output - - - name: Show version - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["version"] - register: ver_output - - - name: Show installed images - vyos.rest.vyos_show: - hostname: 192.168.1.1 - api_key: MY-KEY - path: ["system", "image"] - register: images -""" - -RETURN = r""" -output: - description: Raw text output from the show command. - returned: success - type: str - sample: | - Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down - Interface IP Address S/L Description - eth0 10.0.0.1/24 u/u WAN -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - path=dict(type="list", elements="str", required=True), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - try: - result = client.show(module.params["path"]) - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=False, output=result.get("data", "")) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_static_routes.py.preview b/plugins/modules/vyos_static_routes.py.preview deleted file mode 100644 index f203603..0000000 --- a/plugins/modules/vyos_static_routes.py.preview +++ /dev/null @@ -1,397 +0,0 @@ -#!/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_static_routes -short_description: Manage static routes on VyOS via the REST API. -description: - - Manages IPv4 and IPv6 static routes on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_static_routes) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - config: - description: List of static route address-family configurations. - type: list - elements: dict - suboptions: - address_families: - description: List of address family route groups. - type: list - elements: dict - suboptions: - afi: - description: Address family indicator. - type: str - choices: [ipv4, ipv6] - required: true - routes: - description: List of static route entries. - type: list - elements: dict - suboptions: - dest: - description: Destination prefix in CIDR notation. - type: str - required: true - blackhole_config: - description: Blackhole route configuration. - type: dict - suboptions: - distance: - description: Administrative distance (1-255). - type: int - type: - description: Blackhole type. - type: str - next_hops: - description: List of next-hop addresses. - type: list - elements: dict - suboptions: - forward_router_address: - description: Next-hop IP address. - type: str - required: true - admin_distance: - description: Administrative distance for this next-hop. - type: int - enabled: - description: Whether this next-hop is enabled. - type: bool - default: true - interface: - description: Outgoing interface name. - type: str - state: - description: - - C(merged): Add routes (preserve existing). - - C(replaced): Replace routes for listed destinations. - - C(overridden): Replace the entire static route table. - - C(deleted): Remove listed (or all) static routes. - - C(gathered): Read static routes from device. - type: str - choices: [merged, replaced, overridden, deleted, gathered] - default: merged - hostname: - description: IP address or FQDN of the VyOS device. - type: str - required: true - port: - description: HTTPS port for the REST API. - type: int - default: 443 - api_key: - description: API key configured on the device. - type: str - required: true - no_log: true - timeout: - description: Request timeout in seconds. - type: int - default: 30 - verify_ssl: - description: Validate the device's TLS certificate. - type: bool - default: false -requirements: - - VyOS 1.3+ -seealso: - - module: vyos.vyos.vyos_static_routes -examples: | - - name: Add IPv4 static routes - vyos.rest.vyos_static_routes: - hostname: 192.168.1.1 - api_key: MY-KEY - config: - - address_families: - - afi: ipv4 - routes: - - dest: 192.0.2.0/24 - next_hops: - - forward_router_address: 10.0.0.1 - - dest: 203.0.113.0/24 - blackhole_config: - distance: 200 - state: merged - - - name: Delete all static routes - vyos.rest.vyos_static_routes: - hostname: 192.168.1.1 - api_key: MY-KEY - state: deleted -""" - -RETURN = r""" -before: - description: Static route config before the module ran. - returned: always - type: list -after: - description: Static route config after the module ran. - returned: when changed - type: list -gathered: - description: Static routes read from device (state=gathered). - returned: when state is gathered - type: list -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_ROUTE_BASE = { - "ipv4": ["protocols", "static", "route"], - "ipv6": ["protocols", "static", "route6"], -} - - -def _get_static_routes(client): - try: - result = client.retrieve_show_config(["protocols", "static"]) - data = result.get("data") or {} - out = [] - for afi, route_key in [("ipv4", "route"), ("ipv6", "route6")]: - routes_data = data.get(route_key, {}) - if not isinstance(routes_data, dict): - continue - routes = [] - for dest, rdata in routes_data.items(): - route_entry = {"dest": dest} - if isinstance(rdata, dict): - if "blackhole" in rdata: - bh = rdata["blackhole"] - bc = {} - if isinstance(bh, dict) and "distance" in bh: - bc["distance"] = int(bh["distance"]) - route_entry["blackhole_config"] = bc - next_hops = [] - for nh_key in ("next-hop", "next_hop"): - nh_data = rdata.get(nh_key, {}) - if isinstance(nh_data, dict): - for nh_addr, nh_opts in nh_data.items(): - nh = {"forward_router_address": nh_addr} - if isinstance(nh_opts, dict): - if "distance" in nh_opts: - nh["admin_distance"] = int(nh_opts["distance"]) - nh["enabled"] = "disable" not in nh_opts - next_hops.append(nh) - if next_hops: - route_entry["next_hops"] = next_hops - routes.append(route_entry) - if routes: - out.append({"address_families": [{"afi": afi, "routes": routes}]}) - return out - except VyOSRestError: - return [] - - -def _apply_route(client, afi, route, commands): - base = _ROUTE_BASE[afi] - dest = route["dest"] - - if route.get("blackhole_config") is not None: - bh_path = base + [dest, "blackhole"] - client.configure_set(bh_path) - commands.append("set {p}".format(p=" ".join(bh_path))) - dist = route["blackhole_config"].get("distance") - if dist: - client.configure_set(bh_path + ["distance"], str(dist)) - commands.append( - "set {p} distance {d}".format(p=" ".join(bh_path), d=dist), - ) - - for nh in route.get("next_hops") or []: - nh_addr = nh["forward_router_address"] - nh_path = base + [dest, "next-hop", nh_addr] - client.configure_set(nh_path) - commands.append("set {p}".format(p=" ".join(nh_path))) - if nh.get("admin_distance"): - client.configure_set(nh_path + ["distance"], str(nh["admin_distance"])) - commands.append( - "set {p} distance {d}".format( - p=" ".join(nh_path), - d=nh["admin_distance"], - ), - ) - if "enabled" in nh and not nh["enabled"]: - client.configure_set(nh_path + ["disable"]) - commands.append("set {p} disable".format(p=" ".join(nh_path))) - if nh.get("interface"): - client.configure_set(nh_path + ["interface"], nh["interface"]) - commands.append( - "set {p} interface {i}".format( - p=" ".join(nh_path), - i=nh["interface"], - ), - ) - - -def main(): - argument_spec = dict( - config=dict( - type="list", - elements="dict", - options=dict( - address_families=dict( - type="list", - elements="dict", - options=dict( - afi=dict(type="str", required=True, choices=["ipv4", "ipv6"]), - routes=dict( - type="list", - elements="dict", - options=dict( - dest=dict(type="str", required=True), - blackhole_config=dict( - type="dict", - options=dict( - distance=dict(type="int"), - type=dict(type="str"), - ), - ), - next_hops=dict( - type="list", - elements="dict", - options=dict( - forward_router_address=dict( - type="str", - required=True, - ), - admin_distance=dict(type="int"), - enabled=dict(type="bool", default=True), - interface=dict(type="str"), - ), - ), - ), - ), - ), - ), - ), - ), - state=dict( - type="str", - default="merged", - choices=["merged", "replaced", "overridden", "deleted", "gathered"], - ), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - - module = AnsibleModule( - argument_spec=argument_spec, - supports_check_mode=True, - ) - - client = VyOSRestClient(module) - state = module.params["state"] - config = module.params.get("config") or [] - commands = [] - changed = False - - before = _get_static_routes(client) - - if state == "gathered": - module.exit_json(changed=False, gathered=before, before=before, commands=[]) - - if module.check_mode: - module.exit_json(changed=True, before=before, commands=["(check mode)"]) - - try: - if state in ("overridden", "deleted") and not config: - # Delete all static routes - for afi_key in ("route", "route6"): - try: - client.configure_delete(["protocols", "static", afi_key]) - commands.append( - "delete protocols static {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - changed = True - - elif state == "deleted" and config: - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - base = _ROUTE_BASE[afi] - for route in af.get("routes") or []: - try: - client.configure_delete(base + [route["dest"]]) - commands.append( - "delete {p} {d}".format( - p=" ".join(base), - d=route["dest"], - ), - ) - except VyOSRestError: - pass - changed = True - - elif state in ("merged", "replaced", "overridden"): - if state in ("replaced", "overridden"): - # Remove existing routes for affected destinations - dests_by_afi = {} - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - dests_by_afi.setdefault(afi, set()) - for route in af.get("routes") or []: - dests_by_afi[afi].add(route["dest"]) - if state == "overridden": - for afi_key in ("route", "route6"): - try: - client.configure_delete( - ["protocols", "static", afi_key], - ) - commands.append( - "delete protocols static {k}".format(k=afi_key), - ) - except VyOSRestError: - pass - else: - for afi, dests in dests_by_afi.items(): - base = _ROUTE_BASE[afi] - for dest in dests: - try: - client.configure_delete(base + [dest]) - commands.append( - "delete {p} {d}".format( - p=" ".join(base), - d=dest, - ), - ) - except VyOSRestError: - pass - - for entry in config: - for af in entry.get("address_families") or []: - afi = af["afi"] - for route in af.get("routes") or []: - _apply_route(client, afi, route, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - after = _get_static_routes(client) if changed else before - module.exit_json(changed=changed, before=before, after=after, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_system.py.preview b/plugins/modules/vyos_system.py.preview deleted file mode 100644 index d1d696e..0000000 --- a/plugins/modules/vyos_system.py.preview +++ /dev/null @@ -1,147 +0,0 @@ -#!/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_system -short_description: Manage system settings on VyOS via the REST API. -description: - - Manages system-level settings — hostname, domain name, name servers, - domain search list — using the VyOS HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_system) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - host_name: - description: System hostname. - type: str - domain_name: - description: System domain name. - type: str - name_server: - description: List of DNS name servers. Mutually exclusive with domain_search. - type: list - elements: str - aliases: [name_servers] - domain_search: - description: List of domain search suffixes. Mutually exclusive with name_server. - type: list - elements: str - state: - description: C(present) to apply, C(absent) to remove. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def main(): - argument_spec = dict( - host_name=dict(type="str"), - domain_name=dict(type="str"), - name_server=dict(type="list", elements="str", aliases=["name_servers"]), - domain_search=dict(type="list", elements="str"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["name_server", "domain_search"]], - supports_check_mode=True, - ) - - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - state = module.params["state"] - commands = [] - changed = False - - try: - if state == "present": - if module.params.get("host_name"): - client.configure_set(["system", "host-name"], module.params["host_name"]) - commands.append("set system host-name '{h}'".format(h=module.params["host_name"])) - changed = True - if module.params.get("domain_name"): - client.configure_set(["system", "domain-name"], module.params["domain_name"]) - commands.append( - "set system domain-name '{d}'".format(d=module.params["domain_name"]), - ) - changed = True - for ns in module.params.get("name_server") or []: - client.configure_set(["system", "name-server"], ns) - commands.append("set system name-server {ns}".format(ns=ns)) - changed = True - for ds in module.params.get("domain_search") or []: - client.configure_set(["system", "domain-search", "domain"], ds) - commands.append("set system domain-search domain {ds}".format(ds=ds)) - changed = True - else: - if module.params.get("host_name"): - try: - client.configure_delete(["system", "host-name"]) - commands.append("delete system host-name") - changed = True - except VyOSRestError: - pass - if module.params.get("domain_name"): - try: - client.configure_delete(["system", "domain-name"]) - commands.append("delete system domain-name") - changed = True - except VyOSRestError: - pass - if module.params.get("name_server"): - try: - client.configure_delete(["system", "name-server"]) - commands.append("delete system name-server") - changed = True - except VyOSRestError: - pass - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_user.py.preview b/plugins/modules/vyos_user.py.preview deleted file mode 100644 index 41e014d..0000000 --- a/plugins/modules/vyos_user.py.preview +++ /dev/null @@ -1,282 +0,0 @@ -#!/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_user -short_description: Manage local users on VyOS via the REST API. -description: - - Manages local user accounts on VyOS devices using the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_user) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - aggregate: - description: List of user definitions. Mutually exclusive with I(name). - type: list - elements: dict - aliases: [users, collection] - suboptions: - name: - description: Username. - type: str - required: true - full_name: - description: Full name. - type: str - configured_password: - description: Plaintext password (will be hashed on device). - type: str - no_log: true - encrypted_password: - description: Pre-hashed password string. - type: str - no_log: true - update_password: - description: When to update the password. - type: str - choices: [always, on_create] - level: - description: User privilege level. - type: str - choices: [admin, operator] - public_keys: - description: SSH public keys for authentication. - type: list - elements: dict - suboptions: - name: - description: Key identifier (e.g. user@host). - type: str - required: true - key: - description: Base64-encoded public key. - type: str - required: true - type: - description: Key type. - type: str - required: true - choices: [ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, - ecdsa-sha2-nistp384, ssh-ed25519, ecdsa-sha2-nistp521] - state: - description: Present or absent. - type: str - choices: [present, absent] - name: - description: Single username. Mutually exclusive with I(aggregate). - type: str - full_name: - description: Full name for the single user. - type: str - configured_password: - description: Plaintext password. - type: str - no_log: true - encrypted_password: - description: Pre-hashed password. - type: str - no_log: true - update_password: - type: str - choices: [always, on_create] - level: - type: str - choices: [admin, operator] - public_keys: - type: list - elements: dict - suboptions: - name: - type: str - required: true - key: - type: str - required: true - type: - type: str - required: true - state: - description: Whether to create (present) or remove (absent) the user. - type: str - choices: [present, absent] - default: present - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -_KEY_TYPES = [ - "ssh-dss", - "ssh-rsa", - "ecdsa-sha2-nistp256", - "ecdsa-sha2-nistp384", - "ssh-ed25519", - "ecdsa-sha2-nistp521", -] - - -def _apply_user(client, user_def, commands, update_password="always"): - name = user_def["name"] - base = ["system", "login", "user", name] - client.configure_set(base) - commands.append("set system login user {n}".format(n=name)) - - if user_def.get("full_name"): - client.configure_set(base + ["full-name"], user_def["full_name"]) - if user_def.get("level"): - client.configure_set(base + ["level"], user_def["level"]) - if user_def.get("configured_password") and update_password in ("always",): - client.configure_set( - base + ["authentication", "plaintext-password"], - user_def["configured_password"], - ) - commands.append( - "set system login user {n} authentication plaintext-password".format(n=name), - ) - if user_def.get("encrypted_password"): - client.configure_set( - base + ["authentication", "encrypted-password"], - user_def["encrypted_password"], - ) - for pk in user_def.get("public_keys") or []: - kb = base + ["authentication", "public-keys", pk["name"]] - client.configure_set(kb + ["key"], pk["key"]) - client.configure_set(kb + ["type"], pk["type"]) - commands.append( - "set system login user {n} authentication public-keys {k}".format( - n=name, - k=pk["name"], - ), - ) - - -def _delete_user(client, name, commands): - try: - client.configure_delete(["system", "login", "user", name]) - commands.append("delete system login user {n}".format(n=name)) - except VyOSRestError: - pass - - -def main(): - pk_spec = dict( - type="list", - elements="dict", - options=dict( - name=dict(type="str", required=True), - key=dict(type="str", required=True), - type=dict(type="str", required=True, choices=_KEY_TYPES), - ), - ) - user_spec = dict( - name=dict(type="str", required=True), - full_name=dict(type="str"), - configured_password=dict(type="str", no_log=True), - encrypted_password=dict(type="str", no_log=True), - update_password=dict(type="str", choices=["always", "on_create"]), - level=dict(type="str", choices=["admin", "operator"]), - public_keys=pk_spec, - state=dict(type="str", choices=["present", "absent"]), - ) - argument_spec = dict( - aggregate=dict( - type="list", - elements="dict", - aliases=["users", "collection"], - options=user_spec, - ), - name=dict(type="str"), - full_name=dict(type="str"), - configured_password=dict(type="str", no_log=True), - encrypted_password=dict(type="str", no_log=True), - update_password=dict(type="str", choices=["always", "on_create"]), - level=dict(type="str", choices=["admin", "operator"]), - public_keys=pk_spec, - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["aggregate", "name"]], - supports_check_mode=True, - ) - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - commands = [] - changed = False - - # Build unified user list - users = module.params.get("aggregate") or [] - if module.params.get("name"): - users = [ - { - "name": module.params["name"], - "full_name": module.params.get("full_name"), - "configured_password": module.params.get("configured_password"), - "encrypted_password": module.params.get("encrypted_password"), - "update_password": module.params.get("update_password", "always"), - "level": module.params.get("level"), - "public_keys": module.params.get("public_keys"), - "state": module.params.get("state", "present"), - }, - ] - - try: - for u in users: - u_state = u.get("state") or module.params.get("state", "present") - if u_state == "absent": - _delete_user(client, u["name"], commands) - else: - _apply_user( - client, - u, - commands, - update_password=u.get("update_password") or "always", - ) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/vyos_vlan.py.preview b/plugins/modules/vyos_vlan.py.preview deleted file mode 100644 index 18eca92..0000000 --- a/plugins/modules/vyos_vlan.py.preview +++ /dev/null @@ -1,197 +0,0 @@ -#!/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_vlan -short_description: Manage VLANs on VyOS network devices via REST API. -description: - - Manages 802.1Q VLAN sub-interfaces on VyOS Ethernet interfaces using - the HTTPS REST API. - - Mirrors C(vyos.vyos.vyos_vlan) but uses the HTTP API. -version_added: "1.0.0" -author: - - VyOS Community (@vyos) -options: - vlan_id: - description: VLAN ID (0-4094). - type: int - name: - description: VLAN name (used as description on the VIF). - type: str - address: - description: IP address for the VLAN interface. - type: str - interfaces: - description: List of physical interfaces to configure the VLAN on. - type: list - elements: str - aggregate: - description: List of VLAN definitions. - type: list - elements: dict - suboptions: - vlan_id: - type: int - required: true - name: - type: str - address: - type: str - interfaces: - type: list - elements: str - required: true - state: - type: str - choices: [present, absent] - state: - description: C(present) to configure, C(absent) to remove. - type: str - choices: [present, absent] - default: present - purge: - description: Remove VLANs not defined in aggregate. - type: bool - default: false - hostname: - type: str - required: true - port: - type: int - default: 443 - api_key: - type: str - required: true - no_log: true - timeout: - type: int - default: 30 - verify_ssl: - type: bool - default: false -""" - -RETURN = r""" -commands: - description: set/delete commands issued. - returned: always - type: list -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( - VYOS_REST_CONNECTION_ARGSPEC, - VyOSRestClient, - VyOSRestError, -) - - -def _apply_vlan(client, vlan, commands): - vid = str(vlan["vlan_id"]) - for iface in vlan.get("interfaces") or []: - # Detect interface type - if iface.startswith("bond"): - itype = "bonding" - else: - itype = "ethernet" - vif_base = ["interfaces", itype, iface, "vif", vid] - client.configure_set(vif_base) - commands.append("set interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) - if vlan.get("name"): - client.configure_set(vif_base + ["description"], vlan["name"]) - commands.append( - "set interfaces {t} {i} vif {v} description '{n}'".format( - t=itype, - i=iface, - v=vid, - n=vlan["name"], - ), - ) - if vlan.get("address"): - client.configure_set(vif_base + ["address"], vlan["address"]) - commands.append( - "set interfaces {t} {i} vif {v} address {a}".format( - t=itype, - i=iface, - v=vid, - a=vlan["address"], - ), - ) - - -def _delete_vlan(client, vlan, commands): - vid = str(vlan["vlan_id"]) - for iface in vlan.get("interfaces") or []: - itype = "bonding" if iface.startswith("bond") else "ethernet" - try: - client.configure_delete(["interfaces", itype, iface, "vif", vid]) - commands.append("delete interfaces {t} {i} vif {v}".format(t=itype, i=iface, v=vid)) - except VyOSRestError: - pass - - -def main(): - vlan_spec = dict( - vlan_id=dict(type="int", required=True), - name=dict(type="str"), - address=dict(type="str"), - interfaces=dict(type="list", elements="str"), - state=dict(type="str", choices=["present", "absent"]), - ) - argument_spec = dict( - vlan_id=dict(type="int"), - name=dict(type="str"), - address=dict(type="str"), - interfaces=dict(type="list", elements="str"), - aggregate=dict(type="list", elements="dict", options=vlan_spec), - state=dict(type="str", default="present", choices=["present", "absent"]), - purge=dict(type="bool", default=False), - ) - argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) - module = AnsibleModule( - argument_spec=argument_spec, - mutually_exclusive=[["aggregate", "vlan_id"]], - supports_check_mode=True, - ) - if module.check_mode: - module.exit_json(changed=True, commands=["(check mode)"]) - - client = VyOSRestClient(module) - commands = [] - changed = False - - vlans = module.params.get("aggregate") or [] - if module.params.get("vlan_id"): - vlans = [ - { - "vlan_id": module.params["vlan_id"], - "name": module.params.get("name"), - "address": module.params.get("address"), - "interfaces": module.params.get("interfaces") or [], - "state": module.params.get("state", "present"), - }, - ] - - try: - for vlan in vlans: - v_state = vlan.get("state") or module.params.get("state", "present") - if v_state == "absent": - _delete_vlan(client, vlan, commands) - else: - _apply_vlan(client, vlan, commands) - changed = True - except VyOSRestError as exc: - module.fail_json(msg=str(exc)) - - module.exit_json(changed=changed, commands=commands) - - -if __name__ == "__main__": - main() -- cgit v1.2.3 From 066ab3c5795cfc30337831708da1a73a449f404f Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 2 Jun 2026 21:11:33 +1000 Subject: Hostname module fixes --- docs/vyos.rest.vyos_banner_module.rst | 16 ++- docs/vyos.rest.vyos_hostname_module.rst | 76 ++++++++++++- plugins/modules/vyos_banner.py | 58 ++++++++-- plugins/modules/vyos_hostname.py | 120 ++++++++++++++++++--- .../tests/httpapi/_populate_config.yaml | 17 +-- .../vyos_banner/tests/httpapi/_remove_config.yaml | 2 +- .../targets/vyos_banner/tests/httpapi/deleted.yaml | 6 +- .../targets/vyos_banner/tests/httpapi/merged.yaml | 30 +++--- 8 files changed, 269 insertions(+), 56 deletions(-) (limited to 'plugins/modules') diff --git a/docs/vyos.rest.vyos_banner_module.rst b/docs/vyos.rest.vyos_banner_module.rst index b459bfb..4e0e3ab 100644 --- a/docs/vyos.rest.vyos_banner_module.rst +++ b/docs/vyos.rest.vyos_banner_module.rst @@ -73,7 +73,6 @@ Parameters
string - / required
@@ -157,8 +156,8 @@ Parameters
Desired state of the banner configuration.
merged - set the banner if it differs from the current value.
replaced - replace the banner text unconditionally.
-
deleted - remove the banner.
-
gathered - return the current banner in gathered without making any changes.
+
deleted - remove the banner. If config.banner is omitted, both pre-login and post-login banners are removed.
+
gathered - return the current banner in gathered without making any changes. If config.banner is omitted, all banners are returned.
@@ -231,12 +230,16 @@ Examples text: "Welcome. Authorised access only." state: replaced - - name: Remove pre-login banner + - name: Remove pre-login banner only vyos.rest.vyos_banner: config: banner: pre-login state: deleted + - name: Remove all banners + vyos.rest.vyos_banner: + state: deleted + - name: Read current pre-login banner without changing it vyos.rest.vyos_banner: config: @@ -244,6 +247,11 @@ Examples state: gathered register: result + - name: Read all banners + vyos.rest.vyos_banner: + state: gathered + register: result + - name: Print gathered banner ansible.builtin.debug: msg: "Current banner: {{ result.gathered.text }}" diff --git a/docs/vyos.rest.vyos_hostname_module.rst b/docs/vyos.rest.vyos_hostname_module.rst index b8fb5fd..c8ea1c5 100644 --- a/docs/vyos.rest.vyos_hostname_module.rst +++ b/docs/vyos.rest.vyos_hostname_module.rst @@ -19,6 +19,7 @@ Synopsis -------- - Manages the ``set system host-name`` configuration on a VyOS device using the HTTPS REST API. - Mirrors the behaviour of ``vyos.vyos.vyos_hostname`` but uses the HTTP API instead of SSH/network_cli. +- The states ``replaced``, ``overridden`` behave identically to ``merged`` for this single-value resource. @@ -47,7 +48,6 @@ Parameters
string - / required
@@ -96,13 +96,12 @@ Parameters
string - / required
-
IP address or FQDN of the VyOS device.
+
IP address or FQDN of the VyOS device (not needed with httpapi inventory).
@@ -121,6 +120,22 @@ Parameters
HTTPS port for the REST API.
+ + +
+ running_config + +
+ string +
+ + + + +
Used only with state parsed.
+
The value should be the output of show configuration commands | grep host-name from the device.
+ +
@@ -133,14 +148,22 @@ Parameters
    Choices:
  • merged ←
  • +
  • replaced
  • +
  • overridden
  • deleted
  • gathered
  • +
  • rendered
  • +
  • parsed
merged - Ensure the hostname is set to the value in config.
+
replaced - Identical to merged for this single-value resource.
+
overridden - Identical to merged for this single-value resource.
deleted - Remove the configured hostname (resets to default).
gathered - Read the current hostname from the device and return it in gathered without making changes.
+
rendered - Return the CLI commands for the given config without connecting to the device.
+
parsed - Parse the running_config string and return structured data without connecting to the device.
@@ -203,6 +226,12 @@ Examples hostname: vyos-core-01 state: merged + - name: Replace hostname + vyos.rest.vyos_hostname: + config: + hostname: vyos-core-02 + state: replaced + - name: Gather current hostname vyos.rest.vyos_hostname: state: gathered @@ -212,6 +241,17 @@ Examples vyos.rest.vyos_hostname: state: deleted + - name: Render commands without connecting + vyos.rest.vyos_hostname: + config: + hostname: vyos-core-01 + state: rendered + + - name: Parse running config + vyos.rest.vyos_hostname: + running_config: "set system host-name 'vyos'" + state: parsed + Return Values @@ -286,6 +326,36 @@ Common return values are documented `here + + +
+ parsed + +
+ dictionary +
+ + when state is parsed + +
Structured data parsed from running_config (state=parsed only).
+
+ + + + +
+ rendered + +
+ list +
+ + when state is rendered + +
CLI commands for the provided config (state=rendered only).
+
+ +

diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py index b9df27d..4f734cd 100644 --- a/plugins/modules/vyos_banner.py +++ b/plugins/modules/vyos_banner.py @@ -31,7 +31,6 @@ options: description: - Which banner to configure. type: str - required: true choices: - pre-login - post-login @@ -47,9 +46,11 @@ options: - Desired state of the banner configuration. - C(merged) - set the banner if it differs from the current value. - C(replaced) - replace the banner text unconditionally. - - C(deleted) - remove the banner. + - C(deleted) - remove the banner. If C(config.banner) is omitted, + both pre-login and post-login banners are removed. - C(gathered) - return the current banner in I(gathered) without - making any changes. + making any changes. If C(config.banner) is omitted, all banners + are returned. type: str default: merged choices: @@ -97,12 +98,16 @@ EXAMPLES = r""" text: "Welcome. Authorised access only." state: replaced -- name: Remove pre-login banner +- name: Remove pre-login banner only vyos.rest.vyos_banner: config: banner: pre-login state: deleted +- name: Remove all banners + vyos.rest.vyos_banner: + state: deleted + - name: Read current pre-login banner without changing it vyos.rest.vyos_banner: config: @@ -110,6 +115,11 @@ EXAMPLES = r""" state: gathered register: result +- name: Read all banners + vyos.rest.vyos_banner: + state: gathered + register: result + - name: Print gathered banner ansible.builtin.debug: msg: "Current banner: {{ result.gathered.text }}" @@ -184,7 +194,7 @@ def main(): options=dict( banner=dict( type="str", - required=True, + required=False, choices=["pre-login", "post-login"], ), text=dict(type="str"), @@ -203,17 +213,47 @@ def main(): required_if=[ ("state", "merged", ["config"]), ("state", "replaced", ["config"]), - ("state", "deleted", ["config"]), - ("state", "gathered", ["config"]), ], supports_check_mode=True, ) client = VyOSRestClient(module) state = module.params["state"] - config = module.params["config"] - banner_type = config["banner"] + config = module.params["config"] or {} + banner_type = config.get("banner") desired_text = config.get("text") or "" + + # deleted without banner_type — delete all banners + if state == "deleted" and not banner_type: + commands = [] + changed = False + for bt in ["pre-login", "post-login"]: + current = _get_current(client, bt) + if current.get("text"): + if not module.check_mode: + try: + client.configure_delete(_BANNER_PATH[bt]) + except VyOSRestError as exc: + module.fail_json(msg=str(exc)) + commands.append("delete {p}".format(p=" ".join(_BANNER_PATH[bt]))) + changed = True + module.exit_json(changed=changed, commands=commands) + + # gathered without banner_type — return all banners + if state == "gathered" and not banner_type: + gathered = {} + for bt in ["pre-login", "post-login"]: + current = _get_current(client, bt) + if current.get("text"): + gathered[bt] = current + module.exit_json(changed=False, gathered=gathered, commands=[]) + + # all other states require banner_type + if not banner_type: + module.fail_json( + msg="config.banner is required for state={0}".format(state), + ) + path = _BANNER_PATH[banner_type] commands = [] changed = False diff --git a/plugins/modules/vyos_hostname.py b/plugins/modules/vyos_hostname.py index 10255b5..461a552 100644 --- a/plugins/modules/vyos_hostname.py +++ b/plugins/modules/vyos_hostname.py @@ -16,6 +16,8 @@ description: using the HTTPS REST API. - Mirrors the behaviour of C(vyos.vyos.vyos_hostname) but uses the HTTP API instead of SSH/network_cli. + - The states C(replaced), C(overridden) behave identically to C(merged) + for this single-value resource. version_added: "1.0.0" author: - VyOS Community (@vyos) @@ -27,42 +29,59 @@ options: suboptions: hostname: description: - - System hostname (max 63 characters, no underscores). + - System hostname (max 63 characters, no underscores). type: str required: true + running_config: + description: + - Used only with state C(parsed). + - The value should be the output of + B(show configuration commands | grep host-name) from the device. + type: str state: description: - C(merged) - Ensure the hostname is set to the value in I(config). + - C(replaced) - Identical to C(merged) for this single-value resource. + - C(overridden) - Identical to C(merged) for this single-value resource. - C(deleted) - Remove the configured hostname (resets to default). - C(gathered) - Read the current hostname from the device and return it in I(gathered) without making changes. + - C(rendered) - Return the CLI commands for the given config without + connecting to the device. + - C(parsed) - Parse the C(running_config) string and return structured + data without connecting to the device. type: str - choices: [merged, deleted, gathered] + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed default: merged hostname: description: - - IP address or FQDN of the VyOS device. + - IP address or FQDN of the VyOS device (not needed with httpapi inventory). type: str - required: true port: description: - - HTTPS port for the REST API. + - HTTPS port for the REST API. type: int default: 443 api_key: description: - - API key configured on the device. + - API key configured on the device. type: str - required: true no_log: true timeout: description: - - Request timeout in seconds. + - Request timeout in seconds. type: int default: 30 verify_ssl: description: - - Validate the device's TLS certificate. + - Validate the device's TLS certificate. type: bool default: false requirements: @@ -84,13 +103,20 @@ gathered: description: Hostname read from the device (state=gathered only). returned: when state is gathered type: dict +rendered: + description: CLI commands for the provided config (state=rendered only). + returned: when state is rendered + type: list +parsed: + description: Structured data parsed from running_config (state=parsed only). + returned: when state is parsed + type: dict commands: description: REST API commands dispatched. returned: always type: list """ - EXAMPLES = r""" - name: Set hostname vyos.rest.vyos_hostname: @@ -98,6 +124,12 @@ EXAMPLES = r""" hostname: vyos-core-01 state: merged +- name: Replace hostname + vyos.rest.vyos_hostname: + config: + hostname: vyos-core-02 + state: replaced + - name: Gather current hostname vyos.rest.vyos_hostname: state: gathered @@ -106,8 +138,21 @@ EXAMPLES = r""" - name: Delete hostname configuration vyos.rest.vyos_hostname: state: deleted + +- name: Render commands without connecting + vyos.rest.vyos_hostname: + config: + hostname: vyos-core-01 + state: rendered + +- name: Parse running config + vyos.rest.vyos_hostname: + running_config: "set system host-name 'vyos'" + state: parsed """ +import re + from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import ( VYOS_REST_CONNECTION_ARGSPEC, @@ -127,6 +172,12 @@ def _get_hostname(client): return "" +def _parse_hostname(running_config): + """Parse hostname from 'show configuration commands | grep host-name' output.""" + match = re.search(r"host-name\s+['\"]?(\S+?)['\"]?\s*$", running_config, re.M) + return match.group(1) if match else "" + + def main(): argument_spec = dict( config=dict( @@ -135,22 +186,59 @@ def main(): hostname=dict(type="str", required=True), ), ), + running_config=dict(type="str"), state=dict( type="str", default="merged", - choices=["merged", "deleted", "gathered"], + choices=[ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], ), ) argument_spec.update(VYOS_REST_CONNECTION_ARGSPEC) module = AnsibleModule( argument_spec=argument_spec, - required_if=[("state", "merged", ["config"])], + mutually_exclusive=[["config", "running_config"]], + required_if=[ + ("state", "merged", ["config"]), + ("state", "replaced", ["config"]), + ("state", "overridden", ["config"]), + ("state", "rendered", ["config"]), + ("state", "parsed", ["running_config"]), + ], supports_check_mode=True, ) - client = VyOSRestClient(module) state = module.params["state"] + + # rendered — offline, no device connection needed + if state == "rendered": + hostname = module.params["config"]["hostname"] + module.exit_json( + rendered=["set system host-name '{h}'".format(h=hostname)], + commands=[], + ) + + # parsed — offline, no device connection needed + if state == "parsed": + hostname = _parse_hostname(module.params["running_config"] or "") + module.exit_json( + parsed={"hostname": hostname}, + commands=[], + ) + + # collapsed states — replaced and overridden are identical to merged + if state in ("replaced", "overridden"): + state = "merged" + + client = VyOSRestClient(module) commands = [] changed = False @@ -168,15 +256,15 @@ def main(): desired = module.params["config"]["hostname"] if current != desired: client.configure_set(_PATH, desired) - commands.append( - "set system host-name '{h}'".format(h=desired), - ) + commands.append("set system host-name '{h}'".format(h=desired)) changed = True + elif state == "deleted": if current: client.configure_delete(_PATH) commands.append("delete system host-name") changed = True + except VyOSRestError as exc: module.fail_json(msg=str(exc)) diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml index 2557b70..7bd2698 100644 --- a/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml +++ b/tests/integration/targets/vyos_banner/tests/httpapi/_populate_config.yaml @@ -1,11 +1,14 @@ --- -- name: Populate banner config for testing +- name: Populate pre-login banner for testing vyos.rest.vyos_banner: config: - banners: - - banner: pre-login - text: "Ansible test pre-login banner" - - banner: post-login - text: "Ansible test post-login banner" + banner: pre-login + text: "Ansible test pre-login banner" + state: merged + +- name: Populate post-login banner for testing + vyos.rest.vyos_banner: + config: + banner: post-login + text: "Ansible test post-login banner" state: merged - ignore_errors: true diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml index cc56d27..2b43847 100644 --- a/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml +++ b/tests/integration/targets/vyos_banner/tests/httpapi/_remove_config.yaml @@ -1,5 +1,5 @@ --- -- name: Remove pre-existing banner config +- name: Remove all banners vyos.rest.vyos_banner: state: deleted ignore_errors: true diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml index 5d0db34..82e0f6e 100644 --- a/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml +++ b/tests/integration/targets/vyos_banner/tests/httpapi/deleted.yaml @@ -6,16 +6,18 @@ - include_tasks: _populate_config.yaml - block: - - name: Delete all banner configuration + - name: Delete pre-login banner register: result vyos.rest.vyos_banner: &id001 + config: + banner: pre-login state: deleted - assert: that: - result.changed == true - - name: Delete banner configuration (IDEMPOTENT) + - name: Delete pre-login banner (IDEMPOTENT) register: result vyos.rest.vyos_banner: *id001 diff --git a/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml index 027702a..96a984e 100644 --- a/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml +++ b/tests/integration/targets/vyos_banner/tests/httpapi/merged.yaml @@ -1,32 +1,34 @@ --- - debug: - msg: START vyos_banner merged integration tests on connection={{ ansible_connection }} + msg: START vyos_banner gathered integration tests on connection={{ ansible_connection }} - include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml - block: - - name: Merge banner configuration + - name: Gather all banners (no config specified) register: result - vyos.rest.vyos_banner: &id001 - config: - banners: - - banner: pre-login - text: "Ansible test pre-login banner" - state: merged + vyos.rest.vyos_banner: + state: gathered - assert: that: - - result.changed == true + - result.changed == false + - result.gathered is defined + - "'pre-login' in result.gathered" + - "'post-login' in result.gathered" - - name: Merge banner configuration (IDEMPOTENT) + - name: Gather specific pre-login banner register: result - vyos.rest.vyos_banner: *id001 + vyos.rest.vyos_banner: + config: + banner: pre-login + state: gathered - - name: Assert idempotent - assert: + - assert: that: - result.changed == false - - result.commands == [] + - result.gathered.text == "Ansible test pre-login banner" always: - include_tasks: _remove_config.yaml -- cgit v1.2.3 From b5e17eb5e9239cf45a51d39f2a7a9726aa8f4fbb Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 2 Jun 2026 21:36:02 +1000 Subject: lldp_global module --- docs/vyos.rest.vyos_lldp_global_module.rst | 45 ++++++++- plugins/modules/vyos_lldp_global.py | 112 +++++++++++++++++---- .../tests/httpapi/_populate_config.yaml | 9 +- 3 files changed, 140 insertions(+), 26 deletions(-) (limited to 'plugins/modules') diff --git a/docs/vyos.rest.vyos_lldp_global_module.rst b/docs/vyos.rest.vyos_lldp_global_module.rst index 0f24668..9016792 100644 --- a/docs/vyos.rest.vyos_lldp_global_module.rst +++ b/docs/vyos.rest.vyos_lldp_global_module.rst @@ -18,6 +18,7 @@ Version added: 1.0.0 Synopsis -------- - Manage LLDP global configuration on VyOS via REST API. +- Uses ``ansible_connection=ansible.netcommon.httpapi``. @@ -45,6 +46,7 @@ Parameters +
LLDP global configuration.
@@ -61,6 +63,7 @@ Parameters +
List of management addresses to advertise.
@@ -80,6 +83,7 @@ Parameters +
Enable LLDP service.
@@ -102,6 +106,7 @@ Parameters +
Legacy protocols to support.
@@ -117,7 +122,9 @@ Parameters -
enable or disable.
+
enable to enable LLDP SNMP subagent.
+
disable to disable it.
+
Requires SNMP service to be configured on the device.
@@ -139,6 +146,10 @@ Parameters +
merged - merge config with existing.
+
replaced - replace existing config with provided.
+
deleted - delete LLDP configuration.
+
gathered - return current config without changes.
@@ -152,14 +163,34 @@ Examples .. code-block:: yaml - - vyos.rest.vyos_lldp_global: + - name: Enable LLDP with management address and legacy protocols + vyos.rest.vyos_lldp_global: config: enable: true - addresses: [192.0.2.17] - snmp: enable - legacy_protocols: [cdp, fdp] + addresses: + - 192.0.2.17 + legacy_protocols: + - cdp + - fdp state: merged + - name: Replace LLDP configuration + vyos.rest.vyos_lldp_global: + config: + enable: true + addresses: + - 192.0.2.99 + state: replaced + + - name: Delete all LLDP configuration + vyos.rest.vyos_lldp_global: + state: deleted + + - name: Gather current LLDP configuration + vyos.rest.vyos_lldp_global: + state: gathered + register: result + Return Values @@ -185,6 +216,7 @@ Common return values are documented `here when changed +
LLDP configuration after the module ran.

@@ -199,6 +231,7 @@ Common return values are documented `here always +
LLDP configuration before the module ran.

@@ -213,6 +246,7 @@ Common return values are documented `here always +
List of commands sent to the device.

@@ -227,6 +261,7 @@ Common return values are documented `here when state is gathered +
Current LLDP configuration from the device.

diff --git a/plugins/modules/vyos_lldp_global.py b/plugins/modules/vyos_lldp_global.py index 1eb10bd..cc24be9 100644 --- a/plugins/modules/vyos_lldp_global.py +++ b/plugins/modules/vyos_lldp_global.py @@ -4,59 +4,99 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type + DOCUMENTATION = r""" --- module: vyos_lldp_global short_description: Manage LLDP global configuration on VyOS via REST API. description: - Manage LLDP global configuration on VyOS via REST API. + - Uses C(ansible_connection=ansible.netcommon.httpapi). version_added: "1.0.0" author: VyOS Community (@vyos) options: config: + description: LLDP global configuration. type: dict suboptions: enable: + description: Enable LLDP service. type: bool addresses: + description: List of management addresses to advertise. type: list elements: str snmp: description: - - C(enable) or C(disable). + - C(enable) to enable LLDP SNMP subagent. + - C(disable) to disable it. + - Requires SNMP service to be configured on the device. type: str legacy_protocols: + description: Legacy protocols to support. type: list elements: str choices: [cdp, edp, fdp, sonmp] state: + description: + - C(merged) - merge config with existing. + - C(replaced) - replace existing config with provided. + - C(deleted) - delete LLDP configuration. + - C(gathered) - return current config without changes. type: str default: merged choices: [merged, replaced, deleted, gathered] """ + EXAMPLES = r""" -- vyos.rest.vyos_lldp_global: +- name: Enable LLDP with management address and legacy protocols + vyos.rest.vyos_lldp_global: config: enable: true - addresses: [192.0.2.17] - snmp: enable - legacy_protocols: [cdp, fdp] + addresses: + - 192.0.2.17 + legacy_protocols: + - cdp + - fdp state: merged + +- name: Replace LLDP configuration + vyos.rest.vyos_lldp_global: + config: + enable: true + addresses: + - 192.0.2.99 + state: replaced + +- name: Delete all LLDP configuration + vyos.rest.vyos_lldp_global: + state: deleted + +- name: Gather current LLDP configuration + vyos.rest.vyos_lldp_global: + state: gathered + register: result """ + RETURN = r""" before: + description: LLDP configuration before the module ran. returned: always type: dict after: + description: LLDP configuration after the module ran. returned: when changed type: dict commands: + description: List of commands sent to the device. returned: always type: list gathered: + description: Current LLDP configuration from the device. returned: when state is gathered type: dict """ + from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule @@ -93,19 +133,42 @@ def _get(vyos): def _build(want, have, state): cmds = [] - if state in ("replaced", "deleted") and have: - cmds.append(("delete", _BASE)) - if state == "deleted": - return cmds - - # enable — only set if not already enabled - if want.get("enable") is not False and not have.get("enable"): + if state == "deleted": + if have: + cmds.append(("delete", _BASE)) + return cmds + + if state == "replaced": + # only wipe and rebuild if have differs from want + want_addrs = sorted(want.get("addresses") or []) + have_addrs = sorted(have.get("addresses") or []) + want_protos = sorted(want.get("legacy_protocols") or []) + have_protos = sorted(have.get("legacy_protocols") or []) + want_snmp = want.get("snmp") + have_snmp = have.get("snmp") + want_enable = bool(want.get("enable")) + have_enable = bool(have.get("enable")) + + if ( + want_addrs != have_addrs + or want_protos != have_protos + or want_snmp != have_snmp + or want_enable != have_enable + ): + if have: + cmds.append(("delete", _BASE)) + # fall through to set everything from want + else: + return cmds # already matches — idempotent + + # enable + if want.get("enable") and not have.get("enable"): cmds.append(("set", _BASE)) - # addresses — only add missing ones - have_addrs = set(have.get("addresses") or []) + # addresses + have_addrs_set = set(have.get("addresses") or []) if state != "replaced" else set() for addr in want.get("addresses") or []: - if addr not in have_addrs: + if addr not in have_addrs_set: cmds.append(("set", _BASE + ["management-address", addr])) # snmp @@ -115,10 +178,10 @@ def _build(want, have, state): elif want["snmp"] != "disable" and not have.get("snmp"): cmds.append(("set", _BASE + ["snmp"])) - # legacy_protocols — only add missing ones - have_protos = set(have.get("legacy_protocols") or []) + # legacy_protocols + have_protos_set = set(have.get("legacy_protocols") or []) if state != "replaced" else set() for p in want.get("legacy_protocols") or []: - if p not in have_protos: + if p not in have_protos_set: cmds.append(("set", _BASE + ["legacy-protocols", p])) return cmds @@ -126,7 +189,7 @@ def _build(want, have, state): def main(): module = AnsibleModule( - dict( + argument_spec=dict( config=dict( type="dict", options=dict( @@ -140,19 +203,27 @@ def main(): ), ), ), - state=dict(default="merged", choices=["merged", "replaced", "deleted", "gathered"]), + state=dict( + default="merged", + choices=["merged", "replaced", "deleted", "gathered"], + ), ), supports_check_mode=True, ) + vyos = VyOSModule(module) state = module.params["state"] config = module.params.get("config") or {} have = _get(vyos) + if state == "gathered": module.exit_json(changed=False, gathered=have) + commands = _build(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() @@ -164,6 +235,7 @@ def main(): saved=saved, response=response, ) + module.exit_json(changed=False, before=have, after=have, commands=[]) diff --git a/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml index 3bf4fb0..6c3acf4 100644 --- a/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml +++ b/tests/integration/targets/vyos_lldp_global/tests/httpapi/_populate_config.yaml @@ -1,4 +1,12 @@ --- +- name: Configure SNMP first (required for LLDP snmp) + vyos.rest.vyos_snmp_server: + config: + communities: + - name: lldp-test + authorization_type: ro + state: merged + - name: Populate lldp_global config for testing vyos.rest.vyos_lldp_global: config: @@ -10,4 +18,3 @@ - cdp - sonmp state: merged - ignore_errors: true -- cgit v1.2.3 From 2d6ca6bd1d3243269709805257947484ddd273ec Mon Sep 17 00:00:00 2001 From: omnom62 Date: Tue, 2 Jun 2026 22:05:13 +1000 Subject: route_maps module updates --- plugins/modules/vyos_route_maps.py | 71 ++++------------------ .../vyos_prefix_lists/tests/httpapi/_deleted.yaml | 28 +++++++++ .../vyos_prefix_lists/tests/httpapi/_gathered.yaml | 20 ++++++ .../vyos_prefix_lists/tests/httpapi/_merged.yaml | 42 +++++++++++++ .../vyos_prefix_lists/tests/httpapi/_replaced.yaml | 37 +++++++++++ .../vyos_prefix_lists/tests/httpapi/deleted.yaml | 28 --------- .../vyos_prefix_lists/tests/httpapi/gathered.yaml | 20 ------ .../vyos_prefix_lists/tests/httpapi/merged.yaml | 42 ------------- .../vyos_prefix_lists/tests/httpapi/replaced.yaml | 37 ----------- 9 files changed, 140 insertions(+), 185 deletions(-) create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_deleted.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_gathered.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_merged.yaml create mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/_replaced.yaml delete mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml delete mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml delete mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml delete mode 100644 tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_route_maps.py b/plugins/modules/vyos_route_maps.py index 5bb1916..c72c66a 100644 --- a/plugins/modules/vyos_route_maps.py +++ b/plugins/modules/vyos_route_maps.py @@ -143,7 +143,6 @@ from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule _BASE = ["policy", "route-map"] -# Argspec key → API path segments under "set" _SET_MAP = { "as_path_prepend": ["as-path", "prepend"], "as_path_exclude": ["as-path", "exclude"], @@ -161,7 +160,6 @@ _SET_MAP = { "table": ["table"], } -# Argspec key → API path segments under "match" _MATCH_MAP = { "interface": ["interface"], "metric": ["metric"], @@ -171,23 +169,11 @@ _MATCH_MAP = { } -# ------------------------------------------------------------ -# Parsing: API response → argspec list -# ------------------------------------------------------------ - - def get_running_config(vyos): - """ - Fetch current route map config in argspec list format. - - API returns: {"route-map": {"RM-NAME": {"rule": {"10": {...}}}}} - Note the extra "route-map" nesting level that must be unwrapped. - """ raw = vyos.get_config(["policy", "route-map"]) if not raw or not isinstance(raw, dict): return [] - # API wraps everything under a "route-map" key — unwrap it rm_data = raw.get("route-map", raw) if not isinstance(rm_data, dict): return [] @@ -211,7 +197,6 @@ def get_running_config(vyos): rule["call"] = rule_data["call"] if rule_data.get("continue"): rule["continue_sequence"] = int(rule_data["continue"]) - # match and set returned as raw API dicts if rule_data.get("match"): rule["match"] = rule_data["match"] if rule_data.get("set"): @@ -223,13 +208,7 @@ def get_running_config(vyos): return result -# ------------------------------------------------------------ -# Command builders -# ------------------------------------------------------------ - - def _match_cmds(rbase, match): - """Convert match dict to API path commands.""" cmds = [] if not match: return cmds @@ -259,7 +238,6 @@ def _match_cmds(rbase, match): def _set_cmds(rbase, setv): - """Convert set dict to API path commands.""" cmds = [] if not setv: return cmds @@ -297,14 +275,6 @@ def _set_cmds(rbase, setv): def _want_to_api_set(setv): - """ - Convert argspec set dict to the nested API shape for comparison with have. - - API stores: - as_path_exclude "111" → {"as-path": {"exclude": "111"}} - metric "5" → {"metric": "5"} - aggregator.as 100 → {"aggregator": {"as": "100"}} - """ if not setv: return {} api = {} @@ -325,10 +295,6 @@ def _want_to_api_set(setv): def _want_to_api_match(match): - """ - Convert argspec match dict to API shape for comparison with have. - Flat fields map directly; prefix_list nests under ip.address. - """ if not match: return {} api = {} @@ -343,15 +309,10 @@ def _want_to_api_match(match): def _rule_cmds(rm_name, rule, have_rule): - """ - Build commands for a single rule, comparing against have_rule to skip - fields that are already correctly configured (idempotency). - """ cmds = [] seq = str(rule["sequence"]) rbase = _BASE + [rm_name, "rule", seq] - # Scalar fields if rule.get("action") and rule["action"] != have_rule.get("action"): cmds.append(("set", rbase + ["action", rule["action"]])) if rule.get("description") and rule["description"] != have_rule.get("description"): @@ -363,12 +324,10 @@ def _rule_cmds(rm_name, rule, have_rule): ): cmds.append(("set", rbase + ["continue", str(rule["continue_sequence"])])) - # match — convert want to API shape, compare top-level keys against have want_match_api = _want_to_api_match(rule.get("match")) have_match = have_rule.get("match") or {} changed_match = {k: v for k, v in want_match_api.items() if have_match.get(k) != v} if changed_match: - # rebuild from original argspec for changed keys only match = rule.get("match") or {} changed_keys = set(changed_match.keys()) partial_match = { @@ -382,19 +341,15 @@ def _rule_cmds(rm_name, rule, have_rule): partial_match = match cmds += _match_cmds(rbase, partial_match) - # set — convert want to API shape, compare top-level keys against have want_set_api = _want_to_api_set(rule.get("set")) have_set = have_rule.get("set") or {} if want_set_api != have_set: - # something changed — regenerate all set commands - # (simpler than surgical per-key diff for deeply nested structures) cmds += _set_cmds(rbase, rule.get("set")) return cmds def build_commands(config, have_raw, state): - """Build command tuples to move from have → want.""" cmds = [] if state == "deleted": @@ -418,10 +373,20 @@ def build_commands(config, have_raw, state): have_rm = have_map.get(rm_name, {}) if state == "replaced" and rm_name in have_map: - cmds.append(("delete", _BASE + [rm_name])) - have_rm = {} + # Only delete and rebuild if something actually differs + have_entries = {str(r["sequence"]): r for r in (have_rm.get("entries") or [])} + want_seqs = {str(r["sequence"]) for r in (rm.get("entries") or [])} + extra_seqs = set(have_entries) - want_seqs + 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) + if test_cmds or extra_seqs: + cmds.append(("delete", _BASE + [rm_name])) + have_rm = {} + else: + continue # already matches — idempotent - # Index have entries by sequence for O(1) lookup have_entries = {str(r["sequence"]): r for r in (have_rm.get("entries") or [])} for rule in rm.get("entries") or []: @@ -431,10 +396,6 @@ def build_commands(config, have_raw, state): return cmds -# ------------------------------------------------------------ -# Argument spec — minimal, VyOS API validates field values -# ------------------------------------------------------------ - ARGUMENT_SPEC = dict( config=dict(type="list", elements="dict"), state=dict( @@ -444,13 +405,7 @@ ARGUMENT_SPEC = dict( ) -# ------------------------------------------------------------ -# Main -# ------------------------------------------------------------ - - def main(): - module = AnsibleModule(ARGUMENT_SPEC, supports_check_mode=True) vyos = VyOSModule(module) diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_deleted.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_deleted.yaml new file mode 100644 index 0000000..6cfb063 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_deleted.yaml @@ -0,0 +1,28 @@ +--- +- debug: + msg: START vyos_prefix_lists deleted integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Delete all prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + state: deleted + + - assert: + that: + - result.changed == true + + - name: Delete prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *id001 + + - name: Assert idempotent + assert: + that: + - result.changed == false + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_gathered.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_gathered.yaml new file mode 100644 index 0000000..083cb48 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_gathered.yaml @@ -0,0 +1,20 @@ +--- +- debug: + msg: START vyos_prefix_lists gathered integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Gather prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: + state: gathered + + - assert: + that: + - result.changed == false + - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 + + always: + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_merged.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_merged.yaml new file mode 100644 index 0000000..a70f826 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_merged.yaml @@ -0,0 +1,42 @@ +--- +- debug: + msg: START vyos_prefix_lists merged integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + - name: Merge prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + 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 + state: merged + + - assert: + that: + - result.changed == true + + - name: Merge prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *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/tests/httpapi/_replaced.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_replaced.yaml new file mode 100644 index 0000000..bd8c5c1 --- /dev/null +++ b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/_replaced.yaml @@ -0,0 +1,37 @@ +--- +- debug: + msg: START vyos_prefix_lists replaced integration tests on connection={{ ansible_connection }} + +- include_tasks: _remove_config.yaml +- include_tasks: _populate_config.yaml + +- block: + - name: Replace prefix_lists configuration + register: result + vyos.rest.vyos_prefix_lists: &id001 + config: + - afi: ipv4 + prefix_lists: + - name: AnsibleIPv4PrefixList + entries: + - sequence: 10 + action: permit + prefix: 10.0.0.0/8 + state: replaced + + - assert: + that: + - result.changed == true + + - name: Replace prefix_lists configuration (IDEMPOTENT) + register: result + vyos.rest.vyos_prefix_lists: *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/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml deleted file mode 100644 index 6cfb063..0000000 --- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/deleted.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -- debug: - msg: START vyos_prefix_lists deleted integration tests on connection={{ ansible_connection }} - -- include_tasks: _remove_config.yaml -- include_tasks: _populate_config.yaml - -- block: - - name: Delete all prefix_lists configuration - register: result - vyos.rest.vyos_prefix_lists: &id001 - state: deleted - - - assert: - that: - - result.changed == true - - - name: Delete prefix_lists configuration (IDEMPOTENT) - register: result - vyos.rest.vyos_prefix_lists: *id001 - - - name: Assert idempotent - assert: - that: - - result.changed == false - - always: - - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml deleted file mode 100644 index 083cb48..0000000 --- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/gathered.yaml +++ /dev/null @@ -1,20 +0,0 @@ ---- -- debug: - msg: START vyos_prefix_lists gathered integration tests on connection={{ ansible_connection }} - -- include_tasks: _remove_config.yaml -- include_tasks: _populate_config.yaml - -- block: - - name: Gather prefix_lists configuration - register: result - vyos.rest.vyos_prefix_lists: - state: gathered - - - assert: - that: - - result.changed == false - - result.gathered | selectattr('afi','eq','ipv4') | list | length > 0 - - always: - - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml deleted file mode 100644 index a70f826..0000000 --- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/merged.yaml +++ /dev/null @@ -1,42 +0,0 @@ ---- -- debug: - msg: START vyos_prefix_lists merged integration tests on connection={{ ansible_connection }} - -- include_tasks: _remove_config.yaml - -- block: - - name: Merge prefix_lists configuration - register: result - vyos.rest.vyos_prefix_lists: &id001 - 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 - state: merged - - - assert: - that: - - result.changed == true - - - name: Merge prefix_lists configuration (IDEMPOTENT) - register: result - vyos.rest.vyos_prefix_lists: *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/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml deleted file mode 100644 index bd8c5c1..0000000 --- a/tests/integration/targets/vyos_prefix_lists/tests/httpapi/replaced.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -- debug: - msg: START vyos_prefix_lists replaced integration tests on connection={{ ansible_connection }} - -- include_tasks: _remove_config.yaml -- include_tasks: _populate_config.yaml - -- block: - - name: Replace prefix_lists configuration - register: result - vyos.rest.vyos_prefix_lists: &id001 - config: - - afi: ipv4 - prefix_lists: - - name: AnsibleIPv4PrefixList - entries: - - sequence: 10 - action: permit - prefix: 10.0.0.0/8 - state: replaced - - - assert: - that: - - result.changed == true - - - name: Replace prefix_lists configuration (IDEMPOTENT) - register: result - vyos.rest.vyos_prefix_lists: *id001 - - - name: Assert idempotent - assert: - that: - - result.changed == false - - result.commands == [] - - always: - - include_tasks: _remove_config.yaml -- cgit v1.2.3 From 00aa66292e3aab9215458e45da0f88dce695577a Mon Sep 17 00:00:00 2001 From: omnom62 Date: Wed, 3 Jun 2026 21:15:28 +1000 Subject: snmp_server uat --- README.md | 1 + plugins/modules/vyos_configure.py | 137 +++++++++++++++++++++ .../targets/prepare_vyos_tests/meta/main.yaml | 2 + .../targets/prepare_vyos_tests/tasks/main.yaml | 5 + .../targets/vyos_snmp_server/meta/main.yaml | 3 + .../tests/httpapi/_populate_config.yaml | 3 +- .../tests/httpapi/_remove_config.yaml | 2 +- .../vyos_snmp_server/tests/httpapi/deleted.yaml | 1 - .../vyos_snmp_server/tests/httpapi/gathered.yaml | 1 - .../vyos_snmp_server/tests/httpapi/merged.yaml | 2 - .../vyos_snmp_server/tests/httpapi/replaced.yaml | 1 - 11 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 plugins/modules/vyos_configure.py create mode 100644 tests/integration/targets/prepare_vyos_tests/meta/main.yaml create mode 100644 tests/integration/targets/prepare_vyos_tests/tasks/main.yaml create mode 100644 tests/integration/targets/vyos_snmp_server/meta/main.yaml (limited to 'plugins/modules') diff --git a/README.md b/README.md index 2430ebb..3cb3525 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Name | Description 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_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. [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 diff --git a/plugins/modules/vyos_configure.py b/plugins/modules/vyos_configure.py new file mode 100644 index 0000000..02fc7ad --- /dev/null +++ b/plugins/modules/vyos_configure.py @@ -0,0 +1,137 @@ +#!/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_configure +short_description: Send raw set/delete commands to a VyOS device via REST API. +description: + - Sends one or more set/delete configuration commands to a VyOS device + via the HTTPS REST API as a single atomic batch commit. + - Useful for configuration not covered by dedicated resource modules, + or for test setup and teardown tasks. + - Commands are parsed from CLI-style strings (C(set ...) / C(delete ...)). +version_added: "1.0.0" +author: + - VyOS Community (@vyos) +options: + commands: + description: + - List of CLI-style configuration commands. + - Each command must start with C(set) or C(delete). + type: list + elements: str + required: true + save: + description: + - Whether to save the configuration after applying commands. + type: bool + default: false +seealso: + - module: vyos.vyos.vyos_config +""" + +EXAMPLES = r""" +- name: Add loopback address for testing + vyos.rest.vyos_configure: + commands: + - set interfaces loopback lo address 20.1.1.1/32 + save: false + +- name: Remove loopback address after testing + vyos.rest.vyos_configure: + commands: + - delete interfaces loopback lo address 20.1.1.1/32 + save: false + +- name: Multiple commands in one atomic commit + vyos.rest.vyos_configure: + commands: + - set system host-name vyos-test + - set system domain-name example.com + save: true +""" + +RETURN = r""" +commands: + description: Parsed command payloads sent to the device. + returned: always + type: list +response: + description: Raw API response. + returned: when commands are applied + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule + + +def _parse_command(line): + """Parse a CLI-style command string into (op, path) tuple. + + Examples: + "set interfaces loopback lo address 20.1.1.1/32" + -> ("set", ["interfaces", "loopback", "lo", "address", "20.1.1.1/32"]) + "delete service snmp" + -> ("delete", ["service", "snmp"]) + """ + line = line.strip() + if line.startswith("set "): + parts = line[4:].split() + return ("set", parts) + elif line.startswith("delete "): + parts = line[7:].split() + return ("delete", parts) + else: + return None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + commands=dict(type="list", elements="str", required=True), + save=dict(type="bool", default=False), + ), + supports_check_mode=True, + ) + + vyos = VyOSModule(module) + commands_raw = module.params["commands"] + do_save = module.params["save"] + + commands = [] + for line in commands_raw: + parsed = _parse_command(line) + if parsed is None: + module.fail_json( + msg="Invalid command '{c}' — must start with 'set' or 'delete'".format(c=line), + ) + commands.append(parsed) + + if not commands: + module.exit_json(changed=False, commands=[]) + + if module.check_mode: + module.exit_json(changed=True, commands=commands) + + response = vyos.apply_commands(commands) + + if do_save: + vyos.save_config() + + module.exit_json( + changed=True, + commands=commands, + response=response, + ) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/prepare_vyos_tests/meta/main.yaml b/tests/integration/targets/prepare_vyos_tests/meta/main.yaml new file mode 100644 index 0000000..61d3ffe --- /dev/null +++ b/tests/integration/targets/prepare_vyos_tests/meta/main.yaml @@ -0,0 +1,2 @@ +--- +allow_duplicates: true diff --git a/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml b/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml new file mode 100644 index 0000000..9527685 --- /dev/null +++ b/tests/integration/targets/prepare_vyos_tests/tasks/main.yaml @@ -0,0 +1,5 @@ +--- +- name: Add dummy loopback address for SNMP listen test + vyos.rest.vyos_configure: + commands: + - set interfaces loopback lo address 20.1.1.1/32 diff --git a/tests/integration/targets/vyos_snmp_server/meta/main.yaml b/tests/integration/targets/vyos_snmp_server/meta/main.yaml new file mode 100644 index 0000000..7413320 --- /dev/null +++ b/tests/integration/targets/vyos_snmp_server/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_vyos_tests diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml index c847765..d3762c1 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_populate_config.yaml @@ -1,4 +1,6 @@ --- +- ansible.builtin.include_tasks: _remove_config.yaml + - name: Populate snmp_server config for testing vyos.rest.vyos_snmp_server: config: @@ -12,4 +14,3 @@ listen_addresses: - address: 20.1.1.1 state: merged - ignore_errors: true diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml index c77493a..e954df7 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/_remove_config.yaml @@ -1,5 +1,5 @@ +# _remove_config.yaml --- - name: Remove pre-existing snmp_server config vyos.rest.vyos_snmp_server: state: deleted - ignore_errors: true diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml index 90ee0ae..a626a26 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/deleted.yaml @@ -2,7 +2,6 @@ - debug: msg: START vyos_snmp_server deleted integration tests on connection={{ ansible_connection }} -- include_tasks: _remove_config.yaml - include_tasks: _populate_config.yaml - block: diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml index 740446f..770845c 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/gathered.yaml @@ -2,7 +2,6 @@ - debug: msg: START vyos_snmp_server gathered integration tests on connection={{ ansible_connection }} -- include_tasks: _remove_config.yaml - include_tasks: _populate_config.yaml - block: diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml index 475b5fe..e1c6531 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/merged.yaml @@ -2,8 +2,6 @@ - debug: msg: START vyos_snmp_server merged integration tests on connection={{ ansible_connection }} -- include_tasks: _remove_config.yaml - - block: - name: Merge snmp_server configuration register: result diff --git a/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml b/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml index cd9bef2..d989272 100644 --- a/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml +++ b/tests/integration/targets/vyos_snmp_server/tests/httpapi/replaced.yaml @@ -2,7 +2,6 @@ - debug: msg: START vyos_snmp_server replaced integration tests on connection={{ ansible_connection }} -- include_tasks: _remove_config.yaml - include_tasks: _populate_config.yaml - block: -- cgit v1.2.3 From 1d563009815dbbf6eeeb368ef14edc6f5ce4a2a2 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Thu, 4 Jun 2026 06:34:07 +1000 Subject: Sanity fixes --- docs/vyos.rest.vyos_logging_global_module.rst | 30 ++++++++++++++- docs/vyos.rest.vyos_snmp_server_module.rst | 53 ++++++++++++++++++++------ plugins/httpapi/vyos.py | 13 ++++--- plugins/module_utils/vyos_rest.py | 4 +- plugins/modules/vyos_lldp_global.py | 1 + plugins/modules/vyos_logging_global.py | 38 +++++++++++++++--- plugins/modules/vyos_ntp_global.py | 16 +++++++- plugins/modules/vyos_route_maps.py | 2 +- plugins/modules/vyos_snmp_server.py | 55 +++++++++++++++++++++------ 9 files changed, 172 insertions(+), 40 deletions(-) (limited to 'plugins/modules') diff --git a/docs/vyos.rest.vyos_logging_global_module.rst b/docs/vyos.rest.vyos_logging_global_module.rst index 718e646..da5572e 100644 --- a/docs/vyos.rest.vyos_logging_global_module.rst +++ b/docs/vyos.rest.vyos_logging_global_module.rst @@ -81,6 +81,7 @@ Parameters +
List of syslog facilities to log to the console.
@@ -98,6 +99,7 @@ Parameters +
Syslog facility name (e.g. local7, all, kern).
@@ -115,6 +117,7 @@ Parameters +
Minimum severity level to log (e.g. err, debug, all).
@@ -150,6 +153,7 @@ Parameters +
Log file archive/rotation settings.
@@ -167,6 +171,7 @@ Parameters +
Number of archived log files to keep.
@@ -184,6 +189,7 @@ Parameters +
Maximum size of log file in kilobytes before rotation.
@@ -202,6 +208,7 @@ Parameters +
List of syslog facilities to log to this file.
@@ -219,6 +226,7 @@ Parameters +
Syslog facility name.
@@ -236,6 +244,7 @@ Parameters +
Minimum severity level to log.
@@ -253,6 +262,7 @@ Parameters +
Path to the log file on the device.
@@ -286,6 +296,7 @@ Parameters +
Global log archive/rotation settings.
@@ -303,6 +314,7 @@ Parameters +
Number of archived log files to keep.
@@ -320,6 +332,7 @@ Parameters +
Maximum size of log file in kilobytes before rotation.
@@ -338,6 +351,7 @@ Parameters +
List of syslog facilities for global logging.
@@ -355,6 +369,7 @@ Parameters +
Syslog facility name.
@@ -372,6 +387,7 @@ Parameters +
Minimum severity level to log.
@@ -389,6 +405,7 @@ Parameters +
Interval in seconds between marker log entries.
@@ -409,6 +426,7 @@ Parameters +
Use the fully qualified domain name in syslog messages.
@@ -444,6 +462,7 @@ Parameters +
List of syslog facilities to forward to this host.
@@ -461,6 +480,7 @@ Parameters +
Syslog facility name.
@@ -478,7 +498,7 @@ Parameters -
Per-facility protocol override.
+
Per-facility protocol override (udp or tcp).
@@ -496,6 +516,7 @@ Parameters +
Minimum severity level to forward.
@@ -513,6 +534,7 @@ Parameters +
IP address or hostname of the remote syslog server.
@@ -529,6 +551,7 @@ Parameters +
UDP/TCP port on the remote syslog server (default 514).
@@ -545,6 +568,7 @@ Parameters +
Transport protocol (udp or tcp).
@@ -580,6 +604,7 @@ Parameters +
List of syslog facilities to send to this user.
@@ -597,6 +622,7 @@ Parameters +
Syslog facility name.
@@ -614,6 +640,7 @@ Parameters +
Minimum severity level to send.
@@ -631,6 +658,7 @@ Parameters +
Local username whose terminal receives log messages.
diff --git a/docs/vyos.rest.vyos_snmp_server_module.rst b/docs/vyos.rest.vyos_snmp_server_module.rst index dfa5259..29a64c6 100644 --- a/docs/vyos.rest.vyos_snmp_server_module.rst +++ b/docs/vyos.rest.vyos_snmp_server_module.rst @@ -85,7 +85,7 @@ Parameters -
Authorization type.
+
Authorization type (ro=read-only, rw=read-write).
@@ -207,7 +207,7 @@ Parameters -
IP address.
+
IP address to listen on.
@@ -308,6 +308,7 @@ Parameters +
SNMPv3 group configuration.
@@ -326,6 +327,7 @@ Parameters +
Group name.
@@ -347,6 +349,7 @@ Parameters +
Access mode (ro=read-only, rw=read-write).
@@ -368,6 +371,7 @@ Parameters +
Minimum security level required for group members.
@@ -385,6 +389,7 @@ Parameters +
View name the group has access to.
@@ -403,6 +408,7 @@ Parameters +
SNMPv3 trap target configuration.
@@ -420,6 +426,7 @@ Parameters +
IP address of the SNMPv3 trap target.
@@ -437,6 +444,7 @@ Parameters +
Authentication parameters for trap target.
@@ -455,6 +463,7 @@ Parameters +
Encrypted authentication key.
@@ -473,6 +482,7 @@ Parameters +
Plaintext authentication key.
@@ -491,6 +501,7 @@ Parameters +
Authentication algorithm.
@@ -509,6 +520,7 @@ Parameters +
UDP port on the trap target host.
@@ -526,6 +538,7 @@ Parameters +
Privacy parameters for trap target.
@@ -544,6 +557,7 @@ Parameters +
Encrypted privacy key.
@@ -562,6 +576,7 @@ Parameters +
Plaintext privacy key.
@@ -580,6 +595,7 @@ Parameters +
Privacy algorithm.
@@ -602,6 +618,7 @@ Parameters +
Transport protocol for traps.
@@ -623,6 +640,7 @@ Parameters +
Trap type.
@@ -641,6 +659,7 @@ Parameters +
SNMPv3 user configuration.
@@ -658,6 +677,7 @@ Parameters +
Authentication parameters for this user.
@@ -676,7 +696,7 @@ Parameters -
Encrypted key (stored as encrypted-password on device).
+
Encrypted authentication key (stored as encrypted-password on device).
@@ -695,7 +715,7 @@ Parameters -
Plaintext key (device encrypts it; recommended for 1.5+).
+
Plaintext authentication key (device encrypts it).
@@ -712,12 +732,9 @@ Parameters -
    Choices: -
  • md5
  • -
  • sha
  • -
+
Authentication algorithm.
@@ -736,6 +753,7 @@ Parameters +
Group this user belongs to.
@@ -757,6 +775,7 @@ Parameters +
Access mode for this user.
@@ -774,6 +793,7 @@ Parameters +
Privacy (encryption) parameters for this user.
@@ -792,6 +812,7 @@ Parameters +
Encrypted privacy key.
@@ -810,6 +831,7 @@ Parameters +
Plaintext privacy key (device encrypts it).
@@ -826,12 +848,9 @@ Parameters -
    Choices: -
  • des
  • -
  • aes
  • -
+
Privacy algorithm.
@@ -850,6 +869,7 @@ Parameters +
TSM fingerprint of the certificate mapped to this user.
@@ -868,6 +888,7 @@ Parameters +
Username.
@@ -886,6 +907,7 @@ Parameters +
SNMPv3 view configuration.
@@ -903,6 +925,7 @@ Parameters +
OID subtree excluded from this view.
@@ -920,6 +943,7 @@ Parameters +
OID mask for the view.
@@ -937,6 +961,7 @@ Parameters +
OID subtree included in this view.
@@ -955,6 +980,7 @@ Parameters +
View name.
@@ -1005,6 +1031,7 @@ Parameters +
IP address of the trap target host.
@@ -1021,6 +1048,7 @@ Parameters +
Community name to use for traps.
@@ -1037,6 +1065,7 @@ Parameters +
UDP port on the trap target host.
diff --git a/plugins/httpapi/vyos.py b/plugins/httpapi/vyos.py index 6dd6042..07a60fe 100644 --- a/plugins/httpapi/vyos.py +++ b/plugins/httpapi/vyos.py @@ -1,6 +1,5 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- -# GNU General Public License v3.0+ +# 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 @@ -90,13 +89,14 @@ class HttpApi(HttpApiBase): ) return key - def send_request(self, endpoint, **payload): + def send_request(self, data, **payload): # pylint: disable=arguments-renamed """POST to a VyOS REST endpoint. Args: - endpoint (str): API path, e.g. '/configure' or '/retrieve'. - Named 'endpoint' not 'path' to avoid collision - with the VyOS payload field also called 'path'. + data (str): API path, e.g. '/configure' or '/retrieve'. + Named 'data' to match the HttpApiBase signature. + Internally referred to as endpoint to avoid collision + with the VyOS payload field also called 'data'. **payload: VyOS API fields: op, path, value, url, file, etc. Returns: @@ -105,6 +105,7 @@ class HttpApi(HttpApiBase): Raises: ConnectionError: on HTTP error or VyOS success=false response. """ + endpoint = data try: api_key = self._get_api_key() diff --git a/plugins/module_utils/vyos_rest.py b/plugins/module_utils/vyos_rest.py index aaf2b23..f4aa0dc 100644 --- a/plugins/module_utils/vyos_rest.py +++ b/plugins/module_utils/vyos_rest.py @@ -80,7 +80,7 @@ class VyOSRestClient: def _post_httpapi(self, endpoint, payload): try: - result = self._conn.send_request(endpoint=endpoint, **payload) + result = self._conn.send_request(endpoint, **payload) return result except ConnectionError as exc: raise VyOSRestError(str(exc)) @@ -192,7 +192,7 @@ class VyOSRestClient: if self._mode == "httpapi": try: return self._conn.send_request( - endpoint="/configure", + "/configure", _raw_list=commands, ) except ConnectionError as exc: diff --git a/plugins/modules/vyos_lldp_global.py b/plugins/modules/vyos_lldp_global.py index cc24be9..9ebbac0 100644 --- a/plugins/modules/vyos_lldp_global.py +++ b/plugins/modules/vyos_lldp_global.py @@ -1,5 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# GNU General Public License v3.0+ from __future__ import absolute_import, division, print_function diff --git a/plugins/modules/vyos_logging_global.py b/plugins/modules/vyos_logging_global.py index be240ff..09fdd6b 100644 --- a/plugins/modules/vyos_logging_global.py +++ b/plugins/modules/vyos_logging_global.py @@ -1,15 +1,12 @@ #!/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 -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule - - DOCUMENTATION = r""" --- module: vyos_logging_global @@ -33,12 +30,15 @@ options: type: dict suboptions: facilities: + description: List of syslog facilities to log to the console. type: list elements: dict suboptions: facility: + description: Syslog facility name (e.g. local7, all, kern). type: str severity: + description: Minimum severity level to log (e.g. err, debug, all). type: str files: description: Logging to local files. @@ -46,44 +46,59 @@ options: elements: dict suboptions: path: + description: Path to the log file on the device. type: str archive: + description: Log file archive/rotation settings. type: dict suboptions: file_num: + description: Number of archived log files to keep. type: int size: + description: Maximum size of log file in kilobytes before rotation. type: int facilities: + description: List of syslog facilities to log to this file. type: list elements: dict suboptions: facility: + description: Syslog facility name. type: str severity: + description: Minimum severity level to log. type: str global_params: description: Global syslog parameters (maps to C(system syslog global)). type: dict suboptions: archive: + description: Global log archive/rotation settings. type: dict suboptions: file_num: + description: Number of archived log files to keep. type: int size: + description: Maximum size of log file in kilobytes before rotation. type: int facilities: + description: List of syslog facilities for global logging. type: list elements: dict suboptions: facility: + description: Syslog facility name. type: str severity: + description: Minimum severity level to log. type: str marker_interval: + description: Interval in seconds between marker log entries. type: int preserve_fqdn: + description: Use the fully qualified domain name in syslog messages. type: bool hosts: description: Logging to remote syslog hosts. @@ -91,21 +106,27 @@ options: elements: dict suboptions: hostname: + description: IP address or hostname of the remote syslog server. type: str port: + description: UDP/TCP port on the remote syslog server (default 514). type: int protocol: + description: Transport protocol (udp or tcp). type: str facilities: + description: List of syslog facilities to forward to this host. type: list elements: dict suboptions: facility: + description: Syslog facility name. type: str severity: + description: Minimum severity level to forward. type: str protocol: - description: Per-facility protocol override. + description: Per-facility protocol override (udp or tcp). type: str users: description: Logging to local user terminals. @@ -113,14 +134,18 @@ options: elements: dict suboptions: username: + description: Local username whose terminal receives log messages. type: str facilities: + description: List of syslog facilities to send to this user. type: list elements: dict suboptions: facility: + description: Syslog facility name. type: str severity: + description: Minimum severity level to send. type: str running_config: @@ -213,6 +238,9 @@ saved: type: dict """ +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule + # ------------------------------------------------------------ # Normalization diff --git a/plugins/modules/vyos_ntp_global.py b/plugins/modules/vyos_ntp_global.py index 88073e1..dd5fe4e 100644 --- a/plugins/modules/vyos_ntp_global.py +++ b/plugins/modules/vyos_ntp_global.py @@ -1,5 +1,6 @@ #!/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 @@ -338,7 +339,20 @@ def main(): elements="dict", options=dict( server=dict(type="str", required=True), - options=dict(type="list", elements="str"), + options=dict( + type="list", + elements="str", + choices=[ + "dynamic", + "noselect", + "pool", + "preempt", + "prefer", + "nts", + "ptp", + "interleave", + ], + ), ), ), ), diff --git a/plugins/modules/vyos_route_maps.py b/plugins/modules/vyos_route_maps.py index c72c66a..55b1706 100644 --- a/plugins/modules/vyos_route_maps.py +++ b/plugins/modules/vyos_route_maps.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +# GNU General Public License v3.0+ from __future__ import absolute_import, division, print_function diff --git a/plugins/modules/vyos_snmp_server.py b/plugins/modules/vyos_snmp_server.py index 9bdf451..3b40344 100644 --- a/plugins/modules/vyos_snmp_server.py +++ b/plugins/modules/vyos_snmp_server.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# GNU General Public License v3.0+ +# 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 @@ -41,7 +41,7 @@ options: type: list elements: str authorization_type: - description: Authorization type. + description: Authorization type (ro=read-only, rw=read-write). type: str choices: ['ro', 'rw'] contact: @@ -65,7 +65,7 @@ options: elements: dict suboptions: address: - description: IP address. + description: IP address to listen on. type: str required: true port: @@ -76,10 +76,13 @@ options: type: dict suboptions: address: + description: IP address of the trap target host. type: str community: + description: Community name to use for traps. type: str port: + description: UDP port on the trap target host. type: int snmp_v3: description: SNMPv3 configuration. @@ -89,104 +92,132 @@ options: description: EngineID as a hex string. type: str groups: + description: SNMPv3 group configuration. type: list elements: dict suboptions: group: + description: Group name. type: str required: true mode: + description: Access mode (ro=read-only, rw=read-write). type: str choices: ['ro', 'rw'] seclevel: + description: Minimum security level required for group members. type: str choices: ['auth', 'priv'] view: + description: View name the group has access to. type: str users: + description: SNMPv3 user configuration. type: list elements: dict suboptions: user: + description: Username. type: str required: true authentication: + description: Authentication parameters for this user. type: dict suboptions: type: + description: Authentication algorithm. type: str - choices: ['md5', 'sha'] encrypted_key: - description: Encrypted key (stored as encrypted-password on device). + description: Encrypted authentication key (stored as encrypted-password on device). type: str plaintext_key: - description: Plaintext key (device encrypts it; recommended for 1.5+). + description: Plaintext authentication key (device encrypts it). type: str - no_log: true privacy: + description: Privacy (encryption) parameters for this user. type: dict suboptions: type: + description: Privacy algorithm. type: str - choices: ['des', 'aes'] encrypted_key: + description: Encrypted privacy key. type: str plaintext_key: + description: Plaintext privacy key (device encrypts it). type: str - no_log: true group: + description: Group this user belongs to. type: str mode: + description: Access mode for this user. type: str choices: ['ro', 'rw'] tsm_key: + description: TSM fingerprint of the certificate mapped to this user. type: str trap_targets: + description: SNMPv3 trap target configuration. type: list elements: dict suboptions: address: + description: IP address of the SNMPv3 trap target. type: str port: + description: UDP port on the trap target host. type: int protocol: + description: Transport protocol for traps. type: str choices: ['tcp', 'udp'] type: + description: Trap type. type: str choices: ['inform', 'trap'] authentication: + description: Authentication parameters for trap target. type: dict suboptions: type: + description: Authentication algorithm. type: str encrypted_key: + description: Encrypted authentication key. type: str plaintext_key: + description: Plaintext authentication key. type: str - no_log: true privacy: + description: Privacy parameters for trap target. type: dict suboptions: type: + description: Privacy algorithm. type: str encrypted_key: + description: Encrypted privacy key. type: str plaintext_key: + description: Plaintext privacy key. type: str - no_log: true views: + description: SNMPv3 view configuration. type: list elements: dict suboptions: view: + description: View name. type: str required: true oid: + description: OID subtree included in this view. type: str exclude: + description: OID subtree excluded from this view. type: str mask: + description: OID mask for the view. type: str state: description: @@ -781,7 +812,7 @@ ARGUMENT_SPEC = dict( privacy=dict(type="dict", options=_auth_privacy_spec()), group=dict(type="str"), mode=dict(type="str", choices=["ro", "rw"]), - tsm_key=dict(type="str"), + tsm_key=dict(type="str", no_log=True), ), ), trap_targets=dict( -- cgit v1.2.3 From 63641482f79e6fb81603762f61478969c1ac57f8 Mon Sep 17 00:00:00 2001 From: omnom62 Date: Thu, 4 Jun 2026 06:39:05 +1000 Subject: Sanity check fixes --- plugins/modules/vyos_banner.py | 1 - plugins/modules/vyos_hostname.py | 1 - tests/unit/modules/base.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) (limited to 'plugins/modules') diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py index 4f734cd..304fa20 100644 --- a/plugins/modules/vyos_banner.py +++ b/plugins/modules/vyos_banner.py @@ -68,7 +68,6 @@ options: api_key: description: REST API key (not needed when ansible_httpapi_api_key is set). type: str - no_log: true timeout: description: Request timeout in seconds. type: int diff --git a/plugins/modules/vyos_hostname.py b/plugins/modules/vyos_hostname.py index 461a552..86444ad 100644 --- a/plugins/modules/vyos_hostname.py +++ b/plugins/modules/vyos_hostname.py @@ -73,7 +73,6 @@ options: description: - API key configured on the device. type: str - no_log: true timeout: description: - Request timeout in seconds. diff --git a/tests/unit/modules/base.py b/tests/unit/modules/base.py index e5a602e..4d49fbb 100644 --- a/tests/unit/modules/base.py +++ b/tests/unit/modules/base.py @@ -10,7 +10,7 @@ import json import os import unittest -from unittest.mock import MagicMock, patch # noqa: F401 +from unittest.mock import MagicMock # noqa: F401 def load_fixture(filename): -- cgit v1.2.3