#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2017, Ansible by Red Hat, inc # 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 = """ module: iosxr_banner author: - Trishna Guha (@trishnaguha) - Kedar Kekan (@kedarX) short_description: Manage multiline banners on Cisco IOS XR devices description: - This module will configure both exec and motd banners on remote device running Cisco IOS XR. It allows playbooks to add or remove banner text from the running configuration. version_added: 1.0.0 requirements: - ncclient >= 0.5.3 when using netconf - lxml >= 4.1.1 when using netconf extends_documentation_fragment: - cisco.iosxr.iosxr notes: - This module works with connection C(network_cli) and C(netconf). See L(the IOS-XR Platform Options,../network/user_guide/platform_iosxr.html). options: banner: description: - Specifies the type of banner to configure on remote device. required: true type: str choices: - login - motd text: description: - Banner text to be configured. Accepts multi line string, without empty lines. When using a multi line string, the first and last characters must be the start and end delimiters for the banner Requires I(state=present). type: str state: description: - Existential state of the configuration on the device. default: present type: str choices: - present - absent """ EXAMPLES = """ - name: configure the login banner cisco.iosxr.iosxr_banner: banner: login text: | @this is my login banner that contains a multiline string@ state: present - name: remove the motd banner cisco.iosxr.iosxr_banner: banner: motd state: absent - name: Configure banner from file cisco.iosxr.iosxr_banner: banner: motd text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}" state: present """ RETURN = """ commands: description: The list of configuration mode commands sent to device with transport C(cli) returned: always (empty list when no commands to send) type: list sample: - banner login - "@this is my login banner" - that contains a multiline - string@ xml: description: NetConf rpc xml sent to device with transport C(netconf) returned: always (empty list when no xml rpc to send) type: list sample: - ' motd Ansible banner example ' """ import re import collections from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.iosxr import ( get_config, load_config, ) from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.iosxr import ( iosxr_argument_spec, ) from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.iosxr import ( build_xml, is_cliconf, ) from ansible_collections.cisco.iosxr.plugins.module_utils.network.iosxr.iosxr import ( etree_find, is_netconf, ) class ConfigBase(object): def __init__(self, module): self._module = module self._result = {"changed": False, "warnings": []} self._want = {} self._have = {} def map_params_to_obj(self): text = self._module.params["text"] if text: text = "{0}".format(str(text).strip()) self._want.update( { "banner": self._module.params["banner"], "text": text, "state": self._module.params["state"], } ) class CliConfiguration(ConfigBase): def __init__(self, module): super(CliConfiguration, self).__init__(module) def map_obj_to_commands(self): commands = list() state = self._module.params["state"] if state == "absent": if self._have.get("state") != "absent" and ( "text" in self._have.keys() and self._have["text"] ): commands.append( "no banner {0!s}".format(self._module.params["banner"]) ) elif state == "present": if self._want["text"] and self._want["text"].encode().decode( "unicode_escape" ) != self._have.get("text"): banner_cmd = "banner {0!s} ".format( self._module.params["banner"] ) banner_cmd += self._want["text"].strip() commands.append(banner_cmd) self._result["commands"] = commands if commands: commit = not self._module.check_mode diff = load_config(self._module, commands, commit=commit) if diff: self._result["diff"] = dict(prepared=diff) self._result["changed"] = True def map_config_to_obj(self): cli_filter = "banner {0!s}".format(self._module.params["banner"]) output = get_config(self._module, config_filter=cli_filter) match = re.search(r"banner (\S+) (.*)", output, re.DOTALL) if match: text = match.group(2) else: text = None obj = {"banner": self._module.params["banner"], "state": "absent"} if output: obj["text"] = text obj["state"] = "present" self._have.update(obj) def run(self): self.map_params_to_obj() self.map_config_to_obj() self.map_obj_to_commands() return self._result class NCConfiguration(ConfigBase): def __init__(self, module): super(NCConfiguration, self).__init__(module) self._banners_meta = collections.OrderedDict() self._banners_meta.update( [ ( "banner", { "xpath": "banners/banner", "tag": True, "attrib": "operation", }, ), ("a:banner", {"xpath": "banner/banner-name"}), ( "a:text", {"xpath": "banner/banner-text", "operation": "edit"}, ), ] ) def map_obj_to_xml_rpc(self): state = self._module.params["state"] _get_filter = build_xml( "banners", xmap=self._banners_meta, params=self._module.params, opcode="filter", ) running = get_config( self._module, source="running", config_filter=_get_filter ) banner_name = None banner_text = None if etree_find(running, "banner-text") is not None: banner_name = etree_find(running, "banner-name").text banner_text = etree_find(running, "banner-text").text opcode = None if ( state == "absent" and banner_name == self._module.params["banner"] and len(banner_text) ): opcode = "delete" elif state == "present": opcode = "merge" self._result["xml"] = [] if opcode: _edit_filter = build_xml( "banners", xmap=self._banners_meta, params=self._module.params, opcode=opcode, ) if _edit_filter is not None: commit = not self._module.check_mode diff = load_config( self._module, _edit_filter, commit=commit, running=running, nc_get_filter=_get_filter, ) if diff: self._result["xml"] = _edit_filter if self._module._diff: self._result["diff"] = dict(prepared=diff) self._result["changed"] = True def run(self): self.map_params_to_obj() self.map_obj_to_xml_rpc() return self._result def main(): """ main entry point for module execution """ argument_spec = dict( banner=dict(required=True, choices=["login", "motd"]), text=dict(), state=dict(default="present", choices=["present", "absent"]), ) argument_spec.update(iosxr_argument_spec) required_if = [("state", "present", ("text",))] module = AnsibleModule( argument_spec=argument_spec, required_if=required_if, supports_check_mode=True, ) config_object = None if is_cliconf(module): # Commenting the below cliconf deprecation support call for Ansible 2.9 as it'll be continued to be supported # module.deprecate("cli support for 'iosxr_interface' is deprecated. Use transport netconf instead", # version='2.9') config_object = CliConfiguration(module) elif is_netconf(module): config_object = NCConfiguration(module) result = None if config_object is not None: result = config_object.run() module.exit_json(**result) if __name__ == "__main__": main()