summaryrefslogtreecommitdiff
path: root/plugins/modules/vyos_banner.py
diff options
context:
space:
mode:
authoromnom62 <omnom62@outlook.com>2026-03-20 21:50:13 +1000
committeromnom62 <omnom62@outlook.com>2026-03-20 21:50:13 +1000
commit7599ba96bc3b659cb2a5f00b1be46ab6f55f48a6 (patch)
tree35b39a126fe94f8917352aa2c229d3022d1223e4 /plugins/modules/vyos_banner.py
parent9dc03bb92e08455812e0969505495309456a4bfb (diff)
downloadrest.vyos-7599ba96bc3b659cb2a5f00b1be46ab6f55f48a6.tar.gz
rest.vyos-7599ba96bc3b659cb2a5f00b1be46ab6f55f48a6.zip
precommit changes
Diffstat (limited to 'plugins/modules/vyos_banner.py')
-rw-r--r--plugins/modules/vyos_banner.py366
1 files changed, 366 insertions, 0 deletions
diff --git a/plugins/modules/vyos_banner.py b/plugins/modules/vyos_banner.py
new file mode 100644
index 0000000..0c35f78
--- /dev/null
+++ b/plugins/modules/vyos_banner.py
@@ -0,0 +1,366 @@
+#!/usr/bin/python
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule
+
+
+DOCUMENTATION = r"""
+---
+module: vyos_banner
+short_description: Manage login banners on VyOS devices using REST API
+description:
+ - Configure pre-login and post-login banners on VyOS devices via REST API.
+ - Supports idempotent configuration using structured data.
+ - Multiline banner text is supported.
+ - Uses REST API (C(connection=httpapi)) instead of CLI.
+version_added: "1.0.0"
+author:
+ - Your Name (@yourhandle)
+
+options:
+ config:
+ description:
+ - Banner configuration.
+ type: dict
+ required: true
+ suboptions:
+ banner:
+ description:
+ - Banner type to configure.
+ type: str
+ required: true
+ choices:
+ - pre-login
+ - post-login
+ text:
+ description:
+ - Banner text (supports multiline string).
+ type: str
+
+ state:
+ description:
+ - Desired state of the configuration.
+ type: str
+ default: merged
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+
+notes:
+ - This module requires C(ansible_connection=httpapi).
+ - Banner text comparison is whitespace-normalized for idempotency.
+"""
+
+EXAMPLES = r"""
+- name: Configure pre-login banner
+ vyos.rest.vyos_banner:
+ config:
+ banner: pre-login
+ text: |
+ Unauthorized access is prohibited
+ Disconnect immediately
+ state: merged
+
+- name: Replace post-login banner
+ vyos.rest.vyos_banner:
+ config:
+ banner: post-login
+ text: |
+ Welcome to VyOS
+ state: replaced
+
+- name: Remove pre-login banner
+ vyos.rest.vyos_banner:
+ config:
+ banner: pre-login
+ state: deleted
+
+- name: Gather banner configuration
+ vyos.rest.vyos_banner:
+ config:
+ banner: pre-login
+ state: gathered
+"""
+
+RETURN = r"""
+before:
+ description: Configuration before changes.
+ returned: always
+ type: dict
+
+after:
+ description: Configuration after changes.
+ returned: when changed
+ type: dict
+
+commands:
+ description: List of commands sent to the device.
+ returned: when changes are required
+ type: list
+
+gathered:
+ description: Current device configuration.
+ returned: when state is gathered
+ type: dict
+
+response:
+ description: Raw response from VyOS REST API.
+ returned: when changes are applied
+ type: dict
+"""
+
+
+# ------------------------------------------------------------
+# Helpers
+# ------------------------------------------------------------
+
+
+def normalize_text(text):
+ if text is None:
+ return None
+ return text.strip()
+
+
+def get_running_config(vyos, banner):
+ try:
+ raw = vyos.get_config(["system", "login", "banner"])
+ except Exception as e:
+ if "Configuration under specified path is empty" in str(e):
+ return {"banner": banner, "text": None}
+ raise
+
+ if not raw or not isinstance(raw, dict):
+ return {"banner": banner, "text": None}
+
+ val = raw.get(banner)
+
+ if not val:
+ return {"banner": banner, "text": None}
+
+ if isinstance(val, str):
+ return {
+ "banner": banner,
+ "text": val.strip(),
+ }
+
+ if isinstance(val, list):
+ lines = [line.strip() for line in val if line.strip()]
+ return {
+ "banner": banner,
+ "text": "\n".join(lines) if lines else None,
+ }
+
+ return {"banner": banner, "text": None}
+
+
+def build_commands(want, have, state):
+ """
+ Build list of VyOS REST API commands to apply `want` configuration
+ based on current `have` configuration and desired `state`.
+ Handles:
+ - missing paths
+ - idempotency
+ - multiline banners
+ - merged, replaced, overridden, deleted
+ """
+ commands = []
+
+ banner = want["banner"]
+ want_text = want.get("text")
+ have_text = have.get("text")
+
+ base_path = ["system", "login", "banner", banner]
+
+ want_lines = want_text.splitlines() if want_text else []
+ have_lines = have_text.splitlines() if have_text else []
+
+ banner_exists = have_text is not None
+ # --------------------------------------------------------
+ # deleted
+ # --------------------------------------------------------
+ if state == "deleted":
+ if banner_exists or have_text is not None:
+ commands.append(
+ {
+ "op": "delete",
+ "path": base_path,
+ },
+ )
+ return commands
+
+ # --------------------------------------------------------
+ # merged
+ # --------------------------------------------------------
+
+ if state == "merged":
+
+ # normalize both
+ want_lines = want_text.splitlines() if want_text else []
+ have_lines = have_text.splitlines() if have_text else []
+
+ # if identical → idempotent
+ if want_lines == have_lines:
+ return []
+
+ # if device empty → just push all
+ if not have_lines:
+ return [{"op": "set", "path": base_path + [line]} for line in want_lines]
+
+ # append missing lines only
+ commands = []
+ for line in want_lines:
+ if line not in have_lines:
+ commands.append(
+ {
+ "op": "set",
+ "path": base_path + [line],
+ },
+ )
+
+ return commands
+
+ # if state == "merged":
+ # # create parent path if missing
+
+ # for line in want_lines:
+ # if line not in have_lines:
+ # commands.append({
+ # "op": "set",
+ # "path": base_path + [line]
+ # })
+
+ # return commands
+
+ # --------------------------------------------------------
+ # replaced / overridden
+ # --------------------------------------------------------
+ if state in ["replaced", "overridden"]:
+ # if existing lines differ from desired lines, delete first
+ if banner_exists and want_lines != have_lines:
+ commands.append(
+ {
+ "op": "delete",
+ "path": base_path,
+ },
+ )
+ # create parent path again before setting lines
+ if want_lines:
+ commands.append(
+ {
+ "op": "set",
+ "path": base_path,
+ "value": "",
+ },
+ )
+
+ # set all desired lines
+ for line in want_lines:
+ commands.append(
+ {
+ "op": "set",
+ "path": base_path + [line],
+ },
+ )
+
+ return commands
+
+ return commands
+
+
+# ------------------------------------------------------------
+# Main
+# ------------------------------------------------------------
+
+
+def main():
+
+ argument_spec = dict(
+ config=dict(
+ type="dict",
+ required=True,
+ options=dict(
+ banner=dict(
+ type="str",
+ required=True,
+ choices=["pre-login", "post-login"],
+ ),
+ text=dict(type="str"),
+ ),
+ ),
+ state=dict(
+ default="merged",
+ choices=[
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ ],
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ vyos = VyOSModule(module)
+
+ state = module.params["state"]
+ config = module.params.get("config") or {}
+
+ banner = config.get("banner")
+
+ if not banner:
+ module.fail_json(msg="banner is required in config")
+
+ have = get_running_config(vyos, banner)
+
+ # --------------------------------------------------------
+ # gathered
+ # --------------------------------------------------------
+
+ if state == "gathered":
+ module.exit_json(
+ changed=False,
+ gathered=have,
+ )
+
+ want = {
+ "banner": banner,
+ "text": config.get("text"),
+ }
+
+ commands = build_commands(want, have, state)
+
+ if module.check_mode:
+ module.exit_json(
+ changed=bool(commands),
+ commands=commands,
+ )
+ if commands:
+
+ response = vyos.apply_commands(commands)
+ saved = vyos.save_config()
+
+ module.exit_json(
+ changed=True,
+ before=have,
+ after=want,
+ commands=commands,
+ saved=saved,
+ response=response,
+ )
+
+ module.exit_json(
+ changed=False,
+ before=have,
+ after=have,
+ )
+
+
+if __name__ == "__main__":
+ main()