summaryrefslogtreecommitdiff
path: root/plugins/modules/vyos_logging_global.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/modules/vyos_logging_global.py')
-rw-r--r--plugins/modules/vyos_logging_global.py460
1 files changed, 187 insertions, 273 deletions
diff --git a/plugins/modules/vyos_logging_global.py b/plugins/modules/vyos_logging_global.py
index f18ba3f..4a89a12 100644
--- a/plugins/modules/vyos_logging_global.py
+++ b/plugins/modules/vyos_logging_global.py
@@ -1,7 +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
@@ -13,8 +12,7 @@ module: vyos_logging_global
short_description: Manage syslog configuration on VyOS devices using REST API
description:
- Manages syslog (logging) configuration on VyOS devices via the REST API.
- - Supports console, file, host, user, and global logging targets with
- per-target facility and severity configuration.
+ - Targets VyOS 1.5+ syslog schema under C(system syslog).
- Uses REST API (C(connection=httpapi)) instead of CLI.
version_added: "1.0.0"
author:
@@ -40,51 +38,12 @@ options:
severity:
description: Minimum severity level to log (e.g. err, debug, all).
type: str
- files:
- description: Logging to local files.
- type: list
- 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)).
+ description: Global syslog parameters (maps to C(system syslog local) on device).
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.
+ description: List of syslog facilities for local logging.
type: list
elements: dict
suboptions:
@@ -101,7 +60,7 @@ options:
description: Use the fully qualified domain name in syslog messages.
type: bool
hosts:
- description: Logging to remote syslog hosts.
+ description: Logging to remote syslog hosts (maps to C(system syslog remote)).
type: list
elements: dict
suboptions:
@@ -148,9 +107,6 @@ options:
description: Minimum severity level to send.
type: str
- running_config:
- description: Used only with state C(parsed).
- type: str
state:
description:
@@ -163,8 +119,6 @@ options:
- overridden
- deleted
- gathered
- - rendered
- - parsed
"""
EXAMPLES = r"""
@@ -175,30 +129,18 @@ EXAMPLES = r"""
facilities:
- facility: local7
severity: err
- files:
- - path: logFile
- archive:
- file_num: 2
- facilities:
- - facility: local6
- severity: emerg
hosts:
- hostname: 172.16.0.1
- port: 223
+ port: 514
facilities:
- facility: local7
severity: all
- - facility: all
- protocol: udp
users:
- username: vyos
facilities:
- facility: local7
severity: debug
global_params:
- archive:
- file_num: 2
- size: 111
facilities:
- facility: cron
severity: debug
@@ -234,223 +176,184 @@ gathered:
type: dict
saved:
description: Result of save_config after applying changes.
- returned: when changes are applied
+ returned: when changed
type: dict
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
-
-
-# ------------------------------------------------------------
-# Normalization
-# ------------------------------------------------------------
-
-
-def normalize_config(cfg):
- result = {
- "console": {"facilities": {}},
- "global": {"facilities": {}},
- "hosts": {},
- "files": {},
- "users": {},
- }
-
- for f in cfg.get("console", {}).get("facilities", []):
- result["console"]["facilities"][f["facility"]] = f.get("severity")
-
- gp = cfg.get("global_params", {})
- for f in gp.get("facilities", []):
- result["global"]["facilities"][f["facility"]] = f.get("severity")
- if gp.get("archive"):
- result["global"]["archive"] = gp["archive"]
- if gp.get("marker_interval"):
- result["global"]["marker_interval"] = gp["marker_interval"]
- if gp.get("preserve_fqdn"):
- result["global"]["preserve_fqdn"] = True
-
- for h in cfg.get("hosts", []):
- host = {"port": h.get("port"), "facilities": {}}
- for f in h.get("facilities", []):
- host["facilities"][f["facility"]] = {k: v for k, v in f.items() if k != "facility"}
- result["hosts"][h["hostname"]] = host
-
- for f in cfg.get("files", []):
- facilities = {x["facility"]: x.get("severity") for x in f.get("facilities", [])}
- result["files"][f["path"]] = {
- "archive": f.get("archive"),
- "facilities": facilities,
- }
-
- for u in cfg.get("users", []):
- result["users"][u["username"]] = {
- "facilities": {f["facility"]: f.get("severity") for f in u.get("facilities", [])},
- }
-
- return result
-
-
-def normalize_running(raw):
- result = {
- "console": {"facilities": {}},
- "global": {"facilities": {}},
- "hosts": {},
- "files": {},
- "users": {},
- }
-
- if not raw:
- return result
-
- for f, data in raw.get("console", {}).get("facility", {}).items():
- result["console"]["facilities"][f] = data.get("level")
-
- g = raw.get("local", {})
- for f, data in g.get("facility", {}).items():
- result["global"]["facilities"][f] = data.get("level")
- if "archive" in g:
- result["global"]["archive"] = g["archive"]
- if "marker" in g and "interval" in g["marker"]:
- result["global"]["marker_interval"] = g["marker"]["interval"]
- if "preserve-fqdn" in g:
- result["global"]["preserve_fqdn"] = True
-
- for host, data in raw.get("remote", {}).items():
- h = {"port": data.get("port"), "facilities": {}}
- for f, fd in data.get("facility", {}).items():
- h["facilities"][f] = {
- "severity": fd.get("level"),
- "protocol": fd.get("protocol"),
- }
- result["hosts"][host] = h
-
- for path, data in raw.get("file", {}).items():
- facilities = {}
- for f, fd in data.get("facility", {}).items():
- facilities[f] = fd.get("level")
- result["files"][path] = {
- "archive": data.get("archive"),
- "facilities": facilities,
- }
-
- for user, data in raw.get("user", {}).items():
- facilities = {}
- for f, fd in data.get("facility", {}).items():
- facilities[f] = fd.get("level")
- result["users"][user] = {"facilities": facilities}
-
- return result
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import (
+ VyOSModule,
+ dict_op,
+)
-# ------------------------------------------------------------
-# Diff helpers
-# ------------------------------------------------------------
+_BASE = ["system", "syslog"]
-def diff_facilities(base, want, have, state):
- cmds = []
- want_keys = set(want)
- have_keys = set(have)
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
- for f in want_keys:
- if f not in have_keys or want[f] != have[f]:
- path = base + ["facility", f]
- if want[f]:
- path += ["level", want[f]]
- cmds.append(("set", path))
- if state in ["replaced", "deleted"]:
- for f in have_keys - want_keys:
- cmds.append(("delete", base + ["facility", f]))
-
- return cmds
-
-
-def diff_map(base, want, have, state):
- cmds = []
- w = set(want)
- h = set(have)
-
- if state in ["merged", "replaced"]:
- for k in w - h:
- cmds.append(("set", base + [k]))
-
- if state in ["replaced", "deleted"]:
- for k in h - w:
- cmds.append(("delete", base + [k]))
-
- return cmds
-
-
-# ------------------------------------------------------------
-# Build commands
-# ------------------------------------------------------------
-
-
-def build_commands(want, have, state):
- cmds = []
-
- if state == "overridden":
- cmds.append(("delete", ["system", "syslog"]))
- state = "merged"
-
- cmds += diff_facilities(
- ["system", "syslog", "console"],
- want["console"]["facilities"],
- have["console"]["facilities"],
- state,
- )
-
- cmds += diff_facilities(
- ["system", "syslog", "local"],
- want["global"]["facilities"],
- have["global"]["facilities"],
- state,
- )
-
- cmds += diff_map(
- ["system", "syslog", "file"],
- want["files"],
- have["files"],
- state,
- )
-
- cmds += diff_map(
- ["system", "syslog", "remote"],
- want["hosts"],
- have["hosts"],
- state,
- )
-
- cmds += diff_map(
- ["system", "syslog", "user"],
- want["users"],
- have["users"],
- state,
- )
+def _fac_list_to_device(facilities):
+ """Convert [{facility, severity, protocol}] -> {"name": {"level": s, ...}}"""
+ result = {}
+ for fac in facilities or []:
+ name = fac["facility"]
+ entry = {}
+ if fac.get("severity"):
+ entry["level"] = fac["severity"]
+ if fac.get("protocol"):
+ entry["protocol"] = fac["protocol"]
+ result[name] = entry
+ return result
- return cmds
+def _fac_device_to_list(raw_fac):
+ """Convert {"name": {"level": s}} -> [{facility, severity}]"""
+ if not raw_fac or not isinstance(raw_fac, dict):
+ return []
+ result = []
+ for name, data in sorted(raw_fac.items()):
+ entry = {"facility": name}
+ if isinstance(data, dict):
+ if data.get("level"):
+ entry["severity"] = data["level"]
+ if data.get("protocol"):
+ entry["protocol"] = data["protocol"]
+ result.append(entry)
+ return result
-# ------------------------------------------------------------
-# Running config
-# ------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# Shape adapters
+# ---------------------------------------------------------------------------
+
+
+def _want_to_device(config):
+ """Convert argspec config to device shape for dict_op.
+
+ VyOS 1.5 syslog schema:
+ system syslog console facility <f> level <s>
+ system syslog local facility <f> level <s> (was: global)
+ system syslog remote <host> facility <f> ... (was: host)
+ system syslog user <u> facility <f> ...
+ system syslog marker interval <n> (was: global marker)
+ system syslog preserve-fqdn (was: global preserve-fqdn)
+ NOTE: file and archive are removed in VyOS 1.5
+ """
+ if not config:
+ return {}
+ want = {}
+
+ # console
+ console = config.get("console") or {}
+ if console.get("facilities"):
+ want["console"] = {"facility": _fac_list_to_device(console["facilities"])}
+
+ # global_params -> local + top-level marker/preserve-fqdn
+ gp = config.get("global_params") or {}
+ if gp:
+ if gp.get("facilities"):
+ want["local"] = {"facility": _fac_list_to_device(gp["facilities"])}
+ if gp.get("marker_interval") is not None:
+ want["marker"] = {"interval": gp["marker_interval"]}
+ if gp.get("preserve_fqdn"):
+ want["preserve-fqdn"] = {}
+
+ # hosts -> remote (keyed by hostname)
+ for h in config.get("hosts") or []:
+ hd = {}
+ if h.get("port") is not None:
+ hd["port"] = h["port"]
+ if h.get("protocol"):
+ hd["protocol"] = h["protocol"]
+ if h.get("facilities"):
+ hd["facility"] = _fac_list_to_device(h["facilities"])
+ want.setdefault("remote", {})[h["hostname"]] = hd
+
+ # users -> user (keyed by username)
+ for u in config.get("users") or []:
+ ud = {}
+ if u.get("facilities"):
+ ud["facility"] = _fac_list_to_device(u["facilities"])
+ want.setdefault("user", {})[u["username"]] = ud
+
+ return want
+
+
+def _device_to_argspec(raw):
+ """Convert raw device response to argspec shape for before/after/gathered."""
+ if not raw:
+ return {}
+ result = {}
+
+ # console
+ console = raw.get("console") or {}
+ if console:
+ facs = _fac_device_to_list(console.get("facility"))
+ if facs:
+ result["console"] = {"facilities": facs}
+
+ # local -> global_params
+ local = raw.get("local") or {}
+ marker = raw.get("marker") or {}
+ preserve_fqdn = "preserve-fqdn" in raw
+ if local or marker or preserve_fqdn:
+ gp = {}
+ facs = _fac_device_to_list(local.get("facility") if isinstance(local, dict) else {})
+ if facs:
+ gp["facilities"] = facs
+ if isinstance(marker, dict) and "interval" in marker:
+ gp["marker_interval"] = marker["interval"]
+ if preserve_fqdn:
+ gp["preserve_fqdn"] = True
+ if gp:
+ result["global_params"] = gp
+
+ # remote -> hosts
+ remote_raw = raw.get("remote") or {}
+ if remote_raw and isinstance(remote_raw, dict):
+ hosts = []
+ for hostname, data in sorted(remote_raw.items()):
+ h = {"hostname": hostname}
+ if isinstance(data, dict):
+ if data.get("port") is not None:
+ h["port"] = data["port"]
+ if data.get("protocol"):
+ h["protocol"] = data["protocol"]
+ facs = _fac_device_to_list(data.get("facility"))
+ if facs:
+ h["facilities"] = facs
+ hosts.append(h)
+ if hosts:
+ result["hosts"] = hosts
+
+ # user -> users
+ user_raw = raw.get("user") or {}
+ if user_raw and isinstance(user_raw, dict):
+ users = []
+ for username, data in sorted(user_raw.items()):
+ u = {"username": username}
+ if isinstance(data, dict):
+ facs = _fac_device_to_list(data.get("facility"))
+ if facs:
+ u["facilities"] = facs
+ users.append(u)
+ if users:
+ result["users"] = users
-def get_running_config(vyos):
- raw = vyos.get_config(["system", "syslog"])
- return normalize_running(raw)
+ return result
-# ------------------------------------------------------------
+# ---------------------------------------------------------------------------
# Main
-# ------------------------------------------------------------
+# ---------------------------------------------------------------------------
def main():
argument_spec = dict(
config=dict(type="dict"),
- running_config=dict(type="str"),
state=dict(
default="merged",
choices=[
@@ -459,8 +362,6 @@ def main():
"overridden",
"deleted",
"gathered",
- "rendered",
- "parsed",
],
),
)
@@ -471,22 +372,34 @@ def main():
state = module.params["state"]
config = module.params.get("config") or {}
+ raw_have = vyos.get_config(_BASE)
+ have = _device_to_argspec(raw_have)
+
if state == "gathered":
- module.exit_json(gathered=get_running_config(vyos))
+ module.exit_json(changed=False, gathered=have)
- want = normalize_config(config)
- have = get_running_config(vyos)
+ want_device = _want_to_device(config)
if state == "deleted":
- want = {
- "console": {"facilities": {}},
- "global": {"facilities": {}},
- "hosts": {},
- "files": {},
- "users": {},
- }
-
- commands = build_commands(want, have, state)
+ commands = [("delete", _BASE)] if raw_have else []
+ elif state == "overridden":
+ commands = []
+ for section in list(raw_have.keys()):
+ if section not in want_device:
+ commands.append(("delete", _BASE + [section]))
+ else:
+ commands += dict_op(
+ want_device[section],
+ raw_have[section],
+ _BASE + [section],
+ op="purge",
+ )
+ commands += dict_op(want_device, raw_have, _BASE, op="set")
+ else:
+ commands = []
+ if state == "replaced":
+ commands += dict_op(want_device, raw_have, _BASE, op="purge")
+ commands += dict_op(want_device, raw_have, _BASE, op="set")
if module.check_mode:
module.exit_json(changed=bool(commands), commands=commands, before=have)
@@ -494,10 +407,11 @@ def main():
if commands:
response = vyos.apply_commands(commands)
saved = vyos.save_config()
+ after = {} if state == "deleted" else _device_to_argspec(vyos.get_config(_BASE))
module.exit_json(
changed=True,
before=have,
- after=want,
+ after=after,
commands=commands,
saved=saved,
response=response,