summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authoromnom62 <75066712+omnom62@users.noreply.github.com>2026-06-04 06:48:22 +1000
committerGitHub <noreply@github.com>2026-06-04 06:48:22 +1000
commit4d03dd58cd5d14b8f8094e2490034b31187aea2b (patch)
treeb42be5a26b1881fd81542d510a88aef8d4247d3b /plugins
parent6a660fbbb1a855ca88b4926eda118f28f61518ca (diff)
parentcc8b7230098feb057ce5d903ded00ac0a7e648c0 (diff)
downloadrest.vyos-4d03dd58cd5d14b8f8094e2490034b31187aea2b.tar.gz
rest.vyos-4d03dd58cd5d14b8f8094e2490034b31187aea2b.zip
Merge pull request #3 from vyos/init
Init Collection
Diffstat (limited to 'plugins')
-rw-r--r--plugins/httpapi/vyos.py21
-rw-r--r--plugins/module_utils/vyos.py113
-rw-r--r--plugins/module_utils/vyos_rest.py50
-rw-r--r--plugins/modules/vyos_banner.py59
-rw-r--r--plugins/modules/vyos_bgp_address_family.py354
-rw-r--r--plugins/modules/vyos_bgp_global.py365
-rw-r--r--plugins/modules/vyos_config_file.py130
-rw-r--r--plugins/modules/vyos_configure.py276
-rw-r--r--plugins/modules/vyos_facts.py161
-rw-r--r--plugins/modules/vyos_firewall_global.py360
-rw-r--r--plugins/modules/vyos_firewall_interfaces.py244
-rw-r--r--plugins/modules/vyos_firewall_rules.py492
-rw-r--r--plugins/modules/vyos_generate.py102
-rw-r--r--plugins/modules/vyos_hostname.py121
-rw-r--r--plugins/modules/vyos_image.py131
-rw-r--r--plugins/modules/vyos_info.py115
-rw-r--r--plugins/modules/vyos_interfaces.py401
-rw-r--r--plugins/modules/vyos_l3_interfaces.py381
-rw-r--r--plugins/modules/vyos_lag_interfaces.py228
-rw-r--r--plugins/modules/vyos_lldp_global.py127
-rw-r--r--plugins/modules/vyos_lldp_interfaces.py227
-rw-r--r--plugins/modules/vyos_logging_global.py38
-rw-r--r--plugins/modules/vyos_ntp_global.py29
-rw-r--r--plugins/modules/vyos_ospf_interfaces.py290
-rw-r--r--plugins/modules/vyos_ospfv2.py487
-rw-r--r--plugins/modules/vyos_ospfv3.py257
-rw-r--r--plugins/modules/vyos_ping.py180
-rw-r--r--plugins/modules/vyos_prefix_lists.py267
-rw-r--r--plugins/modules/vyos_reset.py108
-rw-r--r--plugins/modules/vyos_route_maps.py654
-rw-r--r--plugins/modules/vyos_show.py119
-rw-r--r--plugins/modules/vyos_snmp_server.py55
-rw-r--r--plugins/modules/vyos_static_routes.py397
-rw-r--r--plugins/modules/vyos_system.py147
-rw-r--r--plugins/modules/vyos_user.py282
-rw-r--r--plugins/modules/vyos_vlan.py197
36 files changed, 891 insertions, 7074 deletions
diff --git a/plugins/httpapi/vyos.py b/plugins/httpapi/vyos.py
index 1719254..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,11 +105,16 @@ class HttpApi(HttpApiBase):
Raises:
ConnectionError: on HTTP error or VyOS success=false response.
"""
+ endpoint = data
+
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 <prefix>`` -> ``allow-client <prefix>``
- 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 <prefix>`` -> ``allow-client <prefix>``
+ # 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..f4aa0dc 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
@@ -111,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))
@@ -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(
+ "/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_banner.py b/plugins/modules/vyos_banner.py
index b9df27d..304fa20 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:
@@ -67,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
@@ -97,12 +97,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 +114,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 +193,7 @@ def main():
options=dict(
banner=dict(
type="str",
- required=True,
+ required=False,
choices=["pre-login", "post-login"],
),
text=dict(type="str"),
@@ -203,17 +212,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_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_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_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_configure.py b/plugins/modules/vyos_configure.py
index 355a78c..02fc7ad 100644
--- a/plugins/modules/vyos_configure.py
+++ b/plugins/modules/vyos_configure.py
@@ -1,6 +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)
+# GNU General Public License v3.0+
from __future__ import absolute_import, division, print_function
@@ -10,249 +10,127 @@ __metaclass__ = type
DOCUMENTATION = r"""
---
module: vyos_configure
-short_description: Manage VyOS device configuration via the REST API.
+short_description: Send raw set/delete commands to a VyOS device via 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.
+ - 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 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).
+ - List of CLI-style configuration commands.
+ - Each command must start with C(set) or C(delete).
type: list
- elements: raw
+ elements: str
required: true
save:
description:
- - Whether to save the configuration to disk after a successful commit.
+ - Whether to save the configuration after applying commands.
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 <ID> key <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
+ - module: vyos.vyos.vyos_config
"""
-import re
-
-
EXAMPLES = r"""
-- name: Set hostname via REST API
+- name: Add loopback address for testing
vyos.rest.vyos_configure:
- hostname: 192.168.1.1
- api_key: MY-KEY
commands:
- - "set system host-name vyos-router"
+ - set interfaces loopback lo address 20.1.1.1/32
+ save: false
-- name: Configure an interface description and address
+- name: Remove loopback address after testing
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
+ - delete interfaces loopback lo address 20.1.1.1/32
+ save: false
-- name: Delete a static route using dict syntax
+- name: Multiple commands in one atomic commit
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"]
+ - set system host-name vyos-test
+ - set system domain-name example.com
+ save: true
"""
-from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.vyos.rest.plugins.module_utils.vyos_rest import (
- VYOS_REST_CONNECTION_ARGSPEC,
- VyOSRestClient,
- VyOSRestError,
-)
-
+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
+"""
-def _parse_cli_command(cmd_str):
- """Parse a VyOS CLI-syntax string into an API payload dict.
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
- Supports:
- set <path> [<value>]
- delete <path>
- comment <path> <comment>
- Args:
- cmd_str (str): e.g. "set interfaces ethernet eth0 description 'WAN'"
+def _parse_command(line):
+ """Parse a CLI-style command string into (op, path) tuple.
- Returns:
- dict: e.g. {"op": "set", "path": [...], "value": "WAN"}
+ 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"])
"""
- 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
+ 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():
- 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,
+ argument_spec=dict(
+ commands=dict(type="list", elements="str", required=True),
+ save=dict(type="bool", default=False),
+ ),
supports_check_mode=True,
)
- try:
- commands = _normalise_commands(module.params["commands"])
- except ValueError as exc:
- module.fail_json(msg=str(exc))
+ vyos = VyOSModule(module)
+ commands_raw = module.params["commands"]
+ do_save = module.params["save"]
- if module.check_mode:
- module.exit_json(
- changed=True,
- commands_sent=commands,
- saved=False,
- msg="Check mode: no changes applied.",
- )
+ 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)
- client = VyOSRestClient(module)
- changed = False
- saved = False
+ if not commands:
+ module.exit_json(changed=False, commands=[])
- 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.check_mode:
+ module.exit_json(changed=True, commands=commands)
- if module.params["save"]:
- client.config_file_save()
- saved = True
+ response = vyos.apply_commands(commands)
- except VyOSRestError as exc:
- module.fail_json(msg=str(exc), commands_sent=commands)
+ if do_save:
+ vyos.save_config()
- module.exit_json(changed=changed, commands_sent=commands, saved=saved)
+ module.exit_json(
+ changed=True,
+ commands=commands,
+ response=response,
+ )
if __name__ == "__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_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_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_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_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_hostname.py b/plugins/modules/vyos_hostname.py
index 10255b5..86444ad 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,58 @@ 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 +102,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 +123,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 +137,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 +171,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 +185,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 +255,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/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_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_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_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_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_lldp_global.py b/plugins/modules/vyos_lldp_global.py
index 0a9618d..9ebbac0 100644
--- a/plugins/modules/vyos_lldp_global.py
+++ b/plugins/modules/vyos_lldp_global.py
@@ -1,62 +1,103 @@
#!/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_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
@@ -74,41 +115,82 @@ 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:
+
+ 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
+ have_addrs_set = set(have.get("addresses") or []) if state != "replaced" else set()
for addr in want.get("addresses") or []:
- cmds.append(("set", _BASE + ["management-address", addr]))
+ if addr not in have_addrs_set:
+ 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
+ have_protos_set = set(have.get("legacy_protocols") or []) if state != "replaced" else set()
for p in want.get("legacy_protocols") or []:
- cmds.append(("set", _BASE + ["legacy-protocols", p]))
+ if p not in have_protos_set:
+ cmds.append(("set", _BASE + ["legacy-protocols", p]))
+
return cmds
def main():
module = AnsibleModule(
- dict(
+ argument_spec=dict(
config=dict(
type="dict",
options=dict(
@@ -122,19 +204,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()
@@ -146,6 +236,7 @@ def main():
saved=saved,
response=response,
)
+
module.exit_json(changed=False, before=have, after=have, commands=[])
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_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 185dc02..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
@@ -218,13 +219,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",
@@ -339,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_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_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_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_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_prefix_lists.py b/plugins/modules/vyos_prefix_lists.py
deleted file mode 100644
index 63fa584..0000000
--- a/plugins/modules/vyos_prefix_lists.py
+++ /dev/null
@@ -1,267 +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_prefix_lists
-short_description: Prefix-Lists resource module via REST API.
-description:
- - Manages IPv4 and IPv6 prefix lists on VyOS via the HTTPS REST API.
-version_added: "1.0.0"
-author:
- - VyOS Community (@vyos)
-options:
- config:
- description: List of prefix-list configurations.
- type: list
- elements: dict
- suboptions:
- afi:
- description: Address family.
- 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: Description.
- type: str
- entries:
- description: Prefix list rules.
- type: list
- elements: dict
- suboptions:
- sequence:
- description: Rule sequence number.
- type: int
- required: true
- 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:
- 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,
-)
-
-
-_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:
- return []
-
-
-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"])
-
-
-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)
- 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" 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))
-
- 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_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_route_maps.py b/plugins/modules/vyos_route_maps.py
index d13a88d..55b1706 100644
--- a/plugins/modules/vyos_route_maps.py
+++ b/plugins/modules/vyos_route_maps.py
@@ -1,7 +1,6 @@
#!/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,416 @@ 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
-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:
+_BASE = ["policy", "route-map"]
+
+_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"],
+}
+
+_MATCH_MAP = {
+ "interface": ["interface"],
+ "metric": ["metric"],
+ "origin": ["origin"],
+ "peer": ["peer"],
+ "protocol": ["protocol"],
+}
+
+
+def get_running_config(vyos):
+ raw = vyos.get_config(["policy", "route-map"])
+ if not raw or not isinstance(raw, dict):
+ return []
+
+ 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"])
+ 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
+
+
+def _match_cmds(rbase, match):
+ 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):
+ cmds = []
+ if not setv:
+ return cmds
-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
+ sbase = rbase + ["set"]
+
+ 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):
+ 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):
+ 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):
+ cmds = []
+ seq = str(rule["sequence"])
+ rbase = _BASE + [rm_name, "rule", seq]
+
+ 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"])]))
+
+ 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:
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)
+
+ want_set_api = _want_to_api_set(rule.get("set"))
+ have_set = have_rule.get("set") or {}
+ if want_set_api != have_set:
+ cmds += _set_cmds(rbase, rule.get("set"))
+
+ return cmds
+
+
+def build_commands(config, have_raw, state):
+ 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:
+ # 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
+
+ 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 = dict(
+ config=dict(type="list", elements="dict"),
+ state=dict(
+ default="merged",
+ choices=["merged", "replaced", "overridden", "deleted", "gathered"],
+ ),
+)
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__":
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_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(
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_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_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_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()