diff --git a/lib/ansible/modules/network/eos/eos_banner.py b/lib/ansible/modules/network/eos/eos_banner.py new file mode 100644 index 00000000000..607dcde827c --- /dev/null +++ b/lib/ansible/modules/network/eos/eos_banner.py @@ -0,0 +1,178 @@ +#!/usr/bin/python +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +DOCUMENTATION = """ +--- +module: eos_banner_ +version_added: "2.3" +author: "Peter Sprygada (@privateip)" +short_description: Manage multiline banners on Arista EOS devices +description: + - This will configure both login and motd banners on remote devices + running Arista EOS. It allows playbooks to add or remote + banner text from the active running configuration. +notes: + - This module requires connection to be network_cli +options: + banner: + description: + - The C(banner) argument specifies the banner that should be + configured on the remote device. Current this module supports + configuration of either C(login) or C(motd) banners. + required: true + default: null + text: + description: + - The C(text) argument specifics the banner text that should be + present in the remote device running configuration. This argument + accepts a multiline string. + required: false + default: null + state: + description: + - The C(state) argument specifies whether or not the configuration is + present in the current devices active running configuration. When + this value is set to C(present), the configuration stanzas should be + in the current device configuration. When this value is set to + C(absent), the configuration should not be in the current running + configuration. + required: false + default: present + choices: ['present', 'absent'] +""" + +EXAMPLES = """ +- name: configure the login banner + eos_banner: + banner: login + text: | + this is my login banner + that contains a multiline + string + state: present + +- name: remove the motd banner + banner: motd + state: absent +""" + +RETURN = """ +commands: + description: The list of configuration mode commands to send to the device + returned: always + type: list + sample: + - banner login + - this is my login banner + - that contains a multiline + - string + - EOF +session_name: + description: The EOS config session name used to load the configuration + returned: always + type: str + sample: ansible_1479315771 +start: + description: The time the job started + returned: always + type: str + sample: "2016-11-16 10:38:15.126146" +end: + description: The time the job ended + returned: always + type: str + sample: "2016-11-16 10:38:25.595612" +delta: + description: The time elapsed to perform all operations + returned: always + type: str + sample: "0:00:10.469466" +""" +from ansible.module_utils.local import LocalAnsibleModule +from ansible.module_utils.eos import load_config, run_commands + +def map_obj_to_commands(updates, module): + commands = list() + want, have = updates + state = module.params['state'] + + if state == 'absent': + commands.append('no banner %s' % module.params['banner']) + + elif state == 'present': + if want['text'] and (want['text'] != have.get('text')): + commands.append('banner %s' % module.params['banner']) + commands.extend(want['text'].strip().split('\n')) + commands.append('EOF') + + return commands + +def map_config_to_obj(module): + output = run_commands(module, ['show banner %s' % module.params['banner']]) + obj = {'banner': module.params['banner'], 'state': 'absent'} + if output: + obj['text'] = output + obj['state'] = 'present' + return obj + +def map_params_to_obj(module): + text = module.params['text'] + if text: + text = str(text).strip() + + return { + 'banner': module.params['banner'], + 'text': text, + 'state': module.params['state'] + } + +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']) + ) + + required_if = [('state', 'present', ('text',))] + + module = LocalAnsibleModule(argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True) + + result = {'changed': False} + + want = map_params_to_obj(module) + have = map_config_to_obj(module) + + commands = map_obj_to_commands((want, have), module) + result['commands'] = commands + + if commands: + commit = not module.check_mode + response = load_config(module, commands, commit=commit) + if response.get('diff') and module._diff: + result['diff'] = {'prepared': response.get('diff')} + result['session_name'] = response.get('session') + result['changed'] = True + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/test/units/modules/network/eos/fixtures/eos_banner_show_banner.txt b/test/units/modules/network/eos/fixtures/eos_banner_show_banner.txt new file mode 100644 index 00000000000..a134a31753c --- /dev/null +++ b/test/units/modules/network/eos/fixtures/eos_banner_show_banner.txt @@ -0,0 +1,3 @@ +this is a sample +mulitline banner +used for testing diff --git a/test/units/modules/network/eos/test_eos_banner.py b/test/units/modules/network/eos/test_eos_banner.py new file mode 100644 index 00000000000..192f6fa49c2 --- /dev/null +++ b/test/units/modules/network/eos/test_eos_banner.py @@ -0,0 +1,106 @@ +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json + +from ansible.compat.tests import unittest +from ansible.compat.tests.mock import patch, MagicMock +from ansible.errors import AnsibleModuleExit +from ansible.modules.network.eos import eos_banner +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except: + pass + + fixture_data[path] = data + return data + + +class TestEosBannerModule(unittest.TestCase): + + def setUp(self): + self.mock_run_commands = patch('ansible.modules.network.eos.eos_banner.run_commands') + self.run_commands = self.mock_run_commands.start() + + self.mock_load_config = patch('ansible.modules.network.eos.eos_banner.load_config') + self.load_config = self.mock_load_config.start() + + def tearDown(self): + self.mock_run_commands.stop() + self.mock_load_config.stop() + + def execute_module(self, failed=False, changed=False, commands=None, sort=True): + + self.run_commands.return_value = load_fixture('eos_banner_show_banner.txt').strip() + self.load_config.return_value = dict(diff=None, session='session') + + with self.assertRaises(AnsibleModuleExit) as exc: + eos_banner.main() + + result = exc.exception.result + + if failed: + self.assertTrue(result['failed'], result) + else: + self.assertEqual(result['changed'], changed, result) + + if commands: + if sort: + self.assertEqual(sorted(commands), sorted(result['commands']), result['commands']) + else: + self.assertEqual(commands, result['commands']) + + return result + + def test_eos_banner_create(self): + set_module_args(dict(banner='login', text='test\nbanner\nstring')) + commands = ['banner login', 'test', 'banner', 'string', 'EOF'] + self.execute_module(changed=True, commands=commands) + + def test_eos_banner_remove(self): + set_module_args(dict(banner='login', state='absent')) + commands = ['no banner login'] + self.execute_module(changed=True, commands=commands) + + def test_eos_banner_nochange(self): + banner_text = load_fixture('eos_banner_show_banner.txt').strip() + set_module_args(dict(banner='login', text=banner_text)) + self.execute_module() +