diff options
Diffstat (limited to 'plugins/modules/vyos_configure.py')
| -rw-r--r-- | plugins/modules/vyos_configure.py | 276 |
1 files changed, 77 insertions, 199 deletions
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__": |
